Compare commits
2 Commits
e9480b6866
...
e7730170f3
| Author | SHA1 | Date |
|---|---|---|
|
|
e7730170f3 | |
|
|
f6bcad6bfb |
19
README.md
19
README.md
|
|
@ -14,6 +14,19 @@
|
|||
|
||||
---
|
||||
|
||||
## ⚠️ 踩坑经验(接手本插件前必读)
|
||||
|
||||
> 本插件经历了一整夜重构调试,发现了大量反直觉的坑。**任何 agent 或开发者接手前,请先阅读这份经验文档,避免重蹈覆辙。**
|
||||
|
||||
📋 **[docs/EXPERIENCES.md](docs/EXPERIENCES.md)** — ShopXO 插件踩坑经验全记录(16条核心教训)
|
||||
|
||||
> **最关键的3条**:
|
||||
> 1. `{{:ModuleInclude('public/footer')}}` 缺失 → 页面无限加载(不是后端死循环)
|
||||
> 2. Vue 3 `[[ ]]` 插值禁止用于 `<textarea>`(导致浏览器卡死)
|
||||
> 3. 字段名不能猜,必须查源码(已有人踩过)
|
||||
|
||||
---
|
||||
|
||||
## 核心能力
|
||||
|
||||
| 能力 | 实现方式 |
|
||||
|
|
@ -72,6 +85,7 @@ mysql -u root -p < database/migrations/004_vr_verifications.sql
|
|||
|
||||
## 技术调研文档
|
||||
|
||||
- [docs/EXPERIENCES.md](docs/EXPERIENCES.md) — ⚠️ **踩坑经验(必读)** — 16条核心教训
|
||||
- [docs/01_SHOPXO_TECHNICAL_RESEARCH.md](docs/01_SHOPXO_TECHNICAL_RESEARCH.md) — ShopXO 技术能力调研
|
||||
- [docs/02_FRONTEND_CUSTOMIZATION.md](docs/02_FRONTEND_CUSTOMIZATION.md) — uni-app 前端定制
|
||||
- [docs/03_VERIFICATION_SYSTEM.md](docs/03_VERIFICATION_SYSTEM.md) — 核销系统设计
|
||||
|
|
@ -93,7 +107,10 @@ mysql -u root -p < database/migrations/004_vr_verifications.sql
|
|||
|
||||
## 项目状态
|
||||
|
||||
🚧 **调研完成,尚未开始编码**
|
||||
✅ **Phase 1 完成**:商品详情页座位图 + 观演人表单
|
||||
🔜 **Phase 2 进行中**:后台管理(场馆/座位模板/电子票/核销员)
|
||||
- ✅ 场馆 CRUD + Vue3 编辑器
|
||||
- 🔜 后台管理添加商品 Hook(快速选择场馆信息)
|
||||
|
||||
## 仓库地址
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,297 @@
|
|||
# VR票务插件 — 踩坑经验文档
|
||||
|
||||
> 本文档源自 2026-04-16 一整夜的重构调试,汇集了所有关键教训。
|
||||
> **任何接手本项目的 agent,请先阅读本文档。**
|
||||
>
|
||||
> 原始日志:`refactoring_log_vrticket_2026.md`(4644行)
|
||||
> 提炼源:`refactoring_learnings.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔴 P0 — 致命陷阱(必读)
|
||||
|
||||
### 1. `public/footer` 缺失 → 无限加载(最反直觉)
|
||||
|
||||
**现象**:列表页秒开,点击"新建/编辑"后页面永远转圈。
|
||||
**假线索**:后端死循环、数据库慢查询、unpkg.com CDN 阻断。
|
||||
**真实根因**:`save.html` 漏掉了 `{{:ModuleInclude('public/footer')}}`。AmazeUI 后台在页面跳转时显示全屏 Loading Spinner,关闭动画的 JS 逻辑在 `footer` 的库文件里。没有 `footer` → Spinner 永不消失。
|
||||
|
||||
```html
|
||||
<!-- ❌ 错误:漏 footer -->
|
||||
{{:ModuleInclude('public/header')}}
|
||||
<div>页面内容</div>
|
||||
|
||||
<!-- ✅ 正确:必须成对 -->
|
||||
{{:ModuleInclude('public/header')}}
|
||||
<div>页面内容</div>
|
||||
{{:ModuleInclude('public/footer')}}
|
||||
```
|
||||
|
||||
**教训**:无限加载 ≠ 后端死循环。AmazeUI Loading Spinner 遮罩是更常见的原因,优先检查 header/footer 是否完整。
|
||||
|
||||
---
|
||||
|
||||
### 2. Vue 3 `[[ ]]` 插值禁止用于 `<textarea>`
|
||||
|
||||
**现象**:Base64 大字符串场景下页面完全无响应(浏览器卡死)。
|
||||
**根因**:`<textarea>[[ compiledJsonRaw ]]</textarea>` 使用双花括号插值绑定 Text Node,Vue 3 在大数据动态赋值时触发虚拟 DOM 补丁机制无限死循环。
|
||||
|
||||
```html
|
||||
<!-- ❌ 错误 -->
|
||||
<textarea>[[ compiledJsonRaw ]]</textarea>
|
||||
|
||||
<!-- ✅ 正确:用隐藏 input -->
|
||||
<input type="hidden" name="seat_map_raw" :value="compiledJsonRaw" />
|
||||
```
|
||||
|
||||
**教训**:Vue 3 插值语法 `[[ ]]` 只用于文本节点,禁止用于 `<textarea>` 的 value 属性。
|
||||
|
||||
---
|
||||
|
||||
### 3. 字段名不能猜,必须查源码
|
||||
|
||||
**现象**:`is_delete_time` 报错,第一轮凭经验改字段名后仍然报错。
|
||||
**根因**:GoodsCategory 表根本没有软删除字段(ShopXO 的软删除用的是 `is_enable` 而非 `is_delete_time`)。
|
||||
|
||||
```php
|
||||
// ❌ 错误:凭经验猜字段名
|
||||
if (!empty($category['is_delete_time']))
|
||||
|
||||
// ✅ 正确:查 GoodsCategoryService.php 源码确认字段
|
||||
if (!empty($category['is_enable']))
|
||||
```
|
||||
|
||||
**教训**:ShopXO 部分表没有软删除字段。改字段名之前必须查对应 Service 层源码或实际表结构。
|
||||
|
||||
---
|
||||
|
||||
## 🟡 P1 — 严重问题
|
||||
|
||||
### 4. 插件视图路径:必须用 `../../../plugins/插件名/view/...`
|
||||
|
||||
ShopXO 插件控制器继承 `app\admin\controller\Common` 后,模板引擎默认去找 `app/admin/view/default/` 而非插件目录。
|
||||
|
||||
```php
|
||||
// ❌ 错误:引擎截断路径
|
||||
return MyView('venue/list');
|
||||
|
||||
// ✅ 正确:跨模块绝对路径
|
||||
return MyView('../../../plugins/vr_ticket/view/venue/list');
|
||||
```
|
||||
|
||||
**教训**:插件视图必须放在插件根目录的 `view/` 下(不是 `admin/view/`),且调用时加 `../../../plugins/插件名/view/...` 前缀。
|
||||
|
||||
---
|
||||
|
||||
### 5. Hook.php 返回值必须完整
|
||||
|
||||
ShopXO 的菜单渲染引擎要求 Hook 返回数组包含 `id`、`url`、`name`、`is_show` 完整字段。缺失任何一项都会导致侧边栏报错(`Undefined array key url` 等)。
|
||||
|
||||
```php
|
||||
// ❌ 错误:缺少字段
|
||||
return [
|
||||
'title' => '场馆管理',
|
||||
'control' => 'Venue',
|
||||
'action' => 'list',
|
||||
];
|
||||
|
||||
// ✅ 正确:完整字段
|
||||
return [
|
||||
'id' => 'venue-list',
|
||||
'name' => '场馆管理',
|
||||
'url' => MyUrl('vr_ticket/admin/venue-list'),
|
||||
'is_show' => 1,
|
||||
'control' => 'Venue',
|
||||
'action' => 'list',
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. CDN 国内阻断:`unpkg.com` 不可用
|
||||
|
||||
**现象**:Vue 3 编辑器完全失效,控制台有 `[Intervention] Slow network is detected`。
|
||||
**根因**:`unpkg.com` 在中国大陆常发生静默阻断挂起。
|
||||
|
||||
```html
|
||||
<!-- ❌ 错误:国内阻断 -->
|
||||
<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 必须用 `cdn.staticfile.net` 或 `cdn.bootcdn.net`,禁止 `unpkg.com`/`cdnjs.cloudflare.com`。
|
||||
|
||||
---
|
||||
|
||||
### 7. PHP 注释块污染:未闭合 `/*` 导致语法错误
|
||||
|
||||
**现象**:整个 Admin 控制器报 `syntax error, unexpected token "public"`。
|
||||
**根因**:调试期间遗留的未闭合 `/*` 注释块吞噬了后面的方法定义。
|
||||
|
||||
```php
|
||||
/* 这是调试代码...
|
||||
// 忘记闭合,public 关键字被吞掉
|
||||
public function index() { ... }
|
||||
```
|
||||
|
||||
**教训**:隔离测试时清理调试代码;用 `php -l` 做语法检查。
|
||||
|
||||
---
|
||||
|
||||
### 8. ShopXO 路由 → `PluginsAdminUrl()` 而非硬编码
|
||||
|
||||
```php
|
||||
// ❌ 错误:硬编码 URL
|
||||
$url = '/adminwatekc.php?s=Plugins/VrTicket/Admin/index';
|
||||
|
||||
// ✅ 正确
|
||||
$url = PluginsAdminUrl('vr_ticket', 'admin', 'index');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. Admin.php `initialize()` 每次请求执行 `SHOW TABLES`
|
||||
|
||||
**现象**:`save.html` 等页面加载极其缓慢。
|
||||
**根因**:Admin.php 的 `initialize()` 构造中每次请求都在执行完整的 `SHOW TABLES` 和重建引用脚本,巨大 I/O 负担。
|
||||
|
||||
**解法**:引入基于时间跨度的缓存锁(Cache Lock),将高昂的表检查操作降低至 1 小时 1 次。
|
||||
|
||||
```php
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
$lockKey = 'vr_ticket_init_lock';
|
||||
$cache = cache($lockKey);
|
||||
if ($cache === false) {
|
||||
// 执行表检查和初始化
|
||||
$this->checkTables();
|
||||
cache($lockKey, 1, 3600); // 锁1小时
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 10. ThinkPHP 6 分页:`->render()` 而非 `->toArray()` 后取 `page`
|
||||
|
||||
```php
|
||||
// ❌ 错误
|
||||
$list = $model->paginate(10)->toArray();
|
||||
return $list['page']; // undefined
|
||||
|
||||
// ✅ 正确
|
||||
$list = $model->paginate(10);
|
||||
return $list->render();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟢 P2 — 重要经验
|
||||
|
||||
### 11. ShopXO 插件目录结构(正确模式)
|
||||
|
||||
```
|
||||
vr_ticket/
|
||||
├── Admin.php ← 根目录,继承 think\Controller(不是 admin/controller/)
|
||||
├── Hook.php ← 根目录
|
||||
├── config.json
|
||||
├── service/
|
||||
│ └── BaseService.php
|
||||
└── view/ ← 不是 admin/view/,是根目录 view/
|
||||
└── venue/
|
||||
├── list.html
|
||||
└── save.html
|
||||
```
|
||||
|
||||
**教训**:参考 `freightfee`/`answers` 插件结构,不要用 `admin/controller/` 子目录模式(会导致 strtolower+ucfirst 类名不匹配)。
|
||||
|
||||
---
|
||||
|
||||
### 12. JSON 传给 JS:用 `<script type="text/json">` + `textContent`
|
||||
|
||||
```html
|
||||
<!-- ❌ 错误:模板插值,含特殊字符时 JS 解析失败 -->
|
||||
<script>var data = {{$seat_map | json_encode | raw}};</script>
|
||||
|
||||
<!-- ✅ 正确:隔绝特殊字符 -->
|
||||
<script type="text/json" id="seat-map-data">{{$seat_map | json_encode | raw}}</script>
|
||||
<script>var data = JSON.parse(document.getElementById('seat-map-data').textContent);</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 13. AmazeUI 必用类名速查
|
||||
|
||||
| 功能 | AmazeUI 类 |
|
||||
|------|-----------|
|
||||
| 表格 | `am-table am-table-striped am-table-hover am-text-middle` |
|
||||
| 按钮 | `am-btn am-btn-default` / `-primary` / `-danger` + `am-btn-xs am-radius` |
|
||||
| 表单验证 | `class="am-form form-validation"`(没有这个 class 不走 AJAX) |
|
||||
| 字段校验提示 | `data-validation-message` 属性 |
|
||||
| 徽章 | `am-badge am-badge-success` / `-danger` |
|
||||
| 搜索栏 | `am-input-group am-input-group-sm am-fl so` |
|
||||
| 分页 | `{{$page\|raw}}` |
|
||||
| 图标 | `am-icon-plus` / `am-icon-edit` / `am-icon-trash-o` |
|
||||
|
||||
---
|
||||
|
||||
### 14. URL 截断:Base64 编码兜底
|
||||
|
||||
ShopXO 框架内部有多层正则扫描"净化"资源路径,CDN URL 会被截断。
|
||||
|
||||
```javascript
|
||||
// 前端:提交前 Base64 编码
|
||||
formData.seat_map = btoa(unescape(encodeURIComponent(jsonString)));
|
||||
|
||||
// 后端:解码还原
|
||||
$seatMap = json_decode(base64_decode($_POST['seat_map']), true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 15. 搜索字段 = 列表主标题(字段一致性原则)
|
||||
|
||||
- 搜索条件固定为数据库可索引字段(`name` 列)
|
||||
- 列表展示为三行式层级:`大字简名` → `(完整场馆名称)` → `📍 地址`
|
||||
- JSON 内字段只做展示,不做搜索条件
|
||||
|
||||
```sql
|
||||
-- ✅ 好:直接索引字段检索
|
||||
WHERE name LIKE '%keyword%'
|
||||
|
||||
-- ❌ 坏:JSON 字段模糊查询,效率低
|
||||
WHERE JSON_EXTRACT(venue_data, '$.full_name') LIKE '%keyword%'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 16. char 匹配需 `toUpperCase()` 归一化
|
||||
|
||||
座位标识字符比较时,务必归一化大小写:
|
||||
|
||||
```javascript
|
||||
// ✅ 正确
|
||||
const char = seatChar.toUpperCase();
|
||||
const zone = zones.find(z => z.char.toUpperCase() === char);
|
||||
|
||||
// ❌ 错误:大小写不一致导致匹配失败
|
||||
const zone = zones.find(z => z.char === seatChar);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📌 开发前检查清单
|
||||
|
||||
接手本插件时,逐项确认以下内容:
|
||||
|
||||
- [ ] `save.html` 有完整的 `{{:ModuleInclude('public/header')}}` 和 `{{:ModuleInclude('public/footer')}}`
|
||||
- [ ] Vue 3 的 `<textarea>` 没有使用 `[[ ]]` 插值绑定 value
|
||||
- [ ] Vue CDN 使用 `cdn.staticfile.net` 而非 `unpkg.com`
|
||||
- [ ] Hook.php 返回数组包含 `id`、`url`、`name`、`is_show`
|
||||
- [ ] Admin.php 有缓存锁机制保护 `initialize()` 免于每次请求检查表
|
||||
- [ ] 改字段名之前查过 Service 层源码或实际表结构
|
||||
- [ ] 插件视图路径使用 `../../../plugins/vr_ticket/view/...` 前缀
|
||||
- [ ] `php -l` 语法检查通过后再提交
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
# 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}`
|
||||
|
||||
---
|
||||
|
||||
+++++ 原始日志第二块(行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:'...'}"`
|
||||
|
||||
---
|
||||
|
||||
### 轮次3:Vue CDN 国内阻断问题(venue/save.html)
|
||||
|
||||
- **核心问题**:`https://unpkg.com/vue@3/dist/vue.global.prod.js` 在中国大陆无法访问,导致场馆编辑页 Vue3 编辑器完全失效
|
||||
|
||||
- **解决方案**:更换 CDN 源
|
||||
```html
|
||||
<!-- 错误(阻断) -->
|
||||
<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'])` 获取数据
|
||||
|
||||
**数据结构设计**:
|
||||
```javascript
|
||||
// 分区
|
||||
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 注入的默认值
|
||||
|
||||
- **关键教训**:
|
||||
- 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 自带日期控件)
|
||||
- 核销记录只读,无编辑/删除操作
|
||||
|
||||
---
|
||||
|
||||
### 核心经验总结
|
||||
|
||||
1. **ShopXO 插件视图 = AmazeUI 规范 + PHP 模板语法**,禁止混用其他 UI 框架
|
||||
2. **Vue3 只用于复杂交互场景**(如场馆座位编辑器),普通 CRUD 用纯 AmazeUI+jQuery
|
||||
3. **CDN 必须在 `.cn` 域名或国内可用源**,`unpkg.com` 不可用
|
||||
4. **PHP ↔ Vue 数据桥接**:Vue computed → hidden input → PHP `$_POST`
|
||||
5. **所有列表页统一模式**:搜索栏 → 操作按钮 → `am-table` → 分页
|
||||
6. **ShopXO 表单验证依赖 `form-validation` class**,没有这个 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`
|
||||
|
||||
### 轮次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 解决
|
||||
|
||||
### 轮次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 内的详情展示名称
|
||||
- 列表场馆信息列改为三行式层级:
|
||||
1. **简短名称**(大字加粗,可索引)
|
||||
2. `(完整场馆名称)`(灰色小字,括号)
|
||||
3. `📍 地址`(灰色小字)
|
||||
- 表单字段命名:`基础名称` → `场馆名称(简短名称)`,`详情展示名称` → `完整场馆名称`
|
||||
- 关键教训:
|
||||
- 搜索字段与列表主标题必须一一对应,数据库可索引字段优于 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 混用时需用 <template> 包装
|
||||
|
||||
### 轮次3:Vue 渲染页面 SyntaxError Bug(两次)
|
||||
- 核心问题:用户报告"添加场馆"页面只剩"返回 添加场馆",控制台报 SyntaxError: Invalid or unexpected token
|
||||
- 第一次修复:
|
||||
- 原因:后端 seat_map JSON 通过模板直接插到 JS 模板字符串时,含换行/特殊字符导致 JS 解析失败
|
||||
- 修复方案:改用 <script type="text/json"> 隐藏块 + textContent DOM 提取,隔绝特殊字符
|
||||
- 第二次修复(1305行):
|
||||
- 原因:Python 生成 JS 代码时,字符串字面量 split('\\n') 和 join('\\n') 中出现了物理换行,自动生成器错误导致字符串被折行,JS 不允许在单引号字符串内直接换行
|
||||
- 修复方法:用 grep -n "'\\$" 定位行尾单引号,定位到被折断的字符串语句
|
||||
- 关键教训:
|
||||
- ShopXO 插件页面传输 JSON 给 JS 时,用 <script type="text/json"> + textContent 比模板插值更健壮
|
||||
- 自动生成代码时注意 JS 字符串字面量不能被物理换行分割
|
||||
- 浏览器缓存可能导致修复后仍显示旧版本,强制刷新(Cmd+Shift+R)后才能验证
|
||||
|
||||
### 轮次4:分区配置与座位预览实时联动
|
||||
- 核心问题:分区价格修改后座位预览颜色变化但价格 tooltip 未实时更新
|
||||
- 解决方案:
|
||||
- getSeatTooltip(char, rowIdx, colIdx) 每次调用时从 currentRoom.sections 动态查找对应分区的当前价格
|
||||
- currentSeatRows 和 currentTotalSeats 均为 computed,依赖 currentRoom.value.map 变化时自动重新计算
|
||||
- 座位标识字符强制 toUpperCase(),保证 char 匹配不区分大小写
|
||||
- 关键教训:
|
||||
- Vue 3 Composition API 中 computed 依赖链要清晰:tooltip 价格应直接从 sections 数组实时读取,而非某处缓存
|
||||
- char 匹配要考虑大小写归一化
|
||||
|
|
@ -35,6 +35,11 @@ class Admin extends Common
|
|||
{
|
||||
parent::__construct();
|
||||
}
|
||||
public function index()
|
||||
{
|
||||
return $this->VenueList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 座位模板(SeatTemplate)
|
||||
|
|
@ -60,7 +65,7 @@ class Admin extends Common
|
|||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
$list = \Db::name('plugins_vr_seat_templates')
|
||||
$list = \Db::name('vr_seat_templates')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->paginate(20)
|
||||
|
|
@ -79,7 +84,9 @@ class Admin extends Common
|
|||
unset($item);
|
||||
}
|
||||
|
||||
return view('seat_template/list', [
|
||||
// Leading / = ThinkPHP absolute path resolved from app/admin/view/default/
|
||||
// Files are at: app/admin/view/default/plugins/view/vr_ticket/admin/view/seat_template/list.html
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/seat_template/list', [
|
||||
'list' => $list['data'],
|
||||
'page' => $list['page'],
|
||||
'count' => $list['total'],
|
||||
|
|
@ -117,12 +124,12 @@ class Admin extends Common
|
|||
}
|
||||
|
||||
if ($id > 0) {
|
||||
\Db::name('plugins_vr_seat_templates')->where('id', $id)->update($data);
|
||||
\Db::name('vr_seat_templates')->where('id', $id)->update($data);
|
||||
return DataReturn('更新成功', 0);
|
||||
} else {
|
||||
$data['add_time'] = time();
|
||||
$data['upd_time'] = time();
|
||||
\Db::name('plugins_vr_seat_templates')->insert($data);
|
||||
\Db::name('vr_seat_templates')->insert($data);
|
||||
return DataReturn('添加成功', 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +137,7 @@ class Admin extends Common
|
|||
// 编辑时加载数据
|
||||
$info = [];
|
||||
if ($id > 0) {
|
||||
$info = \Db::name('plugins_vr_seat_templates')->find($id);
|
||||
$info = \Db::name('vr_seat_templates')->find($id);
|
||||
}
|
||||
|
||||
// 加载分类列表(用于下拉选择)
|
||||
|
|
@ -139,7 +146,7 @@ class Admin extends Common
|
|||
->order('id', 'asc')
|
||||
->select();
|
||||
|
||||
return view('seat_template/save', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/seat_template/save', [
|
||||
'info' => $info,
|
||||
'categories' => $categories,
|
||||
]);
|
||||
|
|
@ -159,8 +166,8 @@ class Admin extends Common
|
|||
return DataReturn('参数错误', -1);
|
||||
}
|
||||
|
||||
$template = \Db::name('plugins_vr_seat_templates')->where('id', $id)->find();
|
||||
\Db::name('plugins_vr_seat_templates')
|
||||
$template = \Db::name('vr_seat_templates')->where('id', $id)->find();
|
||||
\Db::name('vr_seat_templates')
|
||||
->where('id', $id)
|
||||
->update(['status' => 0, 'upd_time' => time()]);
|
||||
|
||||
|
|
@ -202,7 +209,7 @@ class Admin extends Common
|
|||
$where[] = ['goods_id', '=', $goods_id];
|
||||
}
|
||||
|
||||
$list = \Db::name('plugins_vr_tickets')
|
||||
$list = \Db::name('vr_tickets')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->paginate(20)
|
||||
|
|
@ -227,7 +234,7 @@ class Admin extends Common
|
|||
2 => ['text' => '已退款', 'color' => 'red'],
|
||||
];
|
||||
|
||||
return view('ticket/list', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/ticket/list', [
|
||||
'list' => $list['data'],
|
||||
'page' => $list['page'],
|
||||
'count' => $list['total'],
|
||||
|
|
@ -245,7 +252,7 @@ class Admin extends Common
|
|||
return DataReturn('参数错误', -1);
|
||||
}
|
||||
|
||||
$ticket = \Db::name('plugins_vr_tickets')->find($id);
|
||||
$ticket = \Db::name('vr_tickets')->find($id);
|
||||
if (empty($ticket)) {
|
||||
return DataReturn('票不存在', -1);
|
||||
}
|
||||
|
|
@ -254,17 +261,17 @@ class Admin extends Common
|
|||
|
||||
$verifier = [];
|
||||
if ($ticket['verifier_id'] > 0) {
|
||||
$verifier = \Db::name('plugins_vr_verifiers')->find($ticket['verifier_id']);
|
||||
$verifier = \Db::name('vr_verifiers')->find($ticket['verifier_id']);
|
||||
}
|
||||
|
||||
$ticket['qr_code_url'] = \app\plugins\vr_ticket\service\TicketService::getQrCodeUrl($ticket['ticket_code']);
|
||||
|
||||
$verifiers = \Db::name('plugins_vr_verifiers')
|
||||
$verifiers = \Db::name('vr_verifiers')
|
||||
->where('status', 1)
|
||||
->order('id', 'asc')
|
||||
->select();
|
||||
|
||||
return view('ticket/detail', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/ticket/detail', [
|
||||
'ticket' => $ticket,
|
||||
'goods' => $goods,
|
||||
'verifier' => $verifier,
|
||||
|
|
@ -311,7 +318,7 @@ class Admin extends Common
|
|||
}
|
||||
|
||||
$header = ['ID', '订单号', '票码', '观演人', '手机', '座位', '核销状态', '发放时间'];
|
||||
$rows = \Db::name('plugins_vr_tickets')
|
||||
$rows = \Db::name('vr_tickets')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->cursor();
|
||||
|
|
@ -359,7 +366,7 @@ class Admin extends Common
|
|||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
$list = \Db::name('plugins_vr_verifiers')
|
||||
$list = \Db::name('vr_verifiers')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->paginate(20)
|
||||
|
|
@ -381,7 +388,7 @@ class Admin extends Common
|
|||
unset($item);
|
||||
}
|
||||
|
||||
return view('verifier/list', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/verifier/list', [
|
||||
'list' => $list['data'],
|
||||
'page' => $list['page'],
|
||||
'count' => $list['total'],
|
||||
|
|
@ -407,7 +414,7 @@ class Admin extends Common
|
|||
return DataReturn('核销员名称不能为空', -1);
|
||||
}
|
||||
|
||||
$exist = \Db::name('plugins_vr_verifiers')
|
||||
$exist = \Db::name('vr_verifiers')
|
||||
->where('user_id', $user_id)
|
||||
->where('id', '<>', $id)
|
||||
->find();
|
||||
|
|
@ -416,12 +423,12 @@ class Admin extends Common
|
|||
}
|
||||
|
||||
if ($id > 0) {
|
||||
\Db::name('plugins_vr_verifiers')
|
||||
\Db::name('vr_verifiers')
|
||||
->where('id', $id)
|
||||
->update(['name' => $name, 'status' => $status]);
|
||||
return DataReturn('更新成功', 0);
|
||||
} else {
|
||||
\Db::name('plugins_vr_verifiers')->insert([
|
||||
\Db::name('vr_verifiers')->insert([
|
||||
'user_id' => $user_id,
|
||||
'name' => $name,
|
||||
'status' => $status,
|
||||
|
|
@ -433,7 +440,7 @@ class Admin extends Common
|
|||
|
||||
$info = [];
|
||||
if ($id > 0) {
|
||||
$info = \Db::name('plugins_vr_verifiers')->find($id);
|
||||
$info = \Db::name('vr_verifiers')->find($id);
|
||||
}
|
||||
|
||||
$users = \Db::name('User')
|
||||
|
|
@ -442,7 +449,7 @@ class Admin extends Common
|
|||
->order('id', 'desc')
|
||||
->select();
|
||||
|
||||
return view('verifier/save', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/verifier/save', [
|
||||
'info' => $info,
|
||||
'users' => $users,
|
||||
]);
|
||||
|
|
@ -462,8 +469,8 @@ class Admin extends Common
|
|||
return DataReturn('参数错误', -1);
|
||||
}
|
||||
|
||||
$verifier = \Db::name('plugins_vr_verifiers')->where('id', $id)->find();
|
||||
\Db::name('plugins_vr_verifiers')
|
||||
$verifier = \Db::name('vr_verifiers')->where('id', $id)->find();
|
||||
\Db::name('vr_verifiers')
|
||||
->where('id', $id)
|
||||
->update(['status' => 0]);
|
||||
|
||||
|
|
@ -503,7 +510,7 @@ class Admin extends Common
|
|||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
$list = \Db::name('plugins_vr_seat_templates')
|
||||
$list = \Db::name('vr_seat_templates')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->paginate(20)
|
||||
|
|
@ -519,7 +526,7 @@ class Admin extends Common
|
|||
}
|
||||
unset($item);
|
||||
|
||||
return view('venue/list', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/venue/list', [
|
||||
'list' => $list['data'],
|
||||
'page' => $list['page'],
|
||||
'count' => $list['total'],
|
||||
|
|
@ -613,20 +620,20 @@ class Admin extends Common
|
|||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
if ($id > 0) {
|
||||
\Db::name('plugins_vr_seat_templates')->where('id', $id)->update($data);
|
||||
\Db::name('vr_seat_templates')->where('id', $id)->update($data);
|
||||
return DataReturn('更新成功', 0);
|
||||
} else {
|
||||
$data['add_time'] = time();
|
||||
$data['upd_time'] = time();
|
||||
$data['spec_base_id_map'] = '';
|
||||
\Db::name('plugins_vr_seat_templates')->insert($data);
|
||||
\Db::name('vr_seat_templates')->insert($data);
|
||||
return DataReturn('添加成功', 0);
|
||||
}
|
||||
}
|
||||
|
||||
$info = [];
|
||||
if ($id > 0) {
|
||||
$row = \Db::name('plugins_vr_seat_templates')->find($id);
|
||||
$row = \Db::name('vr_seat_templates')->find($id);
|
||||
if (!empty($row)) {
|
||||
$seatMap = json_decode($row['seat_map'] ?? '{}', true);
|
||||
$row['venue_name'] = $seatMap['venue']['name'] ?? '';
|
||||
|
|
@ -648,7 +655,7 @@ class Admin extends Common
|
|||
->order('id', 'asc')
|
||||
->select();
|
||||
|
||||
return view('venue/save', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/venue/save', [
|
||||
'info' => $info,
|
||||
'categories' => $categories,
|
||||
]);
|
||||
|
|
@ -668,8 +675,8 @@ class Admin extends Common
|
|||
return DataReturn('参数错误', -1);
|
||||
}
|
||||
|
||||
$template = \Db::name('plugins_vr_seat_templates')->where('id', $id)->find();
|
||||
\Db::name('plugins_vr_seat_templates')
|
||||
$template = \Db::name('vr_seat_templates')->where('id', $id)->find();
|
||||
\Db::name('vr_seat_templates')
|
||||
->where('id', $id)
|
||||
->update(['status' => 0, 'upd_time' => time()]);
|
||||
|
||||
|
|
@ -715,7 +722,7 @@ class Admin extends Common
|
|||
$where[] = ['created_at', '<=', strtotime($end_date . ' 23:59:59')];
|
||||
}
|
||||
|
||||
$list = \Db::name('plugins_vr_verifications')
|
||||
$list = \Db::name('vr_verifications')
|
||||
->where($where)
|
||||
->order('id', 'desc')
|
||||
->paginate(20)
|
||||
|
|
@ -724,7 +731,7 @@ class Admin extends Common
|
|||
// 补充票信息
|
||||
$ticket_ids = array_filter(array_column($list['data'], 'ticket_id'));
|
||||
if (!empty($ticket_ids)) {
|
||||
$tickets_raw = \Db::name('plugins_vr_tickets')
|
||||
$tickets_raw = \Db::name('vr_tickets')
|
||||
->where('id', 'in', $ticket_ids)
|
||||
->select();
|
||||
$tickets = [];
|
||||
|
|
@ -753,11 +760,11 @@ class Admin extends Common
|
|||
}
|
||||
|
||||
// 核销员列表(用于筛选)
|
||||
$verifiers = \Db::name('plugins_vr_verifiers')
|
||||
$verifiers = \Db::name('vr_verifiers')
|
||||
->where('status', 1)
|
||||
->column('name', 'id');
|
||||
|
||||
return view('verification/list', [
|
||||
return MyView('../../../plugins/vr_ticket/admin/view/verification/list', [
|
||||
'list' => $list['data'],
|
||||
'page' => $list['page'],
|
||||
'count' => $list['total'],
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class BaseService
|
|||
*/
|
||||
public static function table($name)
|
||||
{
|
||||
return 'plugins_vr_' . $name;
|
||||
return 'vr_' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue