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 { IconArrowDownLine, IconCloseCircle } from "@halo-dev/components";
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
|
defineEmits,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
ref,
|
|
||||||
defineEmits,
|
|
||||||
type PropType,
|
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
ref,
|
||||||
|
type PropType,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
|
||||||
import SelectSelector from "./SelectSelector.vue";
|
import { Dropdown } from "floating-vue";
|
||||||
import MultipleSelectSelector from "./MultipleSelectSelector.vue";
|
import MultipleSelectSelector from "./MultipleSelectSelector.vue";
|
||||||
import SelectDropdownContainer from "./SelectDropdownContainer.vue";
|
import SelectDropdownContainer from "./SelectDropdownContainer.vue";
|
||||||
import { Dropdown } from "floating-vue";
|
import SelectSelector from "./SelectSelector.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
multiple: {
|
multiple: {
|
||||||
|
@ -95,7 +95,7 @@ const inputRef = ref<HTMLInputElement | null>();
|
||||||
const searchKeyword = ref<string>("");
|
const searchKeyword = ref<string>("");
|
||||||
const isDropdownVisible = ref<boolean>(false);
|
const isDropdownVisible = ref<boolean>(false);
|
||||||
const selectedOptions = computed({
|
const selectedOptions = computed({
|
||||||
get: () => props.selected,
|
get: () => [...props.selected],
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
emit("update", value);
|
emit("update", value);
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,7 +45,7 @@ export interface SelectProps {
|
||||||
remoteOptimize?: boolean;
|
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;
|
allowCreate?: boolean;
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ const initSelectProps = () => {
|
||||||
selectProps.maxCount = nodeProps.maxCount ?? NaN;
|
selectProps.maxCount = nodeProps.maxCount ?? NaN;
|
||||||
selectProps.placeholder = nodeProps.placeholder ?? "";
|
selectProps.placeholder = nodeProps.placeholder ?? "";
|
||||||
selectProps.action = nodeProps.action ?? "";
|
selectProps.action = nodeProps.action ?? "";
|
||||||
selectProps.remoteOptimize = nodeProps.remoteOptimize ?? true;
|
selectProps.remoteOptimize = !isFalse(nodeProps.remoteOptimize, true);
|
||||||
selectProps.requestOption = {
|
selectProps.requestOption = {
|
||||||
...{
|
...{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -225,12 +225,12 @@ const initSelectProps = () => {
|
||||||
...(nodeProps.requestOption ?? {}),
|
...(nodeProps.requestOption ?? {}),
|
||||||
};
|
};
|
||||||
selectProps.multiple = !isFalse(nodeProps.multiple);
|
selectProps.multiple = !isFalse(nodeProps.multiple);
|
||||||
selectProps.sortable = !isFalse(nodeProps.sortable);
|
selectProps.sortable = !isFalse(nodeProps.sortable, true);
|
||||||
selectProps.remote = !isFalse(nodeProps.remote);
|
selectProps.remote = !isFalse(nodeProps.remote);
|
||||||
selectProps.allowCreate = !isFalse(nodeProps.allowCreate);
|
selectProps.allowCreate = !isFalse(nodeProps.allowCreate);
|
||||||
selectProps.clearable = !isFalse(nodeProps.clearable);
|
selectProps.clearable = !isFalse(nodeProps.clearable);
|
||||||
selectProps.searchable = !isFalse(nodeProps.searchable);
|
selectProps.searchable = !isFalse(nodeProps.searchable);
|
||||||
selectProps.autoSelect = !isFalse(nodeProps.autoSelect) || true;
|
selectProps.autoSelect = !isFalse(nodeProps.autoSelect, true);
|
||||||
if (selectProps.remote) {
|
if (selectProps.remote) {
|
||||||
if (!nodeProps.remoteOption) {
|
if (!nodeProps.remoteOption) {
|
||||||
throw new Error("remoteOption is required when remote is true.");
|
throw new Error("remoteOption is required when remote is true.");
|
||||||
|
@ -543,14 +543,25 @@ const getAutoSelectedOption = ():
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopSelectedWatch = watch(
|
watch(
|
||||||
() => [options.value, props.context.value],
|
() => props.context.value,
|
||||||
async () => {
|
async (newValue) => {
|
||||||
if (options.value) {
|
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();
|
const selectedOption = await fetchSelectedOptions();
|
||||||
if (selectedOption) {
|
if (selectedOption) {
|
||||||
selectOptions.value = selectedOption;
|
selectOptions.value = selectedOption;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const isAutoSelect =
|
const isAutoSelect =
|
||||||
selectProps.autoSelect &&
|
selectProps.autoSelect &&
|
||||||
|
@ -562,12 +573,12 @@ const stopSelectedWatch = watch(
|
||||||
// Automatically select the first option when the selected value is empty.
|
// Automatically select the first option when the selected value is empty.
|
||||||
const autoSelectedOption = getAutoSelectedOption();
|
const autoSelectedOption = getAutoSelectedOption();
|
||||||
if (autoSelectedOption) {
|
if (autoSelectedOption) {
|
||||||
selectOptions.value = [autoSelectedOption];
|
handleUpdate([autoSelectedOption]);
|
||||||
handleUpdate(selectOptions.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// When attr options are processed asynchronously, it is necessary to monitor
|
// When attr options are processed asynchronously, it is necessary to monitor
|
||||||
|
@ -581,14 +592,27 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectedUpdate = (
|
const handleUpdate = async (value: Array<{ label: string; value: string }>) => {
|
||||||
value: Array<{ label: string; value: string }>
|
const oldSelectValue = selectOptions.value;
|
||||||
) => {
|
if (
|
||||||
stopSelectedWatch();
|
oldSelectValue &&
|
||||||
handleUpdate(value);
|
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);
|
const values = value.map((item) => item.value);
|
||||||
selectOptions.value = value;
|
selectOptions.value = value;
|
||||||
if (selectProps.multiple) {
|
if (selectProps.multiple) {
|
||||||
|
@ -725,7 +749,7 @@ const handleNextPage = async () => {
|
||||||
:clearable="selectProps.clearable"
|
:clearable="selectProps.clearable"
|
||||||
:searchable="selectProps.searchable"
|
:searchable="selectProps.searchable"
|
||||||
:auto-select="selectProps.autoSelect"
|
:auto-select="selectProps.autoSelect"
|
||||||
@update="handleSelectedUpdate"
|
@update="handleUpdate"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
@load-more="handleNextPage"
|
@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);
|
return [undefined, null, "false", false].includes(value);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue