refactor(module-management): 阶段二——抽取字段查询与共享纯函数
- 新建 composables/useFieldLookup.js:FIELD_TYPE_LABELS 常量 +
useFieldLookup(fieldsRef) 返回 fieldNameMap/fieldMap/fieldOf/fieldName/controlTypeLabel
- 新建 editPanelShared.js(纯函数,无 Vue 依赖):renumberRows/buildVisibilityMap/
buildClipboardProps/applyClipboardProps/toggleFlag/filterLibraryFields/createEditRowFactory
- ModuleTableEditPanel/ModuleFormEditPanel/ModuleStructureTree 三处同体字段查询
统一改用 useFieldLookup,删除各自 FIELD_TYPE_LABELS/fieldNameMap/fieldMap/fieldOf/
fieldName/controlTypeLabel 复制体
- 两面板 syncToList/syncToQuery 共享 buildVisibilityMap(emit 与提示文案留在各自面板)
- 两面板 copyRowProps/pastePropsToSelected 共享 buildClipboardProps/applyClipboardProps:
键集合 diff 后参数化(子表 b_width / 主表 b_colspan+spanOf 归一),行为不变
- createEditRow 语义相同、细节不同(子表写 b_width、主表写 b_colspan)→
抽参数化工厂 createEditRowFactory({ moduleId, getExtra }),两面板各自传参
- 主表 groupIdOf 与 useGroupTree.groupIdOfRow 函数体逐字相同 → 改导入(别名保持调用点不变)
- 字段库 libraryFields 的过滤排序逻辑抽为 filterLibraryFields(.sort→.toSorted,
消除 oxlint 变异告警);toggleInspector/toggleRowFlag 核心抽为 toggleFlag
行为不变;测试 184 通过(2 个既有失败与本重构无关);lint 告警 60→59(净减 1)
This commit is contained in:
1 parent
158eb3c029
commit
27752351b9
5 files changed
+209
-241
No files matched your search
+33
-108
@@ -36,6 +36,17 @@ import {
|
|||||||
} from '@/components/ui'
|
} from '@/components/ui'
|
||||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||||
import { createTempId } from '@/utils/tempId'
|
import { createTempId } from '@/utils/tempId'
|
||||||
|
import { groupIdOfRow as groupIdOf } from '../composables/useGroupTree'
|
||||||
|
import { useFieldLookup } from '../composables/useFieldLookup'
|
||||||
|
import {
|
||||||
|
applyClipboardProps,
|
||||||
|
buildClipboardProps,
|
||||||
|
buildVisibilityMap,
|
||||||
|
createEditRowFactory,
|
||||||
|
filterLibraryFields,
|
||||||
|
renumberRows,
|
||||||
|
toggleFlag,
|
||||||
|
} from '../editPanelShared'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
moduleId: { type: String, required: true },
|
moduleId: { type: String, required: true },
|
||||||
@@ -186,14 +197,6 @@ function emitRows(rows) {
|
|||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
function renumberRows(rows) {
|
|
||||||
return rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupIdOf(row) {
|
|
||||||
return row?.b_group_id == null ? null : String(row.b_group_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 逻辑行推导:与 CSS grid 自动换行一致——从分组段首部顺序向前打包,
|
/* 逻辑行推导:与 CSS grid 自动换行一致——从分组段首部顺序向前打包,
|
||||||
累计宽度超过 24 即换行。返回目标字段所在视觉行的索引,及行内其它字段宽度和 */
|
累计宽度超过 24 即换行。返回目标字段所在视觉行的索引,及行内其它字段宽度和 */
|
||||||
function collectLineIndexes(rows, insertedIndex) {
|
function collectLineIndexes(rows, insertedIndex) {
|
||||||
@@ -245,21 +248,9 @@ function rebalanceInsert(rows, insertedIndex, { fill = true } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ================= 公共查询表 ================= */
|
/* ================= 公共查询表 ================= */
|
||||||
const fieldNameMap = computed(() => {
|
const { fieldNameMap, fieldMap, fieldOf, fieldName, controlTypeLabel } = useFieldLookup(
|
||||||
const map = new Map()
|
() => props.fields,
|
||||||
for (const field of props.fields || []) {
|
)
|
||||||
map.set(String(field.b_id), field.b_name || field.b_field)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
const fieldMap = computed(() => {
|
|
||||||
const map = new Map()
|
|
||||||
for (const field of props.fields || []) {
|
|
||||||
map.set(String(field.b_id), field)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
const groupMap = computed(() => {
|
const groupMap = computed(() => {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
@@ -281,12 +272,7 @@ const groupOptions = computed(() => {
|
|||||||
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
||||||
function syncToList() {
|
function syncToList() {
|
||||||
if (props.loading) return
|
if (props.loading) return
|
||||||
const visibleMap = new Map()
|
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||||
for (const row of localEdit.value) {
|
|
||||||
if (row.b_field_id != null) {
|
|
||||||
visibleMap.set(String(row.b_field_id), Number(row.b_visible) === 1 ? 1 : 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (visibleMap.size === 0) {
|
if (visibleMap.size === 0) {
|
||||||
Message.info('编辑配置中没有字段')
|
Message.info('编辑配置中没有字段')
|
||||||
return
|
return
|
||||||
@@ -306,12 +292,7 @@ function syncToList() {
|
|||||||
|
|
||||||
function syncToQuery() {
|
function syncToQuery() {
|
||||||
if (props.loading) return
|
if (props.loading) return
|
||||||
const visibleMap = new Map()
|
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||||
for (const row of localEdit.value) {
|
|
||||||
if (row.b_field_id != null) {
|
|
||||||
visibleMap.set(String(row.b_field_id), Number(row.b_visible) === 1 ? 1 : 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (visibleMap.size === 0) {
|
if (visibleMap.size === 0) {
|
||||||
Message.info('编辑配置中没有字段')
|
Message.info('编辑配置中没有字段')
|
||||||
return
|
return
|
||||||
@@ -389,17 +370,6 @@ function formDataColumns() {
|
|||||||
const dataColumns = computed(() => formDataColumns())
|
const dataColumns = computed(() => formDataColumns())
|
||||||
|
|
||||||
/* ================= 设计视图:字段/分组推导 ================= */
|
/* ================= 设计视图:字段/分组推导 ================= */
|
||||||
const FIELD_TYPE_LABELS = {
|
|
||||||
input: '输入框',
|
|
||||||
textarea: '文本域',
|
|
||||||
number: '数字',
|
|
||||||
money: '金额',
|
|
||||||
date: '日期',
|
|
||||||
datetime: '日期时间',
|
|
||||||
checkbox: '复选框',
|
|
||||||
select: '下拉选择',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 24 栅格制下的常用宽度选项(栅格宽度 b_colspan)
|
// 24 栅格制下的常用宽度选项(栅格宽度 b_colspan)
|
||||||
const COLSPAN_OPTIONS = [
|
const COLSPAN_OPTIONS = [
|
||||||
{ label: '整行 24/24', value: '24' },
|
{ label: '整行 24/24', value: '24' },
|
||||||
@@ -421,22 +391,6 @@ function spanOf(row) {
|
|||||||
return v >= 1 && v <= 24 ? v : 24
|
return v >= 1 && v <= 24 ? v : 24
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldOf(row) {
|
|
||||||
if (row?.b_field_id == null) return null
|
|
||||||
return fieldMap.value.get(String(row.b_field_id)) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldName(row) {
|
|
||||||
const fid = row?.b_field_id
|
|
||||||
if (fid == null) return ''
|
|
||||||
return fieldNameMap.value.get(String(fid)) || String(fid)
|
|
||||||
}
|
|
||||||
|
|
||||||
function controlTypeLabel(row) {
|
|
||||||
const field = fieldOf(row)
|
|
||||||
return FIELD_TYPE_LABELS[field?.b_type] || field?.b_type || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTextarea(row) {
|
function isTextarea(row) {
|
||||||
return fieldOf(row)?.b_type === 'textarea'
|
return fieldOf(row)?.b_type === 'textarea'
|
||||||
}
|
}
|
||||||
@@ -470,19 +424,7 @@ function heightOf(row) {
|
|||||||
const libSearch = ref('')
|
const libSearch = ref('')
|
||||||
// 字段库:展示模块的全部字段(含未加入表单的字段),按名称 a-z 排序(无类型分组小标题);
|
// 字段库:展示模块的全部字段(含未加入表单的字段),按名称 a-z 排序(无类型分组小标题);
|
||||||
// 搜索时按名称/编码过滤,命中才显示
|
// 搜索时按名称/编码过滤,命中才显示
|
||||||
const libraryFields = computed(() => {
|
const libraryFields = computed(() => filterLibraryFields(props.fields, libSearch.value))
|
||||||
const kw = libSearch.value.trim().toLowerCase()
|
|
||||||
return [...(props.fields || [])]
|
|
||||||
.filter(
|
|
||||||
(f) =>
|
|
||||||
!kw ||
|
|
||||||
String(f.b_name || '').toLowerCase().includes(kw) ||
|
|
||||||
String(f.b_field || '').toLowerCase().includes(kw),
|
|
||||||
)
|
|
||||||
.sort((a, b) =>
|
|
||||||
String(a.b_name || a.b_field || '').localeCompare(String(b.b_name || b.b_field || ''), 'zh'),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function isFieldAdded(field) {
|
function isFieldAdded(field) {
|
||||||
return localEdit.value.some((row) => String(row.b_field_id) === String(field.b_id))
|
return localEdit.value.some((row) => String(row.b_field_id) === String(field.b_id))
|
||||||
@@ -732,24 +674,14 @@ function selectRow(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 字段库 → 画布:已加入则定位选中,未加入则按目标位置插入。
|
// 字段库 → 画布:已加入则定位选中,未加入则按目标位置插入。
|
||||||
function createEditRow(field, groupId) {
|
// 表单行默认:b_colspan 取该分组设置的默认宽度(默认 24 独占一行),无 b_width
|
||||||
const sectionKey = groupId == null ? 'ungrouped' : `g-${String(groupId)}`
|
const createEditRow = createEditRowFactory({
|
||||||
return {
|
moduleId: props.moduleId,
|
||||||
b_id: createTempId(),
|
getExtra: (field, groupId) => {
|
||||||
b_module_id: props.moduleId,
|
const sectionKey = groupId == null ? 'ungrouped' : `g-${String(groupId)}`
|
||||||
b_field_id: String(field.b_id),
|
return { b_colspan: fieldColsOf(sectionKey) }
|
||||||
b_group_id: groupId || null,
|
},
|
||||||
b_visible: 1,
|
})
|
||||||
b_required: 0,
|
|
||||||
b_readonly: 0,
|
|
||||||
b_disabled: 0,
|
|
||||||
// 新字段宽度取该分组设置的默认宽度(默认 24 独占一行)
|
|
||||||
b_colspan: fieldColsOf(sectionKey),
|
|
||||||
b_height: field.b_type === 'textarea' ? 120 : null,
|
|
||||||
b_placeholder: null,
|
|
||||||
b_xh: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertField(
|
function insertField(
|
||||||
field,
|
field,
|
||||||
@@ -1219,7 +1151,7 @@ function moveSelectedRow(delta) {
|
|||||||
|
|
||||||
function toggleRowFlag(row, key) {
|
function toggleRowFlag(row, key) {
|
||||||
if (!row || props.loading) return
|
if (!row || props.loading) return
|
||||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
toggleFlag(row, key)
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1230,28 +1162,21 @@ function onLabelChip(row, key) {
|
|||||||
|
|
||||||
/* 复制/粘贴:仅复制布局与状态属性(不含标识、排序号、分组),用于把某字段的配置套用到其它字段 */
|
/* 复制/粘贴:仅复制布局与状态属性(不含标识、排序号、分组),用于把某字段的配置套用到其它字段 */
|
||||||
const clipboardProps = ref(null)
|
const clipboardProps = ref(null)
|
||||||
|
// 表单复制属性键:布局与状态属性(栅格宽度 b_colspan 需按 spanOf 归一)
|
||||||
|
const CLIP_KEYS = ['b_colspan', 'b_required', 'b_readonly', 'b_disabled', 'b_visible', 'b_placeholder', 'b_height']
|
||||||
|
|
||||||
function copyRowProps() {
|
function copyRowProps() {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading) return
|
if (!row || props.loading) return
|
||||||
clipboardProps.value = {
|
clipboardProps.value = buildClipboardProps(row, fieldName, CLIP_KEYS, {
|
||||||
name: fieldName(row),
|
b_colspan: (r) => spanOf(r),
|
||||||
props: {
|
})
|
||||||
b_colspan: spanOf(row),
|
|
||||||
b_required: Number(row.b_required) === 1 ? 1 : 0,
|
|
||||||
b_readonly: Number(row.b_readonly) === 1 ? 1 : 0,
|
|
||||||
b_disabled: Number(row.b_disabled) === 1 ? 1 : 0,
|
|
||||||
b_visible: Number(row.b_visible) === 1 ? 1 : 0,
|
|
||||||
b_placeholder: row.b_placeholder || null,
|
|
||||||
b_height: row.b_height == null ? null : Number(row.b_height) || null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pastePropsToSelected() {
|
function pastePropsToSelected() {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading || !clipboardProps.value) return
|
if (!row || props.loading || !clipboardProps.value) return
|
||||||
Object.assign(row, clipboardProps.value.props)
|
applyClipboardProps(row, clipboardProps.value.props)
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1354,7 +1279,7 @@ function libClone(field) {
|
|||||||
function toggleInspector(key) {
|
function toggleInspector(key) {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading) return
|
if (!row || props.loading) return
|
||||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
toggleFlag(row, key)
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-103
@@ -27,6 +27,16 @@ import ModuleStructureTree from './structure-tree/ModuleStructureTree.vue'
|
|||||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||||
import { createTempId } from '@/utils/tempId'
|
import { createTempId } from '@/utils/tempId'
|
||||||
import { useGroupTree, buildGroupTree } from '../composables/useGroupTree'
|
import { useGroupTree, buildGroupTree } from '../composables/useGroupTree'
|
||||||
|
import { useFieldLookup } from '../composables/useFieldLookup'
|
||||||
|
import {
|
||||||
|
applyClipboardProps,
|
||||||
|
buildClipboardProps,
|
||||||
|
buildVisibilityMap,
|
||||||
|
createEditRowFactory,
|
||||||
|
filterLibraryFields,
|
||||||
|
renumberRows,
|
||||||
|
toggleFlag,
|
||||||
|
} from '../editPanelShared'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
moduleId: { type: String, required: true },
|
moduleId: { type: String, required: true },
|
||||||
@@ -83,41 +93,19 @@ function emitUpdate() {
|
|||||||
|
|
||||||
/* ================= 数据视图:行排序 ================= */
|
/* ================= 数据视图:行排序 ================= */
|
||||||
function onRowOrderChange(rows) {
|
function onRowOrderChange(rows) {
|
||||||
const sorted = rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
localEdit.value = renumberRows(rows)
|
||||||
localEdit.value = sorted
|
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
function renumberRows(rows) {
|
|
||||||
return rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= 公共查询表 ================= */
|
/* ================= 公共查询表 ================= */
|
||||||
const fieldNameMap = computed(() => {
|
const { fieldNameMap, fieldMap, fieldOf, fieldName, controlTypeLabel } = useFieldLookup(
|
||||||
const map = new Map()
|
() => props.fields,
|
||||||
for (const field of props.fields || []) {
|
)
|
||||||
map.set(String(field.b_id), field.b_name || field.b_field)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
const fieldMap = computed(() => {
|
|
||||||
const map = new Map()
|
|
||||||
for (const field of props.fields || []) {
|
|
||||||
map.set(String(field.b_id), field)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
||||||
function syncToList() {
|
function syncToList() {
|
||||||
if (props.loading) return
|
if (props.loading) return
|
||||||
const visibleMap = new Map()
|
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||||
for (const row of localEdit.value) {
|
|
||||||
if (row.b_field_id != null) {
|
|
||||||
visibleMap.set(String(row.b_field_id), Number(row.b_visible) === 1 ? 1 : 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (visibleMap.size === 0) {
|
if (visibleMap.size === 0) {
|
||||||
Message.info('编辑配置中没有字段')
|
Message.info('编辑配置中没有字段')
|
||||||
return
|
return
|
||||||
@@ -137,12 +125,7 @@ function syncToList() {
|
|||||||
|
|
||||||
function syncToQuery() {
|
function syncToQuery() {
|
||||||
if (props.loading) return
|
if (props.loading) return
|
||||||
const visibleMap = new Map()
|
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||||
for (const row of localEdit.value) {
|
|
||||||
if (row.b_field_id != null) {
|
|
||||||
visibleMap.set(String(row.b_field_id), Number(row.b_visible) === 1 ? 1 : 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (visibleMap.size === 0) {
|
if (visibleMap.size === 0) {
|
||||||
Message.info('编辑配置中没有字段')
|
Message.info('编辑配置中没有字段')
|
||||||
return
|
return
|
||||||
@@ -205,16 +188,6 @@ function tableDataColumns() {
|
|||||||
const dataColumns = computed(() => tableDataColumns())
|
const dataColumns = computed(() => tableDataColumns())
|
||||||
|
|
||||||
/* ================= 设计视图:字段/分组推导 ================= */
|
/* ================= 设计视图:字段/分组推导 ================= */
|
||||||
const FIELD_TYPE_LABELS = {
|
|
||||||
input: '输入框',
|
|
||||||
textarea: '文本域',
|
|
||||||
number: '数字',
|
|
||||||
money: '金额',
|
|
||||||
date: '日期',
|
|
||||||
datetime: '日期时间',
|
|
||||||
checkbox: '复选框',
|
|
||||||
select: '下拉选择',
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 子表常用列宽(px) */
|
/* 子表常用列宽(px) */
|
||||||
const COLUMN_WIDTH_OPTIONS = [
|
const COLUMN_WIDTH_OPTIONS = [
|
||||||
@@ -225,22 +198,6 @@ const COLUMN_WIDTH_OPTIONS = [
|
|||||||
{ label: '大 240', value: '240' },
|
{ label: '大 240', value: '240' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function fieldOf(row) {
|
|
||||||
if (row?.b_field_id == null) return null
|
|
||||||
return fieldMap.value.get(String(row.b_field_id)) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldName(row) {
|
|
||||||
const fid = row?.b_field_id
|
|
||||||
if (fid == null) return ''
|
|
||||||
return fieldNameMap.value.get(String(fid)) || String(fid)
|
|
||||||
}
|
|
||||||
|
|
||||||
function controlTypeLabel(row) {
|
|
||||||
const field = fieldOf(row)
|
|
||||||
return FIELD_TYPE_LABELS[field?.b_type] || field?.b_type || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- 表格(子表)模式:列宽 ---- */
|
/* ---- 表格(子表)模式:列宽 ---- */
|
||||||
function widthOf(row) {
|
function widthOf(row) {
|
||||||
const v = Number(row?.b_width)
|
const v = Number(row?.b_width)
|
||||||
@@ -298,19 +255,7 @@ const libSearch = ref('')
|
|||||||
const draggingFieldId = ref(null)
|
const draggingFieldId = ref(null)
|
||||||
// 字段库:展示模块的全部字段(含未加入表格的字段),按名称 a-z 排序;
|
// 字段库:展示模块的全部字段(含未加入表格的字段),按名称 a-z 排序;
|
||||||
// 搜索时按名称/编码过滤,命中才显示
|
// 搜索时按名称/编码过滤,命中才显示
|
||||||
const libraryFields = computed(() => {
|
const libraryFields = computed(() => filterLibraryFields(props.fields, libSearch.value))
|
||||||
const kw = libSearch.value.trim().toLowerCase()
|
|
||||||
return [...(props.fields || [])]
|
|
||||||
.filter(
|
|
||||||
(f) =>
|
|
||||||
!kw ||
|
|
||||||
String(f.b_name || '').toLowerCase().includes(kw) ||
|
|
||||||
String(f.b_field || '').toLowerCase().includes(kw),
|
|
||||||
)
|
|
||||||
.toSorted((a, b) =>
|
|
||||||
String(a.b_name || a.b_field || '').localeCompare(String(b.b_name || b.b_field || ''), 'zh'),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function isFieldAdded(field) {
|
function isFieldAdded(field) {
|
||||||
return localEdit.value.some((row) => String(row.b_field_id) === String(field.b_id))
|
return localEdit.value.some((row) => String(row.b_field_id) === String(field.b_id))
|
||||||
@@ -781,23 +726,11 @@ function toggleBandEnable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ================= 列:增删改 ================= */
|
/* ================= 列:增删改 ================= */
|
||||||
function createEditRow(field, groupId) {
|
// 子表行默认:b_colspan=24(表格不使用),额外补 b_width(按字段类型取默认列宽)
|
||||||
return {
|
const createEditRow = createEditRowFactory({
|
||||||
b_id: createTempId(),
|
moduleId: props.moduleId,
|
||||||
b_module_id: props.moduleId,
|
getExtra: (field) => ({ b_width: defaultWidthOf(field) }),
|
||||||
b_field_id: String(field.b_id),
|
})
|
||||||
b_group_id: groupId || null,
|
|
||||||
b_visible: 1,
|
|
||||||
b_required: 0,
|
|
||||||
b_readonly: 0,
|
|
||||||
b_disabled: 0,
|
|
||||||
b_colspan: 24,
|
|
||||||
b_height: field.b_type === 'textarea' ? 120 : null,
|
|
||||||
b_placeholder: null,
|
|
||||||
b_width: defaultWidthOf(field),
|
|
||||||
b_xh: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入到某分组带的直属列末尾(空分组按树前序位置)
|
// 插入到某分组带的直属列末尾(空分组按树前序位置)
|
||||||
function appendIntoGroup(row, groupId, rows) {
|
function appendIntoGroup(row, groupId, rows) {
|
||||||
@@ -938,28 +871,19 @@ function scrollToRowCell(fieldId) {
|
|||||||
|
|
||||||
/* ================= 复制/粘贴(列属性) ================= */
|
/* ================= 复制/粘贴(列属性) ================= */
|
||||||
const clipboardProps = ref(null)
|
const clipboardProps = ref(null)
|
||||||
|
// 子表复制属性键:布局与状态属性(不含标识、排序号、分组)
|
||||||
|
const CLIP_KEYS = ['b_required', 'b_readonly', 'b_disabled', 'b_visible', 'b_placeholder', 'b_height', 'b_width']
|
||||||
|
|
||||||
function copyRowProps() {
|
function copyRowProps() {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading) return
|
if (!row || props.loading) return
|
||||||
clipboardProps.value = {
|
clipboardProps.value = buildClipboardProps(row, fieldName, CLIP_KEYS)
|
||||||
name: fieldName(row),
|
|
||||||
props: {
|
|
||||||
b_required: Number(row.b_required) === 1 ? 1 : 0,
|
|
||||||
b_readonly: Number(row.b_readonly) === 1 ? 1 : 0,
|
|
||||||
b_disabled: Number(row.b_disabled) === 1 ? 1 : 0,
|
|
||||||
b_visible: Number(row.b_visible) === 1 ? 1 : 0,
|
|
||||||
b_placeholder: row.b_placeholder || null,
|
|
||||||
b_height: row.b_height == null ? null : Number(row.b_height) || null,
|
|
||||||
b_width: row.b_width == null ? null : Number(row.b_width) || null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pastePropsToSelected() {
|
function pastePropsToSelected() {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading || !clipboardProps.value) return
|
if (!row || props.loading || !clipboardProps.value) return
|
||||||
Object.assign(row, clipboardProps.value.props)
|
applyClipboardProps(row, clipboardProps.value.props)
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1241,7 +1165,7 @@ function onColResizePointerEnd() {
|
|||||||
function toggleInspector(key) {
|
function toggleInspector(key) {
|
||||||
const row = selectedRow.value
|
const row = selectedRow.value
|
||||||
if (!row || props.loading) return
|
if (!row || props.loading) return
|
||||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
toggleFlag(row, key)
|
||||||
emitUpdate()
|
emitUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-30
@@ -27,6 +27,7 @@ import {
|
|||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import StructureTreeNode from './StructureTreeNode.vue'
|
import StructureTreeNode from './StructureTreeNode.vue'
|
||||||
import { useGroupTree } from '../../composables/useGroupTree'
|
import { useGroupTree } from '../../composables/useGroupTree'
|
||||||
|
import { useFieldLookup } from '../../composables/useFieldLookup'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
groups: { type: Array, default: () => [] },
|
groups: { type: Array, default: () => [] },
|
||||||
@@ -53,36 +54,7 @@ const emit = defineEmits([
|
|||||||
])
|
])
|
||||||
|
|
||||||
/* ================= 字段/名称推导 ================= */
|
/* ================= 字段/名称推导 ================= */
|
||||||
const FIELD_TYPE_LABELS = {
|
const { fieldName, controlTypeLabel } = useFieldLookup(() => props.fields)
|
||||||
input: '输入框',
|
|
||||||
textarea: '文本域',
|
|
||||||
number: '数字',
|
|
||||||
money: '金额',
|
|
||||||
date: '日期',
|
|
||||||
datetime: '日期时间',
|
|
||||||
checkbox: '复选框',
|
|
||||||
select: '下拉选择',
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldNameMap = computed(() => {
|
|
||||||
const map = new Map()
|
|
||||||
for (const field of props.fields || []) map.set(String(field.b_id), field.b_name || field.b_field)
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
const fieldMap = computed(() => {
|
|
||||||
const map = new Map()
|
|
||||||
for (const field of props.fields || []) map.set(String(field.b_id), field)
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
function fieldName(row) {
|
|
||||||
if (row?.b_field_id == null) return ''
|
|
||||||
return fieldNameMap.value.get(String(row.b_field_id)) || String(row.b_field_id)
|
|
||||||
}
|
|
||||||
function controlTypeLabel(row) {
|
|
||||||
const field = row?.b_field_id != null ? fieldMap.value.get(String(row.b_field_id)) : null
|
|
||||||
return FIELD_TYPE_LABELS[field?.b_type] || field?.b_type || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= 分组树构建 =================
|
/* ================= 分组树构建 =================
|
||||||
* 树构建与元信息推导见 composables/useGroupTree(面板与结构树共用)。 */
|
* 树构建与元信息推导见 composables/useGroupTree(面板与结构树共用)。 */
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 字段查询与标签共享逻辑。
|
||||||
|
* 供 ModuleTableEditPanel / ModuleFormEditPanel / ModuleStructureTree 共用:
|
||||||
|
* 字段名 Map、字段对象 Map、字段查询函数、控件类型标签。
|
||||||
|
*/
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
export const FIELD_TYPE_LABELS = {
|
||||||
|
input: '输入框',
|
||||||
|
textarea: '文本域',
|
||||||
|
number: '数字',
|
||||||
|
money: '金额',
|
||||||
|
date: '日期',
|
||||||
|
datetime: '日期时间',
|
||||||
|
checkbox: '复选框',
|
||||||
|
select: '下拉选择',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('vue').Ref<Array>|import('vue').ComputedRef<Array>|(() => Array)} fieldsRef
|
||||||
|
* 字段数组 ref/computed,或返回字段数组的 getter 函数
|
||||||
|
*/
|
||||||
|
export function useFieldLookup(fieldsRef) {
|
||||||
|
const fields = typeof fieldsRef === 'function' ? computed(fieldsRef) : fieldsRef
|
||||||
|
|
||||||
|
const fieldNameMap = computed(() => {
|
||||||
|
const map = new Map()
|
||||||
|
for (const field of fields.value || []) {
|
||||||
|
map.set(String(field.b_id), field.b_name || field.b_field)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
const fieldMap = computed(() => {
|
||||||
|
const map = new Map()
|
||||||
|
for (const field of fields.value || []) {
|
||||||
|
map.set(String(field.b_id), field)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
function fieldOf(row) {
|
||||||
|
if (row?.b_field_id == null) return null
|
||||||
|
return fieldMap.value.get(String(row.b_field_id)) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldName(row) {
|
||||||
|
const fid = row?.b_field_id
|
||||||
|
if (fid == null) return ''
|
||||||
|
return fieldNameMap.value.get(String(fid)) || String(fid)
|
||||||
|
}
|
||||||
|
|
||||||
|
function controlTypeLabel(row) {
|
||||||
|
const field = fieldOf(row)
|
||||||
|
return FIELD_TYPE_LABELS[field?.b_type] || field?.b_type || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fieldNameMap, fieldMap, fieldOf, fieldName, controlTypeLabel }
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 两个编辑面板共享的纯函数(无 Vue 依赖)。
|
||||||
|
* 栅格重排、列属性复制/粘贴、可见性 Map、字段库搜索、编辑行构建等。
|
||||||
|
*/
|
||||||
|
import { createTempId } from '@/utils/tempId'
|
||||||
|
|
||||||
|
/** 按索引重排 b_xh:从 10 开始,步长 10 */
|
||||||
|
export function renumberRows(rows) {
|
||||||
|
return rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建字段可见性 Map:b_field_id → b_visible(1/0) */
|
||||||
|
export function buildVisibilityMap(rows) {
|
||||||
|
const map = new Map()
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.b_field_id != null) {
|
||||||
|
map.set(String(row.b_field_id), Number(row.b_visible) === 1 ? 1 : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制列属性:提取指定 keys 的标准化值,返回 { name, props }。
|
||||||
|
* 特殊键处理:b_placeholder → 字符串或 null;b_height/b_width → 数值或 null;
|
||||||
|
* 其余按键的 0/1 标志归一。normalize 可覆盖个别键的自定义取值(如 b_colspan 需 spanOf)。
|
||||||
|
*/
|
||||||
|
export function buildClipboardProps(row, fieldNameOf, keys, normalize = {}) {
|
||||||
|
const props = {}
|
||||||
|
for (const k of keys) {
|
||||||
|
if (normalize[k]) props[k] = normalize[k](row)
|
||||||
|
else if (k === 'b_placeholder') props[k] = row.b_placeholder || null
|
||||||
|
else if (k === 'b_height' || k === 'b_width') props[k] = row[k] == null ? null : Number(row[k]) || null
|
||||||
|
else props[k] = Number(row[k]) === 1 ? 1 : 0
|
||||||
|
}
|
||||||
|
return { name: fieldNameOf(row), props }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将剪切板属性对象应用到行的目标属性 */
|
||||||
|
export function applyClipboardProps(row, props) {
|
||||||
|
Object.assign(row, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 切换字段布尔标志(纯 mutation) */
|
||||||
|
export function toggleFlag(row, key) {
|
||||||
|
row[key] = Number(row[key]) === 1 ? 0 : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按关键字过滤字段(名称/编码),按名称排序 */
|
||||||
|
export function filterLibraryFields(fields, keyword) {
|
||||||
|
const kw = String(keyword || '').trim().toLowerCase()
|
||||||
|
return [...(fields || [])]
|
||||||
|
.filter(
|
||||||
|
(f) =>
|
||||||
|
!kw ||
|
||||||
|
String(f.b_name || '').toLowerCase().includes(kw) ||
|
||||||
|
String(f.b_field || '').toLowerCase().includes(kw),
|
||||||
|
)
|
||||||
|
.toSorted((a, b) =>
|
||||||
|
String(a.b_name || a.b_field || '').localeCompare(String(b.b_name || b.b_field || ''), 'zh'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建编辑行工厂函数。
|
||||||
|
* 共享:b_id / b_module_id / b_field_id / b_group_id / b_visible / b_required / b_readonly / b_disabled
|
||||||
|
* / b_colspan(默认24) / b_height(textarea 120) / b_placeholder / b_xh
|
||||||
|
* 差异:由 getExtra(field, groupId) 补充(如 b_width / b_colspan 覆盖)。
|
||||||
|
* @param {{ moduleId: string, getExtra?: (field: object, groupId: string|null) => object }} opts
|
||||||
|
* @returns {(field: object, groupId: string|null) => object}
|
||||||
|
*/
|
||||||
|
export function createEditRowFactory({ moduleId, getExtra }) {
|
||||||
|
return (field, groupId) => ({
|
||||||
|
b_id: createTempId(),
|
||||||
|
b_module_id: moduleId,
|
||||||
|
b_field_id: String(field.b_id),
|
||||||
|
b_group_id: groupId || null,
|
||||||
|
b_visible: 1,
|
||||||
|
b_required: 0,
|
||||||
|
b_readonly: 0,
|
||||||
|
b_disabled: 0,
|
||||||
|
b_colspan: 24,
|
||||||
|
b_height: field.b_type === 'textarea' ? 120 : null,
|
||||||
|
b_placeholder: null,
|
||||||
|
b_xh: 0,
|
||||||
|
...(getExtra ? getExtra(field, groupId) : {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user