20260903221055
This commit is contained in:
1 parent
3d4e2c7413
commit
0928437c67
46 files changed
+2121
-1643
No files matched your search
@@ -13,11 +13,11 @@
|
||||
* v-model:open="visible"
|
||||
* data-code="s_i18n_type"
|
||||
* :record="editingRow" // null=新增
|
||||
* :before-save="handleBeforeSave" // 可选:返回 false 阻止保存;返回 { extraUpdates } 追加
|
||||
* :on-before-save="handleBeforeSave" // 可选:返回 false 阻止保存;返回 { extraUpdates } 追加
|
||||
* @saved="onSaved"
|
||||
* />
|
||||
*
|
||||
* before-save(rowData, ctx): Promise<boolean | { extraUpdates?: [] }>
|
||||
* on-before-save(rowData, ctx): Promise<boolean | { extraUpdates?: [] }>
|
||||
* ctx = { isNew, record }
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
@@ -34,7 +34,12 @@ import {
|
||||
Select,
|
||||
} from '@/components/ui'
|
||||
import { loadDataApi, loadDataBySqlApi, nextIdApi, saveObjectApi } from '@/services/api'
|
||||
import { identifierRe, isEmptyValue, parseOptions } from '@/components/fms-module-list/moduleListUtils'
|
||||
import { diffRow } from '@/utils/dataChanges'
|
||||
import {
|
||||
identifierRe,
|
||||
isEmptyValue,
|
||||
parseOptions,
|
||||
} from '@/components/fms-module-list/moduleListUtils'
|
||||
|
||||
const props = defineProps({
|
||||
/** 受控打开 */
|
||||
@@ -45,8 +50,8 @@ const props = defineProps({
|
||||
record: { type: Object, default: null },
|
||||
/** 弹窗标题;缺省按新增/编辑自动生成 */
|
||||
title: { type: String, default: '' },
|
||||
/** 保存前回调:返回 false 阻止;返回 { extraUpdates: [] } 追加到同表 updates */
|
||||
beforeSave: { type: Function, default: null },
|
||||
/** 保存前业务钩子:返回 false 阻止;返回 { extraUpdates: [] } 追加到同表 updates */
|
||||
onBeforeSave: { type: Function, default: null },
|
||||
/** 额外禁用规则:(fieldName, ctx) => boolean,ctx={ isNew, record, formData } */
|
||||
fieldDisabled: { type: Function, default: null },
|
||||
/** 弹窗宽度(px 或 CSS 宽度);缺省(0)按表单栅格布局自动推断 */
|
||||
@@ -67,6 +72,8 @@ const groups = ref([]) // s_field_group
|
||||
const editRows = ref([]) // s_field_edit 可见行(含关联字段引用)
|
||||
const formData = ref({})
|
||||
const keyField = ref('b_id')
|
||||
/** 编辑基线(origin):打开弹窗时对 record 深拷贝快照,保存只提交与它的差异;新建模式为 null */
|
||||
const originRef = ref(null)
|
||||
|
||||
/* ================= 分组结构推导(与 FormPreview 一致) ================= */
|
||||
const sections = computed(() => {
|
||||
@@ -149,6 +156,10 @@ async function loadConfig() {
|
||||
|
||||
/** 初始化表单数据:编辑用 record 回填,新增按默认值 */
|
||||
function buildFormData() {
|
||||
const isNew = !props.record
|
||||
// 编辑态保留一份不可直接编辑的 origin 基线(深拷贝),保存时与其 diff;
|
||||
// 新建模式没有真实基线,保存走整行 insert
|
||||
originRef.value = isNew ? null : JSON.parse(JSON.stringify(props.record || {}))
|
||||
const source = props.record || {}
|
||||
const data = {}
|
||||
for (const row of editRows.value) {
|
||||
@@ -244,11 +255,21 @@ const resolvedWidth = computed(() => {
|
||||
return (WIDTH_TIERS.find((t) => n <= t.maxFields) || WIDTH_TIERS[WIDTH_TIERS.length - 1]).width
|
||||
})
|
||||
|
||||
/* ================= 保存 ================= */
|
||||
/* ================= 保存(handleSave → beforeSave → buildSaveData → 接口 → afterSave) ================= */
|
||||
/** 保存过程上下文:beforeSave 阶段准备的数据,buildSaveData / afterSave 阶段消费 */
|
||||
let saveCtx = { isNew: false, row: null, extraUpdates: [] }
|
||||
|
||||
/** 模块保存表(s_module.b_savetable 优先,其次 b_viewtable) */
|
||||
function saveTableName() {
|
||||
return String(moduleMeta.value?.b_savetable || moduleMeta.value?.b_viewtable || '')
|
||||
}
|
||||
|
||||
/** 构造完整合并行(含主键;checkbox→0/1、''→null 归一化),供保存前准备与 diff 使用 */
|
||||
function collectRowData() {
|
||||
const isNew = !props.record
|
||||
const row = {}
|
||||
if (isNew) row[keyField.value] = null // 主键待 nextIdApi 分配
|
||||
if (isNew)
|
||||
row[keyField.value] = null // 主键待 nextIdApi 分配
|
||||
else row[keyField.value] = props.record[keyField.value]
|
||||
|
||||
for (const editRow of editRows.value) {
|
||||
@@ -262,8 +283,8 @@ function collectRowData() {
|
||||
return { row, isNew }
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (saving.value) return
|
||||
/** 保存前校验、数据准备与业务钩子;返回 false 终止保存 */
|
||||
async function beforeSave() {
|
||||
// 必填校验:s_field_edit.b_required=1 且值为空 → 提示并定位
|
||||
for (const editRow of editRows.value) {
|
||||
if (Number(editRow.b_required) !== 1) continue
|
||||
@@ -271,60 +292,101 @@ async function handleSave() {
|
||||
const value = formData.value[editRow.field.b_field]
|
||||
if (isEmptyValue(value)) {
|
||||
Message.warning(`请填写「${fieldLabel(editRow)}」`)
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!saveTableName()) {
|
||||
Message.error('该模块未配置保存表')
|
||||
return false
|
||||
}
|
||||
|
||||
const { row, isNew } = collectRowData()
|
||||
|
||||
// 新增时排序字段未填 → 自动取当前最大值 + 10
|
||||
if (
|
||||
isNew &&
|
||||
props.orderField &&
|
||||
identifierRe.test(props.orderField) &&
|
||||
!Number(row[props.orderField])
|
||||
) {
|
||||
try {
|
||||
const viewTable = String(moduleMeta.value?.b_viewtable || '')
|
||||
if (viewTable) {
|
||||
const res = await loadDataBySqlApi(
|
||||
`SELECT ISNULL(MAX([${props.orderField}]), 0) AS mx FROM [${viewTable}]`,
|
||||
)
|
||||
row[props.orderField] = (Number(res.data?.[0]?.mx) || 0) + 10
|
||||
}
|
||||
} catch {
|
||||
// 查最大失败时不阻塞,让用户手填或存 0
|
||||
}
|
||||
}
|
||||
|
||||
// 新增时分配主键
|
||||
if (isNew && !row[keyField.value]) {
|
||||
row[keyField.value] = String((await nextIdApi()).data)
|
||||
}
|
||||
|
||||
// 外部业务钩子:接收完整合并行,返回 false 终止;返回 { extraUpdates } 追加到同表请求
|
||||
let extraUpdates = []
|
||||
if (props.onBeforeSave) {
|
||||
const result = await props.onBeforeSave(row, { isNew, record: props.record })
|
||||
if (result === false) {
|
||||
saveCtx.row = null
|
||||
return false
|
||||
}
|
||||
if (result?.extraUpdates?.length) extraUpdates = result.extraUpdates
|
||||
}
|
||||
saveCtx = { isNew, row, extraUpdates }
|
||||
return true
|
||||
}
|
||||
|
||||
/** 组装保存请求:编辑态用 diffRow 与 origin 做字段级对比,只提交变化字段;无变化返回 null(不调保存接口) */
|
||||
function buildSaveData() {
|
||||
const { isNew, row, extraUpdates } = saveCtx
|
||||
if (!row) return null
|
||||
const request = { table: saveTableName(), key_field: keyField.value }
|
||||
if (isNew) {
|
||||
request.inserts = [row]
|
||||
if (extraUpdates.length) request.updates = extraUpdates
|
||||
return [request]
|
||||
}
|
||||
|
||||
// 编辑态:字段级 diff(checkbox/null/数字归一已内置于 diffRow)
|
||||
const changes = diffRow({ current: row, original: originRef.value, primaryKey: keyField.value })
|
||||
if (!changes.hasChanges && !extraUpdates.length) return null
|
||||
// 本行有变化:主键 + 变化字段;仅外部钩子追加了 extraUpdates:只提交追加行,避免仅主键的空更新
|
||||
request.updates = changes.hasChanges ? [...changes.updates, ...extraUpdates] : extraUpdates
|
||||
return [request]
|
||||
}
|
||||
|
||||
/** 保存成功后的提示、事件与关闭 */
|
||||
function afterSave() {
|
||||
const { isNew, row } = saveCtx
|
||||
Message.success(isNew ? '新增成功' : '保存成功')
|
||||
emit('saved', { row, isNew })
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (saving.value) return false
|
||||
saveCtx = { isNew: false, row: null, extraUpdates: [] }
|
||||
saving.value = true
|
||||
try {
|
||||
const { row, isNew } = collectRowData()
|
||||
const table = String(moduleMeta.value.b_savetable || moduleMeta.value.b_viewtable || '')
|
||||
if (!table) {
|
||||
Message.error('该模块未配置保存表')
|
||||
return
|
||||
if (!(await beforeSave())) return false
|
||||
const saveData = buildSaveData()
|
||||
if (!saveData) {
|
||||
// 编辑态无任何字段变化:按规范不调用保存接口,直接提示并关闭
|
||||
Message.info('没有需要保存的变更')
|
||||
emit('update:open', false)
|
||||
return true
|
||||
}
|
||||
|
||||
// 新增时排序字段未填 → 自动取当前最大值 + 10
|
||||
if (isNew && props.orderField && identifierRe.test(props.orderField) && !Number(row[props.orderField])) {
|
||||
try {
|
||||
const viewTable = String(moduleMeta.value.b_viewtable || '')
|
||||
if (viewTable) {
|
||||
const res = await loadDataBySqlApi(
|
||||
`SELECT ISNULL(MAX([${props.orderField}]), 0) AS mx FROM [${viewTable}]`,
|
||||
)
|
||||
row[props.orderField] = (Number(res.data?.[0]?.mx) || 0) + 10
|
||||
}
|
||||
} catch {
|
||||
// 查最大失败时不阻塞,让用户手填或存 0
|
||||
}
|
||||
}
|
||||
|
||||
// 新增时分配主键
|
||||
if (isNew && !row[keyField.value]) {
|
||||
row[keyField.value] = String((await nextIdApi()).data)
|
||||
}
|
||||
|
||||
// 业务前处理钩子
|
||||
let extraUpdates = []
|
||||
if (props.beforeSave) {
|
||||
const result = await props.beforeSave(row, { isNew, record: props.record })
|
||||
if (result === false) return
|
||||
if (result?.extraUpdates?.length) extraUpdates = result.extraUpdates
|
||||
}
|
||||
|
||||
const request = { table, key_field: keyField.value }
|
||||
if (isNew) {
|
||||
request.inserts = [row]
|
||||
if (extraUpdates.length) request.updates = extraUpdates
|
||||
} else {
|
||||
request.updates = [row, ...extraUpdates]
|
||||
}
|
||||
await saveObjectApi([request])
|
||||
Message.success(isNew ? '新增成功' : '保存成功')
|
||||
emit('saved', { row, isNew })
|
||||
emit('update:open', false)
|
||||
await saveObjectApi(saveData)
|
||||
await afterSave()
|
||||
return true
|
||||
} catch (e) {
|
||||
Message.error('保存失败:' + (e?.message || e))
|
||||
return false
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -383,12 +445,7 @@ const selectOptionsOf = (field) => parseOptions(field).options
|
||||
:model-value="Number(formData[row.field.b_field]) === 1"
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
:model-value="formData[row.field.b_field] ?? ''"
|
||||
disabled
|
||||
readonly
|
||||
/>
|
||||
<Input v-else :model-value="formData[row.field.b_field] ?? ''" disabled readonly />
|
||||
</template>
|
||||
<Input
|
||||
v-else-if="row.field.b_type === 'input'"
|
||||
@@ -426,7 +483,9 @@ const selectOptionsOf = (field) => parseOptions(field).options
|
||||
<Input
|
||||
v-else
|
||||
v-model="formData[row.field.b_field]"
|
||||
:type="row.field.b_type === 'number' || row.field.b_type === 'money' ? 'number' : 'default'"
|
||||
:type="
|
||||
row.field.b_type === 'number' || row.field.b_type === 'money' ? 'number' : 'default'
|
||||
"
|
||||
:placeholder="row.b_placeholder"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
Reference in new issue
Block a user