20260729173317
This commit is contained in:
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"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, h, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { House, PanelLeftClose, PanelLeftOpen, Settings } from "@lucide/vue";
|
||||
import { Database, House, PanelLeftClose, PanelLeftOpen, Settings } from "@lucide/vue";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { usePermissionStore } from "@/stores/permissions";
|
||||
|
||||
@@ -10,7 +10,7 @@ const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
|
||||
const openKeys = ref(["system"]);
|
||||
const openKeys = ref(["base", "system"]);
|
||||
const effectiveCollapsed = computed(() => appStore.sidebarCollapsed);
|
||||
function moduleLabel(moduleId, path, fallback) {
|
||||
const module = permissionStore.modules.find(
|
||||
@@ -29,6 +29,13 @@ function canAccessMenu(item) {
|
||||
return true;
|
||||
}
|
||||
const menuItems = computed(() => {
|
||||
const basePages = [
|
||||
{
|
||||
key: "/base/othercompany",
|
||||
label: moduleLabel("base_othercompany", "/base/othercompany", "合作伙伴"),
|
||||
moduleId: "base_othercompany",
|
||||
},
|
||||
].filter(canAccessMenu);
|
||||
const systemPages = [
|
||||
{
|
||||
key: "/system/module-management",
|
||||
@@ -53,6 +60,13 @@ const menuItems = computed(() => {
|
||||
label: appStore.t("common.dashboard", "工作台"),
|
||||
},
|
||||
];
|
||||
if (basePages.length)
|
||||
items.push({
|
||||
key: "base",
|
||||
icon: menuIcon(Database),
|
||||
label: directoryLabel("基础数据"),
|
||||
children: basePages,
|
||||
});
|
||||
if (systemPages.length)
|
||||
items.push({
|
||||
key: "system",
|
||||
@@ -66,6 +80,8 @@ const selectedKeys = computed(() => [route.meta.menuKey || route.path]);
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
if (path.startsWith("/base") && !openKeys.value.includes("base"))
|
||||
openKeys.value = [...openKeys.value, "base"];
|
||||
if (path.startsWith("/system") && !openKeys.value.includes("system"))
|
||||
openKeys.value = [...openKeys.value, "system"];
|
||||
},
|
||||
|
||||
@@ -14,6 +14,18 @@ const routes = [
|
||||
component: () => import("@/views/dashboard/index.vue"),
|
||||
meta: { title: "工作台", menuKey: "/dashboard" },
|
||||
},
|
||||
{
|
||||
path: "base/othercompany",
|
||||
name: "base-othercompany",
|
||||
component: () => import("@/views/base/othercompany/index.vue"),
|
||||
meta: {
|
||||
title: "合作伙伴",
|
||||
section: "基础数据",
|
||||
menuKey: "/base/othercompany",
|
||||
moduleId: "base_othercompany",
|
||||
dataCode: "b_othercompany",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "system/module-management",
|
||||
name: "system-module-management",
|
||||
@@ -44,7 +56,7 @@ const routes = [
|
||||
title: "登录日志",
|
||||
section: "系统管理",
|
||||
menuKey: "/system/login-log",
|
||||
moduleId: "d_login_log",
|
||||
moduleId: "s_login_log",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -97,6 +97,16 @@ export function nextIdApi(count = 1) {
|
||||
return get("/data/nextid", { params: { count } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模块规则原子获取业务编号;count 大于 1 时一次返回多个编号。
|
||||
* @param {string} moduleId 数据模块 ID。
|
||||
* @param {number} [count=1] 需要获取的编号数量。
|
||||
* @returns {Promise<ApiResult<string|string[]>>} 业务编号或编号数组。
|
||||
*/
|
||||
export function nextCodeApi(moduleId, count = 1) {
|
||||
return get("/data/nextcode", { params: { moduleId: String(moduleId), count } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取表或视图的列结构,用于同步字段等场景。
|
||||
* @param {string} table 表名或视图名,必填,后端会校验标识符。
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { message } from "antdv-next";
|
||||
import { RefreshCw } from "@lucide/vue";
|
||||
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||
import FmsTree from "@/components/FmsTree.vue";
|
||||
import { escapeSqlText } from "@/components/fms-module-list/moduleListUtils";
|
||||
import { loadDataApi } from "@/services/api";
|
||||
import { buildTree, sortTree } from "@/utils/tree";
|
||||
|
||||
const ALL_CATEGORIES_KEY = "__all_categories__";
|
||||
const CATEGORY_COLUMNS = [
|
||||
"b_id",
|
||||
"b_parent_id",
|
||||
"b_code",
|
||||
"b_name",
|
||||
"b_level",
|
||||
"b_path",
|
||||
"b_xh",
|
||||
"b_canuse",
|
||||
];
|
||||
|
||||
const categories = ref([]);
|
||||
const categoryLoading = ref(false);
|
||||
const selectedCategoryKey = ref(ALL_CATEGORIES_KEY);
|
||||
const expandedCategoryKeys = ref([ALL_CATEGORIES_KEY]);
|
||||
|
||||
const categoryById = computed(
|
||||
() => new Map(categories.value.map((item) => [String(item.b_id), item])),
|
||||
);
|
||||
|
||||
const categoryTree = computed(() => {
|
||||
const roots = buildTree(categories.value, {
|
||||
mapNode: (item) => ({
|
||||
key: String(item.b_id),
|
||||
title: Number(item.b_canuse) === 1 ? item.b_name : `${item.b_name}(停用)`,
|
||||
b_id: String(item.b_id),
|
||||
b_parent_id: item.b_parent_id == null ? null : String(item.b_parent_id),
|
||||
b_xh: item.b_xh,
|
||||
children: [],
|
||||
}),
|
||||
});
|
||||
sortTree(roots);
|
||||
return [
|
||||
{
|
||||
key: ALL_CATEGORIES_KEY,
|
||||
title: "全部合作伙伴",
|
||||
b_xh: 0,
|
||||
children: roots,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const categoryFilterCondition = computed(() => {
|
||||
if (selectedCategoryKey.value === ALL_CATEGORIES_KEY) return "";
|
||||
const ids = collectDescendantIds(selectedCategoryKey.value);
|
||||
const literals = ids.map((id) => `N'${escapeSqlText(id)}'`);
|
||||
return literals.length ? `[b_cate_id] IN (${literals.join(", ")})` : "1 = 0";
|
||||
});
|
||||
|
||||
const columnExtensions = computed(() => ({
|
||||
b_cate_id: {
|
||||
formatter: ({ value }) => categoryById.value.get(String(value || ""))?.b_name || value || "",
|
||||
},
|
||||
}));
|
||||
|
||||
function collectDescendantIds(rootId) {
|
||||
const childrenByParent = new Map();
|
||||
for (const item of categories.value) {
|
||||
const parentId = item.b_parent_id == null ? "" : String(item.b_parent_id);
|
||||
if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
|
||||
childrenByParent.get(parentId).push(String(item.b_id));
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const visited = new Set();
|
||||
const pending = [String(rootId)];
|
||||
while (pending.length) {
|
||||
const id = pending.pop();
|
||||
if (visited.has(id)) continue;
|
||||
visited.add(id);
|
||||
result.push(id);
|
||||
for (const childId of childrenByParent.get(id) || []) pending.push(childId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadCategories() {
|
||||
categoryLoading.value = true;
|
||||
try {
|
||||
const response = await loadDataApi(
|
||||
"b_othercompany_bmfl",
|
||||
"",
|
||||
"b_path ASC, b_xh ASC, b_id ASC",
|
||||
CATEGORY_COLUMNS,
|
||||
);
|
||||
categories.value = response.data || [];
|
||||
|
||||
const existingKeys = new Set(categories.value.map((item) => String(item.b_id)));
|
||||
if (
|
||||
selectedCategoryKey.value !== ALL_CATEGORIES_KEY &&
|
||||
!existingKeys.has(selectedCategoryKey.value)
|
||||
) {
|
||||
selectedCategoryKey.value = ALL_CATEGORIES_KEY;
|
||||
}
|
||||
|
||||
const parentKeys = categories.value
|
||||
.map((item) => (item.b_parent_id == null ? "" : String(item.b_parent_id)))
|
||||
.filter(Boolean);
|
||||
expandedCategoryKeys.value = [
|
||||
...new Set([...expandedCategoryKeys.value, ALL_CATEGORIES_KEY, ...parentKeys]),
|
||||
];
|
||||
} catch (error) {
|
||||
message.error("分类加载失败:" + (error?.message || error));
|
||||
} finally {
|
||||
categoryLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onCategorySelect(keys) {
|
||||
if (keys.length) selectedCategoryKey.value = String(keys[0]);
|
||||
}
|
||||
|
||||
onMounted(loadCategories);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="othercompany-page">
|
||||
<a-splitter class="othercompany-splitter" lazy>
|
||||
<a-splitter-panel :default-size="250" :min="210" :max="380" collapsible>
|
||||
<aside class="category-panel">
|
||||
<header class="category-header">
|
||||
<div class="category-title">合作伙伴分类</div>
|
||||
<a-tooltip title="刷新分类">
|
||||
<a-button
|
||||
type="text"
|
||||
:disabled="categoryLoading"
|
||||
aria-label="刷新分类"
|
||||
@click="loadCategories"
|
||||
>
|
||||
<RefreshCw :size="15" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</header>
|
||||
<div class="category-tree-stage">
|
||||
<FmsTree
|
||||
v-model:expanded-keys="expandedCategoryKeys"
|
||||
:tree-data="categoryTree"
|
||||
:selected-keys="[selectedCategoryKey]"
|
||||
show-line
|
||||
@select="onCategorySelect"
|
||||
/>
|
||||
<div v-if="categoryLoading" class="category-loading"><a-spin size="small" /></div>
|
||||
</div>
|
||||
</aside>
|
||||
</a-splitter-panel>
|
||||
|
||||
<a-splitter-panel :min="520">
|
||||
<main class="partner-panel">
|
||||
<FmsModuleListPage
|
||||
data-code="b_othercompany"
|
||||
:fixed-search-condition="categoryFilterCondition"
|
||||
:column-extensions="columnExtensions"
|
||||
/>
|
||||
</main>
|
||||
</a-splitter-panel>
|
||||
</a-splitter>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.othercompany-page,
|
||||
.othercompany-splitter {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.othercompany-page {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
|
||||
.category-panel,
|
||||
.partner-panel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 6px 4px 12px;
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
.category-title {
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.category-header :deep(.ant-btn) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.category-tree-stage {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.category-tree-stage :deep(.ant-tree) {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.category-tree-stage :deep(.ant-tree-node-content-wrapper) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.category-loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: rgb(255 255 255 / 70%);
|
||||
}
|
||||
|
||||
.partner-panel :deep(.fms-module-list-page) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createTempId } from "@/utils/tempId";
|
||||
|
||||
function defaultFieldId(fields) {
|
||||
const field = (fields || []).find(
|
||||
(item) =>
|
||||
Number(item.b_canuse) === 1 &&
|
||||
item.b_field !== "b_id" &&
|
||||
(item.b_type === "input" || item.b_type === "textarea"),
|
||||
);
|
||||
return field?.b_id != null ? String(field.b_id) : null;
|
||||
}
|
||||
|
||||
export function createAutoCodeDraft(moduleId, fields, enabled = false) {
|
||||
return {
|
||||
b_id: createTempId(),
|
||||
b_module_id: String(moduleId),
|
||||
b_field_id: defaultFieldId(fields),
|
||||
b_prefix: "",
|
||||
b_date_format: "yyyyMM",
|
||||
b_separator: "-",
|
||||
b_sequence_width: 5,
|
||||
b_reset_type: "month",
|
||||
b_start_value: 1,
|
||||
b_current_period: null,
|
||||
b_current_value: 0,
|
||||
b_canuse: enabled ? 1 : 0,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { createAutoCodeDraft } from "../autoCode";
|
||||
|
||||
const DATE_FORMAT_OPTIONS = [
|
||||
{ label: "不使用日期", value: "" },
|
||||
{ label: "yyyy", value: "yyyy" },
|
||||
{ label: "yy", value: "yy" },
|
||||
{ label: "yyyyMM", value: "yyyyMM" },
|
||||
{ label: "yyMM", value: "yyMM" },
|
||||
{ label: "yyyyMMdd", value: "yyyyMMdd" },
|
||||
{ label: "yyyy-MM", value: "yyyy-MM" },
|
||||
{ label: "yyyy-MM-dd", value: "yyyy-MM-dd" },
|
||||
{ label: "yyyy/MM", value: "yyyy/MM" },
|
||||
{ label: "yyyy/MM/dd", value: "yyyy/MM/dd" },
|
||||
];
|
||||
|
||||
const RESET_OPTIONS = [
|
||||
{ label: "不重置", value: "none" },
|
||||
{ label: "每年", value: "year" },
|
||||
{ label: "每月", value: "month" },
|
||||
{ label: "每日", value: "day" },
|
||||
];
|
||||
|
||||
const props = defineProps({
|
||||
moduleId: { type: String, required: true },
|
||||
autoCode: { type: Array, default: () => [] },
|
||||
fields: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update"]);
|
||||
const localRow = ref(null);
|
||||
|
||||
const enabled = computed(() => Number(localRow.value?.b_canuse) === 1);
|
||||
|
||||
const fieldOptions = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (Number(field.b_canuse) !== 1) continue;
|
||||
if (field.b_field === "b_id") continue;
|
||||
if (field.b_type !== "input" && field.b_type !== "textarea") continue;
|
||||
options.push({
|
||||
label: `${field.b_name || field.b_field} (${field.b_field})`,
|
||||
value: String(field.b_id),
|
||||
});
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
function notifyUpdate() {
|
||||
emit("update", localRow.value ? [{ ...localRow.value }] : []);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.autoCode,
|
||||
(rows) => {
|
||||
const row = rows && rows.length ? rows[0] : null;
|
||||
localRow.value = row ? { ...row } : createAutoCodeDraft(props.moduleId, props.fields, false);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function setRowValue(field, value) {
|
||||
if (!localRow.value) return;
|
||||
localRow.value[field] = value;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function formatDate(date, format) {
|
||||
const yyyy = String(date.getFullYear());
|
||||
const yy = yyyy.slice(-2);
|
||||
const MM = pad(date.getMonth() + 1);
|
||||
const dd = pad(date.getDate());
|
||||
return String(format || "")
|
||||
.replaceAll("yyyy", yyyy)
|
||||
.replaceAll("yy", yy)
|
||||
.replaceAll("MM", MM)
|
||||
.replaceAll("dd", dd);
|
||||
}
|
||||
|
||||
function currentPeriod(date, resetType) {
|
||||
const yyyy = String(date.getFullYear());
|
||||
const MM = pad(date.getMonth() + 1);
|
||||
const dd = pad(date.getDate());
|
||||
if (resetType === "none") return "*";
|
||||
if (resetType === "year") return yyyy;
|
||||
if (resetType === "day") return yyyy + MM + dd;
|
||||
return yyyy + MM;
|
||||
}
|
||||
|
||||
const preview = computed(() => {
|
||||
if (!localRow.value) return "";
|
||||
const today = new Date();
|
||||
const startValue = Number(localRow.value.b_start_value) || 1;
|
||||
const storedValue = Number(localRow.value.b_current_value) || 0;
|
||||
const activePeriod = currentPeriod(today, localRow.value.b_reset_type);
|
||||
const sequence = localRow.value.b_current_period === activePeriod ? storedValue + 1 : startValue;
|
||||
const parts = [];
|
||||
if (localRow.value.b_prefix) parts.push(localRow.value.b_prefix);
|
||||
const dateText = formatDate(today, localRow.value.b_date_format);
|
||||
if (dateText) parts.push(dateText);
|
||||
parts.push(String(sequence).padStart(Number(localRow.value.b_sequence_width) || 1, "0"));
|
||||
return parts.join(localRow.value.b_separator || "");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auto-code-panel">
|
||||
<div class="panel-body">
|
||||
<a-form layout="vertical" class="auto-code-form" :disabled="loading">
|
||||
<template v-if="localRow">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="业务编号字段" :required="enabled">
|
||||
<a-select
|
||||
:value="String(localRow.b_field_id || '')"
|
||||
:options="fieldOptions"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_field_id', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="固定前缀">
|
||||
<a-input
|
||||
:value="localRow.b_prefix"
|
||||
:disabled="loading"
|
||||
:maxlength="100"
|
||||
@update:value="(value) => setRowValue('b_prefix', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日期格式">
|
||||
<a-auto-complete
|
||||
:value="localRow.b_date_format"
|
||||
:options="DATE_FORMAT_OPTIONS"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_date_format', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="分隔符">
|
||||
<a-input
|
||||
:value="localRow.b_separator"
|
||||
:disabled="loading"
|
||||
:maxlength="10"
|
||||
@update:value="(value) => setRowValue('b_separator', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="流水号位数" required>
|
||||
<a-input-number
|
||||
:value="localRow.b_sequence_width"
|
||||
:disabled="loading"
|
||||
:min="1"
|
||||
:max="18"
|
||||
:precision="0"
|
||||
@update:value="(value) => setRowValue('b_sequence_width', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="起始值" required>
|
||||
<a-input-number
|
||||
:value="localRow.b_start_value"
|
||||
:disabled="loading"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
@update:value="(value) => setRowValue('b_start_value', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="重置周期" required>
|
||||
<a-select
|
||||
:value="localRow.b_reset_type"
|
||||
:options="RESET_OPTIONS"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_reset_type', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-form-item label="编号预览">
|
||||
<div class="code-preview">{{ preview }}</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.auto-code-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.auto-code-form {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
.code-preview {
|
||||
min-height: 32px;
|
||||
padding: 4px 11px;
|
||||
border: 1px solid var(--fms-border, #d9d9d9);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-bg, #f5f5f5);
|
||||
color: var(--fms-text, #1f2937);
|
||||
font-family: Consolas, monospace;
|
||||
line-height: 22px;
|
||||
}
|
||||
</style>
|
||||
@@ -47,7 +47,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
@@ -78,6 +77,13 @@ const columns = shallowRef([
|
||||
options: () => availableFields.value,
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: "b_visible",
|
||||
title: "显示",
|
||||
width: 60,
|
||||
align: "center",
|
||||
editor: { type: "checkbox" },
|
||||
},
|
||||
{
|
||||
dataIndex: "b_required",
|
||||
title: "必填",
|
||||
|
||||
@@ -46,7 +46,11 @@ watch(
|
||||
);
|
||||
|
||||
function emitUpdate() {
|
||||
emit("update", localFields.value);
|
||||
const rows = [];
|
||||
for (const row of localFields.value) {
|
||||
rows.push({ ...row });
|
||||
}
|
||||
emit("update", rows);
|
||||
}
|
||||
|
||||
// 编辑器已直接更新当前行,这里只向父级同步草稿。
|
||||
|
||||
@@ -47,7 +47,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -59,7 +59,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -27,8 +27,10 @@ import ModuleFieldsPanel from "./components/ModuleFieldsPanel.vue";
|
||||
import ModuleListConfigPanel from "./components/ModuleListConfigPanel.vue";
|
||||
import ModuleEditConfigPanel from "./components/ModuleEditConfigPanel.vue";
|
||||
import ModuleQueryConfigPanel from "./components/ModuleQueryConfigPanel.vue";
|
||||
import ModuleAutoCodePanel from "./components/ModuleAutoCodePanel.vue";
|
||||
import ModulePowersPanel from "./components/ModulePowersPanel.vue";
|
||||
import ModuleI18nPanel from "./components/ModuleI18nPanel.vue";
|
||||
import { createAutoCodeDraft } from "./autoCode";
|
||||
|
||||
// ---- 状态 ----
|
||||
const appStore = useAppStore();
|
||||
@@ -63,6 +65,7 @@ const originals = ref({
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
});
|
||||
|
||||
@@ -74,6 +77,7 @@ const drafts = ref({
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
});
|
||||
|
||||
@@ -90,6 +94,7 @@ function setI18nPanelRef(instance) {
|
||||
|
||||
// 当前选中模块对象(从 drafts.module 获取)
|
||||
const selectedModule = computed(() => drafts.value.module);
|
||||
const autoCodeEnabled = computed(() => Number(drafts.value.autoCode?.[0]?.b_canuse) === 1);
|
||||
const busy = computed(() => configLoading.value || configSaving.value || configSyncing.value);
|
||||
const syncDisabled = computed(
|
||||
() => busy.value || !String(selectedModule.value?.b_viewtable || "").trim(),
|
||||
@@ -185,6 +190,7 @@ const visibleTabs = computed(() => {
|
||||
{ key: "list", tab: "列表配置" },
|
||||
{ key: "edit", tab: "编辑配置" },
|
||||
{ key: "query", tab: "查询配置" },
|
||||
{ key: "auto-code", tab: "自动编码" },
|
||||
);
|
||||
}
|
||||
if (type === "page") {
|
||||
@@ -252,6 +258,7 @@ async function loadModuleConfig(moduleId) {
|
||||
let listResponse;
|
||||
let editResponse;
|
||||
let queryResponse;
|
||||
let autoCodeResponse;
|
||||
let powersResponse;
|
||||
|
||||
if (moduleType === "data") {
|
||||
@@ -262,6 +269,7 @@ async function loadModuleConfig(moduleId) {
|
||||
loadDataApi("s_module_field_list", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_field_edit", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_field_query", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_auto_code", `b_module_id = ${id}`, "b_id ASC"),
|
||||
]);
|
||||
moduleResponse = responses[0];
|
||||
fieldsResponse = responses[1];
|
||||
@@ -269,6 +277,7 @@ async function loadModuleConfig(moduleId) {
|
||||
listResponse = responses[3];
|
||||
editResponse = responses[4];
|
||||
queryResponse = responses[5];
|
||||
autoCodeResponse = responses[6];
|
||||
} else if (moduleType === "page") {
|
||||
const responses = await Promise.all([
|
||||
loadDataApi("s_module", `b_id = ${id}`),
|
||||
@@ -289,6 +298,7 @@ async function loadModuleConfig(moduleId) {
|
||||
listConfig: listResponse ? listResponse.data || [] : [],
|
||||
editConfig: editResponse ? editResponse.data || [] : [],
|
||||
queryConfig: queryResponse ? queryResponse.data || [] : [],
|
||||
autoCode: autoCodeResponse ? autoCodeResponse.data || [] : [],
|
||||
powers: powersResponse ? powersResponse.data || [] : [],
|
||||
};
|
||||
originals.value = original;
|
||||
@@ -309,11 +319,20 @@ async function loadModuleConfig(moduleId) {
|
||||
}
|
||||
|
||||
// ---- 树节点选择 ----
|
||||
function onTreeSelect(keys) {
|
||||
function onTreeSelect(keys, info) {
|
||||
if (busy.value) return;
|
||||
if (keys.length > 0) {
|
||||
selectedModuleId.value = String(keys[0]);
|
||||
}
|
||||
const nodeKey = info?.node?.key ?? keys[0];
|
||||
if (nodeKey == null) return;
|
||||
|
||||
const key = String(nodeKey);
|
||||
selectedModuleId.value = key;
|
||||
|
||||
const node = findNode(key);
|
||||
if (!node?.children?.length) return;
|
||||
const expandedKeys = expandedTreeKeys.value.map(String);
|
||||
expandedTreeKeys.value = expandedKeys.includes(key)
|
||||
? expandedKeys.filter((item) => item !== key)
|
||||
: [...expandedKeys, key];
|
||||
}
|
||||
|
||||
watch(selectedModuleId, (id) => {
|
||||
@@ -337,6 +356,128 @@ function onUpdate(section, newData) {
|
||||
drafts.value[section] = newData;
|
||||
}
|
||||
|
||||
function isFieldEnabled(field) {
|
||||
if (!field) return false;
|
||||
return Number(field.b_canuse) === 1;
|
||||
}
|
||||
|
||||
function applyDisabledFieldDefaults(previousFields, nextFields) {
|
||||
const previousEnabledStates = new Map();
|
||||
for (const field of previousFields || []) {
|
||||
if (field.b_id != null) {
|
||||
previousEnabledStates.set(String(field.b_id), isFieldEnabled(field));
|
||||
}
|
||||
}
|
||||
|
||||
const disabledFieldIds = new Set();
|
||||
for (const field of nextFields || []) {
|
||||
if (field.b_id == null) continue;
|
||||
const fieldId = String(field.b_id);
|
||||
if (previousEnabledStates.get(fieldId) && !isFieldEnabled(field)) {
|
||||
disabledFieldIds.add(fieldId);
|
||||
}
|
||||
}
|
||||
if (disabledFieldIds.size === 0) return;
|
||||
|
||||
for (const row of drafts.value.listConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_visible = 0;
|
||||
}
|
||||
for (const row of drafts.value.editConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_visible = 0;
|
||||
}
|
||||
for (const row of drafts.value.queryConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_canuse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function onFieldsUpdate(newFields) {
|
||||
applyDisabledFieldDefaults(drafts.value.fields, newFields);
|
||||
drafts.value.fields = newFields;
|
||||
}
|
||||
|
||||
function onAutoCodeUpdate(rows) {
|
||||
const previousRule = drafts.value.autoCode?.[0];
|
||||
const previousFieldId =
|
||||
previousRule && Number(previousRule.b_canuse) === 1 ? previousRule.b_field_id : null;
|
||||
drafts.value.autoCode = rows;
|
||||
const rule = rows && rows.length ? rows[0] : null;
|
||||
const nextFieldId = rule && Number(rule.b_canuse) === 1 ? rule.b_field_id : null;
|
||||
if (previousFieldId != null && String(previousFieldId) !== String(nextFieldId)) {
|
||||
const original = (originals.value.editConfig || []).find(
|
||||
(config) => String(config.b_field_id) === String(previousFieldId),
|
||||
);
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(previousFieldId)) {
|
||||
config.b_readonly = Number(original?.b_readonly) === 1 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextFieldId == null) return;
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(nextFieldId)) {
|
||||
config.b_readonly = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAutoCodeEnabled(enabled) {
|
||||
const current = drafts.value.autoCode?.[0];
|
||||
const rule = current
|
||||
? { ...current, b_canuse: enabled ? 1 : 0 }
|
||||
: createAutoCodeDraft(selectedModule.value?.b_id, drafts.value.fields, enabled);
|
||||
onAutoCodeUpdate([rule]);
|
||||
}
|
||||
|
||||
function validateAutoCode() {
|
||||
const rows = drafts.value.autoCode || [];
|
||||
const rule = rows.length ? rows[0] : null;
|
||||
if (!rule || Number(rule.b_canuse) !== 1) return "";
|
||||
if (!String(selectedModule.value?.b_savetable || "").trim()) {
|
||||
return "启用自动编码前请先配置保存表";
|
||||
}
|
||||
const field = (drafts.value.fields || []).find(
|
||||
(item) => String(item.b_id) === String(rule.b_field_id),
|
||||
);
|
||||
if (!field || Number(field.b_canuse) !== 1) return "请选择已启用的业务编号字段";
|
||||
if (field.b_type !== "input" && field.b_type !== "textarea") {
|
||||
return "业务编号字段必须使用文本控件类型";
|
||||
}
|
||||
if (!Number.isInteger(Number(rule.b_start_value)) || Number(rule.b_start_value) < 0) {
|
||||
return "起始值必须是大于或等于 0 的整数";
|
||||
}
|
||||
if (!["none", "year", "month", "day"].includes(rule.b_reset_type)) {
|
||||
return "请选择正确的重置周期";
|
||||
}
|
||||
const width = Number(rule.b_sequence_width);
|
||||
if (!Number.isInteger(width) || width < 1 || width > 18) {
|
||||
return "流水号位数必须是 1 到 18 之间的整数";
|
||||
}
|
||||
if (String(rule.b_prefix || "").length > 100) return "固定前缀长度不能超过 100 个字符";
|
||||
if (String(rule.b_separator || "").length > 10) return "分隔符长度不能超过 10 个字符";
|
||||
const dateFormat = String(rule.b_date_format || "").trim();
|
||||
if (dateFormat.length > 30) return "日期格式长度不能超过 30 个字符";
|
||||
const dateLiterals = dateFormat
|
||||
.replaceAll("yyyy", "")
|
||||
.replaceAll("yy", "")
|
||||
.replaceAll("MM", "")
|
||||
.replaceAll("dd", "");
|
||||
if (/[A-Za-z]/.test(dateLiterals)) return "日期格式只支持 yyyy、yy、MM 和 dd";
|
||||
const hasYear = dateFormat.includes("yyyy") || dateFormat.includes("yy");
|
||||
const hasMonth = dateFormat.includes("MM");
|
||||
const hasDay = dateFormat.includes("dd");
|
||||
if (rule.b_reset_type === "year" && !hasYear) return "按年重置时,日期格式必须包含年份";
|
||||
if (rule.b_reset_type === "month" && (!hasYear || !hasMonth)) {
|
||||
return "按月重置时,日期格式必须包含年份和月份";
|
||||
}
|
||||
if (rule.b_reset_type === "day" && (!hasYear || !hasMonth || !hasDay)) {
|
||||
return "按日重置时,日期格式必须包含年月日";
|
||||
}
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(rule.b_field_id)) config.b_readonly = 1;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function onSetField(field, value) {
|
||||
if (drafts.value.module) {
|
||||
drafts.value.module[field] = value;
|
||||
@@ -388,6 +529,7 @@ async function onAddModule(parentId, moduleType = "directory") {
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
};
|
||||
drafts.value = cloneData(originals.value);
|
||||
@@ -469,6 +611,7 @@ async function onDeleteNodeById(moduleId) {
|
||||
deleteByModuleId("s_module_field_list"),
|
||||
deleteByModuleId("s_module_field_edit"),
|
||||
deleteByModuleId("s_module_field_query"),
|
||||
deleteByModuleId("s_module_auto_code"),
|
||||
deleteByModuleId("s_module_power"),
|
||||
deleteByModuleId("s_module_field"),
|
||||
deleteByModuleId("s_module_field_group"),
|
||||
@@ -497,6 +640,12 @@ async function saveAll() {
|
||||
try {
|
||||
const saveReqs = [];
|
||||
|
||||
const autoCodeError = validateAutoCode();
|
||||
if (autoCodeError) {
|
||||
message.warning(autoCodeError);
|
||||
return;
|
||||
}
|
||||
|
||||
// s_module 主表
|
||||
const moduleData = drafts.value.module;
|
||||
const moduleOriginal = originals.value.module;
|
||||
@@ -543,18 +692,25 @@ async function saveAll() {
|
||||
saveReqs.push(moduleRequest);
|
||||
}
|
||||
|
||||
// 子表:fields, listConfig, editConfig, queryConfig, powers
|
||||
// 子表:fields, listConfig, editConfig, queryConfig, autoCode, powers
|
||||
const subTables = [
|
||||
["s_module_field", "fields"],
|
||||
["s_module_field_list", "listConfig"],
|
||||
["s_module_field_edit", "editConfig"],
|
||||
["s_module_field_query", "queryConfig"],
|
||||
["s_module_auto_code", "autoCode"],
|
||||
["s_module_power", "powers"],
|
||||
];
|
||||
for (const [table, section] of subTables) {
|
||||
const current = drafts.value[section] || [];
|
||||
const previous = originals.value[section] || [];
|
||||
const changes = diffRows(current, previous);
|
||||
if (table === "s_module_auto_code") {
|
||||
changes.updates = changes.updates.map((row) => {
|
||||
const { b_current_period: _period, b_current_value: _value, ...config } = row;
|
||||
return config;
|
||||
});
|
||||
}
|
||||
const req = tableChange(table, changes);
|
||||
if (hasChanges(req)) {
|
||||
await allocateTemporaryIds([req.inserts || [], req.updates || [], req.deletes || []]);
|
||||
@@ -654,6 +810,7 @@ function buildMissingDefaultConfigs(module, fields) {
|
||||
b_required: 0,
|
||||
b_readonly: 0,
|
||||
b_disabled: 0,
|
||||
b_visible: 1,
|
||||
b_group_id: null,
|
||||
b_xh: order,
|
||||
});
|
||||
@@ -787,6 +944,7 @@ async function syncFields() {
|
||||
}
|
||||
}
|
||||
|
||||
applyDisabledFieldDefaults(existingFields, mergedFields);
|
||||
const generated = buildMissingDefaultConfigs(module, mergedFields);
|
||||
const generatedSections = ["listConfig", "editConfig", "queryConfig"];
|
||||
let generatedCount = 0;
|
||||
@@ -898,6 +1056,15 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:disabled="busy || drafts.module?.b_module_type === 'data'"
|
||||
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
|
||||
/>
|
||||
<template v-if="drafts.module?.b_module_type === 'data'">
|
||||
<a-divider type="vertical" />
|
||||
<span>自动编码</span>
|
||||
<a-checkbox
|
||||
:checked="autoCodeEnabled"
|
||||
:disabled="busy"
|
||||
@update:checked="setAutoCodeEnabled"
|
||||
/>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
@@ -926,7 +1093,7 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('fields', v)"
|
||||
@update="onFieldsUpdate"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleListConfigPanel
|
||||
@@ -959,6 +1126,14 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleAutoCodePanel
|
||||
v-else-if="tab.key === 'auto-code'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:auto-code="drafts.autoCode"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onAutoCodeUpdate"
|
||||
/>
|
||||
<ModulePowersPanel
|
||||
v-else-if="tab.key === 'powers'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
|
||||
@@ -1,7 +1,93 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { message, Modal } from "antdv-next";
|
||||
import { Trash2 } from "@lucide/vue";
|
||||
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||
import { saveObjectApi } from "@/services/api";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const listRef = ref(null);
|
||||
const selectedRowKeys = ref([]);
|
||||
const saving = ref(false);
|
||||
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: (keys) => {
|
||||
selectedRowKeys.value = keys;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
async function reloadList() {
|
||||
selectedRowKeys.value = [];
|
||||
if (listRef.value) await listRef.value.reload();
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
if (saving.value || !selectedRowKeys.value.length) return;
|
||||
const keys = [...selectedRowKeys.value];
|
||||
Modal.confirm({
|
||||
title: `${appStore.t("common.confirmBatchDelete", "确定删除选中的记录?")} (${keys.length})`,
|
||||
okText: appStore.t("common.delete", "删除"),
|
||||
okType: "danger",
|
||||
cancelText: appStore.t("common.cancel", "取消"),
|
||||
onOk: async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
const deletes = keys.map((b_id) => ({ b_id }));
|
||||
await saveObjectApi([
|
||||
{
|
||||
table: "s_log_login",
|
||||
key_field: "b_id",
|
||||
inserts: [],
|
||||
updates: [],
|
||||
deletes,
|
||||
},
|
||||
]);
|
||||
message.success(appStore.t("common.deleteSuccess", "删除成功"));
|
||||
await reloadList();
|
||||
} catch (error) {
|
||||
message.error(
|
||||
appStore.t("common.deleteFailed", "删除失败") + ":" + (error?.message || error),
|
||||
);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FmsModuleListPage />
|
||||
<div class="login-log-page">
|
||||
<FmsModuleListPage
|
||||
ref="listRef"
|
||||
:row-selection="rowSelection"
|
||||
@loaded="selectedRowKeys = []"
|
||||
>
|
||||
<template #actions="{ loading }">
|
||||
<a-space>
|
||||
<a-button
|
||||
danger
|
||||
:disabled="loading || saving || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="16" />
|
||||
{{ appStore.t("common.batchDelete", "批量删除")
|
||||
}}{{ selectedRowKeys.length ? `(${selectedRowKeys.length})` : "" }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</FmsModuleListPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-log-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user