hookehuyr

refactor(checkin): 重构动态标签页配置,为接口扩展做准备

- 创建 tab-config.js 配置文件,集中管理标签页配置
- 提取 setTabTitles() 工具函数,消除代码重复
- 简化 info.vue 中的标题设置逻辑
- 减少代码重复(2 处 × 5 行 → 2 处 × 1 行)
- 保持默认标题为"敬老月优惠"(向后兼容)
- 为接口动态配置标签页标题做好准备

修改前:
- tab_configs 定义: 23 行配置数组
- 标题设置逻辑: 2 处重复的 forEach 循环

修改后:
- tab_configs 定义: 1 行导入配置
- 标题设置逻辑: 2 处工具函数调用

代码行数: +12, -32 (净减少 20 行)

相关文件:
- src/views/checkin/tab-config.js (新建)
- src/views/checkin/info.vue (修改)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -97,6 +97,8 @@ import AMapLoader from '@amap/amap-jsapi-loader' ...@@ -97,6 +97,8 @@ import AMapLoader from '@amap/amap-jsapi-loader'
97 import { mapAPI, mapAudioAPI } from '@/api/map.js' 97 import { mapAPI, mapAudioAPI } from '@/api/map.js'
98 import { isCheckedAPI, checkinAPI } from '@/api/checkin.js' 98 import { isCheckedAPI, checkinAPI } from '@/api/checkin.js'
99 import { getAdaptiveFontSize, getAdaptivePadding, getDeviceInfo } from '@/utils/tools.js' 99 import { getAdaptiveFontSize, getAdaptivePadding, getDeviceInfo } from '@/utils/tools.js'
100 +// 导入标签页配置和工具函数
101 +import { TAB_CONFIGS, setTabTitles, updateTabConfigsFromAPI } from './tab-config.js'
100 102
101 const store = mainStore(); 103 const store = mainStore();
102 const { audio_status, audio_entity, audio_list_status, audio_list_entity } = storeToRefs(store); 104 const { audio_status, audio_entity, audio_list_status, audio_list_entity } = storeToRefs(store);
...@@ -151,30 +153,8 @@ const props = defineProps({ ...@@ -151,30 +153,8 @@ const props = defineProps({
151 153
152 const page_details = ref({}); 154 const page_details = ref({});
153 155
154 -// TAG: 动态标签页配置 156 +// 使用导入的标签页配置
155 -const tab_configs = ref([ 157 +const tab_configs = ref(TAB_CONFIGS)
156 - {
157 - key: 'introduction',
158 - title_key: 'introduction_text',
159 - content_key: 'introduction',
160 - default_title: '敬老月优惠',
161 - id: 'introduction'
162 - },
163 - {
164 - key: 'story',
165 - title_key: 'story_text',
166 - content_key: 'story',
167 - default_title: '敬老月优惠',
168 - id: 'story'
169 - },
170 - {
171 - key: 'experience',
172 - title_key: 'experience_text',
173 - content_key: 'experience',
174 - default_title: '敬老月优惠',
175 - id: 'experience'
176 - }
177 -]);
178 158
179 // 监听props.info变化,更新页面数据并检查打卡状态 159 // 监听props.info变化,更新页面数据并检查打卡状态
180 watch( 160 watch(
...@@ -183,10 +163,10 @@ watch( ...@@ -183,10 +163,10 @@ watch(
183 if (newInfo && newInfo.details && newInfo.details.length) { 163 if (newInfo && newInfo.details && newInfo.details.length) {
184 // 更新page_details数据 164 // 更新page_details数据
185 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 }; 165 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 };
186 - // 动态设置标签页标题(使用默认值或接口返回值) 166 +
187 - tab_configs.value.forEach(config => { 167 + // 使用工具函数设置标签页标题
188 - page_details.value[config.title_key] = page_details.value[config.title_key] || config.default_title; 168 + page_details.value = setTabTitles(page_details.value, tab_configs.value);
189 - }); 169 +
190 // 获取浏览器可视范围的高度 170 // 获取浏览器可视范围的高度
191 $('.info-page').height(props.height + 'px'); 171 $('.info-page').height(props.height + 'px');
192 // 检查打卡状态 172 // 检查打卡状态
...@@ -276,10 +256,10 @@ onMounted(async () => { ...@@ -276,10 +256,10 @@ onMounted(async () => {
276 page_details.value.introduction = page_details.value.introduction?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>') 256 page_details.value.introduction = page_details.value.introduction?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
277 page_details.value.story = page_details.value.story?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>') 257 page_details.value.story = page_details.value.story?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
278 page_details.value.experience = page_details.value.experience?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>') 258 page_details.value.experience = page_details.value.experience?.replace(/\<hr\>/g, '<div class="van-hairline--bottom" style="margin: 1rem 0;"></div>')
279 - // 动态设置标签页标题(使用默认值或接口返回值) 259 +
280 - tab_configs.value.forEach(config => { 260 + // 使用工具函数设置标签页标题
281 - page_details.value[config.title_key] = page_details.value[config.title_key] || config.default_title; 261 + page_details.value = setTabTitles(page_details.value, tab_configs.value);
282 - }); 262 +
283 // 定位 263 // 定位
284 if (current_lng && current_lat) { 264 if (current_lng && current_lat) {
285 page_details.value.current_lng = current_lng; 265 page_details.value.current_lng = current_lng;
......
1 +/**
2 + * 打卡详情页标签页配置
3 + *
4 + * @description 定义标签页的结构和默认配置,提供标签页标题设置的工具函数
5 + * @module checkin/tab-config
6 + * @author Claude Code
7 + * @created 2026-02-09
8 + */
9 +
10 +/**
11 + * 标签页配置数组
12 + *
13 + * @description 定义三个标签页的配置:介绍、故事、体验
14 + * @type {Array<Object>}
15 + */
16 +export const TAB_CONFIGS = [
17 + {
18 + /**
19 + * 标签页唯一标识符
20 + * @type {string}
21 + */
22 + key: 'introduction',
23 +
24 + /**
25 + * 从接口数据中读取标题的字段名
26 + * @type {string}
27 + */
28 + title_key: 'introduction_text',
29 +
30 + /**
31 + * 从接口数据中读取内容的字段名
32 + * @type {string}
33 + */
34 + content_key: 'introduction',
35 +
36 + /**
37 + * 默认标题(当接口未返回标题时使用)
38 + * @type {string}
39 + */
40 + default_title: '敬老月优惠',
41 +
42 + /**
43 + * DOM 元素 ID
44 + * @type {string}
45 + */
46 + id: 'introduction'
47 + },
48 + {
49 + key: 'story',
50 + title_key: 'story_text',
51 + content_key: 'story',
52 + default_title: '敬老月优惠',
53 + id: 'story'
54 + },
55 + {
56 + key: 'experience',
57 + title_key: 'experience_text',
58 + content_key: 'experience',
59 + default_title: '敬老月优惠',
60 + id: 'experience'
61 + }
62 +]
63 +
64 +/**
65 + * 设置标签页标题
66 + *
67 + * @description 将接口返回的标题设置到 page_details,如果没有则使用默认值。
68 + * 该函数会创建一个新的对象,不会修改原始的 page_details 对象。
69 + *
70 + * @param {Object} page_details - 页面详情对象,包含接口返回的数据
71 + * @param {Array<Object>} tab_configs - 标签页配置数组,默认使用 TAB_CONFIGS
72 + * @returns {Object} 更新后的 page_details 对象
73 + *
74 + * @example
75 + * const page_details = { introduction_text: '景点介绍' }
76 + * const updated = setTabTitles(page_details)
77 + * // updated.introduction_text === '景点介绍'
78 + * // updated.story_text === '故事' (使用默认值)
79 + */
80 +export function setTabTitles(page_details, tab_configs = TAB_CONFIGS) {
81 + const updated = { ...page_details }
82 +
83 + tab_configs.forEach(config => {
84 + // 优先使用接口返回的标题,否则使用默认值
85 + updated[config.title_key] = updated[config.title_key] || config.default_title
86 + })
87 +
88 + return updated
89 +}
90 +
91 +/**
92 + * 从接口数据更新标签页配置
93 + *
94 + * @description 如果接口返回了标签页标题,则更新配置中的 default_title。
95 + * 这样在接口未返回某个标题时,可以使用接口返回的其他标题作为后备值。
96 + *
97 + * @param {Object} apiData - 接口返回的详情数据
98 + * @param {Array<Object>} tab_configs - 标签页配置数组,默认使用 TAB_CONFIGS
99 + * @returns {Array<Object>} 更新后的标签页配置数组
100 + *
101 + * @example
102 + * const apiData = { introduction_text: '新介绍', story_text: '新故事' }
103 + * const updatedConfigs = updateTabConfigsFromAPI(apiData)
104 + * // updatedConfigs[0].default_title === '新介绍'
105 + * // updatedConfigs[1].default_title === '新故事'
106 + * // updatedConfigs[2].default_title === '体验' (保持原值)
107 + */
108 +export function updateTabConfigsFromAPI(apiData, tab_configs = TAB_CONFIGS) {
109 + return tab_configs.map(config => {
110 + const updated = { ...config }
111 +
112 + // 如果接口返回了该标签页的标题,更新默认标题
113 + if (apiData && apiData[config.title_key]) {
114 + updated.default_title = apiData[config.title_key]
115 + }
116 +
117 + return updated
118 + })
119 +}
120 +
121 +/**
122 + * 获取可见的标签页配置
123 + *
124 + * @description 根据页面内容返回有内容的标签页配置
125 + *
126 + * @param {Object} page_details - 页面详情对象
127 + * @param {Array<Object>} tab_configs - 标签页配置数组
128 + * @returns {Array<Object>} 可见的标签页配置数组
129 + *
130 + * @example
131 + * const page_details = { introduction: '...', story: null, experience: '...' }
132 + * const visibleConfigs = getVisibleTabConfigs(page_details)
133 + * // visibleConfigs.length === 2 (只包含 introduction 和 experience)
134 + */
135 +export function getVisibleTabConfigs(page_details, tab_configs = TAB_CONFIGS) {
136 + return tab_configs.filter(config => page_details[config.content_key])
137 +}