12
This commit is contained in:
File diff suppressed because one or more lines are too long
203
admin/src/mixins/tplTableMixin.js
Normal file
203
admin/src/mixins/tplTableMixin.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 演示页 / 轻量业务列表用的表格与编辑辅助方法(原 tableMixin 能力的最小子集)。
|
||||
*/
|
||||
import { mapFormFieldsToEditColumns } from "@/utils/formEditColumns.js";
|
||||
import { getForeignRowLabel } from "@/utils/foreignRowLabel.js";
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
/**
|
||||
* 把表单字段配置转成「编辑弹窗 / 表单」用的列定义(含下拉选项渲染等)。
|
||||
* @param {Array} formFields 表单字段元数据
|
||||
* @param {Object} selectSources 各字段对应的 { key: [{ key, value }] } 下拉数据
|
||||
*/
|
||||
generateEditColumns(formFields, selectSources) {
|
||||
return mapFormFieldsToEditColumns(formFields, selectSources);
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据列表展示字段生成 iView Table 的 columns;select 或 *_id 数字列用下拉文案渲染。
|
||||
* @param {Array} listFields 列表要展示的字段配置
|
||||
* @param {Array} _allFields 预留,与全量字段对齐时可扩展
|
||||
* @param {Object} selectSources 与 generateEditColumns 同源的下拉映射
|
||||
*/
|
||||
generateListColumns(listFields, _allFields, selectSources) {
|
||||
if (!Array.isArray(listFields)) {
|
||||
return [];
|
||||
}
|
||||
return listFields.map((f) => {
|
||||
const col = {
|
||||
title: f.title || f.key,
|
||||
key: f.key,
|
||||
minWidth: f.minWidth || 120,
|
||||
ellipsis: true,
|
||||
};
|
||||
const useLookup =
|
||||
f.type === "select" ||
|
||||
(f.type === "number" && /_id$/.test(String(f.key)));
|
||||
if (useLookup) {
|
||||
const srcKey = f.source_key || f.key;
|
||||
col.render = (h, params) => {
|
||||
const v = params.row[f.key];
|
||||
const opts = (selectSources && selectSources[srcKey]) || [];
|
||||
const o = opts.find(
|
||||
(x) => x.key === v || String(x.key) === String(v)
|
||||
);
|
||||
return h("span", o ? o.value : v != null ? String(v) : "");
|
||||
};
|
||||
}
|
||||
return col;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 把一行记录转成人类可读的主展示文案(用于外键下拉、关联展示等)。
|
||||
* @param {Object} row 后端返回的单条记录
|
||||
*/
|
||||
getRowLabel(row) {
|
||||
return getForeignRowLabel(row);
|
||||
},
|
||||
|
||||
/**
|
||||
* 按字段配置批量请求 `/{model}/all`,组装成 selectSources(下拉 key/value)。
|
||||
* idFieldToModel(fieldKey) 返回的模型名为空则跳过该字段;依赖 this.$http。
|
||||
* @param {Array} fields 表单或列表字段元数据
|
||||
* @param {Function} idFieldToModel 字段 key -> 后端资源名(如 tenant_id -> sys_tenant)
|
||||
* @param {Function} _getRowLabel 预留,可传入自定义取文案函数
|
||||
*/
|
||||
async loadSelectSources(fields, idFieldToModel, _getRowLabel) {
|
||||
const result = {};
|
||||
if (!Array.isArray(fields) || !this.$http) {
|
||||
return result;
|
||||
}
|
||||
for (const f of fields) {
|
||||
const model =
|
||||
typeof idFieldToModel === "function" ? idFieldToModel(f.key) : "";
|
||||
if (!model) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const res = await this.$http.get(`/${model}/all`, {});
|
||||
if (res && res.code === 0 && Array.isArray(res.data)) {
|
||||
const key = f.source_key || f.key;
|
||||
result[key] = res.data.map((r) => ({
|
||||
key: r.id,
|
||||
value: getForeignRowLabel(r),
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("loadSelectSources skip", f.key, e && e.message);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据字段上的 required 生成 iView Form 校验 rules(必填 + 失焦提示)。
|
||||
* @param {Array} fields 表单字段元数据
|
||||
*/
|
||||
generateEditRules(fields) {
|
||||
const rules = {};
|
||||
if (!Array.isArray(fields)) {
|
||||
return rules;
|
||||
}
|
||||
for (const f of fields) {
|
||||
if (f.required) {
|
||||
const label = f.title || f.label || f.key;
|
||||
rules[f.key] = [
|
||||
{
|
||||
required: true,
|
||||
message: "请填写" + label,
|
||||
trigger: "blur",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
},
|
||||
|
||||
/**
|
||||
* 按字段类型生成一份「空表单」初始值(number 为 null,bool 为 false,其余为 "")。
|
||||
* @param {Array} fields 表单字段元数据
|
||||
*/
|
||||
generateEmptyForm(fields) {
|
||||
const form = {};
|
||||
if (!Array.isArray(fields)) {
|
||||
return form;
|
||||
}
|
||||
for (const f of fields) {
|
||||
if (f.type === "number") {
|
||||
form[f.key] = null;
|
||||
} else if (f.type === "bool") {
|
||||
form[f.key] = false;
|
||||
} else {
|
||||
form[f.key] = "";
|
||||
}
|
||||
}
|
||||
return form;
|
||||
},
|
||||
|
||||
/**
|
||||
* 从原始对象中只挑出 fields 里声明过的 key,用于提交前裁剪多余字段。
|
||||
* @param {Object} raw 表单或行数据
|
||||
* @param {Array} fields 允许的字段列表
|
||||
*/
|
||||
generatePayload(raw, fields) {
|
||||
const out = {};
|
||||
if (!raw || !Array.isArray(fields)) {
|
||||
return out;
|
||||
}
|
||||
for (const f of fields) {
|
||||
const k = f.key;
|
||||
if (Object.prototype.hasOwnProperty.call(raw, k)) {
|
||||
out[k] = raw[k];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
/**
|
||||
* 编辑弹窗打开时浅拷贝一行,避免表格行对象与表单双向绑定互相污染。
|
||||
* @param {Object} row 当前行
|
||||
*/
|
||||
normalizeRowForEditModal(row) {
|
||||
return row && typeof row === "object" ? { ...row } : {};
|
||||
},
|
||||
|
||||
/**
|
||||
* 表格「操作」列里渲染一组小按钮;点击会 stopPropagation,避免触发行选择。
|
||||
* @param {Function} h Vue 的 createElement
|
||||
* @param {Array<{ title, type?, click }>} items 按钮文案、样式类型、点击回调
|
||||
*/
|
||||
renderRowActionBtns(h, items) {
|
||||
if (!Array.isArray(items) || !items.length) {
|
||||
return h("span", "");
|
||||
}
|
||||
return h(
|
||||
"div",
|
||||
{ class: "table-row-actions" },
|
||||
items.map((item, i) =>
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
key: i,
|
||||
props: { type: item.type || "default", size: "small" },
|
||||
style: { marginRight: i < items.length - 1 ? "8px" : "0" },
|
||||
on: {
|
||||
click: (e) => {
|
||||
if (e && e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
if (typeof item.click === "function") {
|
||||
item.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
item.title || ""
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -32,9 +32,11 @@
|
||||
|
||||
<script>
|
||||
import tplDemoServer from "@/api/demo/tplDemoServer.js";
|
||||
import tplTableMixin from "@/mixins/tplTableMixin.js";
|
||||
|
||||
export default {
|
||||
name: "TplDemoPage",
|
||||
mixins: [tplTableMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
@@ -131,8 +133,8 @@ export default {
|
||||
buildEmptyForm() {
|
||||
return this.generateEmptyForm(this.meta.fields.filter((f) => f.form !== false));
|
||||
},
|
||||
normalizePayload(raw) {
|
||||
return this.generatePayload(raw, this.meta.fields.filter((f) => f.form !== false));
|
||||
formFields() {
|
||||
return this.meta.fields.filter((f) => f.form !== false);
|
||||
},
|
||||
async query(page) {
|
||||
if (!this.modelName || !this.meta) {
|
||||
@@ -157,27 +159,47 @@ export default {
|
||||
this.resetSearchFromMeta();
|
||||
this.query(1);
|
||||
},
|
||||
/** 仅兜住网络/HTTP 异常;业务 code 由调用方判断 */
|
||||
async safeApi(promise) {
|
||||
try {
|
||||
return await promise;
|
||||
} catch (e) {
|
||||
const d = e.response && e.response.data;
|
||||
this.$Message.error((d && (d.message || d.msg)) || e.message || "请求失败");
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
showAdd() {
|
||||
const fields = this.formFields();
|
||||
this.$refs.editModal.addShow(this.buildEmptyForm(), async (data) => {
|
||||
await this.persist(data, false);
|
||||
const res = await this.safeApi(tplDemoServer.add(this.generatePayload(data, fields)));
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success("新增成功");
|
||||
this.query(1);
|
||||
return;
|
||||
}
|
||||
this.$Message.error((res && (res.message || res.Msg)) || "新增失败");
|
||||
throw new Error("save");
|
||||
});
|
||||
},
|
||||
showEdit(row) {
|
||||
const fields = this.formFields();
|
||||
const rowId = row && row.id;
|
||||
this.$refs.editModal.editShow(this.normalizeRowForEditModal({ ...row }), async (data) => {
|
||||
await this.persist(data, true);
|
||||
});
|
||||
},
|
||||
async persist(data, isEdit) {
|
||||
const payload = this.normalizePayload(data);
|
||||
const req = isEdit ? tplDemoServer.edit(payload) : tplDemoServer.add(payload);
|
||||
const res = await req;
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success(isEdit ? "保存成功" : "新增成功");
|
||||
this.query(1);
|
||||
} else {
|
||||
const merged = { ...data, id: data.id != null && data.id !== "" ? data.id : rowId };
|
||||
const payload = this.generatePayload(merged, fields);
|
||||
if (merged.id != null && merged.id !== "") {
|
||||
payload.id = Number.isFinite(Number(merged.id)) ? Number(merged.id) : merged.id;
|
||||
}
|
||||
const res = await this.safeApi(tplDemoServer.edit(payload));
|
||||
if (res && res.code === 0) {
|
||||
this.$Message.success("保存成功");
|
||||
this.query(1);
|
||||
return;
|
||||
}
|
||||
this.$Message.error((res && (res.message || res.Msg)) || "保存失败");
|
||||
throw new Error("save failed");
|
||||
}
|
||||
throw new Error("save");
|
||||
});
|
||||
},
|
||||
delConfirm(row) {
|
||||
this.$Modal.confirm({
|
||||
|
||||
Reference in New Issue
Block a user