feat(module-management): 字段定义面板增加 表单/列表/查询 参与开关
跨 tab 的「字段参与了哪些界面」在字段定义一行内表达并可就地切换, tab 结构不变。「参与是选择不是缺失」——不引入任何完整性提示。 - 语义沿用 fieldAreaDefaults 既有约定:行存在=已配置,行内开关=是否参与 (表单/列表 b_visible、查询 b_canuse);关=置 0 不删行,保留画布布局与既有查询配置 - fieldParticipation.js(纯逻辑):判定与切换;行不存在时按默认工厂生成且强制参与 (显式意愿覆盖大文本默认 参与=0),表单新行 b_colspan=24 追加到末尾序号 - fieldAreaDefaults 增加列表/查询默认行工厂,useModuleConfig.buildMissingDefaultConfigs 改用工厂——「同步字段自动补全」与「手动打开开关」生成的行结构同源 - ParticipationCell(h 渲染 ui Switch)接 FmsTable customCell;表单列仅 data 模块展示 - 新 spec field-participation 9 用例:判定/两路切换/大文本强制参与/不变性
This commit is contained in:
1 parent
08646c547f
commit
f019a1c1ac
8 files changed
+363
-26
No files matched your search
+73
-3
@@ -2,21 +2,45 @@
|
|||||||
/**
|
/**
|
||||||
* 字段定义面板(s_field):可编辑表格维护模块字段。
|
* 字段定义面板(s_field):可编辑表格维护模块字段。
|
||||||
* i18n 键按 field.{moduleCode}.{fieldName} 自动同步。
|
* i18n 键按 field.{moduleCode}.{fieldName} 自动同步。
|
||||||
|
* 表单/列表/查询三列为「参与开关」:在一行内表达该字段参与了哪些界面,
|
||||||
|
* 开关即改即生效(生成/隐藏默认配置行);布局细节仍在对应设计器调整。
|
||||||
|
* 语义见 fieldParticipation.js(沿用 fieldAreaDefaults 的行内开关约定)。
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
|
isFormOn,
|
||||||
|
isListOn,
|
||||||
|
isQueryOn,
|
||||||
|
toggleForm,
|
||||||
|
toggleList,
|
||||||
|
toggleQuery,
|
||||||
|
} from '../fieldParticipation'
|
||||||
|
|
||||||
const props = defineProps({
|
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 传入,切换后事件回写) */
|
||||||
|
editConfig: { type: Array, default: () => [] },
|
||||||
|
listConfig: { type: Array, default: () => [] },
|
||||||
|
queryConfig: { type: Array, default: () => [] },
|
||||||
|
/** 模块类型:表单参与列仅 data 模块展示(查询模块无表单设计) */
|
||||||
|
moduleType: { type: String, default: null },
|
||||||
loading: { type: Boolean, default: false },
|
loading: { type: Boolean, default: false },
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update', 'i18nSaved'])
|
const emit = defineEmits([
|
||||||
|
'update',
|
||||||
|
'i18nSaved',
|
||||||
|
'updateEditConfig',
|
||||||
|
'updateListConfig',
|
||||||
|
'updateQueryConfig',
|
||||||
|
])
|
||||||
|
|
||||||
const FIELD_TYPES = [
|
const FIELD_TYPES = [
|
||||||
{ label: '输入框', value: 'input' },
|
{ label: '输入框', value: 'input' },
|
||||||
@@ -74,7 +98,41 @@ function openI18n(row) {
|
|||||||
|
|
||||||
const I18nKeyCell = createI18nKeyCell({ onOpen: openI18n })
|
const I18nKeyCell = createI18nKeyCell({ onOpen: openI18n })
|
||||||
|
|
||||||
const columns = shallowRef([
|
/* ---------- 参与开关(表单/列表/查询) ---------- */
|
||||||
|
/* 字段行 → 配置行:字段定义行的 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())
|
||||||
|
|
||||||
|
// 列宽拖拽结果随 v-model 写回 columns,仅在模块类型变化时重建
|
||||||
|
watch(
|
||||||
|
() => props.moduleType,
|
||||||
|
() => {
|
||||||
|
columns.value = buildColumns()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function buildColumns() {
|
||||||
|
const baseColumns = [
|
||||||
{ dataIndex: 'b_field', title: '字段名', width: 150 },
|
{ dataIndex: 'b_field', title: '字段名', width: 150 },
|
||||||
{ dataIndex: 'b_name', title: '字段名称', width: 140, editor: { type: 'input' } },
|
{ dataIndex: 'b_name', title: '字段名称', width: 140, editor: { type: 'input' } },
|
||||||
{
|
{
|
||||||
@@ -97,6 +155,16 @@ const columns = shallowRef([
|
|||||||
},
|
},
|
||||||
{ 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 },
|
||||||
|
]
|
||||||
|
// 表单参与列仅数据模块展示(查询模块无表单设计)
|
||||||
|
const participationColumns = [
|
||||||
|
...(props.moduleType === 'data'
|
||||||
|
? [{ dataIndex: '__form', title: '表单', width: 64, align: 'center', customCell: FormCell }]
|
||||||
|
: []),
|
||||||
|
{ dataIndex: '__list', title: '列表', width: 64, align: 'center', customCell: ListCell },
|
||||||
|
{ dataIndex: '__query', title: '查询', width: 64, align: 'center', customCell: QueryCell },
|
||||||
|
]
|
||||||
|
const tailColumns = [
|
||||||
{
|
{
|
||||||
dataIndex: 'b_canuse',
|
dataIndex: 'b_canuse',
|
||||||
title: '启用',
|
title: '启用',
|
||||||
@@ -104,7 +172,9 @@ const columns = shallowRef([
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
editor: { type: 'checkbox' },
|
editor: { type: 'checkbox' },
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
|
return [...baseColumns, ...participationColumns, ...tailColumns]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 字段参与开关单元格(h 渲染函数):ui Switch 居中渲染,点击切换参与状态。
|
||||||
|
* 供字段定义面板 FmsTable 的 表单/列表/查询 三列 customCell 使用。
|
||||||
|
* 表格 cell 由 h() 动态渲染、不受 scoped 样式约束,样式放独立 scss 随本模块全局引入。
|
||||||
|
*/
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Switch } 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(Switch, {
|
||||||
|
modelValue: isOn(row),
|
||||||
|
title,
|
||||||
|
'aria-label': title,
|
||||||
|
onClick: (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
onToggle(row)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/* 字段参与开关单元格(ParticipationCell):随 JS 模块全局引入 */
|
||||||
|
.mm-part-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -10,7 +10,10 @@ import { loadDataApi, describeApi } from '@/services/api'
|
|||||||
import { cloneData } from '@/utils/dataChanges'
|
import { cloneData } from '@/utils/dataChanges'
|
||||||
import { createTempId } from '@/utils/tempId'
|
import { createTempId } from '@/utils/tempId'
|
||||||
import { buildModuleItemI18nKey } from '@/utils/i18n'
|
import { buildModuleItemI18nKey } from '@/utils/i18n'
|
||||||
import { listRowDefaults, queryRowDefaults } from '../fieldAreaDefaults'
|
import {
|
||||||
|
createListConfigRow,
|
||||||
|
createQueryConfigRow,
|
||||||
|
} from '../fieldAreaDefaults'
|
||||||
|
|
||||||
export function useModuleConfig(deps) {
|
export function useModuleConfig(deps) {
|
||||||
const {
|
const {
|
||||||
@@ -214,7 +217,7 @@ export function useModuleConfig(deps) {
|
|||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为尚未配置的字段生成列表/查询默认行(参考旧系统 buildMissingDefaultConfigs)。
|
// 为尚未配置的字段生成列表/查询默认行(行结构统一走 fieldAreaDefaults 的工厂)。
|
||||||
// 默认属性见 fieldAreaDefaults:默认不参与的字段(大文本等)同样生成行、开关置 0,
|
// 默认属性见 fieldAreaDefaults:默认不参与的字段(大文本等)同样生成行、开关置 0,
|
||||||
// 保留配置空间;表单设计(editConfig)不自动生成:字段由用户在表单设计器中逐个添加,
|
// 保留配置空间;表单设计(editConfig)不自动生成:字段由用户在表单设计器中逐个添加,
|
||||||
// 自动铺满会与设计器的画布/分组/栅格编辑冲突。
|
// 自动铺满会与设计器的画布/分组/栅格编辑冲突。
|
||||||
@@ -230,27 +233,10 @@ export function useModuleConfig(deps) {
|
|||||||
const fieldId = String(field.b_id)
|
const fieldId = String(field.b_id)
|
||||||
const order = orderIndex * 10
|
const order = orderIndex * 10
|
||||||
if (!listFieldIds.has(fieldId)) {
|
if (!listFieldIds.has(fieldId)) {
|
||||||
generated.listConfig.push({
|
generated.listConfig.push({ ...createListConfigRow(moduleId, field, []), b_xh: order })
|
||||||
b_id: createTempId(),
|
|
||||||
b_module_id: moduleId,
|
|
||||||
b_field_id: fieldId,
|
|
||||||
b_group_id: null,
|
|
||||||
b_i18n: field.b_i18n || null,
|
|
||||||
b_xh: order,
|
|
||||||
...listRowDefaults(field),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (!queryFieldIds.has(fieldId)) {
|
if (!queryFieldIds.has(fieldId)) {
|
||||||
generated.queryConfig.push({
|
generated.queryConfig.push({ ...createQueryConfigRow(moduleId, field, []), b_xh: order })
|
||||||
b_id: createTempId(),
|
|
||||||
b_module_id: moduleId,
|
|
||||||
b_field_module_id: moduleId,
|
|
||||||
b_field_id: fieldId,
|
|
||||||
b_relation_id: null,
|
|
||||||
b_default_value: null,
|
|
||||||
b_xh: order,
|
|
||||||
...queryRowDefaults(field),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return generated
|
return generated
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* 字段区域默认策略(唯一真源):
|
* 字段区域默认策略(唯一真源):
|
||||||
* 「字段定义 → 列表列 / 查询条件默认配置行」的规则集中于此,供
|
* 「字段定义 → 列表列 / 查询条件默认配置行」的规则集中于此,供
|
||||||
* useModuleConfig.buildMissingDefaultConfigs(同步字段、补充缺失)与查询面板
|
* useModuleConfig.buildMissingDefaultConfigs(同步字段、补充缺失)、查询面板
|
||||||
* (选定字段后的组件/操作符重置)共用。
|
* (选定字段后的组件/操作符重置)与字段定义面板的参与开关共用。
|
||||||
*
|
*
|
||||||
* 参与语义约定:配置行存在 = 已配置;是否参与运行时由行内开关表达
|
* 参与语义约定:配置行存在 = 已配置;是否参与运行时由行内开关表达
|
||||||
* (列表 b_visible、查询 b_canuse)。默认不参与的字段(大文本)同样生成行、
|
* (列表 b_visible、查询 b_canuse)。默认不参与的字段(大文本)同样生成行、
|
||||||
* 开关置 0——保留配置空间,勾选即恢复;删除行仅用于清掉不该存在的行(误加的查询条件)。
|
* 开关置 0——保留配置空间,勾选即恢复;删除行仅用于清掉不该存在的行(误加的查询条件)。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createTempId } from '@/utils/tempId'
|
||||||
|
|
||||||
/** 大文本字段:默认不进列表、默认不作为查询条件 */
|
/** 大文本字段:默认不进列表、默认不作为查询条件 */
|
||||||
function isLargeText(field) {
|
function isLargeText(field) {
|
||||||
return field.b_type === 'textarea' || field.b_dbtype === 'text'
|
return field.b_type === 'textarea' || field.b_dbtype === 'text'
|
||||||
@@ -57,3 +59,47 @@ export function queryRowDefaults(field) {
|
|||||||
b_canuse: isLargeText(field) ? 0 : 1,
|
b_canuse: isLargeText(field) ? 0 : 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 默认配置行工厂 ----------
|
||||||
|
* 与 useModuleConfig.buildMissingDefaultConfigs、字段定义面板参与开关共用,
|
||||||
|
* 保证「同步字段自动补全」与「手动打开参与开关」生成的行结构一致。
|
||||||
|
* forceOn:显式意愿(手动开开关)时无视大文本默认不参与的策略,置 1。
|
||||||
|
*/
|
||||||
|
|
||||||
|
function nextXh(rows) {
|
||||||
|
let max = 0
|
||||||
|
for (const row of rows || []) {
|
||||||
|
const xh = Number(row.b_xh) || 0
|
||||||
|
if (xh > max) max = xh
|
||||||
|
}
|
||||||
|
return max + 10
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字段 → 列表列配置行(s_field_view),追加到末尾时使用 */
|
||||||
|
export function createListConfigRow(moduleId, field, rows, { forceOn = false } = {}) {
|
||||||
|
return {
|
||||||
|
b_id: createTempId(),
|
||||||
|
b_module_id: String(moduleId),
|
||||||
|
b_field_id: String(field.b_id),
|
||||||
|
b_group_id: null,
|
||||||
|
b_i18n: field.b_i18n || null,
|
||||||
|
b_xh: nextXh(rows),
|
||||||
|
...listRowDefaults(field),
|
||||||
|
...(forceOn ? { b_visible: 1 } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 字段 → 查询条件配置行(s_field_query) */
|
||||||
|
export function createQueryConfigRow(moduleId, field, rows, { forceOn = false } = {}) {
|
||||||
|
return {
|
||||||
|
b_id: createTempId(),
|
||||||
|
b_module_id: String(moduleId),
|
||||||
|
b_field_module_id: String(moduleId),
|
||||||
|
b_field_id: String(field.b_id),
|
||||||
|
b_relation_id: null,
|
||||||
|
b_default_value: null,
|
||||||
|
b_xh: nextXh(rows),
|
||||||
|
...queryRowDefaults(field),
|
||||||
|
...(forceOn ? { b_canuse: 1 } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 字段参与开关(纯逻辑,无 Vue 依赖):
|
||||||
|
* 「字段是否参与 表单/列表/查询」的判定与切换,供字段定义面板的三个开关列使用。
|
||||||
|
*
|
||||||
|
* 语义沿用 fieldAreaDefaults 的项目约定(行存在 = 已配置,行内开关 = 是否参与):
|
||||||
|
* - 表单参与:editConfig 行存在且 b_visible=1;关 = 置 b_visible=0,不删行
|
||||||
|
* (保留画布布局/栅格,与列表「删除列=隐藏」同一哲学)
|
||||||
|
* - 列表参与:listConfig 行存在且 b_visible=1(行由加载自动补全,通常恒存在)
|
||||||
|
* - 查询参与:queryConfig 行存在且 b_canuse=1(行不自动生成,开 = 需要时按默认生成)
|
||||||
|
*
|
||||||
|
* 所有 toggle 返回新数组:目标行浅拷贝后修改,其余行保持原引用;
|
||||||
|
* 行不存在且要「关」时无操作,原样返回(调用方可直接 emit)。
|
||||||
|
*/
|
||||||
|
import { createListConfigRow, createQueryConfigRow } from './fieldAreaDefaults'
|
||||||
|
import { createEditRowFactory } from './editPanelShared'
|
||||||
|
|
||||||
|
const findRow = (rows, fieldId) =>
|
||||||
|
(rows || []).find((row) => String(row.b_field_id) === String(fieldId))
|
||||||
|
|
||||||
|
/** 表单行工厂:b_colspan 24 独占一行(点击新增语义,见 FormEditPanel.insertField) */
|
||||||
|
function createFormRow(field, moduleId, rows) {
|
||||||
|
const row = createEditRowFactory({ moduleId: String(moduleId) })(field, null)
|
||||||
|
// 末尾追加:b_xh 取现有最大值 + 10(工厂默认 0 会排到表单最前)
|
||||||
|
let max = 0
|
||||||
|
for (const item of rows || []) {
|
||||||
|
const xh = Number(item.b_xh) || 0
|
||||||
|
if (xh > max) max = xh
|
||||||
|
}
|
||||||
|
row.b_xh = max + 10
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
function withRowToggled(rows, row, key) {
|
||||||
|
return rows.map((item) => (item === row ? { ...item, [key]: Number(item[key]) === 1 ? 0 : 1 } : item))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 判定 ---------- */
|
||||||
|
|
||||||
|
export function isFormOn(editConfig, field) {
|
||||||
|
const row = findRow(editConfig, field.b_id)
|
||||||
|
return !!row && Number(row.b_visible) === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isListOn(listConfig, field) {
|
||||||
|
const row = findRow(listConfig, field.b_id)
|
||||||
|
return !!row && Number(row.b_visible) === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isQueryOn(queryConfig, field) {
|
||||||
|
const row = findRow(queryConfig, field.b_id)
|
||||||
|
return !!row && Number(row.b_canuse) === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 切换 ---------- */
|
||||||
|
|
||||||
|
/** 行不存在时追加到末尾;forceOn 的行由工厂置参与标志为 1(显式意愿) */
|
||||||
|
function appendRow(rows, row) {
|
||||||
|
return [...(rows || []), row]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleForm(editConfig, field, moduleId) {
|
||||||
|
const rows = editConfig || []
|
||||||
|
const row = findRow(rows, field.b_id)
|
||||||
|
if (row) return withRowToggled(rows, row, 'b_visible')
|
||||||
|
return appendRow(rows, createFormRow(field, moduleId, rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleList(listConfig, field, moduleId) {
|
||||||
|
const rows = listConfig || []
|
||||||
|
const row = findRow(rows, field.b_id)
|
||||||
|
if (row) return withRowToggled(rows, row, 'b_visible')
|
||||||
|
return appendRow(rows, createListConfigRow(moduleId, field, rows, { forceOn: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleQuery(queryConfig, field, moduleId) {
|
||||||
|
const rows = queryConfig || []
|
||||||
|
const row = findRow(rows, field.b_id)
|
||||||
|
if (row) return withRowToggled(rows, row, 'b_canuse')
|
||||||
|
return appendRow(rows, createQueryConfigRow(moduleId, field, rows, { forceOn: true }))
|
||||||
|
}
|
||||||
@@ -503,8 +503,15 @@ loadModuleTree()
|
|||||||
:module-id="String(selectedModule.b_id)"
|
:module-id="String(selectedModule.b_id)"
|
||||||
:module-code="drafts?.module?.b_code || ''"
|
:module-code="drafts?.module?.b_code || ''"
|
||||||
:fields="drafts?.fields"
|
:fields="drafts?.fields"
|
||||||
|
:edit-config="drafts?.editConfig"
|
||||||
|
:list-config="drafts?.listConfig"
|
||||||
|
:query-config="drafts?.queryConfig"
|
||||||
|
:module-type="moduleType"
|
||||||
:loading="busy"
|
:loading="busy"
|
||||||
@update="onFieldsUpdate"
|
@update="onFieldsUpdate"
|
||||||
|
@update-edit-config="(v) => onModuleUpdate('editConfig', v)"
|
||||||
|
@update-list-config="(v) => onModuleUpdate('listConfig', v)"
|
||||||
|
@update-query-config="(v) => onModuleUpdate('queryConfig', v)"
|
||||||
@i18n-saved="onModuleI18nSaved"
|
@i18n-saved="onModuleI18nSaved"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* 字段参与开关(fieldParticipation)测试:
|
||||||
|
* - 判定:行存在 + 行内开关(表单/列表 b_visible、查询 b_canuse),b_field_id 与 b_id 类型不一致按字符串比较
|
||||||
|
* - 切换:行为空/存在两路;关 = 置行内开关 0(不删行,保留布局/配置);开 = 行不存在时按默认生成且强制参与
|
||||||
|
* - 不变性:toggle 返回新数组、非目标行保持原引用
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import {
|
||||||
|
isFormOn,
|
||||||
|
isListOn,
|
||||||
|
isQueryOn,
|
||||||
|
toggleForm,
|
||||||
|
toggleList,
|
||||||
|
toggleQuery,
|
||||||
|
} from '@/views/system/module-management/fieldParticipation'
|
||||||
|
|
||||||
|
const MODULE_ID = 'm1'
|
||||||
|
|
||||||
|
function makeField(over = {}) {
|
||||||
|
return { b_id: '10', b_field: 'amount', b_name: '金额', b_type: 'money', b_canuse: 1, ...over }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('参与判定', () => {
|
||||||
|
const field = makeField()
|
||||||
|
|
||||||
|
it('行存在且开关为 1 即参与;行不存在不参与', () => {
|
||||||
|
expect(isFormOn([{ b_field_id: '10', b_visible: 1 }], field)).toBe(true)
|
||||||
|
expect(isFormOn([{ b_field_id: '10', b_visible: 0 }], field)).toBe(false)
|
||||||
|
expect(isFormOn([], field)).toBe(false)
|
||||||
|
expect(isListOn([{ b_field_id: 10, b_visible: 1 }], field)).toBe(true) // 类型不一致按字符串比较
|
||||||
|
expect(isQueryOn([{ b_field_id: '10', b_canuse: 1 }], field)).toBe(true)
|
||||||
|
expect(isQueryOn([{ b_field_id: '10', b_canuse: 0 }], field)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('空配置安全', () => {
|
||||||
|
expect(isFormOn(undefined, field)).toBe(false)
|
||||||
|
expect(isListOn(null, field)).toBe(false)
|
||||||
|
expect(isQueryOn([], field)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('表单参与切换', () => {
|
||||||
|
it('行存在:切换 b_visible,不删行(保留布局)', () => {
|
||||||
|
const rows = [{ b_id: 'e1', b_field_id: '10', b_visible: 1, b_colspan: 12, b_group_id: 'g1' }]
|
||||||
|
const off = toggleForm(rows, makeField(), MODULE_ID)
|
||||||
|
expect(off).toHaveLength(1)
|
||||||
|
expect(off[0].b_visible).toBe(0)
|
||||||
|
expect(off[0].b_colspan).toBe(12) // 布局属性保留
|
||||||
|
expect(off[0].b_group_id).toBe('g1')
|
||||||
|
const on = toggleForm(off, makeField(), MODULE_ID)
|
||||||
|
expect(on[0].b_visible).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('行不存在:生成默认行追加到末尾(b_visible=1、b_colspan=24、b_xh 为末尾序号)', () => {
|
||||||
|
const rows = [{ b_id: 'e1', b_field_id: '9', b_visible: 1, b_xh: 30 }]
|
||||||
|
const next = toggleForm(rows, makeField(), MODULE_ID)
|
||||||
|
expect(next).toHaveLength(2)
|
||||||
|
const created = next[1]
|
||||||
|
expect(created.b_field_id).toBe('10')
|
||||||
|
expect(created.b_visible).toBe(1)
|
||||||
|
expect(created.b_colspan).toBe(24)
|
||||||
|
expect(created.b_module_id).toBe('m1')
|
||||||
|
expect(created.b_xh).toBe(40) // 末尾追加,不排到表单最前
|
||||||
|
expect(next[0]).toBe(rows[0]) // 非目标行保持原引用
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('列表参与切换', () => {
|
||||||
|
it('行存在:切换 b_visible(与「删除列=隐藏」语义一致)', () => {
|
||||||
|
const rows = [{ b_id: 'v1', b_field_id: '10', b_visible: 1, b_width: 110 }]
|
||||||
|
const off = toggleList(rows, makeField(), MODULE_ID)
|
||||||
|
expect(off[0].b_visible).toBe(0)
|
||||||
|
expect(off[0].b_width).toBe(110)
|
||||||
|
expect(toggleList(off, makeField(), MODULE_ID)[0].b_visible).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('行不存在:按默认生成且强制显示(含大文本)', () => {
|
||||||
|
const textarea = makeField({ b_type: 'textarea' })
|
||||||
|
const next = toggleList([], textarea, MODULE_ID)
|
||||||
|
expect(next).toHaveLength(1)
|
||||||
|
expect(next[0].b_visible).toBe(1) // 显式意愿覆盖大文本默认不参与
|
||||||
|
expect(next[0].b_xh).toBe(10)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('查询参与切换', () => {
|
||||||
|
it('行存在:切换 b_canuse', () => {
|
||||||
|
const rows = [{ b_id: 'q1', b_field_id: '10', b_canuse: 0, b_operator: 'eq' }]
|
||||||
|
const on = toggleQuery(rows, makeField(), MODULE_ID)
|
||||||
|
expect(on[0].b_canuse).toBe(1)
|
||||||
|
expect(on[0].b_operator).toBe('eq') // 既有查询配置保留
|
||||||
|
expect(toggleQuery(on, makeField(), MODULE_ID)[0].b_canuse).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('行不存在:按默认生成(money → number/eq)且强制参与', () => {
|
||||||
|
const next = toggleQuery([], makeField(), MODULE_ID)
|
||||||
|
expect(next).toHaveLength(1)
|
||||||
|
expect(next[0].b_canuse).toBe(1)
|
||||||
|
expect(next[0].b_component).toBe('number')
|
||||||
|
expect(next[0].b_operator).toBe('eq')
|
||||||
|
expect(next[0].b_field_module_id).toBe('m1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('切换的不变性', () => {
|
||||||
|
it('返回新数组,不修改原数组', () => {
|
||||||
|
const rows = [{ b_id: 'e1', b_field_id: '10', b_visible: 1 }]
|
||||||
|
const next = toggleForm(rows, makeField(), MODULE_ID)
|
||||||
|
expect(next).not.toBe(rows)
|
||||||
|
expect(rows[0].b_visible).toBe(1) // 原行未被修改
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in new issue
Block a user