refactor(module-management): 参与勾选改用 FmsTable 内置 checkbox,删除自制 ParticipationCell
- 参与状态物化为字段行的 __form/__list/__query 虚拟字段(watch 就地 patch, 不替换数组引用,不触发整表重建),参与列用内置 editor: checkbox 渲染—— 居中/事件隔离/Enter 切换与「启用」列同款,自制 cell 与配套 scss 删除 - FmsTable change 按列路由:参与列 → toggle 写对应配置草稿;普通列照旧走 字段 i18n 同步。emitUpdate 剥离 __ 虚拟字段,不进草稿与保存请求 - 新 spec module-fields-panel-participation:物化勾选态、勾选路由生成默认 查询行、普通编辑剥离虚拟字段(jsdom 无布局,patch clientWidth 关闭虚拟列裁剪)
This commit is contained in:
1 parent
59b91e9a7b
commit
47c0bf08aa
4 files changed
+178
-81
No files matched your search
+51
-44
@@ -2,16 +2,17 @@
|
|||||||
/**
|
/**
|
||||||
* 字段定义面板(s_field):可编辑表格维护模块字段。
|
* 字段定义面板(s_field):可编辑表格维护模块字段。
|
||||||
* i18n 键按 field.{moduleCode}.{fieldName} 自动同步。
|
* i18n 键按 field.{moduleCode}.{fieldName} 自动同步。
|
||||||
* 表单/列表/查询三列为「参与开关」:在一行内表达该字段参与了哪些界面,
|
* 表单/列表/查询三列为「参与勾选」:在一行内表达该字段参与了哪些界面,
|
||||||
* 开关即改即生效(生成/隐藏默认配置行);布局细节仍在对应设计器调整。
|
* 勾选即改即生效(生成/隐藏默认配置行);布局细节仍在对应设计器调整。
|
||||||
* 语义见 fieldParticipation.js(沿用 fieldAreaDefaults 的行内开关约定)。
|
* 参与是派生态(配置行存在且行内开关为 1),物化为字段行的 __form/__list/__query
|
||||||
|
* 虚拟字段交给 FmsTable 内置 checkbox 渲染(与「启用」列同款),change 按列路由
|
||||||
|
* 回 toggle 写配置草稿。语义见 fieldParticipation.js。
|
||||||
*/
|
*/
|
||||||
import { ref, shallowRef, watch } from 'vue'
|
import { ref, shallowRef, watch } from 'vue'
|
||||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||||
import { buildModuleItemI18nKey, syncModuleItemI18nKeys } from '@/utils/i18n'
|
import { buildModuleItemI18nKey, syncModuleItemI18nKeys } from '@/utils/i18n'
|
||||||
import I18nQuickSetModal from './I18nQuickSetModal.vue'
|
import I18nQuickSetModal from './I18nQuickSetModal.vue'
|
||||||
import { createI18nKeyCell } from './I18nKeyCell'
|
import { createI18nKeyCell } from './I18nKeyCell'
|
||||||
import { createParticipationCell } from './ParticipationCell'
|
|
||||||
import {
|
import {
|
||||||
isFormOn,
|
isFormOn,
|
||||||
isListOn,
|
isListOn,
|
||||||
@@ -25,7 +26,7 @@ const props = defineProps({
|
|||||||
moduleId: { type: String, required: true },
|
moduleId: { type: String, required: true },
|
||||||
moduleCode: { type: String, default: '' },
|
moduleCode: { type: String, default: '' },
|
||||||
fields: { type: Array, default: () => [] },
|
fields: { type: Array, default: () => [] },
|
||||||
/** 参与开关所需的三个配置草稿(由 index.vue 传入,切换后事件回写) */
|
/** 参与勾选所需的三个配置草稿(由 index.vue 传入,切换后事件回写) */
|
||||||
editConfig: { type: Array, default: () => [] },
|
editConfig: { type: Array, default: () => [] },
|
||||||
listConfig: { type: Array, default: () => [] },
|
listConfig: { type: Array, default: () => [] },
|
||||||
queryConfig: { type: Array, default: () => [] },
|
queryConfig: { type: Array, default: () => [] },
|
||||||
@@ -81,11 +82,49 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 参与状态物化到字段行上的 __ 虚拟字段(就地 patch,不替换数组引用,避免整表重建),
|
||||||
|
// 供 FmsTable 内置 checkbox 渲染;虚拟字段不进草稿/保存请求(emitUpdate 时剥离)。
|
||||||
|
// 勾选变化经 onCellChange 路由到 toggle 写配置草稿,随后本 watch 重算虚拟字段
|
||||||
|
const PARTICIPATION_KEYS = ['__form', '__list', '__query']
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [localFields.value, props.editConfig, props.listConfig, props.queryConfig],
|
||||||
|
() => {
|
||||||
|
for (const row of localFields.value) {
|
||||||
|
row.__form = isFormOn(props.editConfig, row) ? 1 : 0
|
||||||
|
row.__list = isListOn(props.listConfig, row) ? 1 : 0
|
||||||
|
row.__query = isQueryOn(props.queryConfig, row) ? 1 : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
function emitUpdate() {
|
function emitUpdate() {
|
||||||
emit('update', localFields.value.map((row) => ({ ...row })))
|
// 剥离参与虚拟字段:__ 前缀仅供表格渲染,不进字段草稿与保存请求
|
||||||
|
emit(
|
||||||
|
'update',
|
||||||
|
localFields.value.map((row) => {
|
||||||
|
const { __form, __list, __query, ...field } = row
|
||||||
|
return field
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCellChange() {
|
function onCellChange(row, col) {
|
||||||
|
// 参与勾选:内置 checkbox 已把值写在字段行虚拟字段上(展示用途),
|
||||||
|
// 真实状态经 toggle 写回对应配置草稿,不走字段 i18n 同步
|
||||||
|
if (PARTICIPATION_KEYS.includes(col?.dataIndex)) {
|
||||||
|
if (!props.loading) {
|
||||||
|
if (col.dataIndex === '__form') {
|
||||||
|
emit('updateEditConfig', toggleForm(props.editConfig, row, props.moduleId))
|
||||||
|
} else if (col.dataIndex === '__list') {
|
||||||
|
emit('updateListConfig', toggleList(props.listConfig, row, props.moduleId))
|
||||||
|
} else {
|
||||||
|
emit('updateQueryConfig', toggleQuery(props.queryConfig, row, props.moduleId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
syncModuleItemI18nKeys(localFields.value, 'field', props.moduleCode, 'b_field')
|
syncModuleItemI18nKeys(localFields.value, 'field', props.moduleCode, 'b_field')
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
@@ -98,29 +137,6 @@ function openI18n(row) {
|
|||||||
|
|
||||||
const I18nKeyCell = createI18nKeyCell({ onOpen: openI18n })
|
const I18nKeyCell = createI18nKeyCell({ onOpen: openI18n })
|
||||||
|
|
||||||
/* ---------- 参与开关(表单/列表/查询) ---------- */
|
|
||||||
/* 字段行 → 配置行:字段定义行的 b_id 与配置行的 b_field_id 对应;
|
|
||||||
字段本身未启用时开关禁用(停用字段运行时不显示,参与无意义) */
|
|
||||||
|
|
||||||
function onToggleForm(field) {
|
|
||||||
if (props.loading) return
|
|
||||||
emit('updateEditConfig', toggleForm(props.editConfig, field, props.moduleId))
|
|
||||||
}
|
|
||||||
|
|
||||||
function onToggleList(field) {
|
|
||||||
if (props.loading) return
|
|
||||||
emit('updateListConfig', toggleList(props.listConfig, field, props.moduleId))
|
|
||||||
}
|
|
||||||
|
|
||||||
function onToggleQuery(field) {
|
|
||||||
if (props.loading) return
|
|
||||||
emit('updateQueryConfig', toggleQuery(props.queryConfig, field, props.moduleId))
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormCell = createParticipationCell({ isOn: (f) => isFormOn(props.editConfig, f), onToggle: onToggleForm, title: '参与表单' })
|
|
||||||
const ListCell = createParticipationCell({ isOn: (f) => isListOn(props.listConfig, f), onToggle: onToggleList, title: '参与列表显示' })
|
|
||||||
const QueryCell = createParticipationCell({ isOn: (f) => isQueryOn(props.queryConfig, f), onToggle: onToggleQuery, title: '作为查询条件' })
|
|
||||||
|
|
||||||
const columns = shallowRef(buildColumns())
|
const columns = shallowRef(buildColumns())
|
||||||
|
|
||||||
// 列宽拖拽结果随 v-model 写回 columns,仅在模块类型变化时重建
|
// 列宽拖拽结果随 v-model 写回 columns,仅在模块类型变化时重建
|
||||||
@@ -156,23 +172,14 @@ function buildColumns() {
|
|||||||
{ dataIndex: 'b_default_value', title: '默认值', width: 120, editor: { type: 'input' } },
|
{ dataIndex: 'b_default_value', title: '默认值', width: 120, editor: { type: 'input' } },
|
||||||
{ dataIndex: 'b_i18n', title: '多语言键', width: 220, customCell: I18nKeyCell },
|
{ dataIndex: 'b_i18n', title: '多语言键', width: 220, customCell: I18nKeyCell },
|
||||||
]
|
]
|
||||||
// 表单参与列仅数据模块展示(查询模块无表单设计)。
|
// 参与勾选列:内置 checkbox editor(居中与交互隔离随「启用」列同款),
|
||||||
// align 管内容 td、headerAlign 管表头 th(stk-table 分开处理,见 FmsTable.getHeaderAlign)
|
// 表单列仅数据模块展示(查询模块无表单设计)
|
||||||
const participationColumns = [
|
const participationColumns = [
|
||||||
...(props.moduleType === 'data'
|
...(props.moduleType === 'data'
|
||||||
? [
|
? [{ dataIndex: '__form', title: '表单', width: 70, align: 'center', editor: { type: 'checkbox' } }]
|
||||||
{
|
|
||||||
dataIndex: '__form',
|
|
||||||
title: '表单',
|
|
||||||
width: 64,
|
|
||||||
align: 'center',
|
|
||||||
headerAlign: 'center',
|
|
||||||
customCell: FormCell,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
: []),
|
||||||
{ dataIndex: '__list', title: '列表', width: 64, align: 'center', headerAlign: 'center', customCell: ListCell },
|
{ dataIndex: '__list', title: '列表', width: 70, align: 'center', editor: { type: 'checkbox' } },
|
||||||
{ dataIndex: '__query', title: '查询', width: 64, align: 'center', headerAlign: 'center', customCell: QueryCell },
|
{ dataIndex: '__query', title: '查询', width: 70, align: 'center', editor: { type: 'checkbox' } },
|
||||||
]
|
]
|
||||||
const tailColumns = [
|
const tailColumns = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* 字段参与勾选单元格(h 渲染函数):ui Checkbox 居中渲染,勾选切换参与状态。
|
|
||||||
* 供字段定义面板 FmsTable 的 表单/列表/查询 三列 customCell 使用。
|
|
||||||
* 表格 cell 由 h() 动态渲染、不受 scoped 样式约束,样式放独立 scss 随本模块全局引入。
|
|
||||||
*/
|
|
||||||
import { h } from 'vue'
|
|
||||||
import { Checkbox } from '@/components/ui'
|
|
||||||
import './participation-cell.scss'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} opts
|
|
||||||
* @param {(row: object) => boolean} opts.isOn 该行当前是否参与
|
|
||||||
* @param {(row: object) => void} opts.onToggle 勾选切换
|
|
||||||
* @param {string} opts.title 提示文案(如「参与列表显示」)
|
|
||||||
*/
|
|
||||||
export function createParticipationCell({ isOn, onToggle, title }) {
|
|
||||||
return ({ row }) =>
|
|
||||||
h('div', { class: 'mm-part-cell' }, [
|
|
||||||
h(Checkbox, {
|
|
||||||
modelValue: isOn(row),
|
|
||||||
title,
|
|
||||||
'aria-label': title,
|
|
||||||
onChange: () => onToggle(row),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
-11
@@ -1,11 +0,0 @@
|
|||||||
/* 字段参与勾选单元格(ParticipationCell):随 JS 模块全局引入。
|
|
||||||
* 与 FmsTable 内置勾选列(.fms-checkbox)同构:铺满单元格后 flex 居中,
|
|
||||||
* 垂直方向由 height:100% 与行高对齐。 */
|
|
||||||
.mm-part-cell {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* 字段定义面板参与勾选接线测试:
|
||||||
|
* - 虚拟字段 __form/__list/__query 由配置草稿物化(内置 checkbox 的勾选态)
|
||||||
|
* - change 事件按列路由:参与列 → updateXxxConfig(携带 toggle 后的配置),普通列 → update(字段)
|
||||||
|
* - update 事件剥离 __ 虚拟字段,不污染字段草稿
|
||||||
|
*/
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { beforeAll, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
import { registerFeature, useAreaSelection } from 'stk-table-vue'
|
||||||
|
import ModuleFieldsPanel from '@/views/system/module-management/components/ModuleFieldsPanel.vue'
|
||||||
|
|
||||||
|
// 与 main.js 一致:注册 stk-table 的按需 feature
|
||||||
|
registerFeature(useAreaSelection)
|
||||||
|
|
||||||
|
// jsdom 无布局,容器 clientWidth 为 0 时 StkTable 走默认宽度并启用虚拟列裁剪,
|
||||||
|
// 只渲染首列——patch 成宽容器让全部列(含参与勾选列)真实渲染
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
return 5000
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('@/services/api', () => ({
|
||||||
|
loadDataApi: vi.fn(),
|
||||||
|
saveObjectApi: vi.fn(),
|
||||||
|
nextIdApi: vi.fn(),
|
||||||
|
describeApi: vi.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{ b_id: '10', b_field: 'amount', b_name: '金额', b_type: 'money', b_canuse: 1, b_i18n: null },
|
||||||
|
{ b_id: '11', b_field: 'remark', b_name: '备注', b_type: 'textarea', b_canuse: 1, b_i18n: null },
|
||||||
|
]
|
||||||
|
|
||||||
|
const listConfig = [
|
||||||
|
{ b_id: 'v1', b_field_id: '10', b_visible: 1 },
|
||||||
|
{ b_id: 'v2', b_field_id: '11', b_visible: 0 }, // 大文本默认隐藏
|
||||||
|
]
|
||||||
|
const editConfig = [{ b_id: 'e1', b_field_id: '10', b_visible: 1 }]
|
||||||
|
const queryConfig = [] // 均未生成查询行
|
||||||
|
|
||||||
|
// 行内 checkbox 顺序 = 参与三列 + 启用列(普通编辑列不渲染 checkbox)
|
||||||
|
const FORM = 0
|
||||||
|
const LIST = 1
|
||||||
|
const QUERY = 2
|
||||||
|
|
||||||
|
function mountPanel(props = {}) {
|
||||||
|
return mount(ModuleFieldsPanel, {
|
||||||
|
props: {
|
||||||
|
moduleId: 'm1',
|
||||||
|
moduleCode: 'demo',
|
||||||
|
fields: fields.map((f) => ({ ...f })),
|
||||||
|
editConfig: editConfig.map((r) => ({ ...r })),
|
||||||
|
listConfig: listConfig.map((r) => ({ ...r })),
|
||||||
|
queryConfig: queryConfig.map((r) => ({ ...r })),
|
||||||
|
moduleType: 'data',
|
||||||
|
loading: false,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
attachTo: document.body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function settle() {
|
||||||
|
await nextTick()
|
||||||
|
await new Promise((r) => setTimeout(r, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowCheckboxes(wrapper, rowIndex) {
|
||||||
|
return wrapper.findAll('.stk-tbody-main tr')[rowIndex].findAll('input[type="checkbox"]')
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ModuleFieldsPanel 参与勾选', () => {
|
||||||
|
it('虚拟字段按配置草稿物化:amount 表单/列表参与、查询未配置', async () => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
const wrapper = mountPanel()
|
||||||
|
await settle()
|
||||||
|
const boxes = rowCheckboxes(wrapper, 0)
|
||||||
|
expect(boxes.length).toBe(4) // __form / __list / __query / b_canuse
|
||||||
|
expect(boxes[FORM].element.checked).toBe(true)
|
||||||
|
expect(boxes[LIST].element.checked).toBe(true)
|
||||||
|
expect(boxes[QUERY].element.checked).toBe(false)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('勾选查询列 → emit updateQueryConfig,生成默认查询行(b_canuse=1)', async () => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
const wrapper = mountPanel()
|
||||||
|
await settle()
|
||||||
|
await rowCheckboxes(wrapper, 0)[QUERY].setValue(true)
|
||||||
|
const events = wrapper.emitted('updateQueryConfig')
|
||||||
|
expect(events).toHaveLength(1)
|
||||||
|
const rows = events[0][0]
|
||||||
|
expect(rows).toHaveLength(1)
|
||||||
|
expect(rows[0].b_field_id).toBe('10')
|
||||||
|
expect(rows[0].b_canuse).toBe(1)
|
||||||
|
expect(rows[0].b_component).toBe('number')
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('普通列 change → emit update,且剥离 __ 虚拟字段', async () => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
const wrapper = mountPanel()
|
||||||
|
await settle()
|
||||||
|
const table = wrapper.findComponent({ name: 'FmsTable' })
|
||||||
|
table.vm.$emit(
|
||||||
|
'change',
|
||||||
|
{ ...fields[0], __form: 1, __list: 1, __query: 0 },
|
||||||
|
{ dataIndex: 'b_name' },
|
||||||
|
'新名称',
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
const events = wrapper.emitted('update')
|
||||||
|
expect(events).toBeTruthy()
|
||||||
|
const emitted = events.at(-1)[0]
|
||||||
|
expect(emitted[0].b_id).toBe('10')
|
||||||
|
expect('__form' in emitted[0]).toBe(false)
|
||||||
|
expect('__list' in emitted[0]).toBe(false)
|
||||||
|
expect('__query' in emitted[0]).toBe(false)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in new issue
Block a user