This commit is contained in:
张成
2026-04-29 14:14:19 +08:00
parent 418dfe1583
commit 1850ec93a2
4 changed files with 20 additions and 469 deletions

View File

@@ -1,461 +0,0 @@
// 通用表格页面混入
import dynamicModelApi from '@/api/entity/dynamicModelApi.js';
import { mapFormFieldsToEditColumns } from '@/utils/formEditColumns.js';
import { getForeignRowLabel, rowsToIdOptions } from '@/utils/foreignRowLabel.js';
import { orderActionBtnsViewEditFirst } from '@/utils/actionBtnOrder.js';
export default {
created() {
this.ensure_meta_shape();
},
methods: {
/** 操作列:查看、编辑排在最左侧相邻,其余按钮顺序在后 */
renderRowActionBtns(h, btns) {
return window.framework.uiTool.getBtn(h, orderActionBtnsViewEditFirst(btns));
},
field_display_name(f) {
if (!f) return "";
return f.label || f.title || f.key || "";
},
/**
* 按 meta.formFields 整理行数据,供 editModal 绑定InputNumber 不能接收 "")。
* 外键数字字段空值用 undefined其它 number 空值或非数字用 0。
*/
normalizeRowForEditModal(row) {
if (!row || typeof row !== 'object') return row;
const fields =
this.meta && Array.isArray(this.meta.formFields) ? this.meta.formFields : [];
if (!fields.length) return { ...row };
const o = { ...row };
for (const f of fields) {
if (!f || f.form === false || f.type !== 'number') continue;
const k = f.key;
const v = o[k];
if (/_id$/.test(k)) {
if (v === '' || v === null || v === undefined) {
o[k] = undefined;
} else {
const n = Number(v);
o[k] = Number.isNaN(n) ? undefined : n;
}
} else {
if (v === '' || v === null || v === undefined) {
o[k] = 0;
} else {
const n = Number(v);
o[k] = Number.isNaN(n) ? 0 : n;
}
}
}
return o;
},
// 兼容页面只定义 fields 的场景,统一补齐 formFields / columns
ensure_meta_shape() {
if (!this.meta || typeof this.meta !== 'object') {
return;
}
const fields = Array.isArray(this.meta.formFields)
? this.meta.formFields
: (Array.isArray(this.meta.fields) ? this.meta.fields : []);
// code 字段:后端自动生成,表单中不显示
fields.forEach((f) => {
if (f && f.key === 'code') {
f.form = false;
f.required = false;
}
});
if (!Array.isArray(this.meta.formFields)) {
const formFields = fields.filter((f) => f && f.form !== false);
this.$set(this.meta, 'formFields', formFields);
}
if (!Array.isArray(this.meta.columns)) {
const columns = fields
.filter((f) => f && f.list)
.map((f) => ({
key: f.key,
title: f.label || f.title,
minWidth: f.minWidth,
}));
this.$set(this.meta, 'columns', columns);
}
},
// 通用的编辑列配置生成方法(实现见 utils/formEditColumns.js
generateEditColumns(formFields, selectSources) {
return mapFormFieldsToEditColumns(formFields, selectSources);
},
/**
* 标准列表列外键列显示关联名称select_sources、布尔、日期等
* meta 需含 columns以及 fields 或 formFields带 type
*/
buildListColumnsFromMeta(meta, selectSources) {
if (!meta || !Array.isArray(meta.columns)) return [];
const allFields = meta.fields || meta.formFields || [];
return this.generateListColumns(meta.columns, allFields, selectSources);
},
// 通用的列表列配置生成方法
generateListColumns(columns, allFields, selectSources) {
if (!columns || !Array.isArray(columns)) return [];
const fields = Array.isArray(allFields) ? allFields : [];
/** 系统统一时间字段(与后端 create_time / last_modify_time 一致),排在业务列之后、操作列之前 */
const colList = [...columns];
const keySet = new Set(colList.map((c) => c && c.key).filter(Boolean));
if (!keySet.has('create_time')) {
colList.push({ key: 'create_time', title: '创建时间', minWidth: 160 });
keySet.add('create_time');
}
if (!keySet.has('last_modify_time')) {
colList.push({ key: 'last_modify_time', title: '最后修改时间', minWidth: 160 });
keySet.add('last_modify_time');
}
const boolKeys = new Set(fields.filter((f) => f.type === 'bool').map((f) => f.key));
const selectKeys = new Set(fields.filter((f) => f.type === 'select').map((f) => f.key));
return colList.map((c) => {
// 外键:已加载下拉数据时显示名称(含仓库/库区/巷道及 mat_sku 等)
if (
selectSources &&
selectSources[c.key] &&
Array.isArray(selectSources[c.key]) &&
selectSources[c.key].length &&
/_id$/.test(c.key)
) {
return {
...c,
render: (h, params) => {
const id = params.row[c.key];
const source = selectSources[c.key];
const item = source && source.find((w) => String(w.key) === String(id));
return h('span', item ? item.value : id != null && id !== '' ? String(id) : '-');
},
};
}
// 处理选择字段显示status 按字段 source_key
if (selectKeys.has(c.key)) {
const fieldDef = fields.find((f) => f.key === c.key);
const source_key = fieldDef && fieldDef.source_key;
return {
...c,
render: (h, params) => {
const value = params.row[c.key];
let source;
if (source_key && selectSources && selectSources[source_key]) {
source = selectSources[source_key];
} else if (c.key === 'type') {
source = selectSources && selectSources.warehouse_type_options;
} else if (c.key === 'tray_type_id') {
source = selectSources && selectSources.tray_type_id;
} else if (c.key === 'lock_type') {
source = selectSources && selectSources.lock_type_options;
} else if (c.key === 'status' && source_key) {
source = selectSources && selectSources[source_key];
} else if (c.key === 'status') {
source = selectSources && selectSources.tray_status_options;
} else {
source = selectSources && selectSources.yes_no_options;
}
const option = source && source.find((opt) => String(opt.key) === String(value));
return h('span', option ? option.value : value);
},
};
}
// 处理布尔字段显示
if (boolKeys.has(c.key)) {
return {
...c,
render: (h, params) => {
const v = params.row[c.key];
const on = v === true || v === 1 || v === '1';
return h('Tag', { props: { color: on ? 'green' : 'default' } }, on ? '是' : '否');
},
};
}
// 处理日期字段显示
if (this.isDateLikeColumnKey && this.isDateLikeColumnKey(c.key)) {
return {
...c,
render: (h, params) => {
const raw =
c.key === 'create_time' || c.key === 'last_modify_time'
? this.pickRowTimeCell(params.row, c.key)
: params.row[c.key];
return h('span', this.formatCellDate(raw, c.key));
},
};
}
return { ...c };
});
},
// 从 Vuex store (wms_dict) 获取字典选项
getDictOptions(key) {
if (!this.$store || !this.$store.state.wms_dict) return [];
return this.$store.state.wms_dict[key] || [];
},
// 通用的数据源加载方法(从 store 读取字典,不再请求 sys_parameter
async loadSelectSources(formFields, idFieldToModelFn, getRowLabelFn) {
const sources = {};
sources.yes_no_options = this.getDictOptions('yes_no');
sources.warehouse_type_options = this.getDictOptions('warehouse_type');
sources.lock_type_options = this.getDictOptions('lock_type');
sources.tray_status_options = this.getDictOptions('tray_status');
sources.tray_type_status_options = this.getDictOptions('tray_type_status');
sources.receiving_status_options = this.getDictOptions('receiving_status');
sources.inbound_status_options = this.getDictOptions('inbound_status');
sources.send_status_options = this.getDictOptions('send_status');
sources.outbound_status_options = this.getDictOptions('outbound_status');
sources.move_status_options = this.getDictOptions('move_status');
sources.damage_status_options = this.getDictOptions('damage_status');
sources.count_status_options = this.getDictOptions('count_status');
sources.receiving_type_options = this.getDictOptions('receiving_type');
sources.send_type_options = this.getDictOptions('send_type');
sources.fee_charge_type_options = this.getDictOptions('fee_charge_type_options');
sources.fee_generate_type_options = this.getDictOptions('fee_generate_type_options');
sources.fee_collection_type_options = this.getDictOptions('fee_collection_type_options');
sources.fee_item_inbound_options = this.getDictOptions('fee_item_inbound_options');
sources.fee_item_outbound_options = this.getDictOptions('fee_item_outbound_options');
sources.packaging_status_options = this.getDictOptions('packaging_status_options');
// 加载外键数据
const idFields = formFields
.filter((f) => f.type === 'number' && /_id$/.test(f.key))
.map((f) => f.key);
for (const k of idFields) {
const model = idFieldToModelFn(k);
if (!model) {
sources[k] = [];
continue;
}
try {
const res = await dynamicModelApi.all(model);
const rows = res && res.code === 0 && res.data ? res.data : [];
sources[k] = rowsToIdOptions(rows, getRowLabelFn || getForeignRowLabel);
} catch (e) {
sources[k] = [];
}
}
return sources;
},
// 通用的空表单构建方法
generateEmptyForm(formFields) {
const o = {};
if (!formFields) return o;
for (const f of formFields) {
if (f.type === 'bool') o[f.key] = false;
else if (f.type === 'number') {
// 外键用 Select 时0 易被当成「已选」且无法匹配 Option校验与展示都不直观
o[f.key] = /_id$/.test(f.key) ? undefined : 0;
} else if (f.type === 'select') {
if (f.key === 'lock_type') o[f.key] = 0;
else if (f.data_type === 'number' && /_id$/.test(f.key)) o[f.key] = undefined;
else o[f.key] = '';
} else o[f.key] = '';
}
return o;
},
// 通用的日期相关方法
isDateLikeColumnKey(key) {
if (!key) return false;
return (
key === 'stat_date' ||
key === 'create_time' ||
key === 'last_modify_time' ||
/_time$/.test(key) ||
/_date$/.test(key)
);
},
/**
* 列表「创建时间 / 最后修改时间」与后端 Sequelize 字段对齐:
*/
pickRowTimeCell(row, key) {
if (!row || !key) return undefined;
if (key === 'create_time') {
return row.create_time;
}
if (key === 'last_modify_time') {
return row.last_modify_time;
}
return row[key];
},
formatCellDate(v, key) {
if (v === null || v === undefined || v === '') return '-';
const d = v instanceof Date ? v : new Date(v);
if (Number.isNaN(d.getTime())) return String(v);
const pad = (n) => String(n).padStart(2, '0');
const y = d.getFullYear();
const m = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hh = pad(d.getHours());
const mm = pad(d.getMinutes());
const dateOnly = key === 'stat_date' || (key && /_date$/.test(key) && !/_time$/.test(key));
if (dateOnly) return y + '-' + m + '-' + day;
return y + '-' + m + '-' + day + ' ' + hh + ':' + mm;
},
/** 外键行展示文案;实现见 @/utils/foreignRowLabel.js */
getRowLabel(row) {
return getForeignRowLabel(row);
},
// 通用的数据规范化方法
generatePayload(raw, formFields) {
const o = { ...raw };
if (!formFields) return o;
const typeByKey = {};
const selectDataTypeByKey = {};
for (const f of formFields) {
if (!f || !f.key) continue;
typeByKey[f.key] = f.type;
if (f.data_type) selectDataTypeByKey[f.key] = f.data_type;
}
for (const k of Object.keys(o)) {
const t = typeByKey[k];
let v = o[k];
if (t === 'number') {
if (v === '' || v === null || v === undefined) {
o[k] = null;
} else {
const n = Number(v);
o[k] = Number.isNaN(n) ? v : n;
}
} else if (t === 'bool') {
o[k] = v === true || v === 1 || v === '1';
} else if (t === 'select') {
const numSelect = k === 'lock_type' || selectDataTypeByKey[k] === 'number';
if (numSelect) {
if (v === '' || v === null || v === undefined) {
o[k] = null;
} else {
const n = Number(v);
o[k] = Number.isNaN(n) ? v : n;
}
}
} else if (t === 'date' || t === 'datetime') {
if (v instanceof Date) {
const pad = (n) => String(n).padStart(2, '0');
if (t === 'date') {
o[k] = v.getFullYear() + '-' + pad(v.getMonth() + 1) + '-' + pad(v.getDate());
} else {
o[k] = v.getFullYear() + '-' + pad(v.getMonth() + 1) + '-' + pad(v.getDate()) +
' ' + pad(v.getHours()) + ':' + pad(v.getMinutes()) + ':' + pad(v.getSeconds());
}
}
}
}
return o;
},
// 通用的表单验证规则生成方法(与 iView Form / async-validator 约定一致:输入类 blur选择类 change
generateEditRules(formFields) {
if (!formFields) {
return {};
}
const rules = {};
for (const f of formFields) {
if (f.form === false) continue;
if (!f.required) continue;
if (f.type === 'bool') continue;
const fieldTitle = f.label || f.title || f.key;
if (f.type === 'date' || f.type === 'datetime') {
rules[f.key] = [
{ required: true, type: 'date', message: '请选择' + fieldTitle, trigger: 'change' }
];
continue;
}
// 数字 id 用 Select 渲染(如 tenant_idtype=select + data_type=numberOption 常为字符串;
// 仅用 async-validator required 易与 iView 绑定不一致导致「已选仍报错」
if (f.type === 'select' && f.data_type === 'number' && /_id$/.test(f.key)) {
rules[f.key] = [
{
required: true,
trigger: 'change',
validator(rule, value, callback) {
const n = value === '' || value === null || value === undefined ? NaN : Number(value);
if (Number.isNaN(n) || n === 0) {
callback(new Error('请选择' + fieldTitle));
} else {
callback();
}
}
}
];
continue;
}
if (f.type === 'select') {
rules[f.key] = [
{ required: true, message: '请选择' + fieldTitle, trigger: 'change' }
];
continue;
}
// 外键generateEditColumns 里用 Select值为数字 id但 Option 上可能是字符串;不能用 type:'number' 直接校验
if (f.type === 'number' && /_id$/.test(f.key)) {
rules[f.key] = [
{
required: true,
trigger: 'change',
validator(rule, value, callback) {
const n = value === '' || value === null || value === undefined ? NaN : Number(value);
if (Number.isNaN(n) || n === 0) {
callback(new Error('请选择' + fieldTitle));
} else {
callback();
}
}
}
];
continue;
}
if (f.type === 'number') {
rules[f.key] = [
{
required: true,
type: 'number',
message: '请填写' + fieldTitle,
trigger: 'blur'
}
];
continue;
}
rules[f.key] = [{ required: true, message: '请填写' + fieldTitle, trigger: 'blur' }];
}
return rules;
}
}
};

View File

@@ -32,11 +32,10 @@
<script>
import tplDemoServer from "@/api/demo/tplDemoServer.js";
import tableMixin from "@/mixins/tableMixin.js";
export default {
name: "TplDemoPage",
mixins: [tableMixin],
data() {
return {
select_sources: {},