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 ` @@ -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 = [
- +
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 = [
- +
@@ -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 = [
- +
@@ -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 = [
- +
-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 = [
- +
@@ -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 = [
- +
@@ -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 或说明文字表达意图即可。 ## 四、变更范围