20260729173317

This commit is contained in:
oneao committed 2026-07-29 17:33:21 +08:00
1 parent f6b79ea09a
commit 1c839b707a
26 files changed
+1902 -29

No files matched your search

@@ -2,8 +2,8 @@
import { computed, h, nextTick, reactive, ref, shallowRef, useSlots, watch } from "vue";
import { useRoute } from "vue-router";
import { message } from "antdv-next";
import { Columns3, RefreshCw } from "@lucide/vue";
import { loadDataApi, pageDataApi } from "@/services/api";
import { Columns3, RefreshCw, Save } from "@lucide/vue";
import { loadDataApi, pageDataApi, saveObjectApi } from "@/services/api";
import { useAppStore } from "@/stores/app";
import {
identifierRe,
@@ -18,6 +18,7 @@ import FmsQueryToolbar from "./FmsQueryToolbar.vue";
const props = defineProps({
dataCode: { type: String, default: "" },
fixedSearchCondition: { type: String, default: "" },
columnExtensions: { type: Object, default: () => ({}) },
rowActions: { type: Array, default: () => [] },
rowSelection: { type: Object, default: null },
@@ -44,6 +45,7 @@ const searchConditions = shallowRef([]);
const keyField = ref("");
const configLoading = ref(false);
const dataLoading = ref(false);
const columnSaving = ref(false);
const configError = ref("");
const dataError = ref("");
const pagination = reactive({
@@ -51,6 +53,7 @@ const pagination = reactive({
pageSize: Number(props.pageSize) || 20,
total: 0,
});
let dataRequestVersion = 0;
const loading = computed(() => configLoading.value || dataLoading.value);
@@ -63,9 +66,15 @@ const moreMenuItems = computed(() => [
},
{
key: "reset-columns",
label: appStore.t("common.resetColumns", "恢复配置列宽"),
label: appStore.t("common.resetColumnSettings", "恢复栏位设置"),
icon: h(Columns3, { size: 14 }),
},
{
key: "save-column-settings",
label: appStore.t("common.saveColumnSettings", "保存栏位设置"),
icon: h(Save, { size: 14 }),
disabled: columnSaving.value,
},
]);
const cellSlotNames = computed(() => Object.keys(slots).filter((name) => name.startsWith("cell-")));
@@ -225,12 +234,17 @@ function resolveOrderBy() {
}
async function fetchData() {
if (!dataModule.value || !dataModule.value.b_viewtable || dataLoading.value) return;
if (!dataModule.value || !dataModule.value.b_viewtable) return;
const currentModule = dataModule.value;
const viewName = currentModule.b_viewtable;
const currentCode = getDataCode();
const currentPath = route.path;
const requestVersion = ++dataRequestVersion;
const conditions = [];
const fixedCondition = String(props.fixedSearchCondition || "").trim();
if (fixedCondition) conditions.push(`(${fixedCondition})`);
conditions.push(...searchConditions.value);
dataLoading.value = true;
dataError.value = "";
@@ -239,17 +253,18 @@ async function fetchData() {
viewName,
orderBy: resolveOrderBy(),
searchColumns: [],
searchCondition: searchConditions.value.join(" AND "),
searchCondition: conditions.join(" AND "),
pageNo: pagination.current,
pageSize: pagination.pageSize,
});
if (dataModule.value !== currentModule) return;
if (requestVersion !== dataRequestVersion || dataModule.value !== currentModule) return;
if (getDataCode() !== currentCode || route.path !== currentPath) return;
dataSource.value = res.data?.rows || [];
pagination.total = Number(res.data?.total ?? res.data?.row_count) || 0;
} catch (error) {
if (
requestVersion === dataRequestVersion &&
dataModule.value === currentModule &&
getDataCode() === currentCode &&
route.path === currentPath
@@ -260,6 +275,7 @@ async function fetchData() {
}
} finally {
if (
requestVersion === dataRequestVersion &&
dataModule.value === currentModule &&
getDataCode() === currentCode &&
route.path === currentPath
@@ -271,6 +287,7 @@ async function fetchData() {
// ── 公开方法 ──
async function initialize() {
dataRequestVersion++;
dataModule.value = null;
activeFields.value = [];
activeFieldById.value = new Map();
@@ -324,6 +341,56 @@ async function resetColumns() {
if (tableRef.value) tableRef.value.resetColumns();
}
async function saveColumnSettings() {
if (loading.value || columnSaving.value || !tableRef.value) return;
if (typeof tableRef.value.getColumnSettings !== "function") return;
const settings = tableRef.value.getColumnSettings();
if (!settings.length) {
message.info("当前没有可保存的栏位设置");
return;
}
const updates = [];
for (const row of settings) {
updates.push({
b_id: row.b_id,
b_xh: row.b_xh,
b_width: row.b_width,
});
}
columnSaving.value = true;
try {
await saveObjectApi([
{
table: "s_module_field_list",
key_field: "b_id",
updates,
},
]);
const currentById = new Map();
for (const row of listConfig.value) {
currentById.set(String(row.b_id), row);
}
const savedListConfig = [];
for (const saved of settings) {
const current = currentById.get(String(saved.b_id));
if (!current) continue;
savedListConfig.push({ ...current, b_xh: saved.b_xh, b_width: saved.b_width });
}
listConfig.value = savedListConfig;
message.success("栏位设置已保存");
} catch (error) {
let errorMessage = error;
if (error && error.message) errorMessage = error.message;
message.error("栏位设置保存失败:" + errorMessage);
} finally {
columnSaving.value = false;
}
}
function resolveRowKey(row) {
if (keyField.value) return row[keyField.value];
return JSON.stringify(row);
@@ -336,13 +403,26 @@ async function handleMoreAction({ key }) {
}
if (key === "reset-columns") {
await resetColumns();
message.success(appStore.t("common.columnsReset", "已恢复配置列宽"));
message.success(appStore.t("common.columnSettingsReset", "已恢复栏位设置"));
return;
}
if (key === "save-column-settings") {
await saveColumnSettings();
}
}
// ── 生命周期 ──
watch(() => [getDataCode(), route.path], initialize, { immediate: true });
watch(
() => props.fixedSearchCondition,
() => {
if (!dataModule.value) return;
pagination.current = 1;
fetchData();
},
);
watch(
() => dataSource.value,
(rows) => {
@@ -37,7 +37,7 @@ const expansionOptions = computed(() =>
const expandConfig = computed(() => ({ height: Number(expansionOptions.value.height) || 180 }));
function formatCellValue(value, field, optionInfo) {
if (value === null || value === undefined || value === "") return "-";
if (value === null || value === undefined || value === "") return "";
if (field.b_type === "date") return formatDateValue(value, false);
if (field.b_type === "datetime") return formatDateValue(value, true);
if (field.b_type === "checkbox") {
@@ -199,6 +199,35 @@ function resetColumns() {
tableRef.value?.initVirtualScroll?.();
}
function getColumnSettings() {
const configByFieldId = new Map();
for (const config of props.listConfig || []) {
configByFieldId.set(String(config.b_field_id), config);
}
const settings = [];
let order = 10;
for (const column of columns.value) {
if (!column.dataIndex || String(column.dataIndex).startsWith("__")) continue;
const field = props.fields.find((item) => item.b_field === column.dataIndex);
if (!field) continue;
const config = configByFieldId.get(String(field.b_id));
if (!config) continue;
const parsedWidth = Number.parseFloat(String(column.width));
let columnWidth = config.b_width;
if (Number.isFinite(parsedWidth) && parsedWidth > 0) {
columnWidth = Math.round(parsedWidth);
}
settings.push({
...config,
b_xh: order,
b_width: columnWidth,
});
order += 10;
}
return settings;
}
function handleCellClick(event, record, column, meta) {
if (!column?.dataIndex || String(column.dataIndex).startsWith("__")) return;
const field = props.fields.find((item) => item.b_field === column.dataIndex);
@@ -251,7 +280,11 @@ watch(
},
);
defineExpose({ resetColumns, clearSelectedArea: () => tableRef.value?.clearSelectedArea?.() });
defineExpose({
resetColumns,
getColumnSettings,
clearSelectedArea: () => tableRef.value?.clearSelectedArea?.(),
});
</script>
<template>
@@ -226,7 +226,7 @@ watch(() => props.queryItems, initializeDrafts, { immediate: true });
<Search :size="15" />
{{ appStore.t("common.search", "查询") }}
</a-button>
<a-button :disabled="loading" @click="handleAdvancedReset">
<a-button type="text" :disabled="loading" @click="handleAdvancedReset">
<RotateCcw :size="15" />
{{ appStore.t("common.reset", "重置") }}
</a-button>
@@ -15,8 +15,10 @@ const props = defineProps({
noDataFull: { type: Boolean, default: true },
showOverflow: { type: Boolean, default: true },
columns: { type: Array, default: () => [] },
// 序号列:false=关闭;true=默认;{ title, width }=自定义
seq: { type: [Boolean, Object], default: false },
// 序号列默认显示;false=关闭;{ title, width }=自定义
seq: { type: [Boolean, Object], default: true },
// 列头拖拽默认开启;false=关闭;{ mode, disabled }=自定义
columnDraggable: { type: [Boolean, Object], default: true },
// 行多选:{ selectedRowKeys, onChange },提供时在最前列插入复选框列
rowSelection: { type: Object, default: null },
dataSource: { type: Array, default: () => [] },
@@ -137,6 +139,27 @@ const seqConfig = computed(() => {
width: Number(cfg.width ?? 50),
};
});
const columnDragConfig = computed(() => {
if (!props.columnDraggable) return false;
const configured = props.columnDraggable === true ? {} : props.columnDraggable;
return {
...configured,
disabled: (column) => {
if (
isSelectionColumn(column) ||
column.type === "seq" ||
column.type === "dragRow" ||
column.type === "expand" ||
column.dataIndex === "" ||
column.dataIndex === "__actions__"
) {
return true;
}
if (typeof configured.disabled === "function") return configured.disabled(column);
return Boolean(configured.disabled);
},
};
});
const hasRowSelection = computed(() => Boolean(props.rowSelection));
const dragRowColumn = {
type: "dragRow",
@@ -158,8 +181,6 @@ function getHeaderAlign(column) {
const displayColumns = computed(() => {
const cols = props.columns.map((col) => resolveCustomCell(col, editingCtx));
if (props.rowDraggable) cols.unshift(dragRowColumn);
if (hasRowSelection.value) cols.unshift(selectionColumn);
const seq = seqConfig.value;
if (seq.enabled) {
cols.unshift({
@@ -170,6 +191,8 @@ const displayColumns = computed(() => {
resizable: false,
});
}
if (props.rowDraggable) cols.unshift(dragRowColumn);
if (hasRowSelection.value) cols.unshift(selectionColumn);
return cols.map((column) => ({
...column,
headerAlign: getHeaderAlign(column),
@@ -435,6 +458,7 @@ defineExpose({
:virtual-x="virtualX"
:auto-resize="false"
:col-resizable="colResizable"
:header-drag="columnDragConfig"
:columns="displayColumns"
@update:columns="onColumnsUpdate"
:data-source="dataSource"