feat(web): 订票地图升级为静态SVG并叠加动态坐标交互;完善断点支持
This commit is contained in:
BIN
Binary file not shown.
@@ -49,7 +49,7 @@ const buildRouteGraph = (stations, lines, fares, cfg) => {
|
|||||||
for (let i = 0; i < stops.length - 1; i++) {
|
for (let i = 0; i < stops.length - 1; i++) {
|
||||||
const a = String(stops[i] || '').trim();
|
const a = String(stops[i] || '').trim();
|
||||||
const b = String(stops[i + 1] || '').trim();
|
const b = String(stops[i + 1] || '').trim();
|
||||||
if (!a || !b || a === b) continue;
|
if (!a || !b || a === b || a === '-' || b === '-') continue;
|
||||||
const fare = fareBySegment.get(segKey(a, b));
|
const fare = fareBySegment.get(segKey(a, b));
|
||||||
if (!fare) continue;
|
if (!fare) continue;
|
||||||
addEdge(a, b, {
|
addEdge(a, b, {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"style.css": {
|
"style.css": {
|
||||||
"size": 77276,
|
"size": 86271,
|
||||||
"mtimeMs": 1781972131010.4126
|
"mtimeMs": 1783775484985.5583
|
||||||
},
|
},
|
||||||
"blog.css": {
|
"blog.css": {
|
||||||
"size": 994,
|
"size": 994,
|
||||||
@@ -12,40 +12,40 @@
|
|||||||
"mtimeMs": 1781967608378.205
|
"mtimeMs": 1781967608378.205
|
||||||
},
|
},
|
||||||
"public-status.js": {
|
"public-status.js": {
|
||||||
"size": 3814,
|
"size": 2782,
|
||||||
"mtimeMs": 1781967627973.2036
|
"mtimeMs": 1783602619602.658
|
||||||
},
|
},
|
||||||
"ticket-order.js": {
|
"ticket-order.js": {
|
||||||
"size": 23614,
|
"size": 25105,
|
||||||
"mtimeMs": 1781974289008.8884
|
"mtimeMs": 1783775559758.2654
|
||||||
},
|
},
|
||||||
"ticket-search.js": {
|
"ticket-search.js": {
|
||||||
"size": 11647,
|
"size": 17269,
|
||||||
"mtimeMs": 1781966517813.3787
|
"mtimeMs": 1783666241741.0564
|
||||||
},
|
},
|
||||||
"ticket-route.js": {
|
"ticket-route.js": {
|
||||||
"size": 42284,
|
"size": 44004,
|
||||||
"mtimeMs": 1781967610560.6072
|
"mtimeMs": 1783666241687.1482
|
||||||
},
|
},
|
||||||
"index.js": {
|
"index.js": {
|
||||||
"size": 63063,
|
"size": 82021,
|
||||||
"mtimeMs": 1781967609550.651
|
"mtimeMs": 1783775091220.4558
|
||||||
},
|
},
|
||||||
"ic-card-admin.js": {
|
"ic-card-admin.js": {
|
||||||
"size": 15188,
|
"size": 19095,
|
||||||
"mtimeMs": 1781967608706.734
|
"mtimeMs": 1783666241303.3374
|
||||||
},
|
},
|
||||||
"ic-card-order.js": {
|
"ic-card-order.js": {
|
||||||
"size": 11528,
|
"size": 11488,
|
||||||
"mtimeMs": 1781967363353.6257
|
"mtimeMs": 1783234306158.3623
|
||||||
},
|
},
|
||||||
"ic-card-detail.js": {
|
"ic-card-detail.js": {
|
||||||
"size": 5322,
|
"size": 5322,
|
||||||
"mtimeMs": 1781967346854.6172
|
"mtimeMs": 1781967346854.6172
|
||||||
},
|
},
|
||||||
"ic-card-search.js": {
|
"ic-card-search.js": {
|
||||||
"size": 5462,
|
"size": 14392,
|
||||||
"mtimeMs": 1781933937174.1592
|
"mtimeMs": 1783666241398.5178
|
||||||
},
|
},
|
||||||
"token.js": {
|
"token.js": {
|
||||||
"size": 4064,
|
"size": 4064,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"style.css": 12,
|
"style.css": 13,
|
||||||
"blog.css": 2,
|
"blog.css": 2,
|
||||||
"custom-dialog.js": 11,
|
"custom-dialog.js": 11,
|
||||||
"public-status.js": 13,
|
"public-status.js": 14,
|
||||||
"ticket-order.js": 16,
|
"ticket-order.js": 17,
|
||||||
"ticket-search.js": 11,
|
"ticket-search.js": 12,
|
||||||
"ticket-route.js": 2,
|
"ticket-route.js": 3,
|
||||||
"index.js": 2,
|
"index.js": 3,
|
||||||
"ic-card-admin.js": 2,
|
"ic-card-admin.js": 3,
|
||||||
"ic-card-order.js": 2,
|
"ic-card-order.js": 3,
|
||||||
"ic-card-detail.js": 2,
|
"ic-card-detail.js": 2,
|
||||||
"ic-card-search.js": 2,
|
"ic-card-search.js": 3,
|
||||||
"token.js": 2,
|
"token.js": 2,
|
||||||
"login.js": 2,
|
"login.js": 2,
|
||||||
"blog.js": 2
|
"blog.js": 2
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@
|
|||||||
<span class="version">v1.0.12</span>
|
<span class="version">v1.0.12</span>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="blog.js?v=2"></script>
|
<script src="blog.js?v=2"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -181,8 +181,8 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>
|
<script>
|
||||||
const loadPublicAssets = async () => {
|
const loadPublicAssets = async () => {
|
||||||
const fareMapBox = document.getElementById('fareMapBox');
|
const fareMapBox = document.getElementById('fareMapBox');
|
||||||
|
|||||||
@@ -172,9 +172,9 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script src="/ic-card-admin.js?v=2"></script>
|
<script src="/ic-card-admin.js?v=3"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group');
|
const isDomain = location.hostname.includes('fse-media.group');
|
||||||
|
|||||||
@@ -99,9 +99,9 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/ic-card-detail.js?v=2"></script>
|
<script src="/ic-card-detail.js?v=2"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>document.addEventListener('DOMContentLoaded', () => {
|
<script>document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group'); const links = {
|
const isDomain = location.hostname.includes('fse-media.group'); const links = {
|
||||||
home: isDomain ? 'https://ticket.fse-media.group' : '/home.html', order: isDomain ? 'https://ticket.fse-media.group/order' : '/ticket-order.html', search: isDomain ? 'https://ticket.fse-media.group/search' : '/ticket-search.html', 'card-search': isDomain ? 'https://ticket.fse-media.group/ic-card/search' : '/ic-card-search.html', 'card-order': isDomain ? 'https://ticket.fse-media.group/ic-card/order' : '/ic-card-order.html'
|
home: isDomain ? 'https://ticket.fse-media.group' : '/home.html', order: isDomain ? 'https://ticket.fse-media.group/order' : '/ticket-order.html', search: isDomain ? 'https://ticket.fse-media.group/search' : '/ticket-search.html', 'card-search': isDomain ? 'https://ticket.fse-media.group/ic-card/search' : '/ic-card-search.html', 'card-order': isDomain ? 'https://ticket.fse-media.group/ic-card/order' : '/ic-card-order.html'
|
||||||
|
|||||||
@@ -105,9 +105,9 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/ic-card-order.js?v=2"></script>
|
<script src="/ic-card-order.js?v=3"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group');
|
const isDomain = location.hostname.includes('fse-media.group');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<title>IC卡查询|FSE铁路运输票务系统</title>
|
<title>IC卡查询|FSE铁路运输票务系统</title>
|
||||||
<link rel="icon" type="image/png" href="/FSE-ticket.png">
|
<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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<link rel="stylesheet" href="/style.css?v=14">
|
<link rel="stylesheet" href="/style.css?v=13">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="public-search jr-public-page">
|
<body class="public-search jr-public-page">
|
||||||
@@ -109,9 +109,9 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/ic-card-search.js?v=3"></script>
|
<script src="/ic-card-search.js?v=3"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group');
|
const isDomain = location.hostname.includes('fse-media.group');
|
||||||
|
|||||||
+92
-27
@@ -7,7 +7,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>控制台|FSE铁路运输票务系统</title>
|
<title>控制台|FSE铁路运输票务系统</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<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=14">
|
<link rel="stylesheet" href="style.css?v=13">
|
||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -238,6 +238,9 @@
|
|||||||
<button @click="addStationToLine"
|
<button @click="addStationToLine"
|
||||||
:disabled="!newStation.code || !newStation.name"><i class="fas fa-plus"></i>
|
:disabled="!newStation.code || !newStation.name"><i class="fas fa-plus"></i>
|
||||||
添加</button>
|
添加</button>
|
||||||
|
<button @click="addBreakpointToLine" class="btn" style="margin-left: 8px;" title="在末尾插入一个线路断点">
|
||||||
|
<i class="fas fa-cut"></i> 插入断点
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -261,37 +264,45 @@
|
|||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!--车站节点-->
|
<!--车站节点-->
|
||||||
<g v-for="(sCode, index) in selectedLine.stations" :key="sCode"
|
<g v-for="(sCode, index) in selectedLine.stations" :key="index"
|
||||||
@mousedown="onStationDragStart(index)" @mouseup="onStationDrop"
|
@mousedown="onStationDragStart(index)" @mouseup="onStationDrop"
|
||||||
@mousemove="onStationDragOver(index)" @click="handleStationClick(sCode)"
|
@mousemove="onStationDragOver(index)" @click="handleStationClick(sCode, index)"
|
||||||
class="station-node" :class="{
|
class="station-node" :class="{
|
||||||
'selected': isStationSelected(sCode),
|
'selected': isStationSelected(sCode),
|
||||||
'fare-source': fareSelection[0] === sCode,
|
'fare-source': fareSelection[0] === sCode,
|
||||||
'fare-target': fareSelection[1] === sCode
|
'fare-target': fareSelection[1] === sCode
|
||||||
}">
|
}">
|
||||||
<!--车站节点图形-->
|
|
||||||
<circle :cx="50 + index * 120" cy="100" r="14" fill="var(--bg)"
|
|
||||||
:stroke="selectedLine.color" stroke-width="3" />
|
|
||||||
<circle v-if="isStationSelected(sCode)" :cx="50 + index * 120" cy="100" r="8"
|
|
||||||
:fill="selectedLine.color" />
|
|
||||||
|
|
||||||
<!--节点标签-->
|
<template v-if="sCode === '-'">
|
||||||
<text :x="50 + index * 120" y="70" text-anchor="middle" fill="var(--text)"
|
<rect :x="50 + index * 120 - 10" :y="90" width="20" height="20" fill="var(--bg)" stroke="var(--danger)" stroke-dasharray="2,2" />
|
||||||
font-weight="bold" font-size="12" style="pointer-events: none;">{{
|
<text :x="50 + index * 120" y="105" text-anchor="middle" fill="var(--danger)" font-size="12" style="pointer-events: none;">✂</text>
|
||||||
getStationName(sCode) }}</text>
|
<text :x="50 + index * 120" y="135" text-anchor="middle" fill="var(--danger)" font-size="10" style="pointer-events: none;">断点</text>
|
||||||
<text :x="50 + index * 120" y="135" text-anchor="middle" fill="var(--muted)"
|
</template>
|
||||||
font-size="10" style="pointer-events: none;">{{ sCode }}</text>
|
<template v-else>
|
||||||
<g v-if="getTransferLineBadges(sCode).length > 0">
|
<!--车站节点图形-->
|
||||||
<g v-for="(li, liIdx) in getTransferLineBadges(sCode)" :key="`${sCode}-xfer-${li.id}`">
|
<circle :cx="50 + index * 120" cy="100" r="14" fill="var(--bg)"
|
||||||
<circle :cx="(50 + index * 120) + (liIdx - (getTransferLineBadges(sCode).length - 1) / 2) * 14" cy="150" r="5"
|
:stroke="selectedLine.color" stroke-width="3" />
|
||||||
:fill="li.color" stroke="#ffffff" stroke-width="1" />
|
<circle v-if="isStationSelected(sCode)" :cx="50 + index * 120" cy="100" r="8"
|
||||||
<text :x="(50 + index * 120) + (liIdx - (getTransferLineBadges(sCode).length - 1) / 2) * 14" y="165" text-anchor="middle"
|
:fill="selectedLine.color" />
|
||||||
fill="var(--muted)" font-size="7" style="pointer-events: none;">{{ li.id }}</text>
|
|
||||||
|
<!--节点标签-->
|
||||||
|
<text :x="50 + index * 120" y="70" text-anchor="middle" fill="var(--text)"
|
||||||
|
font-weight="bold" font-size="12" style="pointer-events: none;">{{
|
||||||
|
getStationName(sCode) }}</text>
|
||||||
|
<text :x="50 + index * 120" y="135" text-anchor="middle" fill="var(--muted)"
|
||||||
|
font-size="10" style="pointer-events: none;">{{ sCode }}</text>
|
||||||
|
<g v-if="getTransferLineBadges(sCode).length > 0">
|
||||||
|
<g v-for="(li, liIdx) in getTransferLineBadges(sCode)" :key="`${sCode}-xfer-${li.id}`">
|
||||||
|
<circle :cx="(50 + index * 120) + (liIdx - (getTransferLineBadges(sCode).length - 1) / 2) * 14" cy="150" r="5"
|
||||||
|
:fill="li.color" stroke="#ffffff" stroke-width="1" />
|
||||||
|
<text :x="(50 + index * 120) + (liIdx - (getTransferLineBadges(sCode).length - 1) / 2) * 14" y="165" text-anchor="middle"
|
||||||
|
fill="var(--muted)" font-size="7" style="pointer-events: none;">{{ li.id }}</text>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
|
||||||
|
|
||||||
<!--删除-->
|
<!--删除-->
|
||||||
<title>{{ getStationName(sCode) }} ({{ sCode }}){{ getTransferTitleSuffix(sCode) }}</title>
|
<title>{{ getStationName(sCode) }} ({{ sCode }}){{ getTransferTitleSuffix(sCode) }}</title>
|
||||||
|
</template>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div v-else class="empty-state">
|
<div v-else class="empty-state">
|
||||||
@@ -793,8 +804,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="assetsRouteMapUrl" style="border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: #0b0b0f;">
|
<div v-if="assetsRouteMapUrl" style="border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: #0b0b0f; padding: 16px;">
|
||||||
<img :src="assetsRouteMapUrl" alt="线路图" style="display:block; width: 100%; height: auto;">
|
|
||||||
|
<div class="flex mb-4" style="gap: 10px; align-items: center; background: rgba(255,255,255,0.05); padding: 12px; border-radius: 8px;">
|
||||||
|
<label class="switch-label">
|
||||||
|
<input type="checkbox" v-model="mapBindingMode">
|
||||||
|
<span class="slider"></span>
|
||||||
|
<span class="label-text">开启坐标绑定模式</span>
|
||||||
|
</label>
|
||||||
|
<select v-if="mapBindingMode" v-model="mapBindingStation" style="width: 240px;">
|
||||||
|
<option value="">-- 选择要绑定的站点 --</option>
|
||||||
|
<option v-for="s in stations" :key="s.code" :value="s.code">{{ s.name }} ({{ s.code }})</option>
|
||||||
|
</select>
|
||||||
|
<div v-if="mapBindingMode" class="text-muted" style="font-size: 0.9em;">
|
||||||
|
在左侧选择站点后,点击下方图片中的位置即可绑定。绑定的坐标将用于网页订票展示。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="position: relative; display: inline-block; width: 100%;" :style="{ cursor: mapBindingMode ? 'crosshair' : 'default' }" @click="onMapImageClick">
|
||||||
|
<img :src="assetsRouteMapUrl" alt="线路图" style="display:block; width: 100%; height: auto;" ref="mapImageRef">
|
||||||
|
|
||||||
|
<!-- 坐标标记点 -->
|
||||||
|
<template v-for="s in stations" :key="'marker-'+s.code">
|
||||||
|
<div v-if="s.svg_x != null && s.svg_y != null"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: s.svg_x + '%',
|
||||||
|
top: s.svg_y + '%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: s.code === mapBindingStation ? '16px' : '10px',
|
||||||
|
height: s.code === mapBindingStation ? '16px' : '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: s.code === mapBindingStation ? 'var(--primary)' : 'rgba(255,50,50,0.7)',
|
||||||
|
border: '2px solid white',
|
||||||
|
boxShadow: '0 0 4px rgba(0,0,0,0.5)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
transition: 'all 0.2s ease'
|
||||||
|
}">
|
||||||
|
</div>
|
||||||
|
<div v-if="mapBindingMode && s.svg_x != null && s.svg_y != null"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: s.svg_x + '%',
|
||||||
|
top: `calc(${s.svg_y}% + 12px)`,
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
color: 'white',
|
||||||
|
background: 'rgba(0,0,0,0.7)',
|
||||||
|
padding: '2px 4px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '10px',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}">
|
||||||
|
{{ s.name }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="loading">未上传线路图</div>
|
<div v-else class="loading">未上传线路图</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -897,8 +962,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="index.js?v=6"></script>
|
<script src="index.js?v=3"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group');
|
const isDomain = location.hostname.includes('fse-media.group');
|
||||||
|
|||||||
+66
-2
@@ -1139,6 +1139,43 @@ createApp({
|
|||||||
const zoomFareMapOut = () => { fareMapScale.value = Math.max(0.3, Math.round((fareMapScale.value - 0.1) * 100) / 100); };
|
const zoomFareMapOut = () => { fareMapScale.value = Math.max(0.3, Math.round((fareMapScale.value - 0.1) * 100) / 100); };
|
||||||
const zoomFareMapReset = () => { fareMapScale.value = 1; };
|
const zoomFareMapReset = () => { fareMapScale.value = 1; };
|
||||||
|
|
||||||
|
const mapBindingMode = ref(false);
|
||||||
|
const mapBindingStation = ref('');
|
||||||
|
const mapImageRef = ref(null);
|
||||||
|
|
||||||
|
const onMapImageClick = async (e) => {
|
||||||
|
if (!mapBindingMode.value || !mapBindingStation.value) return;
|
||||||
|
const img = mapImageRef.value;
|
||||||
|
if (!img) return;
|
||||||
|
|
||||||
|
const rect = img.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
const percentX = (x / rect.width) * 100;
|
||||||
|
const percentY = (y / rect.height) * 100;
|
||||||
|
|
||||||
|
const station = stations.value.find(s => s.code === mapBindingStation.value);
|
||||||
|
if (!station) return;
|
||||||
|
|
||||||
|
station.svg_x = Number(percentX.toFixed(2));
|
||||||
|
station.svg_y = Number(percentY.toFixed(2));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await requestJson(`/api/stations/${encodeURIComponent(station.code)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(station)
|
||||||
|
}, { expectOk: true });
|
||||||
|
// Optional: find the next station without coordinates and select it
|
||||||
|
const nextStation = stations.value.find(s => s.svg_x == null && s.svg_y == null);
|
||||||
|
if (nextStation) {
|
||||||
|
mapBindingStation.value = nextStation.code;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('保存坐标失败:' + err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// --- Management Actions ---
|
// --- Management Actions ---
|
||||||
|
|
||||||
const createStation = async () => {
|
const createStation = async () => {
|
||||||
@@ -1229,6 +1266,22 @@ createApp({
|
|||||||
await updateLineStations(selectedLine.value.id, newStations);
|
await updateLineStations(selectedLine.value.id, newStations);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeStationFromLineIndex = async (index) => {
|
||||||
|
if (!selectedLine.value) return;
|
||||||
|
const newStations = [...(selectedLine.value.stations || [])];
|
||||||
|
newStations.splice(index, 1);
|
||||||
|
await updateLineStations(selectedLine.value.id, newStations);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addBreakpointToLine = async () => {
|
||||||
|
if (!selectedLine.value) return;
|
||||||
|
await runMutation(async () => {
|
||||||
|
const newStations = [...(selectedLine.value.stations || [])];
|
||||||
|
newStations.push('-');
|
||||||
|
await updateLineStations(selectedLine.value.id, newStations, { skipFetchData: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const updateLineStations = async (lineId, stationsList, { skipFetchData } = {}) => {
|
const updateLineStations = async (lineId, stationsList, { skipFetchData } = {}) => {
|
||||||
// Need to update the whole line object usually, or a specific endpoint
|
// Need to update the whole line object usually, or a specific endpoint
|
||||||
// Assuming PUT /api/lines/:id updates fields provided
|
// Assuming PUT /api/lines/:id updates fields provided
|
||||||
@@ -1336,11 +1389,21 @@ createApp({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStationClick = async (code) => {
|
const handleStationClick = async (code, index) => {
|
||||||
if (lineViewportPan.moved) {
|
if (lineViewportPan.moved) {
|
||||||
lineViewportPan.moved = false;
|
lineViewportPan.moved = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (code === '-') {
|
||||||
|
if (await appDialog.confirm({
|
||||||
|
title: '移除断点',
|
||||||
|
message: `确定移除该断点?`,
|
||||||
|
confirmText: '确认移除'
|
||||||
|
})) {
|
||||||
|
await removeStationFromLineIndex(index);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (stationEditMode.value) {
|
if (stationEditMode.value) {
|
||||||
openStationModal(code);
|
openStationModal(code);
|
||||||
return;
|
return;
|
||||||
@@ -1730,12 +1793,13 @@ createApp({
|
|||||||
showAddLine, showAddStation, newLine, newStation, fareMapSvg, ticketSearch,
|
showAddLine, showAddStation, newLine, newStation, fareMapSvg, ticketSearch,
|
||||||
assetsManifest, assetsFarePreview, assetsRouteMapUrl, assetsFareTableUrl,
|
assetsManifest, assetsFarePreview, assetsRouteMapUrl, assetsFareTableUrl,
|
||||||
uploadRouteMap, uploadFareTable, deleteRouteMap, deleteFareTable,
|
uploadRouteMap, uploadFareTable, deleteRouteMap, deleteFareTable,
|
||||||
|
mapBindingMode, mapBindingStation, mapImageRef, onMapImageClick,
|
||||||
|
|
||||||
// Management
|
// Management
|
||||||
selectedLine, fareMode, stationEditMode, fareSelection, showFareModal, currentFare, availableStations,
|
selectedLine, fareMode, stationEditMode, fareSelection, showFareModal, currentFare, availableStations,
|
||||||
visualLineViewport, lineViewportPan, lineEditorSvgWidth, startLineViewportPan, moveLineViewportPan,
|
visualLineViewport, lineViewportPan, lineEditorSvgWidth, startLineViewportPan, moveLineViewportPan,
|
||||||
selectLine, createLine, deleteLine, deleteStation, updateLineInfo, getFareText,
|
selectLine, createLine, deleteLine, deleteStation, updateLineInfo, getFareText,
|
||||||
isStationInLine, addStationToLine, removeStationFromLine,
|
isStationInLine, addStationToLine, removeStationFromLine, removeStationFromLineIndex, addBreakpointToLine,
|
||||||
handleStationClick, isStationSelected,
|
handleStationClick, isStationSelected,
|
||||||
onStationDragStart, onStationDragOver, onStationDrop, draggingStationIndex,
|
onStationDragStart, onStationDragOver, onStationDrop, draggingStationIndex,
|
||||||
showStationModal, stationForm, stationFormOriginalCode, transferTargets, saveStationSettings, closeStationModal,
|
showStationModal, stationForm, stationFormOriginalCode, transferTargets, saveStationSettings, closeStationModal,
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="login.js?v=2"></script>
|
<script src="login.js?v=2"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<title>票务查询|FSE铁路运输票务系统</title>
|
<title>票务查询|FSE铁路运输票务系统</title>
|
||||||
<link rel="icon" type="image/png" href="/FSE-ticket.png">
|
<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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<link rel="stylesheet" href="/style.css?v=14" />
|
<link rel="stylesheet" href="/style.css?v=13" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="public-search jr-public-page">
|
<body class="public-search jr-public-page">
|
||||||
@@ -120,9 +120,9 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=11"></script>
|
||||||
<script src="/ticket-search.js?v=12"></script>
|
<script src="/ticket-search.js?v=12"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=14"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const isDomain = location.hostname.includes('fse-media.group');
|
const isDomain = location.hostname.includes('fse-media.group');
|
||||||
|
|||||||
@@ -4485,3 +4485,41 @@ body.jr-admin-login-page {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
| ||||||