feat(web): 新增后台线路拓扑图布局编辑器,允许手动拖拽调整所有站点坐标
This commit is contained in:
+63
-18
@@ -74,7 +74,7 @@
|
||||
<span class="nav-icon"><i class="fas fa-credit-card"></i></span> IC卡管理
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'assets'}" @click="currentView = 'assets'">
|
||||
<span class="nav-icon"><i class="fas fa-route"></i></span> 线路图
|
||||
<span class="nav-icon"><i class="fas fa-route"></i></span> 拓扑图布局
|
||||
</div>
|
||||
<div class="nav-item" :class="{active: currentView === 'settings'}" @click="currentView = 'settings'">
|
||||
<span class="nav-icon"><i class="fas fa-cog"></i></span> 系统设置
|
||||
@@ -794,31 +794,76 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentView === 'assets'">
|
||||
<div class="card mb-4">
|
||||
<div v-if="currentView === 'assets'" class="assets-editor-container" style="display: flex; flex-direction: column; height: 80vh;">
|
||||
<div class="card mb-4" style="flex: 1; display: flex; flex-direction: column;">
|
||||
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
||||
<div class="flex" style="align-items: center; gap: 10px;">
|
||||
<h4>线路图</h4>
|
||||
<span class="badge" v-if="assetsManifest.routeMap" style="font-family: monospace;">{{ assetsManifest.routeMap }}</span>
|
||||
<h4>线路拓扑图布局编辑器</h4>
|
||||
<span class="badge" style="background: #2563eb; color: #fff;">拖拽站点以自定义位置</span>
|
||||
</div>
|
||||
<div class="flex" style="gap: 8px;">
|
||||
<label class="btn" style="cursor: pointer;">
|
||||
<i class="fas fa-upload"></i> 上传线路图
|
||||
<input type="file" hidden accept=".png,.jpg,.jpeg,.webp,.svg" @change="uploadRouteMap">
|
||||
</label>
|
||||
<a v-if="assetsRouteMapUrl" :href="assetsRouteMapUrl" target="_blank" class="btn">
|
||||
<i class="fas fa-external-link-alt"></i> 打开
|
||||
</a>
|
||||
<button v-if="assetsManifest.routeMap" class="danger" @click="deleteRouteMap">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
<button class="btn" @click="resetTopologyLayout">
|
||||
<i class="fas fa-undo"></i> 重置布局
|
||||
</button>
|
||||
<button class="btn primary" @click="saveTopologyLayout">
|
||||
<i class="fas fa-save"></i> 保存坐标
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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;" ref="mapImageRef">
|
||||
<div style="flex: 1; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; background: #0f172a; position: relative; user-select: none;"
|
||||
ref="topologyCanvas"
|
||||
@mousedown="onTopologyCanvasMouseDown"
|
||||
@mousemove="onTopologyCanvasMouseMove"
|
||||
@mouseup="onTopologyCanvasMouseUp"
|
||||
@mouseleave="onTopologyCanvasMouseUp"
|
||||
@wheel="onTopologyCanvasWheel">
|
||||
<div :style="{
|
||||
position: 'absolute',
|
||||
left: '0', top: '0',
|
||||
width: '100%', height: '100%',
|
||||
transformOrigin: '0 0',
|
||||
transform: `translate(${topologyPan.x}px, ${topologyPan.y}px) scale(${topologyPan.scale})`
|
||||
}">
|
||||
<!-- Grid Background (Optional) -->
|
||||
<svg width="10000" height="10000" style="position: absolute; left: -5000px; top: -5000px; pointer-events: none;">
|
||||
<defs>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<rect width="40" height="40" fill="none" stroke="rgba(255,255,255,0.05)" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="10000" height="10000" fill="url(#grid)" />
|
||||
<!-- Lines -->
|
||||
<path v-for="l in topologyLines" :key="'path-'+l.id" :d="l.d" fill="none" :stroke="l.color" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" opacity="0.6" />
|
||||
</svg>
|
||||
|
||||
<!-- Stations -->
|
||||
<div v-for="node in topologyNodes" :key="node.canonical"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: node.x + 'px',
|
||||
top: node.y + 'px',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
cursor: 'grab',
|
||||
zIndex: node.canonical === topologyDraggingNode ? 100 : 10
|
||||
}"
|
||||
@mousedown.stop="onTopologyNodeMouseDown($event, node)">
|
||||
|
||||
<div v-if="node.isTransfer" style="width: 24px; height: 24px; border-radius: 50%; background: #fff; border: 3px solid #1f2937; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 6px rgba(0,0,0,0.3);">
|
||||
<div style="width: 10px; height: 10px; border-radius: 50%; background: #cbd5e1;"></div>
|
||||
</div>
|
||||
<div v-else :style="{
|
||||
width: '20px', height: '20px', borderRadius: '50%', background: '#fff',
|
||||
border: `4px solid ${node.color || '#94a3b8'}`,
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
||||
}"></div>
|
||||
|
||||
<div style="position: absolute; top: 26px; left: 50%; transform: translateX(-50%); white-space: nowrap; font-size: 12px; font-weight: 600; color: #f8fafc; text-shadow: 0 1px 3px rgba(0,0,0,0.8); pointer-events: none;">
|
||||
{{ node.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="loading">未上传线路图</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -920,7 +965,7 @@
|
||||
</main>
|
||||
</div>
|
||||
<script src="/custom-dialog.js?v=11"></script>
|
||||
<script src="index.js?v=5"></script>
|
||||
<script src="index.js?v=6"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const isDomain = location.hostname.includes('fse-media.group');
|
||||
|
||||
Reference in New Issue
Block a user