feat(web/route): 新增全局站点管理页面 (Station Management)

1. 在左侧侧边栏新增「站点管理」导航项,并支持在 /route 内无缝切换视图。
2. 实现了全局的站点列表:
   - 支持显示所有已入库站点的编号、中英文名称。
   - 动态计算并展示每个站点目前被关联到了哪些线路(所属线路)。
   - 支持通过站点编号或中英文名称进行快速搜索过滤。
3. 提供了独立于线路外的全局新增、编辑和删除站点功能:
   - 新增:不再强制需要选中线路,直接在库中创建。
   - 编辑:复用原有的站点编辑弹窗。
   - 删除:可从库中全局删除无用站点。
4. 移动端和电脑端均已应用自适应表格和工具栏布局。
This commit is contained in:
2026-08-07 23:56:40 +08:00
parent 0a37c0cba2
commit 0e3f1a6e86
2 changed files with 109 additions and 10 deletions
+39 -8
View File
@@ -17,8 +17,10 @@ createApp({
const map = {
dashboard: '仪表盘',
management: '线路与票价管理',
stations: '站点管理',
tickets: '车票记录',
settings: '系统设置'
settings: '系统设置',
faremap: '票价地图'
};
return map[currentView.value] || '线路规划系统';
});
@@ -691,7 +693,6 @@ createApp({
};
const addStationToLine = async () => {
if (!selectedLine.value) return;
const { code, name, en_name } = newStation;
if (!code || !name) return alert('请填写编号和名称');
@@ -706,12 +707,18 @@ createApp({
}, { expectOk: true });
}
if (isStationInLine(code)) throw new Error('该站点已在此线路中');
const newStations = [...(selectedLine.value.stations || [])];
newStations.push(code);
await updateLineStations(selectedLine.value.id, newStations, { skipFetchData: true });
if (selectedLine.value) {
if (isStationInLine(code)) throw new Error('该站点已在此线路中');
const newStations = [...(selectedLine.value.stations || [])];
newStations.push(code);
await updateLineStations(selectedLine.value.id, newStations, { skipFetchData: true });
}
Object.assign(newStation, { code: '', name: '', en_name: '' });
if (currentView.value === 'stations') {
showAddStation.value = false;
await fetchData();
}
});
};
@@ -999,7 +1006,30 @@ createApp({
/* 计算 */
const recentLogs = computed(() => logs.value);
const orderList = computed(() => orders.value);
const ticketList = computed(() => {
const stationSearch = ref('');
const stationList = computed(() => {
if (!stationSearch.value) return stations.value;
const q = stationSearch.value.toLowerCase();
return stations.value.filter(s =>
(s.code && s.code.toLowerCase().includes(q)) ||
(s.name && s.name.toLowerCase().includes(q)) ||
(s.en_name && s.en_name.toLowerCase().includes(q))
);
});
const getStationLines = (code) => {
return lines.value.filter(l => l.stations && l.stations.includes(code));
};
const editStationGlobal = (code) => {
stationEditMode.value = true;
openStationModal(code);
// reset mode when modal closes is already handled? Wait, we need to ensure stationEditMode state isn't breaking anything, but openStationModal is generic.
};
const deleteStationGlobal = async (code) => {
await deleteStation(code);
};
if (!ticketSearch.value) return tickets.value.slice(0, 50);
const q = ticketSearch.value.toLowerCase();
return tickets.value.filter(t =>
@@ -1063,6 +1093,7 @@ createApp({
showStationModal, stationForm, stationFormOriginalCode, transferTargets, saveStationSettings, closeStationModal,
showLineModal, lineForm, openLineModal, saveLineSettings, closeLineModal,
stationSearch, stationList, getStationLines, editStationGlobal, deleteStationGlobal,
/* 订单 */
fetchOrders, deleteOrder,
showTicketModal, selectedTicket, viewTicketDetails, closeTicketModal, formatTicketStatus, formatTicketEvent, formatTicketEventLocation, formatTicketEventUnified, formatLogType, formatTrainType,