feat(管理后台): 新增线路编辑器拖拽平移并修复代理下Socket连接问题
调整socket.io传输顺序优先使用轮询以适配代理服务器,新增可视化线路编辑器拖拽平移功能,修复多处CSS布局问题并更新静态资源缓存版本。
This commit is contained in:
+9
-5
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<!-- 充满未知和不稳定的票务系统! -->
|
<!-- 充满未知和不稳定的票务系统! -->
|
||||||
|
|
||||||
@@ -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=13">
|
<link rel="stylesheet" href="style.css?v=14">
|
||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
</head>
|
</head>
|
||||||
@@ -293,8 +293,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 可视化线路编辑-->
|
<!-- 可视化线路编辑-->
|
||||||
<div class="visual-line-container">
|
<div class="visual-line-container"
|
||||||
<svg width="100%" height="200"
|
ref="visualLineViewport"
|
||||||
|
:class="{ 'is-panning': lineViewportPan.active }"
|
||||||
|
@mousedown="startLineViewportPan"
|
||||||
|
@mousemove="moveLineViewportPan">
|
||||||
|
<svg :width="lineEditorSvgWidth" height="200"
|
||||||
v-if="selectedLine.stations && selectedLine.stations.length > 0">
|
v-if="selectedLine.stations && selectedLine.stations.length > 0">
|
||||||
<!--站点连接线-->
|
<!--站点连接线-->
|
||||||
<line x1="50" y1="100" :x2="50 + (selectedLine.stations.length - 1) * 120" y2="100"
|
<line x1="50" y1="100" :x2="50 + (selectedLine.stations.length - 1) * 120" y2="100"
|
||||||
@@ -929,7 +933,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=12"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=13"></script>
|
||||||
<script src="index.js?v=3"></script>
|
<script src="index.js?v=5"></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');
|
||||||
|
|||||||
+52
-1
@@ -35,7 +35,9 @@ createApp({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const connected = ref(false);
|
const connected = ref(false);
|
||||||
const socket = io({ transports: ['websocket', 'polling'], upgrade: false });
|
// Prefer polling first so admin remains connected even when the proxy
|
||||||
|
// does not support WebSocket upgrades reliably.
|
||||||
|
const socket = io({ transports: ['polling', 'websocket'] });
|
||||||
|
|
||||||
// Data State
|
// Data State
|
||||||
const stations = ref([]);
|
const stations = ref([]);
|
||||||
@@ -100,6 +102,15 @@ createApp({
|
|||||||
const showFareModal = ref(false);
|
const showFareModal = ref(false);
|
||||||
const currentFare = reactive({ exists: false, cost_regular: 0, cost_express: 0 });
|
const currentFare = reactive({ exists: false, cost_regular: 0, cost_express: 0 });
|
||||||
const draggingStationIndex = ref(null);
|
const draggingStationIndex = ref(null);
|
||||||
|
const visualLineViewport = ref(null);
|
||||||
|
const lineViewportPan = reactive({
|
||||||
|
active: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
scrollLeft: 0,
|
||||||
|
scrollTop: 0,
|
||||||
|
moved: false
|
||||||
|
});
|
||||||
const showStationModal = ref(false);
|
const showStationModal = ref(false);
|
||||||
const stationForm = reactive({ code: '', name: '', en_name: '', transfer_enabled: false, transfer_to: [] });
|
const stationForm = reactive({ code: '', name: '', en_name: '', transfer_enabled: false, transfer_to: [] });
|
||||||
const stationFormOriginalCode = ref('');
|
const stationFormOriginalCode = ref('');
|
||||||
@@ -654,6 +665,36 @@ createApp({
|
|||||||
draggingStationIndex.value = null;
|
draggingStationIndex.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startLineViewportPan = (event) => {
|
||||||
|
const viewport = visualLineViewport.value;
|
||||||
|
if (!viewport) return;
|
||||||
|
if (event.button !== 0) return;
|
||||||
|
if (event.target && event.target.closest('.station-node')) return;
|
||||||
|
lineViewportPan.active = true;
|
||||||
|
lineViewportPan.moved = false;
|
||||||
|
lineViewportPan.startX = event.clientX;
|
||||||
|
lineViewportPan.startY = event.clientY;
|
||||||
|
lineViewportPan.scrollLeft = viewport.scrollLeft;
|
||||||
|
lineViewportPan.scrollTop = viewport.scrollTop;
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveLineViewportPan = (event) => {
|
||||||
|
if (!lineViewportPan.active) return;
|
||||||
|
const viewport = visualLineViewport.value;
|
||||||
|
if (!viewport) return;
|
||||||
|
const deltaX = event.clientX - lineViewportPan.startX;
|
||||||
|
const deltaY = event.clientY - lineViewportPan.startY;
|
||||||
|
if (Math.abs(deltaX) > 2 || Math.abs(deltaY) > 2) {
|
||||||
|
lineViewportPan.moved = true;
|
||||||
|
}
|
||||||
|
viewport.scrollLeft = lineViewportPan.scrollLeft - deltaX;
|
||||||
|
viewport.scrollTop = lineViewportPan.scrollTop - deltaY;
|
||||||
|
};
|
||||||
|
|
||||||
|
const endLineViewportPan = () => {
|
||||||
|
lineViewportPan.active = false;
|
||||||
|
};
|
||||||
|
|
||||||
// --- Order Management ---
|
// --- Order Management ---
|
||||||
const fetchOrders = async () => {
|
const fetchOrders = async () => {
|
||||||
if (loadingState.orders) return;
|
if (loadingState.orders) return;
|
||||||
@@ -1171,6 +1212,10 @@ createApp({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleStationClick = async (code) => {
|
const handleStationClick = async (code) => {
|
||||||
|
if (lineViewportPan.moved) {
|
||||||
|
lineViewportPan.moved = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (stationEditMode.value) {
|
if (stationEditMode.value) {
|
||||||
openStationModal(code);
|
openStationModal(code);
|
||||||
return;
|
return;
|
||||||
@@ -1398,6 +1443,7 @@ createApp({
|
|||||||
startIcCardSync();
|
startIcCardSync();
|
||||||
}
|
}
|
||||||
appMouseupHandler = async () => {
|
appMouseupHandler = async () => {
|
||||||
|
endLineViewportPan();
|
||||||
if (draggingStationIndex.value !== null) {
|
if (draggingStationIndex.value !== null) {
|
||||||
if (selectedLine.value) {
|
if (selectedLine.value) {
|
||||||
try {
|
try {
|
||||||
@@ -1428,6 +1474,10 @@ createApp({
|
|||||||
// Computed
|
// Computed
|
||||||
const recentLogs = computed(() => logs.value);
|
const recentLogs = computed(() => logs.value);
|
||||||
const orderList = computed(() => orders.value);
|
const orderList = computed(() => orders.value);
|
||||||
|
const lineEditorSvgWidth = computed(() => {
|
||||||
|
const count = Array.isArray(selectedLine.value?.stations) ? selectedLine.value.stations.length : 0;
|
||||||
|
return Math.max(960, 100 + Math.max(0, count - 1) * 120 + 120);
|
||||||
|
});
|
||||||
const lastSyncText = computed(() => lastSyncAt.value ? formatTime(lastSyncAt.value) : '尚未同步');
|
const lastSyncText = computed(() => lastSyncAt.value ? formatTime(lastSyncAt.value) : '尚未同步');
|
||||||
const isViewBusy = computed(() => {
|
const isViewBusy = computed(() => {
|
||||||
if (loadingState.core) return true;
|
if (loadingState.core) return true;
|
||||||
@@ -1520,6 +1570,7 @@ createApp({
|
|||||||
|
|
||||||
// Management
|
// Management
|
||||||
selectedLine, fareMode, stationEditMode, fareSelection, showFareModal, currentFare, availableStations,
|
selectedLine, fareMode, stationEditMode, fareSelection, showFareModal, currentFare, availableStations,
|
||||||
|
visualLineViewport, lineViewportPan, lineEditorSvgWidth, startLineViewportPan, moveLineViewportPan,
|
||||||
selectLine, createLine, deleteLine, deleteStation, updateLineInfo, getFareText,
|
selectLine, createLine, deleteLine, deleteStation, updateLineInfo, getFareText,
|
||||||
isStationInLine, addStationToLine, removeStationFromLine,
|
isStationInLine, addStationToLine, removeStationFromLine,
|
||||||
handleStationClick, isStationSelected,
|
handleStationClick, isStationSelected,
|
||||||
|
|||||||
+22
-2
@@ -686,6 +686,7 @@ main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.management-main {
|
.management-main {
|
||||||
@@ -702,6 +703,7 @@ main {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line-item {
|
.line-item {
|
||||||
@@ -771,18 +773,26 @@ main {
|
|||||||
|
|
||||||
.visual-line-container {
|
.visual-line-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-x: auto;
|
overflow: auto;
|
||||||
overflow-y: hidden;
|
|
||||||
background-color: #00000022;
|
background-color: #00000022;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
min-height: 0;
|
||||||
|
cursor: grab;
|
||||||
|
user-select: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-line-container svg {
|
.visual-line-container svg {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visual-line-container.is-panning {
|
||||||
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
.station-node {
|
.station-node {
|
||||||
@@ -3383,6 +3393,12 @@ body.jr-ticket-board-page .jr-board-card:last-child {
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ic-admin-layout .management-sidebar,
|
||||||
|
.ic-admin-layout .management-main,
|
||||||
|
.jr-admin-list-card {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ic-form-grid {
|
.ic-form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
@@ -3886,6 +3902,10 @@ body.jr-admin-login-page {
|
|||||||
|
|
||||||
.jr-admin-list-card .jr-scroll-box {
|
.jr-admin-list-card .jr-scroll-box {
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
|
min-height: 320px;
|
||||||
|
max-height: 560px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jr-admin-summary-grid {
|
.jr-admin-summary-grid {
|
||||||
|
|||||||
@@ -609,7 +609,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/custom-dialog.js?v=12"></script>
|
<script src="/custom-dialog.js?v=12"></script>
|
||||||
<script src="/public-status.js?v=13"></script>
|
<script src="/public-status.js?v=13"></script>
|
||||||
<script src="ticket-route.js?v=2"></script>
|
<script src="ticket-route.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');
|
||||||
|
|||||||
+2
-1
@@ -24,7 +24,8 @@ createApp({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const connected = ref(false);
|
const connected = ref(false);
|
||||||
const socket = io({ transports: ['websocket'], upgrade: false, timeout: 20000 });
|
// Keep the legacy route console usable behind proxies that only allow polling.
|
||||||
|
const socket = io({ transports: ['polling', 'websocket'], timeout: 20000 });
|
||||||
|
|
||||||
|
|
||||||
const stations = ref([]);
|
const stations = ref([]);
|
||||||
|
|||||||
Reference in New Issue
Block a user