checkin-info-动态标签页分析与准备.md
18.9 KB
checkin/info 动态标签页分析与接口接入准备
日期: 2026-02-09 目的: 为动态标签页的 default_title 从接口获取数据做准备
📊 当前实现分析
1. 动态标签页配置(155-177 行)
const tab_configs = ref([
{
key: 'introduction',
title_key: 'introduction_text', // 接口返回的标题字段
content_key: 'introduction', // 接口返回的内容字段
default_title: '敬老月优惠', // ❌ 当前写死
id: 'introduction'
},
{
key: 'story',
title_key: 'story_text',
content_key: 'story',
default_title: '敬老月优惠', // ❌ 当前写死
id: 'story'
},
{
key: 'experience',
title_key: 'experience_text',
content_key: 'experience',
default_title: '敬老月优惠', // ❌ 当前写死
id: 'experience'
}
]);
结构说明:
-
key: 标签页的唯一标识符 -
title_key: 从接口数据中读取标题的字段名(如introduction_text) -
content_key: 从接口数据中读取内容的字段名(如introduction) -
default_title: 当接口未返回标题时的后备值(目前写死为"敬老月优惠") -
id: DOM 元素 ID,用于内容渲染和事件绑定
2. 标题设置逻辑(186-189 行,279-282 行)
有两处相同的逻辑:
// 第 1 处:watch props.info 变化时(186-189 行)
tab_configs.value.forEach(config => {
page_details.value[config.title_key] = page_details.value[config.title_key] || config.default_title;
});
// 第 2 处:onMounted 时(279-282 行)
tab_configs.value.forEach(config => {
page_details.value[config.title_key] = page_details.value[config.title_key] || config.default_title;
});
逻辑说明:
- 如果
page_details.value[config.title_key]有值(如page_details.value.introduction_text),使用接口返回的值 - 否则使用
config.default_title(当前写死为"敬老月优惠")
3. 数据来源(263-293 行)
主要数据来源:mapAPI
const { data } = await mapAPI({ i: id });
const raw_list = data.list[0].list; // 标记点列表
const marker_id = $route.query.marker_id;
const current_marker = raw_list.filter(item => item.id == marker_id)[0];
page_details.value = {
...current_marker.details[0], // 标记点详情
position: current_marker.position,
path: current_marker.path,
// ...
};
当前接口返回的数据结构(推测):
{
code: 1,
data: {
list: [
{
id: 1,
list: [ // 标记点列表
{
id: 123, // marker_id
position: [lng, lat],
path: [],
details: [ // 标记点详情
{
id: 456,
name: "某某景点",
banner: [...],
note: "...",
// 三个标签页的内容
introduction: "<p>介绍内容...</p>",
story: "<p>故事内容...</p>",
experience: "<p>体验内容...</p>",
// ❌ 三个标签页的标题(可能不存在)
introduction_text: undefined, // 接口可能不返回
story_text: undefined,
experience_text: undefined,
// 其他字段
show_audio: true,
experience_audio: [...]
}
]
}
]
}
]
}
}
4. 标签页渲染(41-64 行)
<van-tabs>
<template v-for="config in tab_configs" :key="config.key">
<van-tab
:title="page_details[config.title_key]"
v-if="page_details[config.content_key]"
>
<!-- 内容 -->
<div :id="config.id" v-html="page_details[config.content_key]"></div>
</van-tab>
</template>
</van-tabs>
渲染逻辑:
-
v-if="page_details[config.content_key]":只有当内容存在时才显示该标签页 -
:title="page_details[config.title_key]":标题从page_details读取
🎯 问题总结
当前痛点
-
default_title 写死
- 所有标签页的 default_title 都是"敬老月优惠"
- 无法根据不同场景动态设置
-
接口数据可能不完整
- 接口可能不返回
introduction_text、story_text、experience_text - 导致所有标签页都使用默认值
- 接口可能不返回
-
逻辑重复
- 标题设置逻辑在两处重复(watch 和 onMounted)
🔮 接口数据结构推测
方案 A: 接口直接返回标签页标题(推荐)✅
{
code: 1,
data: {
list: [
{
id: 1,
list: [
{
id: 123,
details: [
{
id: 456,
name: "某某景点",
// ✅ 标签页内容
introduction: "<p>介绍内容...</p>",
story: "<p>故事内容...</p>",
experience: "<p>体验内容...</p>",
// ✅ 标签页标题(新增字段)
introduction_text: "景点介绍", // 从接口获取
story_text: "历史故事", // 从接口获取
experience_text: "游客体验", // 从接口获取
// ✅ 全局默认标题(可选)
default_tab_title: "默认标签" // 统一的后备标题
}
]
}
]
}
]
}
}
方案 B: 接口返回标签页配置对象
{
code: 1,
data: {
list: [
{
id: 1,
list: [
{
id: 123,
details: [
{
id: 456,
name: "某某景点",
// ✅ 标签页配置对象
tabs_config: {
introduction: {
title: "景点介绍",
content: "<p>介绍内容...</p>"
},
story: {
title: "历史故事",
content: "<p>故事内容...</p>"
},
experience: {
title: "游客体验",
content: "<p>体验内容...</p>"
}
}
}
]
}
]
}
]
}
}
方案 C: 单独的标签页配置接口
// 新增接口:/api/getTabConfig
const tabConfigAPI = (params) => fetch.get('/srv/?a=getTabConfig', params);
// 返回数据
{
code: 1,
data: {
tabs: [
{
key: 'introduction',
title: '景点介绍',
default_title: '介绍' // 后备值
},
{
key: 'story',
title: '历史故事',
default_title: '故事'
},
{
key: 'experience',
title: '游客体验',
default_title: '体验'
}
]
}
}
🛠️ 接入准备方案
阶段 1: 代码重构准备(不依赖接口)
1.1 提取标签页配置到独立文件
创建文件:src/views/checkin/tab-config.js
/**
* 打卡详情页标签页配置
*
* @description 定义标签页的结构和默认配置
* @module checkin/tab-config
*/
/**
* 标签页配置数组
* @type {Array<Object>}
*/
export const TAB_CONFIGS = [
{
key: 'introduction',
title_key: 'introduction_text',
content_key: 'introduction',
default_title: '介绍', // ✅ 修改为通用的默认值
id: 'introduction'
},
{
key: 'story',
title_key: 'story_text',
content_key: 'story',
default_title: '故事', // ✅ 修改为通用的默认值
id: 'story'
},
{
key: 'experience',
title_key: 'experience_text',
content_key: 'experience',
default_title: '体验', // ✅ 修改为通用的默认值
id: 'experience'
}
]
/**
* 设置标签页标题
*
* @description 将接口返回的标题设置到 page_details,如果没有则使用默认值
* @param {Object} page_details - 页面详情对象
* @param {Array<Object>} tab_configs - 标签页配置数组
* @returns {Object} 更新后的 page_details
*/
export function setTabTitles(page_details, tab_configs = TAB_CONFIGS) {
const updated = { ...page_details }
tab_configs.forEach(config => {
// 优先使用接口返回的标题,否则使用默认值
updated[config.title_key] = updated[config.title_key] || config.default_title
})
return updated
}
优势:
- ✅ 配置集中管理
- ✅ 便于修改和维护
- ✅ 可以动态加载配置
- ✅ 提供工具函数,减少重复代码
1.2 修改 info.vue 使用新配置
修改 1:导入配置
// 在 <script setup> 顶部添加
import { TAB_CONFIGS, setTabTitles } from './tab-config'
修改 2:替换 tab_configs 定义(155-177 行)
// ❌ 删除旧代码
// const tab_configs = ref([...])
// ✅ 使用导入的配置
const tab_configs = ref(TAB_CONFIGS)
修改 3:简化标题设置逻辑
// 第 1 处:watch props.info(186-189 行)
watch(
() => props.info,
async (newInfo) => {
if (newInfo && newInfo.details && newInfo.details.length) {
page_details.value = { ...newInfo.details[0], position: newInfo.position, path: newInfo.path, current_lng: newInfo.current_lng, current_lat: newInfo.current_lat, openid: newInfo.openid };
// ✅ 使用工具函数设置标题
page_details.value = setTabTitles(page_details.value, tab_configs.value)
// 获取浏览器可视范围的高度
$('.info-page').height(props.height + 'px');
// 检查打卡状态
await checkInitialCheckinStatus();
}
},
{ immediate: true }
)
// 第 2 处:onMounted(279-282 行)
onMounted(async () => {
// ...
// ✅ 使用工具函数设置标题
page_details.value = setTabTitles(page_details.value, tab_configs.value)
// ...
})
阶段 2: 接口数据接入(等接口确定后)
2.1 方案 A: 接口直接返回标题字段
修改 tab-config.js:
/**
* 从接口数据获取标签页配置
*
* @description 如果接口返回了标签页标题,则更新配置
* @param {Object} apiData - 接口返回的详情数据
* @param {Array<Object>} tab_configs - 标签页配置数组
* @returns {Array<Object>} 更新后的标签页配置
*/
export function updateTabConfigsFromAPI(apiData, tab_configs = TAB_CONFIGS) {
return tab_configs.map(config => {
const updated = { ...config }
// 如果接口返回了该标签页的标题
if (apiData[config.title_key]) {
updated.default_title = apiData[config.title_key]
}
return updated
})
}
修改 info.vue:
// onMounted 中
onMounted(async () => {
if (!props.info) {
let id = $route.query.id;
const { data } = await mapAPI({ i: id });
const raw_list = data.list[0].list;
const marker_id = $route.query.marker_id;
const current_marker = raw_list.filter(item => item.id == marker_id)[0];
page_details.value = {
...current_marker.details[0],
position: current_marker.position,
path: current_marker.path,
current_lng: current_lng,
current_lat: current_lat,
openid: openid
};
// ✅ 如果接口返回了全局默认标题,使用它
const globalDefaultTitle = page_details.value.default_tab_title
if (globalDefaultTitle) {
tab_configs.value = tab_configs.value.map(config => ({
...config,
default_title: globalDefaultTitle
}))
}
// ✅ 从接口数据更新标签页配置(如果接口返回了具体标题)
tab_configs.value = updateTabConfigsFromAPI(page_details.value, tab_configs.value)
// 设置标题
page_details.value = setTabTitles(page_details.value, tab_configs.value)
// ...
}
})
2.2 方案 B: 接口返回配置对象
修改 tab-config.js:
/**
* 从接口配置对象生成标签页配置
*
* @description 如果接口返回了 tabs_config,则使用它
* @param {Object} tabsConfig - 接口返回的 tabs_config
* @returns {Array<Object>} 标签页配置数组
*/
export function generateTabConfigsFromAPI(tabsConfig) {
if (!tabsConfig || typeof tabsConfig !== 'object') {
return TAB_CONFIGS // 使用默认配置
}
return Object.entries(tabsConfig).map(([key, config]) => ({
key,
title_key: `${key}_text`,
content_key: key,
default_title: config.title || key,
id: key
}))
}
修改 info.vue:
// onMounted 中
onMounted(async () => {
if (!props.info) {
// ...获取数据
// ✅ 如果接口返回了 tabs_config,使用它生成标签页配置
if (page_details.value.tabs_config) {
tab_configs.value = generateTabConfigsFromAPI(page_details.value.tabs_config)
}
// ...
}
})
2.3 方案 C: 单独的标签页配置接口
创建 API:src/api/checkin.js
/**
* 获取标签页配置
*
* @description 从接口获取标签页配置
* @param {Object} params - 请求参数
* @returns {Promise<Object>} 接口响应
*/
export const tabConfigAPI = (params) => fn(fetch.get('/srv/?a=getTabConfig', params))
修改 info.vue:
import { tabConfigAPI } from '@/api/checkin.js'
// onMounted 中
onMounted(async () => {
// ...获取地图数据
try {
// ✅ 调用标签页配置接口
const { data: tabConfigData } = await tabConfigAPI({ id: page_details.value.id })
if (tabConfigData && tabConfigData.tabs) {
// 更新标签页配置
tab_configs.value = tabConfigData.tabs.map(tab => ({
key: tab.key,
title_key: `${tab.key}_text`,
content_key: tab.key,
default_title: tab.default_title || tab.title || tab.key,
id: tab.key
}))
}
} catch (error) {
console.error('获取标签页配置失败,使用默认配置:', error)
// 保持使用默认配置
}
// 设置标题
page_details.value = setTabTitles(page_details.value, tab_configs.value)
// ...
})
阶段 3: 测试方案
3.1 单元测试(可选)
创建文件:src/views/checkin/tab-config.test.js
import { describe, it, expect } from 'vitest'
import { setTabTitles, updateTabConfigsFromAPI, generateTabConfigsFromAPI } from './tab-config'
describe('tab-config', () => {
describe('setTabTitles', () => {
it('应该使用接口返回的标题', () => {
const page_details = {
introduction_text: '景点介绍',
story_text: '历史故事'
}
const result = setTabTitles(page_details)
expect(result.introduction_text).toBe('景点介绍')
expect(result.story_text).toBe('历史故事')
})
it('应该使用默认标题当接口未返回', () => {
const page_details = {}
const result = setTabTitles(page_details)
expect(result.introduction_text).toBe('介绍')
expect(result.story_text).toBe('故事')
})
})
describe('updateTabConfigsFromAPI', () => {
it('应该从接口数据更新默认标题', () => {
const apiData = {
introduction_text: '新介绍',
story_text: '新故事'
}
const result = updateTabConfigsFromAPI(apiData)
expect(result[0].default_title).toBe('新介绍')
expect(result[1].default_title).toBe('新故事')
})
})
})
3.2 手动测试场景
场景 1: 接口返回所有标题
// 模拟接口数据
const mockData = {
introduction_text: '景点介绍',
story_text: '历史故事',
experience_text: '游客体验'
}
// 预期结果
// ✅ 标签页标题:景点介绍、历史故事、游客体验
场景 2: 接口只返回部分标题
// 模拟接口数据
const mockData = {
introduction_text: '景点介绍',
// story_text 未返回
// experience_text 未返回
}
// 预期结果
// ✅ 标签页标题:景点介绍、故事(默认)、体验(默认)
场景 3: 接口未返回任何标题
// 模拟接口数据
const mockData = {}
// 预期结果
// ✅ 标签页标题:介绍(默认)、故事(默认)、体验(默认)
📋 实施清单
✅ 已完成(准备工作)
- 分析当前实现
- 识别问题点
- 设计重构方案
- 编写准备文档
🔄 待实施(等接口确定后)
第 1 步:代码重构(不依赖接口)
-
创建
src/views/checkin/tab-config.js -
实现
setTabTitles()工具函数 -
修改
info.vue使用新配置 - 测试功能是否正常
第 2 步:接口接入(根据接口方案)
- 确认接口数据结构(方案 A/B/C)
-
实现
updateTabConfigsFromAPI()或generateTabConfigsFromAPI() -
修改
info.vue调用接口或处理数据 - 处理接口失败的情况
第 3 步:测试验证
- 单元测试(可选)
- 手动测试(3 个场景)
- 真机测试
- 边界情况测试
🎯 推荐方案
推荐采用:方案 A(接口直接返回标题字段)
理由:
- ✅ 最简单:只需在接口数据中添加 3 个字段
- ✅ 向后兼容:不影响现有接口结构
- ✅ 灵活:支持全局默认标题和单个标签页标题
- ✅ 性能好:无需额外请求
接口需要添加的字段:
{
details: [
{
// 新增字段
introduction_text: "景点介绍", // 介绍标签页标题
story_text: "历史故事", // 故事标签页标题
experience_text: "游客体验", // 体验标签页标题
// 可选:全局默认标题
default_tab_title: "默认标签" // 当上面三个字段都不存在时使用
}
]
}
💡 使用指南
当接口确定后
告诉我:
- 接口返回的数据结构(JSON 示例)
- 使用哪个方案(A/B/C)
我会:
- 根据接口结构调整代码
- 实现接口接入逻辑
- 添加错误处理
- 编写测试用例
快速参考
文件位置:
- 配置文件:
src/views/checkin/tab-config.js(待创建) - 主要组件:
src/views/checkin/info.vue - API 文件:
src/api/checkin.js
关键函数:
-
setTabTitles():设置标签页标题 -
updateTabConfigsFromAPI():从接口更新配置(方案 A) -
generateTabConfigsFromAPI():从接口生成配置(方案 B)
数据流程:
接口返回数据
↓
提取标签页标题
↓
更新 tab_configs
↓
设置 page_details 标题
↓
渲染标签页
📞 后续行动
当前状态:准备完成,等待接口确定
需要你提供:
- 接口文档或数据结构示例
- 采用哪个方案(A/B/C)
我会立即:
- 根据接口调整代码
- 实现完整的接入逻辑
- 测试验证功能