20260730173012
This commit is contained in:
1 parent
84673b3d3d
commit
4929b78434
25 files changed
+1796
-68
No files matched your search
@@ -1,5 +1,15 @@
|
||||
<script setup>
|
||||
import { computed, h, nextTick, reactive, ref, shallowRef, useSlots, watch } from "vue";
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
nextTick,
|
||||
onActivated,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
useSlots,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { message } from "antdv-next";
|
||||
import { Columns3, RefreshCw, Save } from "@lucide/vue";
|
||||
@@ -33,6 +43,9 @@ const slots = useSlots();
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const tableRef = ref(null);
|
||||
// keep-alive 中的缓存实例必须保留各自的路由上下文,不能跟随全局 route 变化。
|
||||
const instanceRoutePath = route.path;
|
||||
const instanceRouteDataCode = route.meta.dataCode;
|
||||
|
||||
// ── 内部状态 ──
|
||||
const dataModule = ref(null);
|
||||
@@ -91,12 +104,11 @@ function resolveKeyField(module, fields) {
|
||||
}
|
||||
|
||||
function getDataCode() {
|
||||
return String(props.dataCode || route.meta.dataCode || "").trim();
|
||||
return String(props.dataCode || instanceRouteDataCode || "").trim();
|
||||
}
|
||||
|
||||
async function loadModuleConfig() {
|
||||
const code = getDataCode();
|
||||
const currentPath = route.path;
|
||||
configLoading.value = true;
|
||||
configError.value = "";
|
||||
|
||||
@@ -122,7 +134,7 @@ async function loadModuleConfig() {
|
||||
throw new Error(`数据编码 ${runtimeData.b_code} 没有对应的启用页面`);
|
||||
}
|
||||
} else {
|
||||
const routePath = escapeSqlText(currentPath);
|
||||
const routePath = escapeSqlText(instanceRoutePath);
|
||||
const pageRes = await loadDataApi(
|
||||
"s_module",
|
||||
`b_route = N'${routePath}' AND b_canuse = 1`,
|
||||
@@ -160,7 +172,7 @@ async function loadModuleConfig() {
|
||||
"b_xh ASC, b_id ASC",
|
||||
),
|
||||
]);
|
||||
if (getDataCode() !== code || route.path !== currentPath) return false;
|
||||
if (getDataCode() !== code) return false;
|
||||
|
||||
const fields = [];
|
||||
const fieldById = new Map();
|
||||
@@ -205,12 +217,12 @@ async function loadModuleConfig() {
|
||||
keyField.value = resolveKeyField(runtimeData, fields);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (getDataCode() === code && route.path === currentPath) {
|
||||
if (getDataCode() === code) {
|
||||
configError.value = error?.message || String(error);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (getDataCode() === code && route.path === currentPath) {
|
||||
if (getDataCode() === code) {
|
||||
configLoading.value = false;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +251,6 @@ async function fetchData() {
|
||||
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();
|
||||
@@ -259,15 +270,14 @@ async function fetchData() {
|
||||
});
|
||||
|
||||
if (requestVersion !== dataRequestVersion || dataModule.value !== currentModule) return;
|
||||
if (getDataCode() !== currentCode || route.path !== currentPath) return;
|
||||
if (getDataCode() !== currentCode) 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
|
||||
getDataCode() === currentCode
|
||||
) {
|
||||
dataSource.value = [];
|
||||
pagination.total = 0;
|
||||
@@ -277,8 +287,7 @@ async function fetchData() {
|
||||
if (
|
||||
requestVersion === dataRequestVersion &&
|
||||
dataModule.value === currentModule &&
|
||||
getDataCode() === currentCode &&
|
||||
route.path === currentPath
|
||||
getDataCode() === currentCode
|
||||
) {
|
||||
dataLoading.value = false;
|
||||
}
|
||||
@@ -412,7 +421,19 @@ async function handleMoreAction({ key }) {
|
||||
}
|
||||
|
||||
// ── 生命周期 ──
|
||||
watch(() => [getDataCode(), route.path], initialize, { immediate: true });
|
||||
let hasActivated = false;
|
||||
void initialize();
|
||||
|
||||
onActivated(async () => {
|
||||
if (!hasActivated) {
|
||||
hasActivated = true;
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
tableRef.value?.refreshLayout?.();
|
||||
});
|
||||
|
||||
watch(() => props.dataCode, initialize);
|
||||
|
||||
watch(
|
||||
() => props.fixedSearchCondition,
|
||||
|
||||
@@ -41,7 +41,7 @@ function formatCellValue(value, field, optionInfo) {
|
||||
if (field.b_type === "date") return formatDateValue(value, false);
|
||||
if (field.b_type === "datetime") return formatDateValue(value, true);
|
||||
if (field.b_type === "checkbox") {
|
||||
return h(Checkbox, { checked: value === true || Number(value) === 1, disabled: true });
|
||||
return h(Checkbox, { checked: value === true || Number(value) === 1 });
|
||||
}
|
||||
if (field.b_type === "select") {
|
||||
const option = optionInfo.options.find((item) => String(item.value) === String(value));
|
||||
@@ -199,6 +199,10 @@ function resetColumns() {
|
||||
tableRef.value?.initVirtualScroll?.();
|
||||
}
|
||||
|
||||
function refreshLayout() {
|
||||
tableRef.value?.refreshLayout?.();
|
||||
}
|
||||
|
||||
function getColumnSettings() {
|
||||
const configByFieldId = new Map();
|
||||
for (const config of props.listConfig || []) {
|
||||
@@ -282,6 +286,7 @@ watch(
|
||||
|
||||
defineExpose({
|
||||
resetColumns,
|
||||
refreshLayout,
|
||||
getColumnSettings,
|
||||
clearSelectedArea: () => tableRef.value?.clearSelectedArea?.(),
|
||||
});
|
||||
|
||||
@@ -85,6 +85,28 @@ function clearActiveEditing() {
|
||||
if (state) state.value = { editing: false, focusCol: null, initialValues: null };
|
||||
}
|
||||
|
||||
// 父级可能在 change 后回灌同 rowKey 的新对象,编辑状态需要随行对象迁移。
|
||||
function rebindActiveEditingRow(rows) {
|
||||
if (!activeEditingRow || rows.includes(activeEditingRow)) return;
|
||||
|
||||
const previousRow = activeEditingRow;
|
||||
const previousState = rowEditingStates.get(previousRow)?.value;
|
||||
const previousKey = getRowKey(previousRow);
|
||||
const replacement =
|
||||
previousKey == null ? null : rows.find((row) => String(getRowKey(row)) === String(previousKey));
|
||||
|
||||
clearActiveEditing();
|
||||
if (!replacement || !previousState?.editing) return;
|
||||
|
||||
const state = getRowEditingState(replacement);
|
||||
activeEditingRow = replacement;
|
||||
state.value = {
|
||||
editing: true,
|
||||
focusCol: previousState.focusCol,
|
||||
initialValues: captureEditableValues(replacement),
|
||||
};
|
||||
}
|
||||
|
||||
function activateEditing(row, focusCol) {
|
||||
if (!props.editable || !row) return;
|
||||
const state = getRowEditingState(row);
|
||||
@@ -402,7 +424,7 @@ function handleAreaSelectionChange(ranges) {
|
||||
watch(
|
||||
[() => props.activeRowKey, () => props.dataSource],
|
||||
async ([rowKey]) => {
|
||||
if (activeEditingRow && !props.dataSource.includes(activeEditingRow)) clearActiveEditing();
|
||||
rebindActiveEditingRow(props.dataSource);
|
||||
await nextTick();
|
||||
if (rowKey == null) {
|
||||
tableRef.value?.setCurrentRow?.(undefined, { silent: true });
|
||||
|
||||
@@ -1,7 +1,42 @@
|
||||
import { h } from "vue";
|
||||
import { h, toRaw } from "vue";
|
||||
import { Input, Select } from "antdv-next";
|
||||
|
||||
const stop = (event) => event.stopPropagation();
|
||||
const inputDrafts = new WeakMap();
|
||||
|
||||
function getInputDraft(row, dataIndex) {
|
||||
const drafts = inputDrafts.get(row);
|
||||
return drafts?.has(dataIndex) ? drafts.get(dataIndex) : row[dataIndex];
|
||||
}
|
||||
|
||||
function setInputDraft(row, dataIndex, value) {
|
||||
let drafts = inputDrafts.get(row);
|
||||
if (!drafts) {
|
||||
drafts = new Map();
|
||||
inputDrafts.set(row, drafts);
|
||||
}
|
||||
drafts.set(dataIndex, value);
|
||||
}
|
||||
|
||||
function clearInputDraft(row, dataIndex) {
|
||||
const drafts = inputDrafts.get(row);
|
||||
if (!drafts) return;
|
||||
drafts.delete(dataIndex);
|
||||
if (!drafts.size) inputDrafts.delete(row);
|
||||
}
|
||||
|
||||
function commitInputDraft(row, dataIndex, value) {
|
||||
clearInputDraft(row, dataIndex);
|
||||
row[dataIndex] = value;
|
||||
}
|
||||
|
||||
function preserveInputDraft(row, dataIndex) {
|
||||
const drafts = inputDrafts.get(row);
|
||||
if (!drafts?.has(dataIndex)) return;
|
||||
// 卸载兜底不触发整表更新;正常路径会在 blur 中响应式提交并通知父级。
|
||||
toRaw(row)[dataIndex] = drafts.get(dataIndex);
|
||||
clearInputDraft(row, dataIndex);
|
||||
}
|
||||
|
||||
function resolveOptions(editor) {
|
||||
return typeof editor.options === "function" ? editor.options() : (editor.options ?? []);
|
||||
@@ -52,15 +87,16 @@ function createInput(row, col, editor, ctx) {
|
||||
const focus = createFocusHandler(ctx, row, col, true);
|
||||
return h(Input, {
|
||||
type: editor.inputType || "text",
|
||||
value: row[col.dataIndex] ?? "",
|
||||
value: getInputDraft(row, col.dataIndex) ?? "",
|
||||
size: "small",
|
||||
class: "fms-cell-editor fms-cell-editor-root",
|
||||
"aria-label": col.title || col.dataIndex,
|
||||
"onUpdate:value": (value) => {
|
||||
row[col.dataIndex] = value;
|
||||
setInputDraft(row, col.dataIndex, value);
|
||||
},
|
||||
onBlur: (event) => {
|
||||
const value = event.target.value;
|
||||
commitInputDraft(row, col.dataIndex, value);
|
||||
notifyChange(ctx, editor, row, col, value);
|
||||
},
|
||||
onKeydown: (event) => {
|
||||
@@ -75,6 +111,7 @@ function createInput(row, col, editor, ctx) {
|
||||
onClick: stop,
|
||||
onVnodeMounted: focus,
|
||||
onVnodeUpdated: focus,
|
||||
onVnodeBeforeUnmount: () => preserveInputDraft(row, col.dataIndex),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user