路径查询组件优化记录:数据处理与显示逻辑重构
一、优化背景与目标
在路径查询系统中,station-display.vue
组件负责展示查询结果,包括线路名称、站点信息和换乘方案等关键信息。通过代码审查,发现了两个需要优化的问题:
- 线路名称显示错误:连接线上显示的线路名称取自下一个站点而非当前站点
- 数据处理分散:数据字段处理逻辑分散在多处,导致维护困难
优化目标是在不影响现有功能的前提下,修正显示错误并提高代码的可维护性。
二、具体改动点与前后对比
1. 线路名称显示逻辑修正
问题:在站点连接线上,线路名称错误地显示了下一个站点的线路,而非当前站点的线路。
修改前代码:
<template v-for="(station, idx) in path.stations" :key="'conn-' + idx"><div v-if="idx < path.stations.length - 1" class="station-connection" :style="{left: `calc(${(idx + 0.5) * 100 / (path.stations.length - 1)}%)`,transform: 'translateX(-50%)'}"><span class="line-station-info">{{ path.stations[idx + 1].lineName ? path.stations[idx + 1].lineName.replace('地铁', '') : '' }}/{{path.stations[idx + 1].stationNum || 0 }}站</span></div>
</template>
修改后代码:
<template v-for="(station, idx) in path.stations" :key="'conn-' + idx"><div v-if="idx < path.stations.length - 1" class="station-connection" :style="{left: `calc(${(idx + 0.5) * 100 / (path.stations.length - 1)}%)`,transform: 'translateX(-50%)'}"><span class="line-station-info">{{ path.stations[idx].lineName ? path.stations[idx].lineName.replace('地铁', '') : '' }}/{{path.stations[idx + 1].stationNum || 0 }}站</span></div>
</template>
效果对比:
- 修改前:连接线显示的是下一个站点的线路名称,例如从1号线到2号线的连接线会显示"2号线/3站"
- 修改后:连接线显示的是当前站点的线路名称,例如从1号线到2号线的连接线会显示"1号线/3站"
视觉变化:
用户现在能看到正确的线路信息,特别是在换乘点,能清晰地知道当前所在的线路,而不是错误地显示即将换乘的线路。
2. 数据处理逻辑集中化
a. 总站数显示字段统一
修改前代码:
<div class="line-time-station"><span class="line-name">{{ path.lineName || '暂无线路名称' }}</span><span class="time-station" v-if="path.duration">{{secondToMinute(path.duration) }}分钟/{{ path.allNum || 0}}站</span>
</div>
修改后代码:
<div class="line-time-station"><span class="line-name">{{ path.lineName || '暂无线路名称' }}</span><span class="time-station" v-if="path.duration">{{secondToMinute(path.duration) }}分钟/{{ path.totalStations || 0}}站</span>
</div>
效果对比:
- 修改前:直接使用API返回的
allNum
字段 - 修改后:使用统一处理后的
totalStations
字段,但值仍来自API的allNum
b. 数据处理函数重构
修改前代码:
// 处理站点数据
const processStations = (path) => {const stations = [];if (path.schedulesList && path.schedulesList.length > 0) {// 添加起始站stations.push({stationType: '0',stationName: path.originStationName || path.schedulesList[0]?.stationName || '',lineName: `${path.schedulesList[0]?.lineId || ''}号线`,stationNum: path.schedulesList[0]?.stationNum || 0});// 添加中间站/换乘站for (let i = 1; i < path.schedulesList.length; i++) {const station = path.schedulesList[i];const isLastStation = i === path.schedulesList.length - 1;stations.push({stationType: isLastStation ? '1' : '2',stationName: isLastStation ?(path.destStationName || station.stationName || '') :(station.stationName || ''),lineName: `${station.lineId || ''}号线`,stationNum: station.stationNum || 0});}} else if (path.originStationName && path.destStationName) {// 只有起终点信息的情况stations.push({stationType: '0',stationName: path.originStationName,lineName: `${path.originLineId || ''}号线`,stationNum: 0});stations.push({stationType: '1',stationName: path.destStationName,lineName: `${path.endLineId || ''}号线`,stationNum: 0});}return stations;
};// 查询按钮点击处理
const handleQuery = async () => {// ... 验证代码 ...try {// 调用API查询路径const response = await api.findOdPath(params);if (response && Array.isArray(response) && response.length > 0) {pathResults.value = response.map(path => ({programmeType: path.simpleType || 0,programmeIndex: path.programmeIndex || 1,lineName: path.lineName || `${path.endLineId || ''}号线 (${path.originStationName || ''} - ${path.destStationName || ''})`,duration: path.travelTime || 0,stationNum: path.stopList?.length || 0,transferNum: path.transferNum || 0,stations: processStations(path),nameScope: path.nameScope || '',simpleType: path.simpleType || '0',schedulesList: path.schedulesList || [],allNum: path.allNum || 0}));// ... 选择路径代码 ...}} catch (error) {// ... 错误处理 ...}
};// watch中的数据处理
watch(() => props.selectedPath, (newPath) => {// ... 其他代码 ...if (newPath) {try {// ... 线路和站点处理代码 ...// 更新路径结果pathResults.value = [{programmeType: newPath.programmeType || 0,programmeIndex: newPath.programmeIndex || 1,lineName: newPath.lineName || '',duration: newPath.duration || 0,stationNum: newPath.stationNum || 0,transferNum: newPath.transferNum || 0,stations: newPath.stations || [],nameScope: newPath.nameScope || '',simpleType: newPath.simpleType || '0',schedulesList: newPath.schedulesList || []}];selectedPathIndex.value = 0;} catch (error) {// ... 错误处理 ...}}
}, { immediate: true, deep: true });
修改后代码:
// 数据处理工具函数
/*** 处理路径数据,统一数据格式* @param {Object} rawPath - 原始路径数据* @returns {Object} - 处理后的路径数据*/
const processPathData = (rawPath) => {// 处理站点数据const stations = processStations(rawPath);return {programmeType: rawPath.simpleType || 0,programmeIndex: rawPath.programmeIndex || 1,lineName: rawPath.lineName || `${rawPath.endLineId || ''}号线 (${rawPath.originStationName || ''} - ${rawPath.destStationName || ''})`,duration: rawPath.travelTime || 0,stationNum: rawPath.stopList?.length || 0,transferNum: rawPath.transferNum || 0,stations: stations,nameScope: rawPath.nameScope || '',simpleType: rawPath.simpleType || '0',schedulesList: rawPath.schedulesList || [],totalStations: rawPath.allNum || 0 // 直接使用接口返回的allNum};
};/*** 处理站点数据,统一数据格式* @param {Object} path - 路径数据* @returns {Array} - 处理后的站点数组*/
const processStations = (path) => {const stations = [];if (path.schedulesList && path.schedulesList.length > 0) {// 添加起始站stations.push(createStationData({stationType: '0',stationName: path.originStationName || path.schedulesList[0]?.stationName || '',lineId: path.schedulesList[0]?.lineId || '',stationNum: path.schedulesList[0]?.stationNum || 0}));// 添加中间站/换乘站for (let i = 1; i < path.schedulesList.length; i++) {const station = path.schedulesList[i];const isLastStation = i === path.schedulesList.length - 1;stations.push(createStationData({stationType: isLastStation ? '1' : '2',stationName: isLastStation ? (path.destStationName || station.stationName || '') : (station.stationName || ''),lineId: station.lineId || '',stationNum: station.stationNum || 0}));}} else if (path.originStationName && path.destStationName) {// 只有起终点信息的情况stations.push(createStationData({stationType: '0',stationName: path.originStationName,lineId: path.originLineId || '',stationNum: 0}));stations.push(createStationData({stationType: '1',stationName: path.destStationName,lineId: path.endLineId || '',stationNum: 0}));}return stations;
};/*** 创建标准化的站点数据对象* @param {Object} data - 站点原始数据* @returns {Object} - 标准化的站点数据*/
const createStationData = ({ stationType, stationName, lineId, stationNum }) => ({stationType,stationName,lineName: `${lineId}号线`,stationNum
});// 查询按钮点击处理
const handleQuery = async () => {// ... 验证代码 ...try {const response = await api.findOdPath(params);if (response && Array.isArray(response) && response.length > 0) {// 使用processPathData处理每条路径数据pathResults.value = response.map(processPathData);// ... 选择路径代码 ...}} catch (error) {// ... 错误处理 ...}
};// watch中的数据处理
watch(() => props.selectedPath, (newPath) => {// ... 其他代码 ...if (newPath) {try {// ... 线路和站点处理代码 ...// 使用processPathData处理路径数据pathResults.value = [processPathData(newPath)];selectedPathIndex.value = 0;} catch (error) {// ... 错误处理 ...}}
}, { immediate: true, deep: true });
代码结构对比:
-
修改前:
- 数据处理逻辑分散在多处
- 相同的数据转换逻辑在不同地方重复
- 缺乏统一的数据处理入口
- 字段名称不统一(有些地方用
allNum
,有些地方没有)
-
修改后:
- 引入了集中的数据处理函数
processPathData
- 添加了辅助函数
createStationData
标准化站点数据 - 统一了字段命名(使用
totalStations
) - 添加了详细的函数注释
- 引入了集中的数据处理函数
维护性对比:
-
修改前:
- 修改字段名需要在多处搜索和替换
- 添加新字段需要修改多个地方
- 数据格式转换逻辑分散,难以统一修改
-
修改后:
- 修改字段名只需在
processPathData
函数中更新 - 添加新字段只需在
processPathData
和接口定义中添加 - 数据格式转换集中在专门的函数中,便于统一修改
- 修改字段名只需在
三、"一处修改"原则的实现与验证
为了验证"一处修改"原则的实现效果,可以通过几个常见的变更场景来对比:
场景1:修改字段名
需求:将总站数的字段名从allNum
改为stationTotal
修改前需要的操作:
- 搜索所有使用
allNum
的地方(至少2处:handleQuery和模板) - 修改每个地方的字段名
- 确保没有遗漏任何使用点
修改后只需的操作:
- 在
processPathData
函数中修改:
return {// ...其他字段totalStations: rawPath.stationTotal || 0 // 从allNum改为stationTotal
};
- 所有使用
totalStations
的地方自动使用新的数据来源
场景2:添加新字段
需求:添加一个新字段estimatedArrivalTime
(预计到达时间)
修改前需要的操作:
- 在handleQuery中添加字段
- 在watch处理中添加字段
- 在接口定义中添加字段
修改后只需的操作:
- 在
processPathData
函数中添加:
return {// ...其他字段estimatedArrivalTime: rawPath.estimatedArrivalTime || null
};
- 在
PathResult
接口中添加:
interface PathResult {// ...其他字段estimatedArrivalTime?: Date | null;
}
场景3:修改线路名称格式
需求:将线路名称格式从"X号线"改为"Line X"
修改前需要的操作:
- 搜索所有创建lineName的地方(至少6处)
- 修改每个地方的格式转换逻辑
修改后只需的操作:
- 只需修改
createStationData
函数:
const createStationData = ({ stationType, stationName, lineId, stationNum }) => ({stationType,stationName,lineName: `Line ${lineId}`, // 从"X号线"改为"Line X"stationNum
});
四、优化收益分析
1. 功能正确性提升
- 线路名称显示正确:用户现在能看到正确的线路名称,提高了信息准确性
- 数据一致性:统一使用接口返回的
allNum
值,确保与后端数据一致
2. 代码可维护性提升
- 集中的数据处理:所有数据转换逻辑都集中在几个相关函数中
- 单一修改点:修改字段名或添加新字段只需在一处修改
- 清晰的职责划分:每个函数都有明确的职责和注释
3. 开发效率提升
- 降低维护成本:后续需求变更时,只需修改相应的处理函数
- 减少错误风险:标准化的数据处理减少了手动处理的错误可能性
- 提高代码可读性:函数命名和结构更加清晰,新开发者更容易理解
4. 代码量对比
- 修改前:数据处理逻辑约120行代码,分散在多处
- 修改后:数据处理逻辑约100行代码,集中在专门的函数中
五、潜在风险与影响面
1. 风险点
- 数据依赖性:现在完全依赖接口返回的
allNum
值,如果接口不返回该字段,将显示为0 - 渲染一致性:如果其他组件直接使用
allNum
而非totalStations
,可能导致显示不一致
2. 影响范围
- 直接影响:
station-display.vue
组件中的路径显示 - 间接影响:任何依赖此组件输出数据的父组件
- 用户影响:用户将看到更准确的线路名称和站点信息
六、总结
此次优化通过两个关键改动点解决了显示问题并提高了代码质量:
- 修正线路名称显示:确保连接线显示正确的线路信息,提高了用户体验
- 重构数据处理逻辑:通过引入专门的数据处理函数,实现了"一处修改"原则
通过前后代码对比,可以清晰地看到重构后的代码结构更加清晰、职责划分更加明确,维护成本显著降低。特别是在面对字段变更、数据格式调整等常见需求时,新的代码结构可以大大减少修改点,降低出错风险。
虽然createStationData
等辅助函数看起来很简单,但它们是整个数据处理架构的重要组成部分,为代码的长期维护奠定了基础。这种渐进式的重构方法在不影响现有功能的前提下,有效提升了代码质量,为后续的功能迭代和维护创造了更好的条件。