mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-10-29 08:58:22 +08:00
feat(table): Vue 模板钩子支持
This commit is contained in:
@@ -181,7 +181,8 @@
|
||||
|
||||
// (b) 模板引擎优先级解析:tplFunction > tplLay > tplVue > tplLayId > tplVueId,
|
||||
// 提供一种即生效;五种全无且非 table 条目 → console.warn 并回退 table。
|
||||
// lay/layId 引擎挂载于 renderLayoutForm(任务 16,layui.laytpl);vue/vueId 挂载于任务 17。
|
||||
// lay/layId(任务 16,layui.laytpl)与 vue/vueId(任务 17,全局 Vue 2.6.10
|
||||
// 运行时编译)均挂载于 renderLayoutForm。
|
||||
var resolveLayoutEngine = function (entry) {
|
||||
if (entry.name === 'table') {
|
||||
return {kind: 'table', entry: entry};
|
||||
@@ -231,22 +232,76 @@
|
||||
return resolveLayoutEngine(entry);
|
||||
};
|
||||
|
||||
// 模板管线失败回退(tplLayId 节点缺失 / laytpl 渲染错误 / Vue 引擎未挂载):
|
||||
// 当前形态归位 'table'(与"无钩子条目回退"同语义,幂等、不写 localStorage
|
||||
// 记忆,用户重新切换可再次触发),容器清空隐藏。调用方据 false 返回值做
|
||||
// 引擎纯渲染,随后的 syncLayoutVisibility 按 table kind 复显引擎数据区,
|
||||
// 避免"容器残留上一形态旧内容 + 引擎数据被隐藏"的视觉错位。
|
||||
// 模板管线失败回退(tplLayId/tplVueId 节点缺失 / laytpl 渲染错误 / Vue 未引入
|
||||
// 或挂载渲染失败):当前形态归位 'table'(与"无钩子条目回退"同语义,幂等、
|
||||
// 不写 localStorage 记忆,用户重新切换可再次触发),容器销毁 Vue 实例后清空
|
||||
// 隐藏。调用方据 false 返回值做引擎纯渲染,随后的 syncLayoutVisibility 按
|
||||
// table kind 复显引擎数据区,避免"容器残留上一形态旧内容 + 引擎数据被隐藏"
|
||||
// 的视觉错位。
|
||||
var fallbackLayoutToTable = function (tableId, $container) {
|
||||
var layout = controlledLayouts[tableId];
|
||||
if (layout) {
|
||||
layout.current = 'table';
|
||||
}
|
||||
destroyLayoutVueInstances(tableId);
|
||||
if ($container && $container.length) {
|
||||
$container.addClass('layui-hide').empty();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// ========== vue/vueId 引擎(任务 17,全局 Vue 2.6.10 运行时编译)==========
|
||||
// 渲染模式为列表级单实例:模板内 v-for="d in rows" 循环行,一次渲染只挂载
|
||||
// 一个实例(逐行 new Vue 的成本随行数线性膨胀,销毁纪律也难以收口)。
|
||||
// 实例存活表:tableId → 当前实例数组。
|
||||
var layoutVueInstances = {};
|
||||
|
||||
// 销毁本表全部存活实例(渲染入口 / fallbackLayoutToTable /
|
||||
// syncLayoutVisibility 三处统一调用,幂等)。$destroy 只拆 watcher 与事件,
|
||||
// 不动 DOM,容器清空由调用方完成。任何重渲路径(store done 管线/搜索/
|
||||
// 切换/回退)都必须先销毁旧实例再挂新实例——销毁重建式是"实例存活期禁止
|
||||
// 响应式数据变更"约束(约束 2)的落地方式:form.render 美化插入的 DOM
|
||||
// (div.layui-form-switch 等)不受 Vue 虚拟 DOM 管理,存活期 re-patch 会
|
||||
// 清除它们。
|
||||
var destroyLayoutVueInstances = function (tableId) {
|
||||
var list = layoutVueInstances[tableId] || [];
|
||||
$.each(list, function (i, vm) {
|
||||
try {
|
||||
vm.$destroy();
|
||||
} catch (e) {}
|
||||
});
|
||||
layoutVueInstances[tableId] = [];
|
||||
};
|
||||
|
||||
// Vue 生产构建中模板编译错误不抛异常也不打日志(dev 告警被裁剪),坏模板
|
||||
// 只会在挂载渲染时经全局 errorHandler 暴露——链式接管 Vue.config.errorHandler:
|
||||
// 本框架实例(vm.__uaLayout 标记)按形态名 console.error 并记录
|
||||
// lastVueLayoutError,由 renderLayoutForm 探测后回退;其余实例
|
||||
// (propertyInput/tagInput 等页面自有 Vue)透传既有处理器或复刻默认行为。
|
||||
var lastVueLayoutError = null;
|
||||
var vueErrorHandlerInstalled = false;
|
||||
var installVueErrorHandler = function () {
|
||||
if (vueErrorHandlerInstalled) {
|
||||
return;
|
||||
}
|
||||
vueErrorHandlerInstalled = true;
|
||||
var previous = window.Vue.config.errorHandler;
|
||||
window.Vue.config.errorHandler = function (err, vm, info) {
|
||||
if (vm && vm.__uaLayout) {
|
||||
var meta = vm.__uaLayout;
|
||||
lastVueLayoutError = {tableId: meta.tableId, name: meta.name};
|
||||
console.error('[ua.table] Vue 模板渲染失败(形态 "' + meta.name + '"):'
|
||||
+ ((err && err.message) || err) + ',本次回退 table 形态');
|
||||
return;
|
||||
}
|
||||
if (typeof previous === 'function') {
|
||||
previous(err, vm, info);
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
};
|
||||
};
|
||||
|
||||
// 形态可见性同步:引擎数据区(表头+表体+合计行)与模板容器互斥显隐,
|
||||
// 引擎视图的工具栏(刷新/新增/批量删除等)两形态共用保持可见,搜索区
|
||||
// 本就挂在表格元素之前不受影响。非 table 形态下引擎根本不渲染行数据
|
||||
@@ -261,6 +316,7 @@
|
||||
var $total = $view.find('.layui-table-total').first();
|
||||
var $container = $('#ua-layout-' + tableId);
|
||||
if (resolveCurrentLayout(tableId).kind === 'table') {
|
||||
destroyLayoutVueInstances(tableId);
|
||||
$box.add($total).removeClass('layui-hide');
|
||||
$container.addClass('layui-hide').empty();
|
||||
} else {
|
||||
@@ -279,8 +335,9 @@
|
||||
$layoutContainer.addClass('ua-layout-loading');
|
||||
opts.done = function (res) {
|
||||
// 受控渲染分发:table 形态走引擎纯渲染(现状),非 table 形态走
|
||||
// 模板管线(渲染上下文 + 行级循环);模板管线不可用(引擎未挂载,
|
||||
// Vue 待任务 17;tplLayId 节点缺失/laytpl 渲染错误)时回退引擎纯渲染兜底
|
||||
// 模板管线(渲染上下文 + 行级循环);模板管线不可用(Vue 未引入/
|
||||
// 挂载渲染失败;tplLayId/tplVueId 节点缺失/laytpl 渲染错误)时
|
||||
// 回退引擎纯渲染兜底
|
||||
var engineRender = function () {
|
||||
originalTableReloadData.call(table, tableId, {
|
||||
data: res.data,
|
||||
@@ -416,6 +473,25 @@
|
||||
// - laytpl 编译/运行错误不抛异常,render() 返回 "Laytpl Error: ..." 字符串
|
||||
// (laytpl 内部已 console.error 细节),框架探测到后按条目名 console.error
|
||||
// 并返回 false,调用方回退引擎纯渲染
|
||||
// 【Vue 调用约定(tplVue / tplVueId,任务 17)】
|
||||
// 列表级单实例:框架把模板包进单一根节点 <div class="ua-layout-vue">
|
||||
// (Vue 2 实例模板根元素禁用 v-for 且生产构建该错误静默,包裹层规避
|
||||
// 全部根约束问题,多根模板也因此可用),行循环由模板作者用
|
||||
// v-for="d in rows" 承接:
|
||||
// - rows = 行上下文数组,d 与 tplFunction/tplLay 的行上下文同构
|
||||
// (d._cells.xxx / d._operatHtml / d._checked / d._disabled);
|
||||
// - list = 列表上下文 {tableId, rows, page, count, checkedAll}
|
||||
// (laytpl 的 d._list 同物;Vue 实例对 _/$ 前缀 data 键不做代理,
|
||||
// 裸标识符 _list 会 ReferenceError,故 Vue 形态改名为 list);
|
||||
// - _cells/_operatHtml 含 HTML,必须用 v-html 输出,纯文本用 {{ }};
|
||||
// - tplVueId 是页面 <script type="text/x-template"> 节点选择器,节点
|
||||
// 必须位于渲染容器之外(下方硬约束 2),.html() 读取原始文本;
|
||||
// - 挂载成功后一次性 form.render(约束 1);实例存活期禁止响应式数据
|
||||
// 变更(约束 2):渲染上下文经 Object.freeze 冻结,数据不可变即不会
|
||||
// re-patch,form.render 美化 DOM 不被清除,更新一律销毁重建;
|
||||
// - 模板编译错误在 Vue 生产构建不抛异常,经链式全局 errorHandler 记录
|
||||
// (installVueErrorHandler),框架探测后按条目名 console.error 并
|
||||
// 返回 false,调用方回退引擎纯渲染。
|
||||
// 【两条硬约束】
|
||||
// 1. 模板容器恒带 class="layui-form"(form 控件渲染与事件受 .layui-form
|
||||
// 祖先约束,缺失则模板里的 switch/checkbox 全部失效);
|
||||
@@ -428,6 +504,10 @@
|
||||
if (controlledLayouts[tableId] === undefined) {
|
||||
return false;
|
||||
}
|
||||
// 销毁重建式(约束 2):任何重渲路径进入本函数先销毁上一轮 Vue 实例,
|
||||
// 容器被清空的其余路径由 fallbackLayoutToTable / syncLayoutVisibility
|
||||
// 兜底同规则销毁
|
||||
destroyLayoutVueInstances(tableId);
|
||||
var resolved = resolveCurrentLayout(tableId);
|
||||
if (resolved.kind === 'table') {
|
||||
return false;
|
||||
@@ -454,13 +534,95 @@
|
||||
template = resolved.entry.tplLay;
|
||||
}
|
||||
laytplCompiled = laytpl(template);
|
||||
} else if (resolved.kind !== 'function') {
|
||||
// vue/vueId 分发点:引擎挂载于任务 17(Vue)
|
||||
console.warn('[ua.table] 模板引擎未挂载:' + resolved.kind + '(任务 17 实现),本次回退 table 形态');
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
|
||||
// vue/vueId 引擎(任务 17):tplVue = 模板字符串;tplVueId =
|
||||
// <script type="text/x-template"> 节点选择器(节点须在渲染容器之外)
|
||||
var vueTemplate = null;
|
||||
if (resolved.kind === 'vue' || resolved.kind === 'vueId') {
|
||||
if (typeof window.Vue === 'undefined') {
|
||||
console.error('[ua.table] 全局 Vue 未引入(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
installVueErrorHandler();
|
||||
if (resolved.kind === 'vueId') {
|
||||
var $vueTplNode = $(resolved.entry.tplVueId);
|
||||
if ($vueTplNode.length === 0) {
|
||||
console.error('[ua.table] tplVueId 节点不存在:' + resolved.entry.tplVueId
|
||||
+ '(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
vueTemplate = $vueTplNode.html();
|
||||
} else {
|
||||
vueTemplate = resolved.entry.tplVue;
|
||||
}
|
||||
}
|
||||
|
||||
var context = ua.table.buildLayoutContext(tableId);
|
||||
|
||||
if (vueTemplate !== null) {
|
||||
// 自定义模板形态移除内置 card 网格类(与 laytpl 同规则)
|
||||
$container.toggleClass('ua-layout-cards', resolved.entry.builtinCard === true);
|
||||
if (context.rows.length === 0) {
|
||||
$container.html('<div class="ua-layout-empty">无数据</div>');
|
||||
form.render(null, 'ua-layout-' + tableId);
|
||||
return true;
|
||||
}
|
||||
// 冻结渲染上下文(约束 2 的强制保证):顶层数据不可扩展即不会被
|
||||
// observe,模板表达式改写失效、不触发 re-patch,form.render 插入
|
||||
// 的美化 DOM 安全;数据更新只经销毁重建(本函数开头已销毁)
|
||||
Object.freeze(context.rows);
|
||||
$.each(context.rows, function (i, rowCtx) {
|
||||
Object.freeze(rowCtx);
|
||||
});
|
||||
Object.freeze(context);
|
||||
var vm = null;
|
||||
try {
|
||||
vm = new window.Vue({
|
||||
template: '<div class="ua-layout-vue">' + vueTemplate + '</div>',
|
||||
data: {rows: context.rows, list: context}
|
||||
});
|
||||
vm.__uaLayout = {tableId: tableId, name: resolved.entry.name};
|
||||
vm.$mount();
|
||||
} catch (e) {
|
||||
// 挂载期同步异常(CSP Function 构造失败等);渲染期错误已由
|
||||
// 全局 errorHandler 记录并告警,此处不再重复;此时实例尚未
|
||||
// 注册进存活表,需就地销毁
|
||||
if (lastVueLayoutError === null) {
|
||||
console.error('[ua.table] Vue 模板挂载失败(形态 "' + resolved.entry.name + '"):'
|
||||
+ ((e && e.message) || e) + ',本次回退 table 形态');
|
||||
}
|
||||
if (vm) {
|
||||
try {
|
||||
vm.$destroy();
|
||||
} catch (ignored) {}
|
||||
}
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
// 编译/渲染错误探测(errorHandler 已按形态名 console.error);
|
||||
// 同上,实例未注册,先就地销毁再归位
|
||||
if (lastVueLayoutError && lastVueLayoutError.tableId === tableId) {
|
||||
lastVueLayoutError = null;
|
||||
try {
|
||||
vm.$destroy();
|
||||
} catch (ignored) {}
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
lastVueLayoutError = null;
|
||||
if (!vm || !vm.$el) {
|
||||
console.error('[ua.table] Vue 模板渲染为空(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||||
return fallbackLayoutToTable(tableId, $container);
|
||||
}
|
||||
layoutVueInstances[tableId] = [vm];
|
||||
// $destroy 只拆实例不动 DOM(见 destroyLayoutVueInstances 注释),
|
||||
// 上一轮实例的 DOM 残留必须在此显式清空,否则每次重渲都会累积
|
||||
$container.empty().append(vm.$el);
|
||||
// 约束 1:容器恒为 .layui-form(lay-filter 与容器 id 同名),
|
||||
// 挂载后一次性 scoped form.render 美化模板产出的 switch/checkbox
|
||||
form.render(null, 'ua-layout-' + tableId);
|
||||
return true;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
if (context.rows.length === 0) {
|
||||
html = '<div class="ua-layout-empty">无数据</div>';
|
||||
@@ -502,8 +664,9 @@
|
||||
},
|
||||
// 切换当前形态:仅改写运行期状态并重渲染,不发起网络请求(数据取自
|
||||
// store 当页状态)。目标条目不存在返回 false;模板管线不可用(无钩子
|
||||
// 条目 / tplLayId 节点缺失 / laytpl 渲染错误,均已在 renderLayoutForm
|
||||
// 内归位 current='table')时走引擎纯渲染兜底。
|
||||
// 条目 / tplLayId/tplVueId 节点缺失 / laytpl 渲染错误 / Vue 未引入或
|
||||
// 挂载渲染失败,均已在 renderLayoutForm 内归位 current='table')时走
|
||||
// 引擎纯渲染兜底。
|
||||
setLayoutForm: function (tableId, name) {
|
||||
var layout = controlledLayouts[tableId];
|
||||
if (!layout || !layout.byName[name]) {
|
||||
|
||||
Reference in New Issue
Block a user