16 KiB
vr_ticket 插件重构经验总结
来源:原始对话日志
refactoring_log_vrticket_2026.md分块提炼 适用版本:vr_ticket v1.x(ShopXO 插件)
+++++ 原始日志第一块(行1-980)
轮次1:ShopXO 插件架构理解 & 基础搭建
- 核心问题:对 ShopXO 插件目录结构不熟悉,视图路径和 URL 生成方式不明
- 解决方案:
PluginsAdminUrl('vr_ticket', 'admin', 'action')生成后台 URL{{:ModuleInclude('public/header')}}加载全局头尾- 视图放在
admin/view/{module}/list.html
- 关键教训:
- ShopXO 插件后台必须遵循
public/header+public/footer包含模式,否则页面无样式 - 路由格式:
{插件名}_{控制器}_{动作}→ URL 映射到admin/{plugin}/admin/{action}
- ShopXO 插件后台必须遵循
+++++ 原始日志第二块(行981-2272)
轮次2:LayUI → AmazeUI 全局前端架构重构
-
核心问题:所有视图模板使用 LayUI 框架编写,但 ShopXO 整套后台基于 AmazeUI,导致页面无样式、无表单验证、布局完全失效
-
解决方案:批量将 9 个视图模板从 LayUI 迁移到 ShopXO 原生 AmazeUI:
布局结构(所有模板统一):
{{:ModuleInclude('public/header')}} <div class="content-right"> <div class="content"> [页面内容] </div> </div> {{:ModuleInclude('public/footer')}}搜索表单:
request-type="form",GET 提交到当前 action 保存表单:request-type="ajax-url" request-value="{跳转URL}"删除按钮:<button class="submit-delete" data-url="..." data-id="...">分页:{{$page|raw}}时间格式化:{{:date('Y-m-d H:i:s', $var)}} -
关键教训:
- AmazeUI 必须用类名:
am-table/am-table-striped/am-table-hover、am-btn/am-btn-default/am-btn-primary/am-btn-secondary/am-btn-danger、am-badge/am-badge-success/am-badge-danger/am-badge-primary、am-form/am-form-group/am-input-group - 表单验证:必须加
class="am-form form-validation",否则无法触发 ShopXO 的 AJAX 提交 - 字段校验提示:
data-validation-message属性设置错误文案,required标记必填 - 单选/复选框:
am-radio-inline+data-am-ucheck - 加载状态:
data-am-loading="{spinner:'circle-o-notch', loadingText:'...'}"
- AmazeUI 必须用类名:
轮次3:Vue CDN 国内阻断问题(venue/save.html)
-
核心问题:
https://unpkg.com/vue@3/dist/vue.global.prod.js在中国大陆无法访问,导致场馆编辑页 Vue3 编辑器完全失效 -
解决方案:更换 CDN 源
<!-- 错误(阻断) --> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> <!-- 正确 --> <script src="https://cdn.staticfile.net/vue/3.x.x/vue.global.prod.js"></script> -
关键教训:
- 国内项目必须使用
cdn.staticfile.net或cdn.bootcdn.net,禁止使用unpkg.com/cdnjs.cloudflare.com - 涉及国外 CDN 的资源都需要做国内镜像替换
- 国内项目必须使用
轮次4:venue/save.html Vue3 交互式编辑器
-
核心问题:场馆编辑器需要复杂的座位可视化预览,包括分区颜色映射、座位预览、tooltip 等,纯 PHP 无法实现
-
解决方案:ShopXO 混合模式 — PHP 渲染表单外壳 + Vue3 实现交互:
- PHP 表单包含
<input type="hidden">字段,:value绑定 Vue computed 属性 - Vue3 的
computed属性(zonesJson、seatMapRowsJson)自动同步到隐藏字段 - PHP 后端直接
json_decode($_POST['zones'])获取数据
数据结构设计:
// 分区 zones = [{ char: 'A', name: 'VIP区', price: 899, color: '#e74c3c' }, ...] // 座位排布(每排字符串对应各区座位) seatMapRows = ['AAAAAA', 'BBBBBB', 'CCCCCC'] // 场馆元信息 venue = { name: '', address: '', image: '' }座位预览核心逻辑:
- 遍历
seatMapRows,每个字符对应一个 zone 的char getZoneColor(ch)→ 根据字符从 zones 映射取颜色- 座位 tooltip 显示:分区名 + 价格 + 座位号
Vue3 挂载注意点:
compilerOptions: { delimiters: ['[[', ']]'] }避免与 ShopXO 模板语法冲突v-cloak+[v-cloak] { display: none; }防止 Vue 未加载时闪烁mounted()中解析 PHP 注入的默认值
- PHP 表单包含
-
关键教训:
- ShopXO 插件的 Vue 混合模式:Vue 负责交互,PHP 负责数据持久化,通过隐藏字段桥接
- 颜色预设面板需要实现
applyPresetColor()自动填充空白分区 - 座位行追加逻辑:用
new Array(n+1).join(ch)生成重复字符字符串
轮次5:AmazeUI 组件速查(高频使用)
| 功能 | AmazeUI 类 / 写法 |
|---|---|
| 表格 | am-table am-table-striped am-table-hover am-text-middle |
| 按钮 | am-btn am-btn-default / -primary / -secondary / -danger + am-btn-xs am-radius |
| 徽章 | am-badge am-badge-success / -danger / -primary |
| 表单 | am-form form-validation,字段 am-form-group,输入框 am-radius |
| 搜索栏 | am-input-group am-input-group-sm am-fl so + am-input-group-btn |
| 单选/复选 | am-radio-inline + data-am-ucheck |
| 下拉选择 | chosen-select + data-placeholder |
| 分页 | {{$page|raw}} |
| 图标 | am-icon-plus / am-icon-edit / am-icon-trash-o 等 |
| 面板 | am-panel am-panel-default + am-panel-bd |
| 布局 | am-g(行), am-u-sm-12 am-u-md-4(列), am-fl/am-fr |
| 加载动画 | data-am-loading="{spinner:'circle-o-notch', loadingText:'...'}" |
轮次6:各模块视图重构要点
venue/list.html & venue/save.html
- 场馆列表包含座位模板快捷入口:
PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateSave', ['id'=>$v['id']]) - 保存页使用 Vue3 编辑器(见轮次4)
seat_template/list.html & save.html
- 座位模板绑定分类:关联
vr_category表的category_id - seat_map 字段存 JSON:格式
{map: ["AAAAAA"], seats: {A: {price: 599, label: "VIP"}}}
ticket/list.html & detail.html
- 导出 CSV:动态构建隐藏 form,POST 提交到
TicketExport - 二维码预览:
UI.Modal()弹窗展示大图 - 核销操作:表单提交到
TicketVerify,request-type="ajax-reload"自动刷新
verifier/list.html & save.html
- 用户关联后不可修改:编辑时
disabled+ 额外hidden字段传值
verification/list.html
- 日期范围筛选:使用
WdatePicker(ShopXO 自带日期控件) - 核销记录只读,无编辑/删除操作
核心经验总结
- ShopXO 插件视图 = AmazeUI 规范 + PHP 模板语法,禁止混用其他 UI 框架
- Vue3 只用于复杂交互场景(如场馆座位编辑器),普通 CRUD 用纯 AmazeUI+jQuery
- CDN 必须在
.cn域名或国内可用源,unpkg.com不可用 - PHP ↔ Vue 数据桥接:Vue computed → hidden input → PHP
$_POST - 所有列表页统一模式:搜索栏 → 操作按钮 →
am-table→ 分页 - ShopXO 表单验证依赖
form-validationclass,没有这个 class 表单不会走 AJAX
+++++ 原始日志末尾块(行3643-4644)
轮次N+1:座位刷新不及时 / 空行误报 / 数据库索引残留 / Hook失效
- 核心问题:座位预览和排布设计器只在输入时响应,删除或失焦不刷新;空行残留后无法保存;Hook菜单未自动出现
- 解决方案:
- 排布文本框改用
@input实时触发预览 - 后端 VenueSave 加入
array_filter自动剔除空白行 - 提供 SQL:
ALTER TABLE vrt_vr_seat_templates DROP INDEX uk_category_id; - Hook.php 补全顶级菜单项缺失的
url和id字段
- 排布文本框改用
- 关键教训:
- ThinkPHP 6 分页对象 →
paginate()返回对象,->render()返回分页HTML字符串,不能链式.toArray()后访问page键 - Hook菜单项必须有
id、url、name、is_show完整字段,否则渲染引擎报undefined array key
- ThinkPHP 6 分页对象 →
轮次N+2:添加/编辑场馆页面无限加载 / View路径不规范
- 核心问题:从
admin/view/迁移视图文件后,添加/编辑场馆页面一直转圈;插件配置入口未显示 - 解决方案:
- 将视图从
admin/view/迁移至标准view/目录,MyView 调用改用../../../plugins/vr_ticket/view/venue/xxx绝对路径 - 列表页添加"插件设置"按钮;在编辑页地址栏旁添加齿轮图标链接
- Admin.php initialize() 自检逻辑加入 Cache 锁,降低每次请求的数据库 I/O
- 将视图从
- 关键教训:
- ShopXO 插件视图必须放在插件根目录的
view/下(不是admin/view/) - 表格被挤压到屏幕下方 = 搜索区域浮动未清除,需用 clearfix 解决
- ShopXO 插件视图必须放在插件根目录的
轮次N+3:模板文件路径导致 template not exists
- 核心问题:MyView('venue/list') 无法定位到插件 view 目录,报
template not exists - 解决方案:Admin.php 中所有 MyView 调用统一改为
MyView('../../../plugins/vr_ticket/view/venue/xxx')跨模块绝对路径 - 关键教训:
- 插件控制器继承
app\admin\controller\Common后,模板引擎默认去找app/admin/view/default/而非插件目录 - 必须显式指定
../../../plugins/插件名/view/...前缀引导跨模块路径解析
- 插件控制器继承
轮次N+4:添加场馆页面依然无限加载(真实根因)
- 核心问题:列表页秒开,但点击"新建/编辑"必定卡死;控制台出现 Slow network 和 Vue 开发版警告
- 根因定位:
- 假线索:unpkg.com 被阻断 → 实为假象,主请求卡住导致后续资源全部 pending
- 假线索:数据库自检频率 → 实为次要因素
- 真实根因:
save.html漏掉了{{:ModuleInclude('public/footer')}} - ShopXO 后台基于 AmazeUI,页面跳转时显示全屏 Loading Spinner,关闭动画的 JS 信号存在于
public/footer的库文件中;没有 footer 则 Spinner 永不消失
- 解决方案:补全
{{:ModuleInclude('public/footer')}};同步检查 list.html 和 save.html 均有 footer - 关键教训:无限加载 ≠ 一定是后端死循环,AmazeUI 的 Loading Spinner 遮罩也是常见原因,排查时优先检查 header/footer 是否完整
轮次N+5:Vue 3 textarea 插值导致无限渲染循环
- 核心问题:Footer 补全后仍偶发卡死(Base64 大字符串场景下更明显)
- 根因定位:Vue 3 中
<textarea>[[ compiledJsonRaw ]]</textarea>使用双花括号插值绑定 Text Node,在大数据 Base64 字符串动态赋值时触发虚拟 DOM 补丁机制无限死循环,JS 主线程被锁死导致页面完全无响应 - 解决方案:将
<textarea>改为隐藏 input:<input type="hidden" name="seat_map_raw" :value="compiledJsonRaw" /> - 关键教训:Vue 3 禁止用插值语法
[[ ]]绑定<textarea>的内部文本,必须用:value或v-model
轮次N+6:URL截断终极解决方案(Base64编码)
- 核心问题:即使绕过框架 input 过滤,ShopXO 内部仍有多层正则扫描自动"净化"资源路径,CDN URL 被截断
- 解决方案:前端提交前对场馆 JSON 进行 Base64 编码 → 后端解码还原 → 传输过程中URL只是一段加密字符,绕过所有过滤器
- 关键教训:ShopXO 框架对资源路径存在多层自动处理机制;Base64 是最稳健的兜底方案
轮次N+7:搜索逻辑与字段命名重构
- 核心问题:搜索基础名称但列表显示"详情展示名称",导致操作割裂;搜索条件堆在一起;表格列错位
- 解决方案:
- 搜索字段固定为
name列(数据库可索引),不搜 JSON 内的详情展示名称 - 列表场馆信息列改为三行式层级:
- 简短名称(大字加粗,可索引)
(完整场馆名称)(灰色小字,括号)📍 地址(灰色小字)
- 表单字段命名:
基础名称→场馆名称(简短名称),详情展示名称→完整场馆名称
- 搜索字段固定为
- 关键教训:
- 搜索字段与列表主标题必须一一对应,数据库可索引字段优于 JSON 内字段
- 表格列宽和对齐必须显式指定(居中/靠左),不能依赖浏览器默认行为
综合关键教训
- ShopXO 插件视图:必须放在
plugins/插件名/view/,使用../../../plugins/插件名/view/...路径前缀 - 后台页面必须包含 header + footer:缺少 footer = Loading Spinner 永不消失
- Vue 3 textarea 绑定:禁止
[[ ]]插值,必须用:value或v-model - Hook菜单项:必须包含
id、url、name、is_show完整字段 - URL截断:Base64 编码提交是最稳健的绕过方案
- 搜索与显示一致性:搜索字段 = 列表主标题,JSON字段只做展示
- 表格对齐:列宽和 text-align 必须显式声明
+++++ 原始日志第四块(行2611-3642)
轮次1:7项用户需求评审与多放映室架构重构
- 核心问题:用户提出 7 项场馆编辑器体验需求,核心是支持"场馆→多放映室→独立分区/座位"的三级嵌套结构
- 解决方案:
- 重构 seat_map JSON 结构:从旧的扁平 {map, seats, sections} 升级为 {venue: {...}, rooms: [{id, name, map, sections, seats}]} 嵌套结构
- VenueSave() 后端方法改为接收前端 Vue 组装好的完整 JSON,移除了对旧版 map 字段的直接处理
- SeatSkuService::BatchGenerate() 增加向下兼容逻辑:旧数据 map 字段自动包装为 rooms[0]
- 关键教训:
- 数据结构变更需要同步修改前后端:后端解析层、前端组装层、存储层三者缺一不可
- 新旧数据结构兼容要做好,否则旧数据无法读取
轮次2:Vue3 场馆编辑器全量重写
- 核心问题:需要在前端实现多放映室 Tab 切换、分区配置、文本排布设计器、实时座位预览等功能
- 解决方案:
- 使用 Vue 3 CDN + createApp + Composition API
- delimiters: ['', ''] 避免与 ShopXO 模板引擎 {{}} 冲突
- 放映室 tabs → 当前放映室面板 → 分区表格 + 文本排布 textarea → 座位预览 的面板结构
- compiledJsonRaw computed 负责在提交前将 Vue state 编译为完整 JSON,注入到隐藏表单字段
- 高德地图暂时 stub(alert 提示 + 随机坐标预览)
- 关键教训:
- ShopXO 插件视图使用 {{}} 模板语法,Vue 3 模板必须换用 [] 分隔符,否则冲突
- Vue 的 computed 可直接 return 给模板,无需手动 watch
- v-for + v-if 混用时需用 包装
轮次3:Vue 渲染页面 SyntaxError Bug(两次)
- 核心问题:用户报告"添加场馆"页面只剩"返回 添加场馆",控制台报 SyntaxError: Invalid or unexpected token
- 第一次修复:
- 原因:后端 seat_map JSON 通过模板直接插到 JS 模板字符串时,含换行/特殊字符导致 JS 解析失败
- 修复方案:改用