diff --git a/code/fms/.codebuddy/plans/checkbox-cell-unified-config_42430e7a.md b/code/fms/.codebuddy/plans/checkbox-cell-unified-config_42430e7a.md
new file mode 100644
index 00000000..5be413c8
--- /dev/null
+++ b/code/fms/.codebuddy/plans/checkbox-cell-unified-config_42430e7a.md
@@ -0,0 +1,96 @@
+---
+name: checkbox-cell-unified-config
+overview: "将模块管理各面板中的 checkbox 列从 `customCell: createCheckboxCell(...)` 重构为声明式 `editor: { type: \"checkbox\" }`,由 FmsTable 在显示态与编辑态都渲染可交互的 checkbox,面板不再手动导入渲染器。按钮(删除)暂不动,用户仍在考虑。"
+todos:
+ - id: make-checkbox-always-interactive
+ content: 在 createEditableCellRenderer.js 中将 checkbox 分支独立为始终可交互,复用 update 回调
+ status: completed
+ - id: update-panels-checkbox
+ content: 将 4 个面板 5 处 checkbox 列改为 editor:{type:'checkbox',onChange},并删除 createCheckboxCell 导入
+ status: completed
+ dependencies:
+ - make-checkbox-always-interactive
+ - id: remove-dead-renderers
+ content: 从 createCellRenderers.js 删除 createCheckboxCell 与 createSelectCell 死代码
+ status: completed
+ dependencies:
+ - update-panels-checkbox
+ - id: verify-no-residual
+ content: 全局搜索确认无 createCheckboxCell/createSelectCell 残留引用,并运行 read_lints
+ status: completed
+ dependencies:
+ - remove-dead-renderers
+---
+
+## 用户需求
+
+重构表格单元格渲染方式:对于布尔型字段(如 `b_visible`、`b_canuse`、`b_sortable`),在列配置里直接声明 `editor: { type: "checkbox" }` 即可,显示态与编辑态均为可直接点击的 checkbox,无需面板自己 import 并调用 `createCheckboxCell`。
+
+## 核心要点
+
+- checkbox 类列(共 5 处,跨越 4 个面板)从 `customCell: createCheckboxCell(...)` 改为声明式 `editor: { type: "checkbox", onChange: () => emitUpdate() }`。
+- 表格组件内部让 `checkbox` 类型始终可交互(显示态也是可点击 checkbox),不再区分编辑/非编辑态;值按 `col.dataIndex` 自动读写,默认 `checkedValue=1 / uncheckedValue=0`,与现有 1/0 布尔存储一致。
+- 删除按钮等 `createDeleteCell` 本次不重构,保持 `customCell` 现状(用户仍在考虑按钮方案)。
+- 重构后 `createCheckboxCell` 不再被任何面板引用,从 `createCellRenderers.js` 中移除;`createSelectCell` 已是死代码一并移除。
+
+## 技术栈与既有模式
+
+- 前端:Vue 3 `
diff --git a/code/fms/fms-vue/src/components/fms-table/utils/createActionsCellRenderer.js b/code/fms/fms-vue/src/components/fms-table/utils/createActionsCellRenderer.js
new file mode 100644
index 00000000..288d1bdf
--- /dev/null
+++ b/code/fms/fms-vue/src/components/fms-table/utils/createActionsCellRenderer.js
@@ -0,0 +1,117 @@
+import { h } from "vue";
+import { Button, Popconfirm, Dropdown } from "antdv-next";
+
+const stopBubble = (e) => e.stopPropagation();
+
+function resolve(val, row) {
+ return typeof val === "function" ? val(row) : val;
+}
+
+/**
+ * 将 actions 数组中的 type 映射为 antdv-next Button 的 type + danger 属性。
+ *
+ * type 语义是按钮颜色:
+ * "danger" → type="link" danger
+ * "primary" → type="primary"
+ * 其他 → 直接作为 Button type
+ */
+function mapButtonType(type) {
+ if (type === "danger") return { btnType: "link", danger: true };
+ return { btnType: type || "link", danger: false };
+}
+
+/**
+ * 声明式操作列渲染器。
+ *
+ * actions 数组每项:
+ * { type: "primary"|"danger"|"link"|..., label, confirm?, danger?, disabled?, visible?, onClick }
+ * { type: "more", label?:"更多", items: [{ key, label, danger?, disabled?, onClick }], visible? }
+ *
+ * @param {Array} actions
+ * @returns {Function} customCell 渲染函数
+ */
+export function createActionsCellRenderer(actions) {
+ return function ActionsCell({ row }) {
+ const visibleActions = actions.filter((a) => {
+ if (a.visible === undefined) return true;
+ return resolve(a.visible, row);
+ });
+
+ const children = visibleActions.map((action, idx) => {
+ // ── more 下拉菜单 ──
+ if (action.type === "more") {
+ const items = (action.items || []).map((item) => ({
+ key: item.key,
+ label: resolve(item.label, row),
+ danger: item.danger,
+ disabled: resolve(item.disabled, row),
+ }));
+ return h(
+ Dropdown,
+ {
+ key: idx,
+ menu: {
+ items,
+ onClick: ({ key }) => {
+ const item = action.items.find((i) => i.key === key);
+ item?.onClick?.(row);
+ },
+ },
+ trigger: ["click"],
+ },
+ {
+ default: () =>
+ h(
+ Button,
+ {
+ type: "link",
+ size: "small",
+ disabled: resolve(action.disabled, row),
+ onClick: stopBubble,
+ },
+ () => resolve(action.label, row) || "更多",
+ ),
+ },
+ );
+ }
+
+ // ── 普通按钮(可包含 confirm) ──
+ const { btnType, danger } = mapButtonType(action.type);
+ const btnProps = {
+ type: btnType,
+ size: "small",
+ danger: action.danger ?? danger,
+ disabled: resolve(action.disabled, row),
+ onClick: (e) => {
+ stopBubble(e);
+ if (!action.confirm) action.onClick?.(row);
+ },
+ };
+ const btnNode = h(Button, btnProps, () => resolve(action.label, row));
+
+ if (action.confirm) {
+ const confirmConf =
+ typeof action.confirm === "object" ? action.confirm : {};
+ return h(
+ Popconfirm,
+ {
+ key: idx,
+ title: resolve(confirmConf.title, row) ?? "确定?",
+ okText: resolve(confirmConf.okText, row),
+ cancelText: resolve(confirmConf.cancelText, row),
+ onConfirm: () => action.onClick?.(row),
+ },
+ { default: () => btnNode },
+ );
+ }
+
+ return h("span", { key: idx }, btnNode);
+ });
+
+ return h(
+ "span",
+ { style: { display: "inline-flex", gap: "4px", alignItems: "center" } },
+ children,
+ );
+ };
+}
diff --git a/code/fms/fms-vue/src/components/fms-table/utils/createCellRenderers.js b/code/fms/fms-vue/src/components/fms-table/utils/createCellRenderers.js
new file mode 100644
index 00000000..5febf1ea
--- /dev/null
+++ b/code/fms/fms-vue/src/components/fms-table/utils/createCellRenderers.js
@@ -0,0 +1,42 @@
+import { h } from "vue";
+
+// ── 静态常量(避免每次渲染重复创建) ────────────────────────
+
+const stopBubble = (e) => e.stopPropagation();
+
+// ── 基础工厂 ────────────────────────────────────────────────
+
+/**
+ * 通用 cell 渲染器工厂。
+ *
+ * 返回的函数是纯渲染函数,不保存 row 引用、不产生额外组件层级。
+ *
+ * @param {object} config
+ * @param {object} config.component Vue 组件(Button / Checkbox / Select …)
+ * @param {object} [config.props] 静态 props(创建一次,不随行变化)
+ * @param {(row, col, ctx) => object} [config.getProps] 动态 props(每次 cell 渲染调用)
+ * @param {(row, col, ctx) => object} [config.getChildren] 动态 children / slots
+ * @param {boolean} [config.stopPropagation=true] 自动注入 onClick stopPropagation
+ * @returns {Function} (ctx) => VNode customCell 渲染函数
+ */
+export function createCellRenderer({
+ component,
+ props = {},
+ getProps,
+ getChildren,
+ stopPropagation = true,
+}) {
+ return (ctx) => {
+ const merged = getProps ? { ...props, ...getProps(ctx.row, ctx.col, ctx) } : { ...props };
+
+ if (stopPropagation) {
+ const click = merged.onClick;
+ merged.onClick = click ? (e) => (e.stopPropagation(), click(e)) : stopBubble;
+ }
+
+ const children = getChildren ? getChildren(ctx.row, ctx.col, ctx) : undefined;
+ return h(component, merged, children);
+ };
+}
+
+
diff --git a/code/fms/fms-vue/src/components/fms-table/utils/createEditableCellRenderer.js b/code/fms/fms-vue/src/components/fms-table/utils/createEditableCellRenderer.js
index 9b702883..dbaa4a65 100644
--- a/code/fms/fms-vue/src/components/fms-table/utils/createEditableCellRenderer.js
+++ b/code/fms/fms-vue/src/components/fms-table/utils/createEditableCellRenderer.js
@@ -11,13 +11,24 @@ export function createEditableCellRenderer({ getRowKey, getActiveRowKey }) {
const activeRowKey = getActiveRowKey();
const isEditing = activeRowKey != null && String(getRowKey(row)) === String(activeRowKey);
+ const update = (value) => {
+ row[col.dataIndex] = value;
+ editor.onChange?.(value, row, col);
+ };
+
+ // checkbox 始终可交互(显示态与编辑态一致)
+ if (editor.type === "checkbox") {
+ const checkedValue = editor.checkedValue ?? 1;
+ const uncheckedValue = editor.uncheckedValue ?? 0;
+ return h(Checkbox, {
+ checked: cellValue === checkedValue,
+ disabled: resolveOption(editor.disabled, row),
+ onClick: (event) => event.stopPropagation(),
+ "onUpdate:checked": (checked) => update(checked ? checkedValue : uncheckedValue),
+ });
+ }
+
if (!isEditing) {
- if (editor.type === "checkbox") {
- return h(Checkbox, {
- checked: cellValue === (editor.checkedValue ?? 1),
- style: { pointerEvents: "none" },
- });
- }
if (editor.type === "select") {
const options = resolveOption(editor.options, row) || [];
const option = options.find((item) => item.value === cellValue);
@@ -26,14 +37,17 @@ export function createEditableCellRenderer({ getRowKey, getActiveRowKey }) {
return h("span", { class: "fms-cell-text" }, cellValue ?? "");
}
- const update = (value) => {
- row[col.dataIndex] = value;
- editor.onChange?.(value, row, col);
- };
const common = {
size: "small",
style: { width: "100%" },
onClick: (event) => event.stopPropagation(),
+ // 编辑态首次渲染自动聚焦
+ ref: (instance) => {
+ if (instance) {
+ const target = instance.focus ? instance : instance.$el;
+ target?.focus?.();
+ }
+ },
};
if (editor.type === "number") {
@@ -56,16 +70,6 @@ export function createEditableCellRenderer({ getRowKey, getActiveRowKey }) {
"onUpdate:value": update,
});
}
- if (editor.type === "checkbox") {
- const checkedValue = editor.checkedValue ?? 1;
- const uncheckedValue = editor.uncheckedValue ?? 0;
- return h(Checkbox, {
- checked: cellValue === checkedValue,
- disabled: resolveOption(editor.disabled, row),
- onClick: (event) => event.stopPropagation(),
- "onUpdate:checked": (checked) => update(checked ? checkedValue : uncheckedValue),
- });
- }
return h(Input, {
...common,
value: cellValue,
diff --git a/code/fms/fms-vue/src/layouts/components/AppSidebar.vue b/code/fms/fms-vue/src/layouts/components/AppSidebar.vue
index d2c2e4d7..a50a3cc9 100644
--- a/code/fms/fms-vue/src/layouts/components/AppSidebar.vue
+++ b/code/fms/fms-vue/src/layouts/components/AppSidebar.vue
@@ -15,6 +15,7 @@ const effectiveCollapsed = computed(() => appStore.sidebarCollapsed);
const menuItems = computed(() => {
const systemPages = [
{ key: "/system/module-management", label: "模块管理", moduleCode: "m_metadata" },
+ { key: "/system/i18n-type", label: "多语言类型", moduleCode: "m_i18n_type" },
].filter((item) => permissionStore.canModule(item.moduleCode));
const items = [{ key: "/dashboard", icon: menuIcon(House), label: "工作台" }];
if (systemPages.length)
diff --git a/code/fms/fms-vue/src/router/routes.js b/code/fms/fms-vue/src/router/routes.js
index e3d8661f..74db814f 100644
--- a/code/fms/fms-vue/src/router/routes.js
+++ b/code/fms/fms-vue/src/router/routes.js
@@ -35,6 +35,17 @@ const routes = [
moduleCode: "m_metadata",
},
},
+ {
+ path: "system/i18n-type",
+ name: "system-i18n-type",
+ component: () => import("@/views/system/i18n-type/index.vue"),
+ meta: {
+ title: "多语言类型",
+ section: "系统管理",
+ menuKey: "/system/i18n-type",
+ moduleCode: "m_i18n_type",
+ },
+ },
],
},
{
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleBasicPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleBasicPanel.vue
index ebc5124b..6421d86b 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleBasicPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleBasicPanel.vue
@@ -1,10 +1,14 @@
@@ -44,7 +127,7 @@ function setField(field, value) {
setField('b_code', v)"
+ @update:value="onCodeInput"
/>
@@ -75,16 +158,26 @@ function setField(field, value) {
-
+
- setField('b_i18n', v)"
- />
+
@@ -151,6 +244,25 @@ function setField(field, value) {
+
+
+
+
+
+
+
+
+
{{ item.b_name }} ({{ item.b_locale }})
+
+
+
+
+
@@ -171,4 +283,37 @@ function setField(field, value) {
.basic-form {
max-width: 960px;
}
+
+.i18n-key-row {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ .ant-input {
+ flex: 1;
+ min-width: 0;
+ }
+}
+
+.i18n-key-btn {
+ flex: none;
+}
+
+.i18n-entry-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+
+ .i18n-locale-label {
+ flex: none;
+ width: 140px;
+ color: #646a73;
+ font-size: 13px;
+ }
+
+ .i18n-value {
+ flex: 1;
+ }
+}
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleEditConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleEditConfigPanel.vue
index 060182ed..42934d24 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleEditConfigPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleEditConfigPanel.vue
@@ -3,7 +3,6 @@ import { computed, ref, watch } from "vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { createTempId } from "@/utils/tempId";
import FieldGroupManager from "./FieldGroupManager.vue";
-import { createDeleteCell, createSelectCell, createToggleCell } from "./cellRenderers";
const props = defineProps({
moduleId: { type: String, required: true },
@@ -17,6 +16,7 @@ const emit = defineEmits(["update", "updateGroups"]);
const localEdit = ref([]);
const groupManagerVisible = ref(false);
+const activeKey = ref(null);
watch(
() => props.editConfig,
@@ -30,11 +30,6 @@ function emitUpdate() {
emit("update", localEdit.value.map((r) => ({ ...r })));
}
-function setField(row, field, value) {
- row[field] = value === undefined || value === null ? null : value;
- emitUpdate();
-}
-
const availableFields = computed(() =>
(props.fields || [])
.filter((f) => f.b_canuse === 1)
@@ -72,7 +67,7 @@ function makeSwitchCol(field, title, width) {
title,
width: width || 60,
align: "center",
- customCell: createToggleCell(field, (row) => setField(row, field, row[field] === 1 ? 0 : 1)),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
};
}
@@ -81,12 +76,11 @@ const columns = [
dataIndex: "b_field_id",
title: "字段",
width: 160,
- customCell: createSelectCell({
- value: "b_field_id",
- getOptions: () => availableFields.value,
- onChange: (row, v) => setField(row, "b_field_id", v),
- placeholder: "选择字段",
- }),
+ editor: {
+ type: "select",
+ options: () => availableFields.value,
+ onChange: () => emitUpdate(),
+ },
},
makeSwitchCol("b_required", "必填"),
makeSwitchCol("b_readonly", "只读"),
@@ -95,12 +89,11 @@ const columns = [
dataIndex: "b_group_id",
title: "编辑分组",
width: 160,
- customCell: createSelectCell({
- value: "b_group_id",
- getOptions: () => groupOptions.value,
- onChange: (row, v) => setField(row, "b_group_id", v),
- placeholder: "选择分组",
- }),
+ editor: {
+ type: "select",
+ options: () => groupOptions.value,
+ onChange: () => emitUpdate(),
+ },
},
{ dataIndex: "b_xh", title: "排序号", width: 80 },
{
@@ -108,7 +101,7 @@ const columns = [
title: "操作",
width: 60,
align: "center",
- customCell: createDeleteCell((row) => deleteRow(row)),
+ actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }],
},
];
@@ -122,7 +115,13 @@ const columns = [
-
+ (activeKey = row.b_id)"
+ />
import { ref, watch } from "vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
-import { createSelectCell, createToggleCell } from "./cellRenderers";
const FIELD_TYPES = [
{ label: "输入框", value: "input" },
@@ -24,6 +23,7 @@ const props = defineProps({
const emit = defineEmits(["update", "sync"]);
const localFields = ref([]);
+const activeKey = ref(null);
watch(
() => props.fields,
@@ -37,11 +37,6 @@ function emitUpdate() {
emit("update", localFields.value.map((r) => ({ ...r })));
}
-function setField(row, field, value) {
- row[field] = value === "" ? null : value;
- emitUpdate();
-}
-
const columns = [
{ dataIndex: "b_field", title: "字段名", width: 140 },
{ dataIndex: "b_name", title: "字段名称", width: 140 },
@@ -49,11 +44,11 @@ const columns = [
dataIndex: "b_type",
title: "控件类型",
width: 130,
- customCell: createSelectCell({
- value: "b_type",
- getOptions: () => FIELD_TYPES,
- onChange: (row, v) => setField(row, "b_type", v),
- }),
+ editor: {
+ type: "select",
+ options: FIELD_TYPES,
+ onChange: () => emitUpdate(),
+ },
},
{ dataIndex: "b_default_value", title: "默认值", width: 120 },
{ dataIndex: "b_i18n", title: "多语言键", width: 140 },
@@ -62,7 +57,7 @@ const columns = [
title: "启用",
width: 70,
align: "center",
- customCell: createToggleCell("b_canuse", (row) => setField(row, "b_canuse", row.b_canuse === 1 ? 0 : 1)),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
},
];
@@ -81,7 +76,13 @@ const columns = [
-
+ (activeKey = row.b_id)"
+ />
@@ -97,13 +98,13 @@ const columns = [
display: flex;
flex: none;
gap: 8px;
- padding: 12px 20px;
+ padding: 12px 20px 0;
}
.panel-body {
flex: 1;
min-height: 0;
- padding: 16px 20px;
+ padding: 12px 20px 16px;
display: flex;
flex-direction: column;
}
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleI18nPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleI18nPanel.vue
index ed785b7e..5665b5bb 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleI18nPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleI18nPanel.vue
@@ -1,11 +1,10 @@
@@ -105,7 +146,7 @@ const columns = [
-
+ (activeKey = String(row.b_id))" />
@@ -121,13 +162,13 @@ const columns = [
display: flex;
flex: none;
gap: 8px;
- padding: 12px 20px;
+ padding: 12px 20px 0;
}
.panel-body {
flex: 1;
min-height: 0;
- padding: 16px 20px;
+ padding: 12px 20px 16px;
display: flex;
flex-direction: column;
}
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue
index 02bef05d..10b4df79 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleListConfigPanel.vue
@@ -3,7 +3,6 @@ import { computed, ref, watch } from "vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { createTempId } from "@/utils/tempId";
import FieldGroupManager from "./FieldGroupManager.vue";
-import { createDeleteCell, createSelectCell, createToggleCell } from "./cellRenderers";
const props = defineProps({
moduleId: { type: String, required: true },
@@ -17,6 +16,7 @@ const emit = defineEmits(["update", "updateGroups"]);
const localList = ref([]);
const groupManagerVisible = ref(false);
+const activeKey = ref(null);
watch(
() => props.listConfig,
@@ -30,11 +30,6 @@ function emitUpdate() {
emit("update", localList.value.map((r) => ({ ...r })));
}
-function setField(row, field, value) {
- row[field] = value === undefined || value === null ? null : value;
- emitUpdate();
-}
-
const availableFields = computed(() =>
(props.fields || [])
.filter((f) => f.b_canuse === 1)
@@ -73,12 +68,11 @@ const columns = [
dataIndex: "b_field_id",
title: "字段",
width: 160,
- customCell: createSelectCell({
- value: "b_field_id",
- getOptions: () => availableFields.value,
- onChange: (row, v) => setField(row, "b_field_id", v),
- placeholder: "选择字段",
- }),
+ editor: {
+ type: "select",
+ options: () => availableFields.value,
+ onChange: () => emitUpdate(),
+ },
},
{ dataIndex: "b_title", title: "列标题", width: 120 },
{ dataIndex: "b_width", title: "列宽", width: 80 },
@@ -86,26 +80,25 @@ const columns = [
dataIndex: "b_group_id",
title: "分组",
width: 140,
- customCell: createSelectCell({
- value: "b_group_id",
- getOptions: () => groupOptions.value,
- onChange: (row, v) => setField(row, "b_group_id", v),
- placeholder: "选择分组",
- }),
+ editor: {
+ type: "select",
+ options: () => groupOptions.value,
+ onChange: () => emitUpdate(),
+ },
},
{
dataIndex: "b_visible",
title: "显示",
width: 60,
align: "center",
- customCell: createToggleCell("b_visible", (row) => setField(row, "b_visible", row.b_visible === 1 ? 0 : 1)),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
},
{
dataIndex: "b_sortable",
title: "排序",
width: 60,
align: "center",
- customCell: createToggleCell("b_sortable", (row) => setField(row, "b_sortable", row.b_sortable === 1 ? 0 : 1)),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
},
{ dataIndex: "b_xh", title: "排序号", width: 80 },
{
@@ -113,7 +106,7 @@ const columns = [
title: "操作",
width: 60,
align: "center",
- customCell: createDeleteCell((row) => deleteRow(row)),
+ actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }],
},
];
@@ -127,7 +120,13 @@ const columns = [
-
+ (activeKey = row.b_id)"
+ />
-import { ref, watch } from "vue";
+import { nextTick, ref, watch } from "vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { createTempId } from "@/utils/tempId";
-import { createDeleteCell, createToggleCell } from "./cellRenderers";
const props = defineProps({
moduleId: { type: String, required: true },
+ moduleCode: { type: String, default: "" },
powers: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
});
@@ -13,11 +13,20 @@ const props = defineProps({
const emit = defineEmits(["update"]);
const localPowers = ref([]);
+const activeKey = ref(null);
+const tableRef = ref(null);
+// 多语言键按 power.<模块代码>.<操作编码> 自动生成,无需手动填写。
watch(
- () => props.powers,
- (val) => {
- localPowers.value = val?.map((r) => ({ ...r })) || [];
+ () => [props.powers, props.moduleCode],
+ ([val]) => {
+ localPowers.value = (val || []).map((r) => ({
+ ...r,
+ b_i18n:
+ r.b_code && props.moduleCode
+ ? `power.${props.moduleCode}.${r.b_code}`
+ : (r.b_i18n ?? null),
+ }));
},
{ immediate: true },
);
@@ -26,9 +35,10 @@ function emitUpdate() {
emit("update", localPowers.value.map((r) => ({ ...r })));
}
-function addRow() {
+async function addRow() {
+ const newId = createTempId();
localPowers.value.push({
- b_id: createTempId(),
+ b_id: newId,
b_module_id: props.moduleId,
b_code: "",
b_name: "",
@@ -37,6 +47,10 @@ function addRow() {
b_canuse: 1,
});
emitUpdate();
+
+ activeKey.value = String(newId);
+ await nextTick();
+ tableRef.value?.scrollToRow(String(newId));
}
function deleteRow(record) {
@@ -45,26 +59,23 @@ function deleteRow(record) {
}
const columns = [
- { dataIndex: "b_code", title: "操作编码", width: 160 },
- { dataIndex: "b_name", title: "操作名称", width: 160 },
+ { dataIndex: "b_code", title: "操作编码", width: 160, editor: { onChange: () => emitUpdate() } },
+ { dataIndex: "b_name", title: "操作名称", width: 160, editor: { onChange: () => emitUpdate() } },
{ dataIndex: "b_i18n", title: "多语言键", width: 160 },
- { dataIndex: "b_xh", title: "排序", width: 80 },
+ { dataIndex: "b_xh", title: "排序", width: 80, editor: { onChange: () => emitUpdate() } },
{
dataIndex: "b_canuse",
title: "启用",
width: 70,
align: "center",
- customCell: createToggleCell("b_canuse", (row) => {
- row.b_canuse = row.b_canuse === 1 ? 0 : 1;
- emitUpdate();
- }),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
},
{
dataIndex: "actions",
title: "操作",
width: 60,
align: "center",
- customCell: createDeleteCell((row) => deleteRow(row)),
+ actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }],
},
];
@@ -77,7 +88,7 @@ const columns = [
-
+ (activeKey = String(row.b_id))" />
@@ -93,13 +104,13 @@ const columns = [
display: flex;
flex: none;
gap: 8px;
- padding: 12px 20px;
+ padding: 12px 20px 0;
}
.panel-body {
flex: 1;
min-height: 0;
- padding: 16px 20px;
+ padding: 12px 20px 16px;
display: flex;
flex-direction: column;
}
diff --git a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
index 36830518..3cd3b9e5 100644
--- a/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
+++ b/code/fms/fms-vue/src/views/module-management/components/ModuleQueryConfigPanel.vue
@@ -2,7 +2,6 @@
import { computed, ref, watch } from "vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { createTempId } from "@/utils/tempId";
-import { createDeleteCell, createSelectCell, createToggleCell } from "./cellRenderers";
const COMPONENT_OPTIONS = [
{ label: "输入框", value: "input" },
@@ -28,6 +27,7 @@ const props = defineProps({
const emit = defineEmits(["update"]);
const localQuery = ref([]);
+const activeKey = ref(null);
watch(
() => props.queryConfig,
@@ -41,11 +41,6 @@ function emitUpdate() {
emit("update", localQuery.value.map((r) => ({ ...r })));
}
-function setField(row, field, value) {
- row[field] = value === undefined || value === null ? null : value;
- emitUpdate();
-}
-
const availableFields = computed(() =>
(props.fields || [])
.filter((f) => f.b_canuse === 1)
@@ -76,32 +71,31 @@ const columns = [
dataIndex: "b_field_id",
title: "字段",
width: 160,
- customCell: createSelectCell({
- value: "b_field_id",
- getOptions: () => availableFields.value,
- onChange: (row, v) => setField(row, "b_field_id", v),
- placeholder: "选择字段",
- }),
+ editor: {
+ type: "select",
+ options: () => availableFields.value,
+ onChange: () => emitUpdate(),
+ },
},
{
dataIndex: "b_component",
title: "查询组件",
width: 140,
- customCell: createSelectCell({
- value: "b_component",
- getOptions: () => COMPONENT_OPTIONS,
- onChange: (row, v) => setField(row, "b_component", v),
- }),
+ editor: {
+ type: "select",
+ options: COMPONENT_OPTIONS,
+ onChange: () => emitUpdate(),
+ },
},
{
dataIndex: "b_operator",
title: "操作符",
width: 140,
- customCell: createSelectCell({
- value: "b_operator",
- getOptions: () => OPERATOR_OPTIONS,
- onChange: (row, v) => setField(row, "b_operator", v),
- }),
+ editor: {
+ type: "select",
+ options: OPERATOR_OPTIONS,
+ onChange: () => emitUpdate(),
+ },
},
{ dataIndex: "b_default_value", title: "默认查询值", width: 140 },
{
@@ -109,7 +103,7 @@ const columns = [
title: "启用",
width: 60,
align: "center",
- customCell: createToggleCell("b_canuse", (row) => setField(row, "b_canuse", row.b_canuse === 1 ? 0 : 1)),
+ editor: { type: "checkbox", onChange: () => emitUpdate() },
},
{ dataIndex: "b_xh", title: "排序号", width: 80 },
{
@@ -117,7 +111,7 @@ const columns = [
title: "操作",
width: 60,
align: "center",
- customCell: createDeleteCell((row) => deleteRow(row)),
+ actions: [{ type: "danger", label: "删除", confirm: { title: "确定删除该记录?", okText: "删除" }, onClick: (row) => deleteRow(row) }],
},
];
@@ -130,7 +124,13 @@ const columns = [
-
+ (activeKey = row.b_id)"
+ />
@@ -146,13 +146,13 @@ const columns = [
display: flex;
flex: none;
gap: 8px;
- padding: 12px 20px;
+ padding: 12px 20px 0;
}
.panel-body {
flex: 1;
min-height: 0;
- padding: 16px 20px;
+ padding: 12px 20px 16px;
display: flex;
flex-direction: column;
}
diff --git a/code/fms/fms-vue/src/views/module-management/components/cellRenderers.js b/code/fms/fms-vue/src/views/module-management/components/cellRenderers.js
deleted file mode 100644
index bfd6f31a..00000000
--- a/code/fms/fms-vue/src/views/module-management/components/cellRenderers.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { h } from "vue";
-import { Button, Popconfirm, Select } from "antdv-next";
-
-// 单元格渲染器统一采用 stk-table-vue 的 customCell 写法:
-// customCell 会被当作函数式组件调用(props 含 row/col/rowIndex/cellValue),
-// 必须返回 h(...) 生成的 VNode,返回普通对象会渲染成 [object Object]。
-
-// 删除操作单元格
-export function createDeleteCell(onDelete) {
- return (props) =>
- h(
- Popconfirm,
- {
- title: "确定删除该记录?",
- okText: "删除",
- okButtonProps: { danger: true },
- onConfirm: () => onDelete(props.row),
- },
- {
- default: () =>
- h(
- Button,
- { type: "link", size: "small", danger: true, onClick: (e) => e.stopPropagation() },
- "删除",
- ),
- },
- );
-}
-
-// 是否(0/1)切换单元格
-export function createToggleCell(field, onChange) {
- return (props) => {
- const checked = props.row[field] === 1;
- return h(
- "span",
- {
- style: { cursor: "pointer" },
- onClick: (e) => {
- e.stopPropagation();
- onChange(props.row, field);
- },
- },
- checked ? "是" : "否",
- );
- };
-}
-
-// 下拉选择单元格。getOptions 返回最新选项(保持响应式)。
-export function createSelectCell({ value, getOptions, onChange, placeholder, allowClear }) {
- return (props) =>
- h(Select, {
- value:
- props.row[value] != null && props.row[value] !== ""
- ? String(props.row[value])
- : undefined,
- size: "small",
- style: { width: "100%" },
- options: getOptions(),
- allowClear: allowClear !== false,
- placeholder: placeholder || "请选择",
- "onUpdate:value": (v) => onChange(props.row, v),
- onClick: (e) => e.stopPropagation(),
- });
-}
diff --git a/code/fms/fms-vue/src/views/module-management/index.vue b/code/fms/fms-vue/src/views/module-management/index.vue
index d9384b48..77cc6f6e 100644
--- a/code/fms/fms-vue/src/views/module-management/index.vue
+++ b/code/fms/fms-vue/src/views/module-management/index.vue
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 设为默认后,其它语言将自动取消默认
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/code/fms/开发规范.md b/code/fms/开发规范.md
index 80306be1..292f3ae6 100644
--- a/code/fms/开发规范.md
+++ b/code/fms/开发规范.md
@@ -29,6 +29,7 @@
- 新增 CSS 应限制在当前组件作用域内,避免影响其他页面。
- 不需要兼容移动端,按桌面端实现即可。
- 表格尽量使用 `fms-table` 组件(项目封装的表格组件),不重复从零实现表格能力。
+- 表单输入控件不需要添加 placeholder(占位提示文字),用字段 label 或说明文字表达意图即可。
## 四、变更范围