20260730231110
This commit is contained in:
1 parent
4929b78434
commit
318fa0d9e1
23 files changed
+4026
-506
No files matched your search
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useLookupOptions } from "./select/useLookupOptions";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
@@ -15,69 +15,143 @@ const props = defineProps({
|
||||
orderBy: { type: String, default: "" },
|
||||
pageSize: { type: Number, default: 20 },
|
||||
searchDelay: { type: Number, default: 300 },
|
||||
allowClear: { type: Boolean, default: true },
|
||||
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) => ({
|
||||
value: row[props.valueKey],
|
||||
label: String(row[props.labelKey] ?? ""),
|
||||
record: row,
|
||||
}));
|
||||
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 &&
|
||||
!items.some((item) => item.value === props.modelValue)
|
||||
) {
|
||||
items.unshift({ value: props.modelValue, label: props.label, record: null });
|
||||
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;
|
||||
});
|
||||
|
||||
function onOpenChange(open) {
|
||||
if (open) lookup.open();
|
||||
else lookup.resetAfterClose();
|
||||
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 onSelect(value, option) {
|
||||
emit("update:modelValue", value);
|
||||
emit("update:label", String(option.label ?? ""));
|
||||
emit("change", option.record ?? null);
|
||||
function onInputChange(value) {
|
||||
inputValue.value = String(value ?? "");
|
||||
if (!inputValue.value) {
|
||||
clearSelection();
|
||||
return;
|
||||
}
|
||||
clearedByInput.value = false;
|
||||
}
|
||||
|
||||
function onChange(value) {
|
||||
if (value !== undefined && value !== null) return;
|
||||
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-select
|
||||
<a-auto-complete
|
||||
v-bind="$attrs"
|
||||
class="fms-select"
|
||||
:value="modelValue === '' ? undefined : modelValue"
|
||||
:value="inputValue"
|
||||
:options="options"
|
||||
:loading="lookup.loading.value"
|
||||
:filter-option="false"
|
||||
:show-search="{ onSearch }"
|
||||
:not-found-content="lookup.loading.value ? '加载中...' : '暂无数据'"
|
||||
:allow-clear="allowClear"
|
||||
show-search
|
||||
@search="lookup.search"
|
||||
@change="onInputChange"
|
||||
@clear="clearSelection"
|
||||
@blur="onBlur"
|
||||
@focus="lookup.open"
|
||||
@input-keydown="onInputKeydown"
|
||||
@open-change="onOpenChange"
|
||||
@popup-scroll="lookup.onPopupScroll"
|
||||
@select="onSelect"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.fms-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import { computed, ref, useAttrs, watch } from "vue";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const attrs = useAttrs();
|
||||
const appStore = useAppStore();
|
||||
const handledEventAttrs = new Set([
|
||||
"onDrop",
|
||||
"onDragStart",
|
||||
@@ -36,7 +38,6 @@ const props = defineProps({
|
||||
draggable: { type: [Boolean, Function, Object], default: false },
|
||||
allowDrop: { type: Function, default: null },
|
||||
rootKey: { type: [String, Number], default: "__fms_tree_root__" },
|
||||
rootTitle: { type: String, default: "全部分类" },
|
||||
// 右键菜单生成函数:(node) => { items: [...] }
|
||||
contextMenu: { type: Function, default: null },
|
||||
contextMenuDisabled: { type: Boolean, default: false },
|
||||
@@ -48,13 +49,30 @@ const effectiveVirtual = computed(() => {
|
||||
return props.virtual;
|
||||
});
|
||||
|
||||
function getNodeTitle(node) {
|
||||
const fallback = node?.title || node?.b_name || node?.b_code || "";
|
||||
return appStore.t(node?.b_i18n, fallback);
|
||||
}
|
||||
|
||||
function localizeNodes(nodes) {
|
||||
const result = [];
|
||||
for (const node of nodes || []) {
|
||||
result.push({
|
||||
...node,
|
||||
title: getNodeTitle(node),
|
||||
children: localizeNodes(node.children),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const effectiveTreeData = computed(() => {
|
||||
return [
|
||||
{
|
||||
key: props.rootKey,
|
||||
title: props.rootTitle,
|
||||
title: appStore.t("common.allCategories", "全部分类"),
|
||||
isFmsRoot: true,
|
||||
children: props.treeData,
|
||||
children: localizeNodes(props.treeData),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { onBeforeUnmount, ref, watch } from "vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { pageDataApi } from "@/services/api";
|
||||
|
||||
@@ -19,10 +19,6 @@ export function useLookupOptions(props) {
|
||||
const total = ref(0);
|
||||
const query = ref("");
|
||||
|
||||
const searchColumns = computed(() => [
|
||||
...new Set([props.labelKey, props.valueKey, ...props.searchColumns].filter(Boolean)),
|
||||
]);
|
||||
|
||||
let requestId = 0;
|
||||
|
||||
async function load(searchText = query.value, append = false) {
|
||||
@@ -40,7 +36,7 @@ export function useLookupOptions(props) {
|
||||
const response = await pageDataApi({
|
||||
viewName: props.viewName,
|
||||
orderBy: props.orderBy || props.labelKey,
|
||||
searchColumns: searchColumns.value,
|
||||
searchColumns: props.searchColumns.length ? props.searchColumns : undefined,
|
||||
searchCondition: combineConditions(props.searchCondition, searchFilter),
|
||||
pageNo: requestedPage,
|
||||
pageSize: props.pageSize,
|
||||
|
||||
Reference in new issue
Block a user