20260721223033

This commit is contained in:
oneao committed 2026-07-21 22:30:33 +08:00
1 parent bab8e2972f
commit e9f099f044
26 files changed
+1687 -349

No files matched your search

+187 -2
View File
@@ -1,21 +1,200 @@
<script setup>
import { computed, nextTick, ref, useSlots } from "vue";
import { computed, defineComponent, h, nextTick, ref, useSlots } from "vue";
import { useRafFn, useResizeObserver } from "@vueuse/core";
import { Checkbox, Input, InputNumber, Select } from "antdv-next";
import { StkTable } from "stk-table-vue";
defineOptions({ inheritAttrs: false });
defineProps({
const props = defineProps({
virtual: { type: Boolean, default: true },
virtualX: { type: Boolean, default: true },
colResizable: { type: Boolean, default: true },
bordered: { type: [Boolean, String], default: true },
noDataFull: { type: Boolean, default: true },
columns: { type: Array, default: () => [] },
dataSource: { type: Array, default: () => [] },
rowKey: { type: [String, Function], default: "id" },
rowSelection: { type: Object, default: null },
});
const emit = defineEmits(["update:columns"]);
function getRowKey(row) {
return typeof props.rowKey === "function" ? props.rowKey(row) : row?.[props.rowKey];
}
const selectedRowKeySet = computed(
() => new Set(props.rowSelection?.selectedRowKeys || []),
);
function changeSelection(row, checked) {
const key = getRowKey(row);
const nextKeys = new Set(selectedRowKeySet.value);
if (checked) nextKeys.add(key);
else nextKeys.delete(key);
const keys = Array.from(nextKeys);
const rows = props.dataSource.filter((item) => nextKeys.has(getRowKey(item)));
props.rowSelection?.onChange?.(keys, rows);
}
function changeAllSelection(checked) {
const visibleKeys = props.dataSource.map(getRowKey);
const nextKeys = new Set(selectedRowKeySet.value);
visibleKeys.forEach((key) => {
if (checked) nextKeys.add(key);
else nextKeys.delete(key);
});
const keys = Array.from(nextKeys);
const rows = props.dataSource.filter((item) => nextKeys.has(getRowKey(item)));
props.rowSelection?.onChange?.(keys, rows);
}
const SelectionCell = defineComponent({
props: ["row"],
setup(cellProps) {
return () =>
h(Checkbox, {
checked: selectedRowKeySet.value.has(getRowKey(cellProps.row)),
"onUpdate:checked": (checked) => changeSelection(cellProps.row, checked),
onClick: (event) => event.stopPropagation(),
});
},
});
const SelectionHeaderCell = defineComponent({
setup() {
return () => {
const visibleKeys = props.dataSource.map(getRowKey);
const selectedCount = visibleKeys.filter((key) => selectedRowKeySet.value.has(key)).length;
return h(Checkbox, {
checked: visibleKeys.length > 0 && selectedCount === visibleKeys.length,
indeterminate: selectedCount > 0 && selectedCount < visibleKeys.length,
"onUpdate:checked": changeAllSelection,
onClick: (event) => event.stopPropagation(),
});
};
},
});
const EditableCell = defineComponent({
props: ["row", "col", "cellValue"],
setup(cellProps) {
function editorOptions() {
return typeof cellProps.col.editor === "string"
? { type: cellProps.col.editor }
: cellProps.col.editor || {};
}
function updateValue(value) {
cellProps.row[cellProps.col.dataIndex] = value;
editorOptions().onChange?.(value, cellProps.row, cellProps.col);
}
return () => {
const editor = editorOptions();
const disabled =
typeof editor.disabled === "function" ? editor.disabled(cellProps.row) : editor.disabled;
const commonProps = {
size: "small",
disabled,
style: { width: "100%" },
};
if (editor.type === "number") {
return h(InputNumber, {
...commonProps,
value: cellProps.row[cellProps.col.dataIndex],
min: editor.min,
max: editor.max,
precision: editor.precision,
"onUpdate:value": updateValue,
});
}
if (editor.type === "select") {
return h(Select, {
...commonProps,
value: cellProps.row[cellProps.col.dataIndex],
options: editor.options || [],
"onUpdate:value": updateValue,
});
}
if (editor.type === "checkbox") {
const checkedValue = editor.checkedValue ?? 1;
const uncheckedValue = editor.uncheckedValue ?? 0;
return h(Checkbox, {
disabled,
checked: cellProps.row[cellProps.col.dataIndex] === checkedValue,
"onUpdate:checked": (checked) => updateValue(checked ? checkedValue : uncheckedValue),
onClick: (event) => event.stopPropagation(),
});
}
return h(Input, {
...commonProps,
value: cellProps.row[cellProps.col.dataIndex],
allowClear: editor.allowClear ?? true,
maxlength: editor.maxlength,
placeholder:
typeof editor.placeholder === "function"
? editor.placeholder(cellProps.row)
: editor.placeholder,
status: typeof editor.status === "function" ? editor.status(cellProps.row) : editor.status,
"onUpdate:value": updateValue,
});
};
},
});
// StkTable 的编辑单元格和多选列统一在封装层生成,业务页面只声明 editor / rowSelection。
const tableColumnsModel = computed({
get: () => {
const columns = props.columns.map((column) =>
column.editor && !column.customCell
? { ...column, customCell: EditableCell, __fmsEditor: true }
: column,
);
if (!props.rowSelection) return columns;
return [
{
dataIndex: "__fmsSelection",
key: "__fmsSelection",
title: "",
width: 44,
fixed: "left",
align: "center",
headerAlign: "center",
customCell: SelectionCell,
customHeaderCell: SelectionHeaderCell,
__fmsSelection: true,
},
...columns,
];
},
set: (value) =>
emit(
"update:columns",
value
.filter((column) => !column.__fmsSelection)
.map((column) => {
if (!column.__fmsEditor) return column;
const rest = { ...column };
delete rest.customCell;
delete rest.__fmsEditor;
return rest;
}),
),
});
const slots = useSlots();
const tableRef = ref(null);
// 表格样式
const customVars = ref({
"--header-row-height": "40px",
"--row-height": "34px",
"--footer-row-height": "36px",
});
const { resume: refreshLayout } = useRafFn(
async () => {
await nextTick();
@@ -49,10 +228,14 @@ const forwardSlots = computed(() => {
ref="tableRef"
class="fms-table"
v-bind="$attrs"
:style="customVars"
:auto-resize="false"
:virtual="virtual"
:virtual-x="virtualX"
:col-resizable="colResizable"
v-model:columns="tableColumnsModel"
:data-source="dataSource"
:row-key="rowKey"
:bordered="bordered"
:no-data-full="noDataFull"
>
@@ -68,6 +251,8 @@ const forwardSlots = computed(() => {
</StkTable>
</template>
<style scoped>
.fms-table {
width: 100%;
@@ -66,7 +66,7 @@ export function useLookupOptions(props) {
}
const { start: startSearchTimer, stop: stopSearchTimer } = useTimeoutFn(
() => load(query.value),
(searchText) => load(searchText),
() => props.searchDelay,
{ immediate: false },
);
@@ -76,8 +76,11 @@ export function useLookupOptions(props) {
function search(value) {
stopResetTimer();
query.value = String(value ?? "");
startSearchTimer();
const searchText = String(value ?? "");
query.value = searchText;
requestId += 1;
loading.value = false;
startSearchTimer(searchText);
}
function open() {