Files
workspace/code/fms/fms-vue/src/components/FmsSelect.vue
T
2026-07-30 23:11:10 +08:00

159 lines
4.1 KiB
Vue

<script setup>
import { computed, ref, watch } from "vue";
import { useLookupOptions } from "./select/useLookupOptions";
defineOptions({ inheritAttrs: false });
const props = defineProps({
modelValue: { type: null, default: "" },
label: { type: String, default: "" },
labelKey: { type: String, default: "b_name" },
valueKey: { type: String, default: "b_id" },
viewName: { type: String, required: true },
searchCondition: { type: String, default: "" },
searchColumns: { type: Array, default: () => [] },
orderBy: { type: String, default: "" },
pageSize: { type: Number, default: 20 },
searchDelay: { type: Number, default: 300 },
allowClear: { type: Boolean, default: false },
});
const emit = defineEmits(["update:modelValue", "update:label", "change"]);
const lookup = useLookupOptions(props);
const inputValue = ref("");
const clearedByInput = ref(false);
const options = computed(() => {
const items = lookup.rows.value.map((row) => {
const label = String(row[props.labelKey] ?? "");
const value = row[props.valueKey];
return {
value: label,
label,
key: String(value ?? label),
rawValue: value,
record: row,
};
});
if (props.modelValue !== "" && props.modelValue != null && props.label) {
const exists = items.some((item) => String(item.rawValue) === String(props.modelValue));
if (!exists) {
items.unshift({
value: props.label,
label: props.label,
key: String(props.modelValue),
rawValue: props.modelValue,
record: null,
});
}
}
return items;
});
watch(
() => [props.modelValue, props.label],
() => {
if (props.modelValue === "" || props.modelValue == null) {
if (!props.label) inputValue.value = "";
return;
}
clearedByInput.value = false;
inputValue.value = props.label || "";
},
{ immediate: true },
);
function onSearch(value) {
inputValue.value = String(value ?? "");
if (!inputValue.value) {
clearSelection();
return;
}
lookup.search(value);
}
function onInputChange(value) {
inputValue.value = String(value ?? "");
if (!inputValue.value) {
clearSelection();
return;
}
clearedByInput.value = false;
}
function clearSelection() {
inputValue.value = "";
clearedByInput.value = true;
emit("update:modelValue", "");
emit("update:label", "");
emit("change", null);
}
function onBlur() {
if (clearedByInput.value || !inputValue.value) return;
inputValue.value = props.label || "";
}
function onOpenChange(open) {
if (open) lookup.open();
else {
lookup.resetAfterClose();
if (clearedByInput.value || !inputValue.value) return;
inputValue.value = props.label || "";
}
}
function onSelect(_value, option) {
const record = option.record ?? null;
const value = record ? record[props.valueKey] : option.rawValue;
const label = record ? String(record[props.labelKey] ?? "") : String(option.label ?? "");
inputValue.value = label;
clearedByInput.value = false;
emit("update:modelValue", value ?? "");
emit("update:label", label);
emit("change", record);
}
function onInputKeydown(event) {
if (event.key !== "Backspace" && event.key !== "Delete") return;
const checkCleared = () => {
const target = event.target;
const value = target && "value" in target ? String(target.value ?? "") : inputValue.value;
if (!value) clearSelection();
};
if (typeof requestAnimationFrame === "function") {
requestAnimationFrame(checkCleared);
} else {
setTimeout(checkCleared, 0);
}
}
</script>
<template>
<a-auto-complete
v-bind="$attrs"
class="fms-select"
:value="inputValue"
:options="options"
:show-search="{ onSearch }"
:not-found-content="lookup.loading.value ? '加载中...' : '暂无数据'"
:allow-clear="allowClear"
@change="onInputChange"
@clear="clearSelection"
@blur="onBlur"
@focus="lookup.open"
@input-keydown="onInputKeydown"
@open-change="onOpenChange"
@popup-scroll="lookup.onPopupScroll"
@select="onSelect"
/>
</template>
<style scoped lang="scss">
.fms-select {
width: 100%;
}
</style>