fix(vr-ticket): 演出详情 content_app 内容渲染修复

- 新增 detailContent data 字段,从 tree API 的 apiData.goods.content_app 提取
- 参考 goods-detail.vue 的嵌套循环模式渲染 content_app(不用 rich-text)
- 新增 detail-content/detail-item/content-items/item 等 CSS 样式
- 新增 docs/vr-ticket-work-record-20260612.md 工作留痕文档
master
Council 2026-06-13 04:02:26 +08:00
parent 8f2960576c
commit 8ce0e5b7d2
3 changed files with 278 additions and 1 deletions

View File

@ -0,0 +1,221 @@
# 工作留痕 — VR 票务详情页演出详情内容渲染修复
> 创建时间2026-06-12
> 关联对话2fb4912f-8100-45fc-b11f-d2ac37536abf
> 涉及文件:
> - `pages/goods-vr-ticket/goods-vr-ticket.vue`
> - `pages/goods-vr-ticket/goods-vr-ticket.css`
> - `docs/test.json`(后端返回的 tree API 示例数据)
---
## 一、问题描述
票务商品详情页 `/pages/goods-vr-ticket/goods-vr-ticket?id=121` 的"演出详情"区域完全不显示内容。
### 1.1 现象
- 页面加载后,"演出详情" section 只有标题,无正文
- 图片和文字详情均不渲染
### 1.2 根因分析
**原因 A模板层面**:演出详情 section 在模板中只有 `<section-header>`,没有渲染 body
```html
<!-- 原来的模板 -->
<view class="section-card mt-10">
<view class="section-header">
<text class="section-title">演出详情</text>
</view>
<!-- ❌ 没有任何内容渲染 -->
</view>
```
**原因 B数据层面**`handleTreeData` 方法从未从 tree API 响应中提取 `content_app` 字段。
### 1.3 数据来源确认
后端 `vr_ticket` 插件的 tree 接口已更新,`data.goods.content_app` 包含详情内容:
```
tree API: plugins/index?pluginsname=vr_ticket&pluginscontrol=goods&pluginsaction=tree&...&goods_id=121
```
响应结构(关键字段):
```json
{
"code": 0,
"data": {
"goods": {
"id": 121,
"title": "一个多日测试票务商品2",
"content_app": [
{
"id": 13,
"images": "http://localhost:10000/static/upload/images/goods/2026/05/29/1779992828577719.jpeg",
"content": ["这是一条测试详情,这是一条测试详情,这是一条测试详情..."],
"content_old": "这是一条测试详情,这是一条测试详情..."
}
]
}
}
}
```
---
## 二、修复方案
### 2.1 数据层:提取 `content_app`
**文件**`pages/goods-vr-ticket/goods-vr-ticket.vue`
`handleTreeData` 方法中添加对 `apiData.goods.content_app` 的提取:
```javascript
handleTreeData(apiData) {
this.treeData = apiData.tree;
this.seatTemplates = apiData.seat_templates;
// 提取会话元数据和同场次商品
this.sessionMeta = apiData.session_meta || [];
this.peerGoods = apiData.peer_goods || [];
// 提取 goods 属性中的 content_app演出详情 ✅ 新增
if (apiData.goods && apiData.goods.content_app) {
this.detailContent = apiData.goods.content_app;
console.log('[VR Ticket] detailContent loaded:', this.detailContent);
}
// 提取场馆数据...
}
```
同步在 `data()` 中新增字段:
```javascript
data() {
return {
// ...
detailContent: [], // 详情内容(从 tree API 的 goods.content_app 获取)
// ...
}
}
```
### 2.2 渲染层:嵌套循环渲染
参考 `pages/goods-detail/goods-detail.vue`lines 401-408的渲染模式
```html
<!-- goods-detail.vue 原版 -->
<view v-for="(item, index) in goods_content_app" :key="index" class="goods-detail-app">
<image v-if="(item.images || null) != null" ... :src="item.images" mode="widthFix" />
<view v-if="(item.content || null) != null" class="content-items">
<view v-for="(items, index2) in item.content" :key="index2" class="item">{{ items }}</view>
</view>
</view>
```
**关键点**`item.content` 是一个字符串数组,不能直接传给 `rich-text`,而需要对每个字符串做嵌套循环渲染。
修复后的模板(`goods-vr-ticket.vue`
```html
<view class="detail-content" v-if="detailContent && detailContent.length > 0">
<view v-for="(item, index) in detailContent" :key="index" class="detail-item">
<image
v-if="item.images"
:src="item.images"
class="detail-image"
mode="widthFix"
:lazy-load="true"
/>
<view v-if="item.content && item.content.length > 0" class="content-items">
<view v-for="(text, i2) in item.content" :key="i2" class="item">{{ text }}</view>
</view>
</view>
</view>
<view v-else class="detail-empty">暂无演出详情</view>
```
### 2.3 样式层CSS 类名对齐
**文件**`pages/goods-vr-ticket/goods-vr-ticket.css`
新增样式(与 `goods-detail.vue``.goods-detail-app` / `.content-items` / `.item` 体系对齐):
```css
.detail-empty {
font-size: 13px;
color: #999;
padding: 20px 0;
text-align: center;
}
.detail-content {
margin-top: 16px;
}
.detail-item {
margin-bottom: 16px;
}
.detail-image {
width: 100%;
border-radius: 8px;
margin-bottom: 8px;
}
.content-items {
padding: 0 4px;
}
.content-items .item {
font-size: 14px;
color: #333;
line-height: 1.8;
text-align: left;
margin-bottom: 8px;
}
```
---
## 三、变更文件清单
| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `pages/goods-vr-ticket/goods-vr-ticket.vue` | 修改 | 新增 `detailContent` data 字段、`handleTreeData` 提取逻辑、模板渲染 body |
| `pages/goods-vr-ticket/goods-vr-ticket.css` | 修改 | 新增 `.detail-content`、`.detail-item`、`.content-items`、`.item` 等样式 |
| `docs/vr-ticket-work-record-20260612.md` | 新增 | 本工作留痕文档 |
| `docs/test.json` | 修改 | 后端返回的 tree API 完整示例数据goods.content_app 字段) |
---
## 四、验证方式
1. 启动 H5 编译:`npm run dev:h5`(端口 8082
2. 访问:`http://localhost:8082/#/pages/goods-vr-ticket/goods-vr-ticket?id=121`
3. 滚动到"演出详情"区域,应能看到:
- 图片(如有):宽度 100%,圆角 8px
- 文字详情每行文字独立渲染14px行高 1.8
---
## 五、技术备注
### 5.1 为什么不用 `rich-text`
`content_app` 中的 `item.content` 是字符串数组(如 `["文字1", "文字2"]`)。之前错误地将整个数组传给 `rich-text :nodes="item.content"`,导致渲染失败。正确做法是对数组中每个字符串做 `v-for` 嵌套循环渲染,与 `goods-detail.vue` 保持一致。
### 5.2 tree API vs detail API
VR 票务商品详情页**不再调用** `goods/detail` 接口,直接通过 `goods/tree` (pluginsname=vr_ticket) 接口获取所有数据(含 `goods.content_app`),符合 vr_ticket 插件的数据自包含设计。
---
## 六、参考文件
| 文件 | 用途 |
|------|------|
| `pages/goods-detail/goods-detail.vue` (L389-L408) | content_app 渲染模式参考 |
| `docs/test.json` | tree API 完整响应示例 |
| `docs/vr-ticket-uniapp-supplement.md` | VR 票务 UniApp 移植完整文档 |

View File

@ -607,6 +607,39 @@
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.detail-empty {
font-size: 13px;
color: #999;
padding: 20px 0;
text-align: center;
}
.detail-content {
margin-top: 16px;
}
.detail-item {
margin-bottom: 16px;
}
.detail-image {
width: 100%;
border-radius: 8px;
margin-bottom: 8px;
}
.content-items {
padding: 0 4px;
}
.content-items .item {
font-size: 14px;
color: #333;
line-height: 1.8;
text-align: left;
margin-bottom: 8px;
}
.round {
border-radius: 50%;
}

View File

@ -51,6 +51,21 @@
<view class="section-header">
<text class="section-title">演出详情</text>
</view>
<view class="detail-content" v-if="detailContent && detailContent.length > 0">
<view v-for="(item, index) in detailContent" :key="index" class="detail-item">
<image
v-if="item.images"
:src="item.images"
class="detail-image"
mode="widthFix"
:lazy-load="true"
/>
<view v-if="item.content && item.content.length > 0" class="content-items">
<view v-for="(text, i2) in item.content" :key="i2" class="item">{{ text }}</view>
</view>
</view>
</view>
<view v-else class="detail-empty">暂无演出详情</view>
</view>
<!-- Section: 购票须知 -->
@ -186,6 +201,8 @@ export default {
peerGoods: [],
//
sessionMeta: [],
// tree API goods.content_app
detailContent: [],
// ( API )
defaultServices: [
{ title: '电子票', desc: '现场验票时请观演人出示APP票夹中的电子票二维码验票入场不支持截屏。', status: 'success' },
@ -363,11 +380,17 @@ export default {
handleTreeData(apiData) {
this.treeData = apiData.tree;
this.seatTemplates = apiData.seat_templates;
//
this.sessionMeta = apiData.session_meta || [];
this.peerGoods = apiData.peer_goods || [];
// goods content_app
if (apiData.goods && apiData.goods.content_app) {
this.detailContent = apiData.goods.content_app;
console.log('[VR Ticket] detailContent loaded:', this.detailContent);
}
//
const venuesMap = {};
Object.values(apiData.seat_templates || {}).forEach(template => {