987 lines
64 KiB
HTML
987 lines
64 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<!-- 充满未知和不稳定的票务系统! -->
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<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">
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
<script src="/socket.io/socket.io.js"></script>
|
|
</head>
|
|
|
|
<!--侧边栏-->
|
|
|
|
<body class="jr-admin-page jr-public-page">
|
|
<div class="jr-public-shell">
|
|
<header class="jr-topbar">
|
|
<div class="jr-topbar-inner">
|
|
<a href="https://ticket.fse-media.group" class="jr-top-link" id="adminTopLink">
|
|
<i class="fas fa-train"></i>
|
|
<span>FSE铁路票务系统控制台</span>
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="jr-brandbar">
|
|
<div class="jr-brandbar-inner">
|
|
<a href="https://ticket.fse-media.group" class="jr-brand" id="adminBrandLink">
|
|
<img src="/FSE-ticket.png" alt="FSE Railway" class="jr-brand-logo" />
|
|
<div class="jr-brand-copy">
|
|
<strong>FarSight-T.N.E铁路运输</strong>
|
|
<span>控制台</span>
|
|
</div>
|
|
</a>
|
|
<nav class="jr-nav" aria-label="站点导航">
|
|
<a href="https://ticket.fse-media.group/home.html" data-link="home">首页</a>
|
|
<a href="https://ticket.fse-media.group/order" data-link="order">线上预定</a>
|
|
<a href="https://ticket.fse-media.group/search" data-link="search">车票查询</a>
|
|
<a href="https://ticket.fse-media.group/ic-card/search" data-link="card-search">IC 卡查询</a>
|
|
<a href="https://ticket.fse-media.group/ic-card/order" data-link="card-order">线上购卡</a>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<main class="jr-public-main jr-admin-main-shell">
|
|
<div id="app" class="jr-admin-app">
|
|
<div class="sidebar" :class="{ open: sidebarOpen }">
|
|
<div class="jr-admin-sidebar-head">
|
|
<span class="jr-kicker">OPERATIONS CONSOLE</span>
|
|
<div class="brand">FSE铁路票务系统控制台</div>
|
|
<p class="jr-admin-sidebar-copy">集中处理票务、线路、资源和运营日志。</p>
|
|
</div>
|
|
<div class="nav">
|
|
<a href="https://ticket.fse-media.group" id="homeLink" class="nav-item" style="text-decoration: none;">
|
|
<span class="nav-icon"><i class="fas fa-home"></i></span> 返回首页
|
|
</a>
|
|
<div class="nav-item" :class="{active: currentView === 'dashboard'}" @click="currentView = 'dashboard'">
|
|
<span class="nav-icon"><i class="fas fa-chart-pie"></i></span> 仪表盘 </div>
|
|
<div class="nav-item" :class="{active: currentView === 'management'}"
|
|
@click="currentView = 'management'">
|
|
<span class="nav-icon"><i class="fas fa-network-wired"></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>
|
|
<div class="nav-item" :class="{active: currentView === 'tickets'}" @click="currentView = 'tickets'">
|
|
<span class="nav-icon"><i class="fas fa-ticket-alt"></i></span> 车票记录
|
|
</div>
|
|
<div class="nav-item" :class="{active: currentView === 'vouchers'}" @click="currentView = 'vouchers'">
|
|
<span class="nav-icon"><i class="fas fa-receipt"></i></span> 凭证记录
|
|
</div>
|
|
<div class="nav-item" :class="{active: currentView === 'iccards'}" @click="currentView = 'iccards'">
|
|
<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> 线路图上传
|
|
</div>
|
|
<div class="nav-item" :class="{active: currentView === 'settings'}" @click="currentView = 'settings'">
|
|
<span class="nav-icon"><i class="fas fa-cog"></i></span> 系统设置
|
|
</div>
|
|
<div class="nav-item" :class="{active: currentView === 'logs'}" @click="currentView = 'logs'">
|
|
<span class="nav-icon"><i class="fas fa-list"></i></span> 日志
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="sidebarOpen" class="sidebar-overlay" @click="sidebarOpen = false"></div>
|
|
|
|
<div class="main">
|
|
<div class="header">
|
|
<div class="jr-admin-header-copy">
|
|
<div class="flex" style="gap: 12px;">
|
|
<button class="icon-btn mobile-only" @click="sidebarOpen = !sidebarOpen" title="菜单"><i class="fas fa-bars"></i></button>
|
|
<div>
|
|
<span class="jr-kicker">OPERATIONS CONSOLE</span>
|
|
<h3 style="margin: 0;">{{ viewTitle }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="jr-admin-header-side">
|
|
<div class="jr-admin-sync-meta">
|
|
<span class="jr-admin-sync-label">当前模块</span>
|
|
<strong>{{ viewTitle }}</strong>
|
|
</div>
|
|
<div class="jr-admin-sync-meta">
|
|
<span class="jr-admin-sync-label">最近同步</span>
|
|
<strong>{{ lastSyncText }}</strong>
|
|
</div>
|
|
<button class="btn primary" @click="refreshData" :disabled="isViewBusy">
|
|
<i class="fas" :class="isViewBusy ? 'fa-spinner fa-spin' : 'fa-rotate-right'"></i>
|
|
{{ isViewBusy ? '同步中' : '刷新视图' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
<section class="jr-page-intro jr-admin-intro">
|
|
<span class="jr-kicker">OPERATIONS CONSOLE</span>
|
|
<h1>铁路票务后台控制台</h1>
|
|
</section>
|
|
|
|
<!-- 仪表盘-->
|
|
<div v-if="currentView === 'dashboard'">
|
|
<div class="grid">
|
|
<div class="card">
|
|
<div class="stat-label">今日售票额</div>
|
|
<div class="stat-value">{{ stats.sold_tickets || 0 }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">站点数</div>
|
|
<div class="stat-value">{{ stations.length }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">运营线路</div>
|
|
<div class="stat-value">{{ lines.length }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 线路与票价管理 -->
|
|
<div v-if="currentView === 'management'" class="management-container">
|
|
<div class="management-sidebar">
|
|
<div class="card"
|
|
style="height: 100%; display: flex; flex-direction: column; margin-bottom: 0;">
|
|
<div class="flex between mb-4">
|
|
<h4>线路列表</h4>
|
|
<button @click="showAddLine = true" title="新建线路"><i class="fas fa-plus"></i></button>
|
|
</div>
|
|
|
|
<!--添加车站-->
|
|
<div v-if="showAddLine" class="mb-4"
|
|
style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 8px;">
|
|
<input v-model="newLine.id" placeholder="线路编号 (如 L1)"
|
|
style="margin-bottom: 8px; width: 100%;">
|
|
<input v-model="newLine.name" placeholder="中文名称"
|
|
style="margin-bottom: 8px; width: 100%;">
|
|
<input v-model="newLine.en_name" placeholder="英文名称"
|
|
style="margin-bottom: 8px; width: 100%;">
|
|
<div class="flex">
|
|
<input type="text" v-model="newLine.color" placeholder="#HEX颜色" style="flex: 1;">
|
|
<input type="color" v-model="newLine.color" title="选择颜色"
|
|
style="width: 40px; padding: 0; border: none; height: 32px;">
|
|
<button @click="createLine" style="padding: 0 12px;" title="确认创建线路"><i
|
|
class="fas fa-check"></i></button>
|
|
<button class="danger" @click="showAddLine = false" title="取消"
|
|
style="padding: 0 12px;"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="list-lines" style="flex: 1; overflow-y: auto;">
|
|
<div v-for="l in lines" :key="l.id" class="line-item"
|
|
:class="{active: selectedLine && selectedLine.id === l.id}" @click="selectLine(l)">
|
|
<div class="line-color-dot" :style="{background: l.color}"></div>
|
|
<div class="line-info">
|
|
<div class="line-name">{{ l.name || l.id }}</div>
|
|
<div class="line-meta">{{ (l.stations || []).length }} 站</div>
|
|
</div>
|
|
<div class="line-actions" v-if="selectedLine && selectedLine.id === l.id">
|
|
<button class="danger sm" @click.stop="deleteLine(l.id)"><i class="fas fa-trash"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!--右侧面板-->
|
|
<div class="management-main">
|
|
<div class="card mb-4">
|
|
<div class="flex between">
|
|
<div v-if="selectedLine">
|
|
<div class="flex">
|
|
<h3 :style="{color: selectedLine.color}">{{ selectedLine.name || selectedLine.id
|
|
}}</h3>
|
|
<span class="badge">{{ selectedLine.id }}</span>
|
|
</div>
|
|
<div class="flex mt-2" style="align-items:center; gap:8px;">
|
|
<label style="font-size:0.8em; color:var(--muted);">EN:</label>
|
|
<span style="font-size:0.9em;">{{ selectedLine.en_name || 'N/A' }}</span>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<h4>选择左侧线路进行管理</h4>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<button v-if="selectedLine" @click="openLineModal" title="编辑线路"><i class="fas fa-pen"></i></button>
|
|
<button @click="refreshData" title="刷新"><i class="fas fa-sync-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 可视化线路编辑-->
|
|
<div class="card visual-editor" v-if="selectedLine">
|
|
<div class="editor-toolbar flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
|
<div class="flex">
|
|
<label class="switch-label">
|
|
<input type="checkbox" v-model="fareMode">
|
|
<span class="slider"></span>
|
|
<span class="label-text"><i class="fas fa-coins"></i> 票价设置/车站编辑模式</span>
|
|
</label>
|
|
<label class="switch-label" style="margin-left: 10px;">
|
|
<input type="checkbox" v-model="stationEditMode">
|
|
<span class="slider"></span>
|
|
<span class="label-text"><i class="fas fa-exchange-alt"></i> 换乘设置模式</span>
|
|
</label>
|
|
<div v-if="fareMode" class="hint-text text-warning">
|
|
<i class="fas fa-info-circle"></i> 点击两个站点以设置票价 </div>
|
|
<div v-else-if="stationEditMode" class="hint-text text-info">
|
|
<i class="fas fa-info-circle"></i> 点击站点以设置换乘 </div>
|
|
<div v-else class="hint-text text-muted">
|
|
<i class="fas fa-info-circle"></i> 点击站点删除
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex"
|
|
style="background: rgba(255,255,255,0.05); padding: 8px; border-radius: 6px;">
|
|
<div style="font-weight: bold; margin-right: 8px;">添加站点:</div>
|
|
<input v-model="newStation.code" placeholder="编号 (01-01)" style="width: 100px;">
|
|
<input v-model="newStation.name" placeholder="中文名" style="width: 120px;">
|
|
<input v-model="newStation.en_name" placeholder="英文名" style="width: 120px;">
|
|
<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>
|
|
|
|
<!-- 可视化线路编辑-->
|
|
<div class="visual-line-container"
|
|
ref="visualLineViewport"
|
|
:class="{ 'is-panning': lineViewportPan.active }"
|
|
@mousedown="startLineViewportPan"
|
|
@mousemove="moveLineViewportPan">
|
|
<svg :width="lineEditorSvgWidth" height="200"
|
|
v-if="selectedLine.stations && selectedLine.stations.length > 0">
|
|
<!--站点连接线-->
|
|
<line x1="50" y1="100" :x2="50 + (selectedLine.stations.length - 1) * 120" y2="100"
|
|
:stroke="selectedLine.color" stroke-width="4" stroke-linecap="round" />
|
|
|
|
<!--票价显示-->
|
|
<g v-for="(s, i) in selectedLine.stations.slice(0, selectedLine.stations.length-1)"
|
|
:key="'fare-'+i">
|
|
<text :x="50 + i * 120 + 60" y="90" text-anchor="middle" fill="#f59e0b"
|
|
font-size="10" font-weight="bold">{{ getFareText(i) }}</text>
|
|
</g>
|
|
|
|
<!--车站节点-->
|
|
<g v-for="(sCode, index) in selectedLine.stations" :key="index"
|
|
@mousedown="onStationDragStart(index)" @mouseup="onStationDrop"
|
|
@mousemove="onStationDragOver(index)" @click="handleStationClick(sCode, index)"
|
|
class="station-node" :class="{
|
|
'selected': isStationSelected(sCode),
|
|
'fare-source': fareSelection[0] === sCode,
|
|
'fare-target': fareSelection[1] === sCode
|
|
}">
|
|
|
|
<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>
|
|
</g>
|
|
</g>
|
|
|
|
<!--删除-->
|
|
<title>{{ getStationName(sCode) }} ({{ sCode }}){{ getTransferTitleSuffix(sCode) }}</title>
|
|
</template>
|
|
</g>
|
|
</svg>
|
|
<div v-else class="empty-state">
|
|
<i class="fas fa-subway"
|
|
style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;"></i>
|
|
<p>此线路暂无站点,请从上方添加</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 票价设置弹窗 -->
|
|
<div v-if="showFareModal" class="modal show">
|
|
<div class="modal-card">
|
|
<h4 class="modal-title">设置票价</h4>
|
|
<div class="mb-4 text-center">
|
|
<div class="flex between"
|
|
style="justify-content: center; gap: 20px; font-size: 1.1em; font-weight: bold;">
|
|
<span>{{ getStationName(fareSelection[0]) }}</span>
|
|
<i class="fas fa-arrow-right text-muted"></i>
|
|
<span>{{ getStationName(fareSelection[1]) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>常规票价</label>
|
|
<input v-model.number="currentFare.cost_regular" type="number" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>特急票价</label>
|
|
<input v-model.number="currentFare.cost_express" type="number" class="w-100">
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="danger" @click="deleteCurrentFare"
|
|
v-if="currentFare.exists">删除</button>
|
|
<button @click="saveCurrentFare">保存</button>
|
|
<button class="danger" @click="closeFareModal">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="showStationModal" class="modal show">
|
|
<div class="modal-card">
|
|
<h4 class="modal-title">站点编辑</h4>
|
|
<div class="mb-4">
|
|
<label>站点编号</label>
|
|
<input v-model="stationForm.code" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>中文名</label>
|
|
<input v-model="stationForm.name" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>英文名</label>
|
|
<input v-model="stationForm.en_name" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="switch-label">
|
|
<input type="checkbox" v-model="stationForm.transfer_enabled">
|
|
<span class="slider"></span>
|
|
<span class="label-text">设置跨名/强制换乘</span>
|
|
</label>
|
|
<p class="text-muted" style="font-size: 0.85em; margin-top: 6px;">
|
|
<i class="fas fa-magic text-primary"></i> 提示:系统会自动将中文名或英文名相同的站点连接为换乘站,无需手动设置。
|
|
</p>
|
|
</div>
|
|
<div class="mb-4" v-if="stationForm.transfer_enabled">
|
|
<label>可换乘到的站点</label>
|
|
<select v-model="stationForm.transfer_to" multiple class="w-100" style="height: 180px;">
|
|
<optgroup label="同名站点 (已自动换乘)" v-if="transferTargets.sameName.length > 0">
|
|
<option v-for="t in transferTargets.sameName" :key="t.code" :value="t.code">
|
|
{{ t.name }} ({{ t.en_name }}) - {{ t.code }}
|
|
</option>
|
|
</optgroup>
|
|
<optgroup label="其他站点">
|
|
<option v-for="t in transferTargets.others" :key="t.code" :value="t.code">
|
|
{{ t.name }} ({{ t.en_name }}) - {{ t.code }}
|
|
</option>
|
|
</optgroup>
|
|
</select>
|
|
<p class="text-muted" style="font-size: 0.8em; margin-top: 4px;">按住 Ctrl / Cmd 键多选</p>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="danger" @click="deleteStation(stationFormOriginalCode)">删除</button>
|
|
<button @click="saveStationSettings">保存</button>
|
|
<button class="danger" @click="closeStationModal">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="showLineModal" class="modal show">
|
|
<div class="modal-card">
|
|
<h4 class="modal-title">线路编辑</h4>
|
|
<div class="mb-4">
|
|
<label>线路编号</label>
|
|
<input v-model="lineForm.id" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>中文名</label>
|
|
<input v-model="lineForm.name" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>英文名</label>
|
|
<input v-model="lineForm.en_name" class="w-100">
|
|
</div>
|
|
<div class="mb-4">
|
|
<label>颜色</label>
|
|
<div class="flex" style="gap:8px;">
|
|
<input type="text" v-model="lineForm.color" class="w-100" placeholder="#3366cc">
|
|
<input type="color" v-model="lineForm.color" title="选择颜色"
|
|
style="width: 48px; padding: 0; border: none; height: 32px;">
|
|
</div>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button @click="saveLineSettings">保存</button>
|
|
<button class="danger" @click="closeLineModal">取消</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 票价地图 -->
|
|
<div v-if="currentView === 'faremap'">
|
|
<div class="card faremap-card">
|
|
<div class="flex between mb-4">
|
|
<h4>票价地图</h4>
|
|
<div class="flex" style="flex-wrap: wrap; gap: 8px;">
|
|
<button @click="loadFareMap" title="刷新"><i class="fas fa-sync-alt"></i></button>
|
|
<button @click="zoomFareMapOut" title="缩小"><i class="fas fa-minus"></i></button>
|
|
<button @click="zoomFareMapIn" title="放大"><i class="fas fa-plus"></i></button>
|
|
<button @click="zoomFareMapReset" title="重置"><i class="fas fa-crosshairs"></i></button>
|
|
<button @click="exportFareMap" title="导出图像"><i class="fas fa-download"></i></button>
|
|
</div>
|
|
</div>
|
|
<div v-if="fareMapLoading" class="loading">加载中...</div>
|
|
<div v-else-if="fareMapError" class="loading">{{ fareMapError }}</div>
|
|
<div v-else class="faremap-viewport">
|
|
<div class="faremap-canvas" :style="{ transform: `scale(${fareMapScale})` }" v-html="fareMapSvg"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 凭证管理 -->
|
|
<div v-if="currentView === 'vouchers'">
|
|
<div class="grid">
|
|
<div class="card">
|
|
<div class="stat-label">凭证总数</div>
|
|
<div class="stat-value">{{ orderStats.total }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">待使用</div>
|
|
<div class="stat-value">{{ orderStats.valid }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">已使用</div>
|
|
<div class="stat-value">{{ orderStats.used }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">已过期</div>
|
|
<div class="stat-value">{{ orderStats.expired }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="flex between mb-4">
|
|
<h4>凭证列表</h4>
|
|
<div class="flex">
|
|
<button @click="fetchOrders"><i class="fas fa-sync-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
<table class="ticket-table">
|
|
<thead>
|
|
<tr>
|
|
<th>凭证</th>
|
|
<th>线路</th>
|
|
<th>车型</th>
|
|
<th>票价</th>
|
|
<th>状态</th>
|
|
<th>创建时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="o in orderList" :key="o.code">
|
|
<td class="mono" style="font-weight:bold; font-size:1.1em;">{{ o.code }}</td>
|
|
<td>{{ o.start_name }} <i class="fas fa-arrow-right text-muted"></i> {{
|
|
o.terminal_name }}</td>
|
|
<td>{{ formatTrainType(o.train_type) }}</td>
|
|
<td>{{ o.price }}</td>
|
|
<td><span class="badge" :class="formatTicketStatus(o.status).class">{{ formatTicketStatus(o.status).text }}</span></td>
|
|
<td>{{ formatTime(o.created_ts) }}</td>
|
|
<td>
|
|
<div class="flex" style="gap:4px;">
|
|
<a :href="'token.html?code='+o.code" target="_blank" class="btn sm"
|
|
title="查看"
|
|
style="height:28px; width:28px; padding:0; display:flex; align-items:center; justify-content:center;"><i
|
|
class="fas fa-eye"></i></a>
|
|
<button class="danger sm" @click="deleteOrder(o.code)"
|
|
style="height:28px; width:28px; padding:0; display:flex; align-items:center; justify-content:center;"><i
|
|
class="fas fa-trash"></i></button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="currentView === 'iccards'">
|
|
|
|
<div class="grid">
|
|
<div class="card">
|
|
<div class="stat-label">IC 卡总数</div>
|
|
<div class="stat-value">{{ icCardStats.total }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">待领卡</div>
|
|
<div class="stat-value">{{ icCardStats.pending }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">正常启用</div>
|
|
<div class="stat-value">{{ icCardStats.active }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">储值总额</div>
|
|
<div class="stat-value">{{ formatMoney(icCardStats.balance) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="management-container ic-admin-layout">
|
|
<div class="management-sidebar">
|
|
|
|
<div class="card jr-admin-list-card" style="margin-bottom:0; display:flex; flex-direction:column; min-height: 520px;">
|
|
<div class="flex between mb-4">
|
|
<div>
|
|
<h4>卡片列表</h4>
|
|
<div class="jr-admin-list-meta">支持按卡号、订单号、凭证码和持卡人姓名检索。</div>
|
|
</div>
|
|
<span class="badge">{{ icCards.length }}</span>
|
|
</div>
|
|
<div class="flex mb-4" style="flex-wrap:wrap;">
|
|
<input v-model="icCardSearch" placeholder="搜索卡号 / 订单号 / 凭证码 / 姓名" style="flex:1;">
|
|
</div>
|
|
<div class="list-lines jr-scroll-box" style="flex:1; min-height:320px;">
|
|
<div v-if="!icCards.length" class="empty-state" style="padding:24px 0;">
|
|
<p>暂无 IC 卡记录。</p>
|
|
</div>
|
|
<div
|
|
v-for="card in icCards"
|
|
:key="card.card_id"
|
|
class="line-item ic-card-item"
|
|
:class="{ active: icSelectedId === card.card_id }"
|
|
@click="loadIcCard(card.card_id)"
|
|
>
|
|
<div class="line-color-dot" :style="{ background: icStatusColor(card.status) }"></div>
|
|
<div class="line-info">
|
|
<div class="line-name">{{ displayIcCardId(card) }}</div>
|
|
<div class="line-meta">{{ card.holder_name || '未登记持卡人' }} · IC 储值卡</div>
|
|
<div class="line-meta">订单 {{ cardOrderCode(card) }} · 余额 {{ formatMoney(card.balance) }}</div>
|
|
</div>
|
|
<div class="line-actions" style="opacity:1;">
|
|
<span class="badge" :class="icStatusInfo(card.status).className">{{ icStatusInfo(card.status).text }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="management-main">
|
|
<div class="card">
|
|
<div class="flex between mb-4">
|
|
<div>
|
|
<h4>卡片详情</h4>
|
|
</div>
|
|
<div class="flex" style="gap:8px;">
|
|
<button class="btn" @click="loadIcCard(icSelectedId)" :disabled="!icSelectedCard || isViewBusy"><i class="fas fa-rotate-right"></i> 刷新</button>
|
|
|
|
<button class="btn danger" @click="deleteIcCard" :disabled="!icSelectedCard"><i class="fas fa-trash"></i> 删除卡</button>
|
|
<button class="btn primary" @click="saveIcCard" :disabled="!icSelectedCard"><i class="fas fa-save"></i> 保存</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="!icSelectedCard" class="empty-state">
|
|
<i class="fas fa-credit-card" style="font-size:48px; opacity:0.3;"></i>
|
|
<p>从左侧选择一张 IC 卡以查看详情。</p>
|
|
</div>
|
|
<div v-else>
|
|
<div class="flex between mb-4" style="align-items:flex-start;">
|
|
<div>
|
|
<div class="mono" style="font-size:1.4rem; font-weight:700;">{{ displayIcCardId(icSelectedCard) }}</div>
|
|
<div class="text-muted" style="margin-top:6px;">订单号 {{ cardOrderCode(icSelectedCard) }} · 来源 {{ icSelectedCard.source || '---' }}</div>
|
|
</div>
|
|
<span class="badge" :class="icStatusInfo(icSelectedCard.status).className">{{ icStatusInfo(icSelectedCard.status).text }}</span>
|
|
</div>
|
|
<div class="jr-admin-summary-grid">
|
|
<div class="jr-admin-summary-item">
|
|
<span>当前余额</span>
|
|
<strong>{{ formatMoney(icSelectedCard.balance) }}</strong>
|
|
<small>支持直接发起充值。</small>
|
|
</div>
|
|
<div class="jr-admin-summary-item">
|
|
<span>事件记录</span>
|
|
<strong>{{ icSelectedEvents.length }}</strong>
|
|
<small>用于追踪开卡与状态变更。</small>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="ic-detail-grid">
|
|
<label class="ic-field">
|
|
<span>持卡人</span>
|
|
<input v-model="icDetailForm.holder_name">
|
|
</label>
|
|
<label class="ic-field">
|
|
<span>卡片类型</span>
|
|
<input value="IC 储值卡" disabled>
|
|
</label>
|
|
<label class="ic-field">
|
|
<span>状态</span>
|
|
<select v-model="icDetailForm.status">
|
|
<option value="pending_pickup">待领卡</option>
|
|
<option value="active">正常</option>
|
|
<option value="disabled">停用</option>
|
|
<option value="lost">挂失</option>
|
|
<option value="refunded">已退卡</option>
|
|
</select>
|
|
</label>
|
|
<label class="ic-field">
|
|
<span>余额</span>
|
|
<input :value="formatMoney(icSelectedCard?.balance || 0)" disabled>
|
|
</label>
|
|
</div>
|
|
<div class="ic-inline-meta">
|
|
<div class="list-item"><span class="k">创建时间</span><span class="v">{{ formatTime(icSelectedCard.created_ts) }}</span></div>
|
|
<div class="list-item"><span class="k">最后更新</span><span class="v">{{ formatTime(icSelectedCard.last_update_ts) }}</span></div>
|
|
<div class="list-item"><span class="k">首次充值</span><span class="v">{{ formatMoney(icSelectedCard.purchase_amount ?? icSelectedCard.balance) }}</span></div>
|
|
<div class="list-item"><span class="k">购卡金额</span><span class="v">{{ formatMoney(icSelectedCard.purchase_amount) }}</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom:0;">
|
|
<div class="flex between mb-4">
|
|
<h4>操作记录</h4>
|
|
</div>
|
|
<div class="timeline">
|
|
<div v-if="!icSelectedCard" class="loading">选择卡片后显示事件流。</div>
|
|
<div v-else-if="!icSelectedEvents.length" class="loading">暂无事件记录。</div>
|
|
<div v-for="(event, idx) in icSelectedEvents" :key="`${event.ts || 0}-${event.type || 'event'}-${idx}`" class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-content">
|
|
<div class="flex between">
|
|
<span style="font-weight:600;">{{ icEventTitle(event) }}</span>
|
|
<span class="text-muted" style="font-size:0.8rem;">{{ formatTime(event.ts) }}</span>
|
|
</div>
|
|
<div class="log-detail" style="margin-top:8px;">{{ formatIcEventDetail(event) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 车票记录 -->
|
|
<div v-if="currentView === 'tickets'">
|
|
<div class="grid">
|
|
<div class="card">
|
|
<div class="stat-label">车票总数</div>
|
|
<div class="stat-value">{{ ticketStats.total }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">有效</div>
|
|
<div class="stat-value">{{ ticketStats.valid }}</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="stat-label">已使用</div>
|
|
<div class="stat-value">{{ ticketStats.used }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="card mb-4">
|
|
<div class="flex between mb-4">
|
|
<h4>车票记录</h4>
|
|
<div class="flex">
|
|
<input v-model="ticketSearch" placeholder="搜索 Ticket ID / 站点" style="width: 200px;">
|
|
<button @click="refreshData"><i class="fas fa-sync-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
<table class="ticket-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>起点</th>
|
|
<th>终点</th>
|
|
<th>类型</th>
|
|
<th>状态</th>
|
|
<th>时间</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="t in ticketList" :key="t.ticket_id" @click="viewTicketDetails(t)"
|
|
class="clickable-row">
|
|
<td><span class="mono">{{ t.ticket_id }}</span></td>
|
|
<td>
|
|
<div class="st-container">
|
|
<div class="st-main-row">
|
|
<span class="st-name">{{ getStationInfo(t.start).name }}</span>
|
|
<span class="st-code">{{ t.start }}</span>
|
|
</div>
|
|
<div class="st-en">{{ getStationInfo(t.start).en_name }}</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="st-container">
|
|
<div class="st-main-row">
|
|
<span class="st-name">{{ getStationInfo(t.terminal).name }}</span>
|
|
<span class="st-code">{{ t.terminal }}</span>
|
|
</div>
|
|
<div class="st-en">{{ getStationInfo(t.terminal).en_name }}</div>
|
|
</div>
|
|
</td>
|
|
<td>{{ formatTrainType(t.type) }}</td>
|
|
<td><span class="badge" :class="formatTicketStatus(t.status).class">{{
|
|
formatTicketStatus(t.status).text }}</span></td>
|
|
<td>{{ formatTime(t.ts) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 车票详情弹窗 -->
|
|
<div v-if="showTicketModal" class="modal show" @click.self="closeTicketModal">
|
|
<div class="modal-card" style="width: 600px;">
|
|
<div class="flex between mb-4">
|
|
<h4 class="modal-title">车票详情</h4>
|
|
<button class="sm" @click="closeTicketModal" title="关闭"><i
|
|
class="fas fa-times"></i></button>
|
|
</div>
|
|
|
|
<div v-if="selectedTicket">
|
|
<div class="ticket-header mb-4"
|
|
style="background: rgba(255,255,255,0.05); padding: 16px; border-radius: 8px;">
|
|
<div class="flex between mb-2">
|
|
<span class="mono text-muted">{{ selectedTicket.ticket_id }}</span>
|
|
<span class="badge"
|
|
:class="formatTicketStatus(selectedTicket.index.status).class">
|
|
{{ formatTicketStatus(selectedTicket.index.status).text }}
|
|
</span>
|
|
</div>
|
|
<div class="flex between" style="font-size: 1.2rem; font-weight: bold;">
|
|
<div class="st-container">
|
|
<div class="st-main-row">
|
|
<span class="st-name">{{ selectedTicket.index.start_name ||
|
|
selectedTicket.index.start }}</span>
|
|
<span class="st-code">{{ selectedTicket.index.start }}</span>
|
|
</div>
|
|
<div class="st-en">{{ selectedTicket.index.start_en || '' }}</div>
|
|
</div>
|
|
<i class="fas fa-arrow-right text-muted"></i>
|
|
<div class="st-container" style="align-items: flex-end;">
|
|
<div class="st-main-row">
|
|
<span class="st-name">{{ selectedTicket.index.terminal_name ||
|
|
selectedTicket.index.terminal }}</span>
|
|
<span class="st-code">{{ selectedTicket.index.terminal }}</span>
|
|
</div>
|
|
<div class="st-en">{{ selectedTicket.index.terminal_en || '' }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-muted mt-2" style="font-size: 0.9rem;">
|
|
类型: {{ formatTrainType(selectedTicket.index.type ||
|
|
selectedTicket.index.train_type) }} | 票价: {{ selectedTicket.index.price ||
|
|
selectedTicket.index.cost }}
|
|
</div>
|
|
</div>
|
|
|
|
<h5>行程记录</h5>
|
|
<div class="timeline">
|
|
<div v-for="ev in selectedTicket.events" :key="ev.ts || ev['时间戳']" class="timeline-item">
|
|
<div class="timeline-dot"></div>
|
|
<div class="timeline-content">
|
|
<div class="flex between">
|
|
<span style="font-weight: 600;">{{ formatTicketEvent(ev) }}</span>
|
|
</div>
|
|
<div class="text-muted" style="font-size: 0.9rem; margin-top: 4px;">
|
|
{{ formatTicketEventUnified(ev) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="loading">加载中...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="currentView === 'assets'" class="assets-editor-container">
|
|
<div class="card mb-4">
|
|
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
|
<div>
|
|
<h4 style="margin-bottom: 6px;">线路图上传</h4>
|
|
<div class="text-muted">上传后会用于首页展示,也会在购票页未选择线路时显示为总览图。</div>
|
|
</div>
|
|
<div class="flex" style="gap: 8px; flex-wrap: wrap;">
|
|
<label class="btn primary" style="cursor: pointer;">
|
|
<i class="fas fa-upload"></i> 上传线路图
|
|
<input type="file" accept=".png,.jpg,.jpeg,.webp,.svg" style="display: none;" @change="uploadRouteMap">
|
|
</label>
|
|
<button class="btn" @click="deleteRouteMap" :disabled="!assetsManifest.routeMap || mutationBusy">
|
|
<i class="fas fa-trash"></i> 删除当前线路图
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid" style="display: grid; grid-template-columns: minmax(280px, 360px) minmax(0, 1fr); gap: 16px; align-items: start;">
|
|
<div style="border: 1px solid var(--border); padding: 16px; background: var(--surface);">
|
|
<div style="font-weight: 700; margin-bottom: 12px;">上传说明</div>
|
|
<div class="text-muted" style="display: grid; gap: 8px; line-height: 1.7;">
|
|
<div>支持格式:PNG、JPG、WEBP、SVG</div>
|
|
<div>建议上传横向长图,便于首页和购票页展示。</div>
|
|
<div>再次上传会自动替换旧文件。</div>
|
|
</div>
|
|
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); display: grid; gap: 10px;">
|
|
<label style="display: grid; gap: 6px;">
|
|
<span style="font-weight: 700;">线路图署名</span>
|
|
<input v-model="assetsForm.routeMapCredit" class="input" type="text" maxlength="200" placeholder="例如:OpenStreetMap contributors / 作者名 / 来源名">
|
|
</label>
|
|
<div class="text-muted">许可协议固定为 <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">CC BY 4.0</a></div>
|
|
<div class="flex" style="gap: 8px; flex-wrap: wrap;">
|
|
<button class="btn" @click="saveRouteMapCredit" :disabled="mutationBusy">
|
|
<i class="fas fa-pen"></i> 保存署名
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); display: grid; gap: 8px;">
|
|
<div><strong>当前文件:</strong> {{ assetsManifest.routeMap || '未上传' }}</div>
|
|
<div><strong>当前署名:</strong> {{ assetsManifest.routeMapCredit || '未填写' }}</div>
|
|
<div><strong>许可协议:</strong> <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer">{{ assetsManifest.routeMapLicense }}</a></div>
|
|
<div><strong>更新时间:</strong> {{ assetsManifest.updatedAt ? formatTime(assetsManifest.updatedAt) : '暂无' }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border: 1px solid var(--border); background: #0f172a; min-height: 420px; display: flex; align-items: center; justify-content: center; overflow: auto; padding: 16px; box-sizing: border-box;">
|
|
<div style="width: 100%;">
|
|
<template v-if="assetsRouteMapUrl">
|
|
<img :src="assetsRouteMapUrl" alt="线路图预览" style="display: block; max-width: 100%; height: auto; background: #ffffff; margin: 0 auto;">
|
|
<div class="jr-asset-credit" style="color: #cbd5e1;">
|
|
<span>线路图署名:{{ assetsManifest.routeMapCredit || '未填写' }}</span>
|
|
<span>许可协议:</span>
|
|
<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener noreferrer" style="color: #93c5fd;">{{ assetsManifest.routeMapLicense }}</a>
|
|
</div>
|
|
</template>
|
|
<div v-else class="text-muted" style="padding: 24px; text-align: center;">
|
|
暂无线路图,请先上传一张线路图。
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 设置 -->
|
|
<div v-if="currentView === 'settings'">
|
|
<div class="card mb-4">
|
|
<div class="mb-4">
|
|
<label style="display:block; margin-bottom:8px; font-weight:600;">优惠活动</label>
|
|
<div class="flex">
|
|
<input v-model="config.promotion.name" placeholder="活动名称">
|
|
<input v-model.number="config.promotion.discount" type="number" step="0.1"
|
|
placeholder="折扣 (0.1-1.0)">
|
|
<button @click="saveConfig"><i class="fas fa-save"></i> 保存</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label style="display:block; margin-bottom:8px; font-weight:600;">Lua 脚本更新控制</label>
|
|
<div class="flex" style="flex-direction: column; gap: 10px;">
|
|
<div>
|
|
<button @click="pushLuaUpdate" :disabled="isPushingUpdate">
|
|
<i class="fas fa-satellite-dish"></i> {{ isPushingUpdate ? '推送中...' : '手动推送更新' }}
|
|
</button>
|
|
</div>
|
|
<div class="text-muted" style="font-size: 0.9rem;">
|
|
点击按钮后,将向全服所有终端发送更新指令,并在 5 秒内生效。<br>终端将在业务空闲时静默下载更新并重启,未加载的机器在加载后也会第一时间更新。
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<h4>数据管理</h4>
|
|
<div class="flex">
|
|
<button @click="exportData"><i class="fas fa-file-export"></i> 导出数据 JSON</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="currentView === 'logs'">
|
|
<div class="card">
|
|
<div class="flex between mb-4" style="flex-wrap: wrap; gap: 10px;">
|
|
<h4>日志</h4>
|
|
<div class="flex" style="gap: 8px;">
|
|
<button @click="fetchLogs" title="刷新"><i class="fas fa-sync-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-4" style="flex-wrap: wrap; gap: 10px; align-items: center;">
|
|
<select v-model="logCategory" style="width: 150px;">
|
|
<option value="">全部来源</option>
|
|
<option value="admin">admin</option>
|
|
<option value="public">public</option>
|
|
<option value="device">device</option>
|
|
<option value="system">system</option>
|
|
</select>
|
|
<input v-model="logTypeFilter" placeholder="type (可逗号分隔)" style="width: 260px;">
|
|
<input v-model="logQuery" placeholder="关键字" style="width: 220px;">
|
|
<input v-model.number="logMax" type="number" min="10" max="5000" step="10" style="width: 120px;">
|
|
<button @click="fetchLogs" :disabled="logLoading">
|
|
<i class="fas" :class="logLoading ? 'fa-spinner fa-spin' : 'fa-filter'"></i> 筛选
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="logLoading" class="loading">加载中...</div>
|
|
<table v-else>
|
|
<thead>
|
|
<tr>
|
|
<th>时间</th>
|
|
<th>来源</th>
|
|
<th>类型</th>
|
|
<th>IP</th>
|
|
<th>详情</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(l, idx) in logs" :key="idx">
|
|
<td style="white-space: nowrap;">{{ formatTime(l.ts) }}</td>
|
|
<td><span class="badge">{{ l.category || 'legacy' }}</span></td>
|
|
<td class="mono">{{ l.type || 'event' }}</td>
|
|
<td class="mono">{{ l.ip || '' }}</td>
|
|
<td>
|
|
<details>
|
|
<summary class="text-muted" style="cursor: pointer;">查看</summary>
|
|
<pre style="white-space: pre-wrap; margin: 8px 0 0; font-size: 0.85rem; line-height: 1.35;">{{ formatLogDetail(l) }}</pre>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<footer class="site-footer">
|
|
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">粤ICP备2025450093号</a>
|
|
<span class="version">v1.0.12</span>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<script src="/custom-dialog.js?v=11"></script>
|
|
<script src="index.js?v=6"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
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'
|
|
};
|
|
|
|
document.getElementById('homeLink').href = links.home;
|
|
document.getElementById('adminTopLink').href = links.home;
|
|
document.getElementById('adminBrandLink').href = links.home;
|
|
|
|
document.querySelectorAll('[data-link]').forEach((el) => {
|
|
const key = el.getAttribute('data-link');
|
|
if (links[key]) el.href = links[key];
|
|
});
|
|
});
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|