feat(web): 订票地图升级为静态SVG并叠加动态坐标交互;完善断点支持
This commit is contained in:
+92
-27
@@ -7,7 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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="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="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
@@ -238,6 +238,9 @@
|
||||
<button @click="addStationToLine"
|
||||
:disabled="!newStation.code || !newStation.name"><i class="fas fa-plus"></i>
|
||||
添加</button>
|
||||
<button @click="addBreakpointToLine" class="btn" style="margin-left: 8px;" title="在末尾插入一个线路断点">
|
||||
<i class="fas fa-cut"></i> 插入断点
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -261,37 +264,45 @@
|
||||
</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"
|
||||
@mousemove="onStationDragOver(index)" @click="handleStationClick(sCode)"
|
||||
@mousemove="onStationDragOver(index)" @click="handleStationClick(sCode, index)"
|
||||
class="station-node" :class="{
|
||||
'selected': isStationSelected(sCode),
|
||||
'fare-source': fareSelection[0] === 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 === '-'">
|
||||
<rect :x="50 + index * 120 - 10" :y="90" width="20" height="20" fill="var(--bg)" stroke="var(--danger)" stroke-dasharray="2,2" />
|
||||
<text :x="50 + index * 120" y="105" text-anchor="middle" fill="var(--danger)" font-size="12" style="pointer-events: none;">✂</text>
|
||||
<text :x="50 + index * 120" y="135" text-anchor="middle" fill="var(--danger)" font-size="10" style="pointer-events: none;">断点</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!--车站节点图形-->
|
||||
<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" />
|
||||
|
||||
<!--节点标签-->
|
||||
<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>
|
||||
<!--节点标签-->
|
||||
<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>
|
||||
|
||||
<!--删除-->
|
||||
<title>{{ getStationName(sCode) }} ({{ sCode }}){{ getTransferTitleSuffix(sCode) }}</title>
|
||||
<!--删除-->
|
||||
<title>{{ getStationName(sCode) }} ({{ sCode }}){{ getTransferTitleSuffix(sCode) }}</title>
|
||||
</template>
|
||||
</g>
|
||||
</svg>
|
||||
<div v-else class="empty-state">
|
||||
@@ -793,8 +804,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="assetsRouteMapUrl" style="border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: #0b0b0f;">
|
||||
<img :src="assetsRouteMapUrl" alt="线路图" style="display:block; width: 100%; height: auto;">
|
||||
<div v-if="assetsRouteMapUrl" style="border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: #0b0b0f; padding: 16px;">
|
||||
|
||||
<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 v-else class="loading">未上传线路图</div>
|
||||
</div>
|
||||
@@ -897,8 +962,8 @@
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/custom-dialog.js?v=12"></script>
|
||||
<script src="index.js?v=6"></script>
|
||||
<script src="/custom-dialog.js?v=11"></script>
|
||||
<script src="index.js?v=3"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const isDomain = location.hostname.includes('fse-media.group');
|
||||
|
||||
Reference in New Issue
Block a user