mirror of https://github.com/halo-dev/halo
fix: formkit select component not emitting event or updating selection on value change (#6602)
#### What type of PR is this? /kind bug /area ui /milestone 2.20.x #### What this PR does / why we need it: 重新修改 formkit select 初始化值的监听方式,用于当 value 发生变化时,使选项值可以发生变化。 另外在更新数据时,如果数据发生变化,则发出 `onChange` 事件。 > 需要注意的是,通过 v-modal 传入默认值,再将此值改为 `undefined` 时无法触发 `watch` 及 formkit 的 `input` 事件,原因暂时未知,将此值设置为 `""` 即可正常触发。 #### How to test it? 由于此 PR 改动了初始化的方式,因此需要全量测试所有使用 `Select` 组件的位置。包括多选与单选,本地源与远程源。确保功能符合预期。 #### Which issue(s) this PR fixes: Fixes #6594 #### Does this PR introduce a user-facing change? ```release-note 解决 Formkit Select 组件在值变更时不会发出事件及修改选项值的问题。 ```pull/6612/head
parent
2ea063d37f
commit
cf2837d744
|
@ -2,18 +2,18 @@
|
|||
import { IconArrowDownLine, IconCloseCircle } from "@halo-dev/components";
|
||||
import {
|
||||
computed,
|
||||
defineEmits,
|
||||
nextTick,
|
||||
onMounted,
|
||||
ref,
|
||||
defineEmits,
|
||||
type PropType,
|
||||
onUnmounted,
|
||||
ref,
|
||||
type PropType,
|
||||
} from "vue";
|
||||
|
||||
import SelectSelector from "./SelectSelector.vue";
|
||||
import { Dropdown } from "floating-vue";
|
||||
import MultipleSelectSelector from "./MultipleSelectSelector.vue";
|
||||
import SelectDropdownContainer from "./SelectDropdownContainer.vue";
|
||||
import { Dropdown } from "floating-vue";
|
||||
import SelectSelector from "./SelectSelector.vue";
|
||||
|
||||
const props = defineProps({
|
||||
multiple: {
|
||||
|
@ -95,7 +95,7 @@ const inputRef = ref<HTMLInputElement | null>();
|
|||
const searchKeyword = ref<string>("");
|
||||
const isDropdownVisible = ref<boolean>(false);
|
||||
const selectedOptions = computed({
|
||||
get: () => props.selected,
|
||||
get: () => [...props.selected],
|
||||
set: (value) => {
|
||||
emit("update", value);
|
||||
},
|
||||
|
|
|
@ -45,7 +45,7 @@ export interface SelectProps {
|
|||
remoteOptimize?: boolean;
|
||||
|
||||
/**
|
||||
* Allows the creation of new options, only available in local mode.
|
||||
* Allows the creation of new options, only available in local mode. Default is false.
|
||||
*/
|
||||
allowCreate?: boolean;
|
||||
|
||||
|
@ -209,7 +209,7 @@ const initSelectProps = () => {
|
|||
selectProps.maxCount = nodeProps.maxCount ?? NaN;
|
||||
selectProps.placeholder = nodeProps.placeholder ?? "";
|
||||
selectProps.action = nodeProps.action ?? "";
|
||||
selectProps.remoteOptimize = nodeProps.remoteOptimize ?? true;
|
||||
selectProps.remoteOptimize = !isFalse(nodeProps.remoteOptimize, true);
|
||||
selectProps.requestOption = {
|
||||
...{
|
||||
method: "GET",
|
||||
|
@ -225,12 +225,12 @@ const initSelectProps = () => {
|
|||
...(nodeProps.requestOption ?? {}),
|
||||
};
|
||||
selectProps.multiple = !isFalse(nodeProps.multiple);
|
||||
selectProps.sortable = !isFalse(nodeProps.sortable);
|
||||
selectProps.sortable = !isFalse(nodeProps.sortable, true);
|
||||
selectProps.remote = !isFalse(nodeProps.remote);
|
||||
selectProps.allowCreate = !isFalse(nodeProps.allowCreate);
|
||||
selectProps.clearable = !isFalse(nodeProps.clearable);
|
||||
selectProps.searchable = !isFalse(nodeProps.searchable);
|
||||
selectProps.autoSelect = !isFalse(nodeProps.autoSelect) || true;
|
||||
selectProps.autoSelect = !isFalse(nodeProps.autoSelect, true);
|
||||
if (selectProps.remote) {
|
||||
if (!nodeProps.remoteOption) {
|
||||
throw new Error("remoteOption is required when remote is true.");
|
||||
|
@ -543,14 +543,25 @@ const getAutoSelectedOption = ():
|
|||
});
|
||||
};
|
||||
|
||||
const stopSelectedWatch = watch(
|
||||
() => [options.value, props.context.value],
|
||||
async () => {
|
||||
if (options.value) {
|
||||
watch(
|
||||
() => props.context.value,
|
||||
async (newValue) => {
|
||||
const selectedValues = selectOptions.value?.map((item) => item.value) || [];
|
||||
if (selectedValues.length > 0 && selectedValues.includes(newValue)) {
|
||||
return;
|
||||
}
|
||||
const selectedOption = await fetchSelectedOptions();
|
||||
selectOptions.value = selectedOption;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => options.value,
|
||||
async (newOptions) => {
|
||||
if (newOptions && newOptions.length > 0) {
|
||||
const selectedOption = await fetchSelectedOptions();
|
||||
if (selectedOption) {
|
||||
selectOptions.value = selectedOption;
|
||||
return;
|
||||
}
|
||||
const isAutoSelect =
|
||||
selectProps.autoSelect &&
|
||||
|
@ -562,12 +573,12 @@ const stopSelectedWatch = watch(
|
|||
// Automatically select the first option when the selected value is empty.
|
||||
const autoSelectedOption = getAutoSelectedOption();
|
||||
if (autoSelectedOption) {
|
||||
selectOptions.value = [autoSelectedOption];
|
||||
handleUpdate(selectOptions.value);
|
||||
handleUpdate([autoSelectedOption]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
// When attr options are processed asynchronously, it is necessary to monitor
|
||||
|
@ -581,14 +592,27 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
const handleSelectedUpdate = (
|
||||
value: Array<{ label: string; value: string }>
|
||||
) => {
|
||||
stopSelectedWatch();
|
||||
handleUpdate(value);
|
||||
const handleUpdate = async (value: Array<{ label: string; value: string }>) => {
|
||||
const oldSelectValue = selectOptions.value;
|
||||
if (
|
||||
oldSelectValue &&
|
||||
value.length === oldSelectValue.length &&
|
||||
value.every((item, index) => item.value === oldSelectValue[index].value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const newValue = value.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
handleSetNodeValue(newValue);
|
||||
await props.context.node.settled;
|
||||
props.context.attrs.onChange?.(newValue);
|
||||
};
|
||||
|
||||
const handleUpdate = (value: Array<{ label: string; value: string }>) => {
|
||||
const handleSetNodeValue = (value: Array<{ label: string; value: string }>) => {
|
||||
const values = value.map((item) => item.value);
|
||||
selectOptions.value = value;
|
||||
if (selectProps.multiple) {
|
||||
|
@ -725,7 +749,7 @@ const handleNextPage = async () => {
|
|||
:clearable="selectProps.clearable"
|
||||
:searchable="selectProps.searchable"
|
||||
:auto-select="selectProps.autoSelect"
|
||||
@update="handleSelectedUpdate"
|
||||
@update="handleUpdate"
|
||||
@search="handleSearch"
|
||||
@load-more="handleNextPage"
|
||||
/>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
export const isFalse = (value: string | boolean | undefined | null) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isFalse = (value: any, onlyBoolean = false) => {
|
||||
if (onlyBoolean) {
|
||||
return [false, "false"].includes(value);
|
||||
}
|
||||
return [undefined, null, "false", false].includes(value);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue