feat(web/route): 新增全局站点管理页面 (Station Management)
1. 在左侧侧边栏新增「站点管理」导航项,并支持在 /route 内无缝切换视图。 2. 实现了全局的站点列表: - 支持显示所有已入库站点的编号、中英文名称。 - 动态计算并展示每个站点目前被关联到了哪些线路(所属线路)。 - 支持通过站点编号或中英文名称进行快速搜索过滤。 3. 提供了独立于线路外的全局新增、编辑和删除站点功能: - 新增:不再强制需要选中线路,直接在库中创建。 - 编辑:复用原有的站点编辑弹窗。 - 删除:可从库中全局删除无用站点。 4. 移动端和电脑端均已应用自适应表格和工具栏布局。
This commit is contained in:
+70
-2
@@ -8,7 +8,7 @@
|
||||
<title>线路规划|FSE铁路运输票务系统</title>
|
||||
<link rel="icon" type="image/png" href="/FSE-ticket.png">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="/style.css?v=17">
|
||||
<link rel="stylesheet" href="/style.css?v=18">
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
@@ -64,6 +64,10 @@
|
||||
@click="currentView = 'management'">
|
||||
<span class="nav-icon"><i class="fas fa-network-wired"></i></span> 线路规划
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'stations'}"
|
||||
@click="currentView = 'stations'">
|
||||
<span class="nav-icon"><i class="fas fa-map-marker-alt"></i></span> 站点管理
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'faremap'}" @click="currentView = 'faremap'">
|
||||
<span class="nav-icon"><i class="fas fa-map"></i></span> 票价地图
|
||||
</div>
|
||||
@@ -392,6 +396,70 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentView === 'stations'" class="card">
|
||||
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 12px;">
|
||||
<h4 style="margin: 0;">站点总览 ({{ stations.length }})</h4>
|
||||
<div class="flex" style="gap: 12px; flex-wrap: wrap;">
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input v-model="stationSearch" placeholder="搜索站点编号/名称..." style="width: 200px;">
|
||||
</div>
|
||||
<button @click="showAddStation = true"><i class="fas fa-plus"></i> 新增站点</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAddStation" class="mb-4" style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px;">
|
||||
<h5 style="margin-top:0; margin-bottom:12px;">创建新站点</h5>
|
||||
<div class="flex" style="flex-wrap: wrap; gap: 12px;">
|
||||
<input v-model="newStation.code" placeholder="站点编号 (必填)" style="flex: 1; min-width: 120px;">
|
||||
<input v-model="newStation.name" placeholder="中文名称 (必填)" style="flex: 1; min-width: 120px;">
|
||||
<input v-model="newStation.en_name" placeholder="英文名称" style="flex: 1; min-width: 120px;">
|
||||
<button @click="addStationToLine" :disabled="!newStation.code || !newStation.name" style="min-width: 80px;">创建</button>
|
||||
<button class="danger" @click="showAddStation = false" style="min-width: 80px;">取消</button>
|
||||
</div>
|
||||
<p class="text-muted mt-2" style="font-size: 0.85rem; margin-bottom: 0;"><i class="fas fa-info-circle"></i> 在此创建的站点将被保存到数据库中,可以在线路规划中添加到任意线路。</p>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="ticket-table" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>站点编号</th>
|
||||
<th>中文名称</th>
|
||||
<th>英文名称</th>
|
||||
<th>所属线路</th>
|
||||
<th>换乘支持</th>
|
||||
<th style="text-align: right;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="s in stationList" :key="s.code">
|
||||
<td class="mono" style="font-weight:bold;">{{ s.code }}</td>
|
||||
<td>{{ s.name || s.cn_name || '-' }}</td>
|
||||
<td>{{ s.en_name || s.enName || '-' }}</td>
|
||||
<td>
|
||||
<div class="flex" style="gap: 4px; flex-wrap: wrap;">
|
||||
<span v-for="l in getStationLines(s.code)" :key="l.id" class="badge" :style="{background: l.color, color: '#fff', border: 'none'}">{{ l.id }}</span>
|
||||
<span v-if="getStationLines(s.code).length === 0" class="text-muted" style="font-size: 0.85em;">暂无线路</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="s.transfer_enabled" class="badge" style="background: #10b981; color: white;">是</span>
|
||||
<span v-else class="text-muted">-</span>
|
||||
</td>
|
||||
<td style="text-align: right;">
|
||||
<button class="sm" @click="editStationGlobal(s.code)" title="编辑"><i class="fas fa-pen"></i></button>
|
||||
<button class="danger sm" @click="deleteStationGlobal(s.code)" title="删除"><i class="fas fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="stationList.length === 0">
|
||||
<td colspan="6" class="text-center text-muted" style="padding: 30px;">无匹配站点</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 票价地图 -->
|
||||
<div v-if="currentView === 'faremap'">
|
||||
<div class="card faremap-card">
|
||||
@@ -608,7 +676,7 @@
|
||||
</div>
|
||||
<script src="/custom-dialog.js?v=11"></script>
|
||||
<script src="/public-status.js?v=14"></script>
|
||||
<script src="ticket-route.js?v=6"></script>
|
||||
<script src="ticket-route.js?v=7"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const isDomain = location.hostname.includes('fse-media.group');
|
||||
|
||||
+39
-8
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user