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
+208
-240
No files matched your search
+32
-107
@@ -36,6 +36,17 @@ import {
|
||||
} from '@/components/ui'
|
||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||
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({
|
||||
moduleId: { type: String, required: true },
|
||||
@@ -186,14 +197,6 @@ function emitRows(rows) {
|
||||
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 自动换行一致——从分组段首部顺序向前打包,
|
||||
累计宽度超过 24 即换行。返回目标字段所在视觉行的索引,及行内其它字段宽度和 */
|
||||
function collectLineIndexes(rows, insertedIndex) {
|
||||
@@ -245,21 +248,9 @@ function rebalanceInsert(rows, insertedIndex, { fill = true } = {}) {
|
||||
}
|
||||
|
||||
/* ================= 公共查询表 ================= */
|
||||
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
|
||||
})
|
||||
const { fieldNameMap, fieldMap, fieldOf, fieldName, controlTypeLabel } = useFieldLookup(
|
||||
() => props.fields,
|
||||
)
|
||||
|
||||
const groupMap = computed(() => {
|
||||
const map = new Map()
|
||||
@@ -281,12 +272,7 @@ const groupOptions = computed(() => {
|
||||
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
||||
function syncToList() {
|
||||
if (props.loading) return
|
||||
const visibleMap = new Map()
|
||||
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)
|
||||
}
|
||||
}
|
||||
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||
if (visibleMap.size === 0) {
|
||||
Message.info('编辑配置中没有字段')
|
||||
return
|
||||
@@ -306,12 +292,7 @@ function syncToList() {
|
||||
|
||||
function syncToQuery() {
|
||||
if (props.loading) return
|
||||
const visibleMap = new Map()
|
||||
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)
|
||||
}
|
||||
}
|
||||
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||
if (visibleMap.size === 0) {
|
||||
Message.info('编辑配置中没有字段')
|
||||
return
|
||||
@@ -389,17 +370,6 @@ function formDataColumns() {
|
||||
const dataColumns = computed(() => formDataColumns())
|
||||
|
||||
/* ================= 设计视图:字段/分组推导 ================= */
|
||||
const FIELD_TYPE_LABELS = {
|
||||
input: '输入框',
|
||||
textarea: '文本域',
|
||||
number: '数字',
|
||||
money: '金额',
|
||||
date: '日期',
|
||||
datetime: '日期时间',
|
||||
checkbox: '复选框',
|
||||
select: '下拉选择',
|
||||
}
|
||||
|
||||
// 24 栅格制下的常用宽度选项(栅格宽度 b_colspan)
|
||||
const COLSPAN_OPTIONS = [
|
||||
{ label: '整行 24/24', value: '24' },
|
||||
@@ -421,22 +391,6 @@ function spanOf(row) {
|
||||
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) {
|
||||
return fieldOf(row)?.b_type === 'textarea'
|
||||
}
|
||||
@@ -470,19 +424,7 @@ function heightOf(row) {
|
||||
const libSearch = ref('')
|
||||
// 字段库:展示模块的全部字段(含未加入表单的字段),按名称 a-z 排序(无类型分组小标题);
|
||||
// 搜索时按名称/编码过滤,命中才显示
|
||||
const libraryFields = computed(() => {
|
||||
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'),
|
||||
)
|
||||
})
|
||||
const libraryFields = computed(() => filterLibraryFields(props.fields, libSearch.value))
|
||||
|
||||
function isFieldAdded(field) {
|
||||
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 createEditRow = createEditRowFactory({
|
||||
moduleId: props.moduleId,
|
||||
getExtra: (field, groupId) => {
|
||||
const sectionKey = groupId == null ? 'ungrouped' : `g-${String(groupId)}`
|
||||
return {
|
||||
b_id: createTempId(),
|
||||
b_module_id: props.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,
|
||||
// 新字段宽度取该分组设置的默认宽度(默认 24 独占一行)
|
||||
b_colspan: fieldColsOf(sectionKey),
|
||||
b_height: field.b_type === 'textarea' ? 120 : null,
|
||||
b_placeholder: null,
|
||||
b_xh: 0,
|
||||
}
|
||||
}
|
||||
return { b_colspan: fieldColsOf(sectionKey) }
|
||||
},
|
||||
})
|
||||
|
||||
function insertField(
|
||||
field,
|
||||
@@ -1219,7 +1151,7 @@ function moveSelectedRow(delta) {
|
||||
|
||||
function toggleRowFlag(row, key) {
|
||||
if (!row || props.loading) return
|
||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
||||
toggleFlag(row, key)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
@@ -1230,28 +1162,21 @@ function onLabelChip(row, key) {
|
||||
|
||||
/* 复制/粘贴:仅复制布局与状态属性(不含标识、排序号、分组),用于把某字段的配置套用到其它字段 */
|
||||
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() {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading) return
|
||||
clipboardProps.value = {
|
||||
name: fieldName(row),
|
||||
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,
|
||||
},
|
||||
}
|
||||
clipboardProps.value = buildClipboardProps(row, fieldName, CLIP_KEYS, {
|
||||
b_colspan: (r) => spanOf(r),
|
||||
})
|
||||
}
|
||||
|
||||
function pastePropsToSelected() {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading || !clipboardProps.value) return
|
||||
Object.assign(row, clipboardProps.value.props)
|
||||
applyClipboardProps(row, clipboardProps.value.props)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
@@ -1354,7 +1279,7 @@ function libClone(field) {
|
||||
function toggleInspector(key) {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading) return
|
||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
||||
toggleFlag(row, key)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
|
||||
+27
-103
@@ -27,6 +27,16 @@ import ModuleStructureTree from './structure-tree/ModuleStructureTree.vue'
|
||||
import FmsTable from '@/components/fms-table/FmsTable.vue'
|
||||
import { createTempId } from '@/utils/tempId'
|
||||
import { useGroupTree, buildGroupTree } from '../composables/useGroupTree'
|
||||
import { useFieldLookup } from '../composables/useFieldLookup'
|
||||
import {
|
||||
applyClipboardProps,
|
||||
buildClipboardProps,
|
||||
buildVisibilityMap,
|
||||
createEditRowFactory,
|
||||
filterLibraryFields,
|
||||
renumberRows,
|
||||
toggleFlag,
|
||||
} from '../editPanelShared'
|
||||
|
||||
const props = defineProps({
|
||||
moduleId: { type: String, required: true },
|
||||
@@ -83,41 +93,19 @@ function emitUpdate() {
|
||||
|
||||
/* ================= 数据视图:行排序 ================= */
|
||||
function onRowOrderChange(rows) {
|
||||
const sorted = rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
||||
localEdit.value = sorted
|
||||
localEdit.value = renumberRows(rows)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
function renumberRows(rows) {
|
||||
return rows.map((row, index) => ({ ...row, b_xh: (index + 1) * 10 }))
|
||||
}
|
||||
|
||||
/* ================= 公共查询表 ================= */
|
||||
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
|
||||
})
|
||||
const { fieldNameMap, fieldMap, fieldOf, fieldName, controlTypeLabel } = useFieldLookup(
|
||||
() => props.fields,
|
||||
)
|
||||
|
||||
/* ================= 数据视图:同步到列表 / 查询 ================= */
|
||||
function syncToList() {
|
||||
if (props.loading) return
|
||||
const visibleMap = new Map()
|
||||
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)
|
||||
}
|
||||
}
|
||||
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||
if (visibleMap.size === 0) {
|
||||
Message.info('编辑配置中没有字段')
|
||||
return
|
||||
@@ -137,12 +125,7 @@ function syncToList() {
|
||||
|
||||
function syncToQuery() {
|
||||
if (props.loading) return
|
||||
const visibleMap = new Map()
|
||||
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)
|
||||
}
|
||||
}
|
||||
const visibleMap = buildVisibilityMap(localEdit.value)
|
||||
if (visibleMap.size === 0) {
|
||||
Message.info('编辑配置中没有字段')
|
||||
return
|
||||
@@ -205,16 +188,6 @@ function tableDataColumns() {
|
||||
const dataColumns = computed(() => tableDataColumns())
|
||||
|
||||
/* ================= 设计视图:字段/分组推导 ================= */
|
||||
const FIELD_TYPE_LABELS = {
|
||||
input: '输入框',
|
||||
textarea: '文本域',
|
||||
number: '数字',
|
||||
money: '金额',
|
||||
date: '日期',
|
||||
datetime: '日期时间',
|
||||
checkbox: '复选框',
|
||||
select: '下拉选择',
|
||||
}
|
||||
|
||||
/* 子表常用列宽(px) */
|
||||
const COLUMN_WIDTH_OPTIONS = [
|
||||
@@ -225,22 +198,6 @@ const COLUMN_WIDTH_OPTIONS = [
|
||||
{ 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) {
|
||||
const v = Number(row?.b_width)
|
||||
@@ -298,19 +255,7 @@ const libSearch = ref('')
|
||||
const draggingFieldId = ref(null)
|
||||
// 字段库:展示模块的全部字段(含未加入表格的字段),按名称 a-z 排序;
|
||||
// 搜索时按名称/编码过滤,命中才显示
|
||||
const libraryFields = computed(() => {
|
||||
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'),
|
||||
)
|
||||
})
|
||||
const libraryFields = computed(() => filterLibraryFields(props.fields, libSearch.value))
|
||||
|
||||
function isFieldAdded(field) {
|
||||
return localEdit.value.some((row) => String(row.b_field_id) === String(field.b_id))
|
||||
@@ -781,23 +726,11 @@ function toggleBandEnable() {
|
||||
}
|
||||
|
||||
/* ================= 列:增删改 ================= */
|
||||
function createEditRow(field, groupId) {
|
||||
return {
|
||||
b_id: createTempId(),
|
||||
b_module_id: props.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_width: defaultWidthOf(field),
|
||||
b_xh: 0,
|
||||
}
|
||||
}
|
||||
// 子表行默认:b_colspan=24(表格不使用),额外补 b_width(按字段类型取默认列宽)
|
||||
const createEditRow = createEditRowFactory({
|
||||
moduleId: props.moduleId,
|
||||
getExtra: (field) => ({ b_width: defaultWidthOf(field) }),
|
||||
})
|
||||
|
||||
// 插入到某分组带的直属列末尾(空分组按树前序位置)
|
||||
function appendIntoGroup(row, groupId, rows) {
|
||||
@@ -938,28 +871,19 @@ function scrollToRowCell(fieldId) {
|
||||
|
||||
/* ================= 复制/粘贴(列属性) ================= */
|
||||
const clipboardProps = ref(null)
|
||||
// 子表复制属性键:布局与状态属性(不含标识、排序号、分组)
|
||||
const CLIP_KEYS = ['b_required', 'b_readonly', 'b_disabled', 'b_visible', 'b_placeholder', 'b_height', 'b_width']
|
||||
|
||||
function copyRowProps() {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading) return
|
||||
clipboardProps.value = {
|
||||
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,
|
||||
},
|
||||
}
|
||||
clipboardProps.value = buildClipboardProps(row, fieldName, CLIP_KEYS)
|
||||
}
|
||||
|
||||
function pastePropsToSelected() {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading || !clipboardProps.value) return
|
||||
Object.assign(row, clipboardProps.value.props)
|
||||
applyClipboardProps(row, clipboardProps.value.props)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
@@ -1241,7 +1165,7 @@ function onColResizePointerEnd() {
|
||||
function toggleInspector(key) {
|
||||
const row = selectedRow.value
|
||||
if (!row || props.loading) return
|
||||
row[key] = Number(row[key]) === 1 ? 0 : 1
|
||||
toggleFlag(row, key)
|
||||
emitUpdate()
|
||||
}
|
||||
|
||||
|
||||
+2
-30
@@ -27,6 +27,7 @@ import {
|
||||
import draggable from 'vuedraggable'
|
||||
import StructureTreeNode from './StructureTreeNode.vue'
|
||||
import { useGroupTree } from '../../composables/useGroupTree'
|
||||
import { useFieldLookup } from '../../composables/useFieldLookup'
|
||||
|
||||
const props = defineProps({
|
||||
groups: { type: Array, default: () => [] },
|
||||
@@ -53,36 +54,7 @@ const emit = defineEmits([
|
||||
])
|
||||
|
||||
/* ================= 字段/名称推导 ================= */
|
||||
const FIELD_TYPE_LABELS = {
|
||||
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 || '未知'
|
||||
}
|
||||
const { fieldName, controlTypeLabel } = useFieldLookup(() => props.fields)
|
||||
|
||||
/* ================= 分组树构建 =================
|
||||
* 树构建与元信息推导见 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