|
|
@@ -0,0 +1,687 @@
|
|
|
+<template>
|
|
|
+ <div class="crossing-detail-panel">
|
|
|
+ <div class="detail-panel-left">
|
|
|
+ <div class="intersection-video-wrap">
|
|
|
+ <IntersectionMapVideos :mapData="intersectionData" :videoUrls="currentRoute.cornerVideos" />
|
|
|
+ </div>
|
|
|
+ <div class="signal-timing-wrap">
|
|
|
+ <div class="header">
|
|
|
+ <div class="title-area">
|
|
|
+ <span class="main-title">方案状态</span>
|
|
|
+ <span class="sub-info">(周期: {{ cycleLength }} 相位差: 0 协调时间: 0)</span>
|
|
|
+ </div>
|
|
|
+ <div class="checkbox-area">
|
|
|
+ <div class="checkbox-mock" :class="{ 'is-checked': followPhase }"
|
|
|
+ @click="followPhase = !followPhase">
|
|
|
+ <span v-if="followPhase" style="color: #fff; font-size: 12px; margin-left: 1px;">✓</span>
|
|
|
+ </div>
|
|
|
+ <span>跟随相位</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <SignalTimingChart :cycleLength="cycleLength" :currentTime="currentSec" :phaseData="mockPhaseData" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="detail-panel-right">
|
|
|
+ <form class="detail-right-form" @submit.prevent>
|
|
|
+ <div class="form-group">
|
|
|
+ <div class="control-method">
|
|
|
+ <div class="control-label-wrap">
|
|
|
+ <span class="control-label">控制方式</span>
|
|
|
+ <div class="control-operation">
|
|
|
+ <div class="operation-btn"
|
|
|
+ :class="{ 'is-active': isManualMode }"
|
|
|
+ @click="toggleManualMode">
|
|
|
+ {{ isManualMode ? '退出手动控制' : '手动控制' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-interactive-area" :class="{ 'is-disabled': !isManualMode }">
|
|
|
+ <div class="control-method-content">
|
|
|
+ <SegmentedRadio v-model="currentMethod" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="control-scheme">
|
|
|
+ <div class="control-label-wrap">
|
|
|
+ <span class="control-label">控制方案</span>
|
|
|
+ <DropdownSelect v-model="currentScheme" :options="schemeOptions" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="current-stage">
|
|
|
+ <div class="current-stage-warp">
|
|
|
+ <div class="current-stage-label">当前阶段:</div>
|
|
|
+ <div v-for="(item, index) in currentStageList" :key="index" class="stage-item-wrapper">
|
|
|
+ <img :src="require(`@/assets/${item.img}`)" alt="stage"
|
|
|
+ :class="{ 'stage-img': true, 'stage-active': item.value === currentStage }" />
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ v-model.number="item.time"
|
|
|
+ class="stage-input"
|
|
|
+ :disabled="currentMethod !== 'temp'"
|
|
|
+ :title="currentMethod !== 'temp' ? '仅临时方案可修改' : '修改阶段时间'"
|
|
|
+ />
|
|
|
+ <span class="unit">s</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <transition name="fade">
|
|
|
+ <div class="lock-time" v-if="showLockTime">
|
|
|
+ <div class="lock-time-label-wrap glow-header">
|
|
|
+ <div class="lock-time-label">锁定时间</div>
|
|
|
+ <div class="lock-time-close" @click="showLockTime = false">✕</div>
|
|
|
+ </div>
|
|
|
+ <div class="lock-time-options">
|
|
|
+ <div class="lock-time-option">
|
|
|
+ <label>
|
|
|
+ <input type="radio" v-model="lockTimeType" value="continuous" /> 持续放行
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ <div class="lock-time-option">
|
|
|
+ <label>
|
|
|
+ <input type="radio" v-model="lockTimeType" value="timer" /> 放行
|
|
|
+ <DropdownSelect placeholder="锁定时间" v-model="currentLocktime" :options="locktimeOptions"
|
|
|
+ @click.native.prevent />
|
|
|
+ 秒解锁
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="button-group" v-show="isManualMode">
|
|
|
+ <div>
|
|
|
+ <button type="button" class="btn btn-cancel" @click="onCancel()">取消</button>
|
|
|
+ <button type="button" class="btn btn-confirm" @click="onConfirm()">确认</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import SignalTimingChart from '@/components/ui/SignalTimingChart.vue';
|
|
|
+import IntersectionMapVideos from '@/components/ui/IntersectionMapVideos.vue';
|
|
|
+import SegmentedRadio from '@/components/ui/SegmentedRadio.vue';
|
|
|
+import DropdownSelect from '@/components/ui/DropdownSelect.vue';
|
|
|
+
|
|
|
+import { getIntersectionData } from '@/mock/data';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'CrossingPanel',
|
|
|
+ components: {
|
|
|
+ SignalTimingChart,
|
|
|
+ IntersectionMapVideos,
|
|
|
+ SegmentedRadio,
|
|
|
+ DropdownSelect
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 核心状态控制
|
|
|
+ isManualMode: false, // 是否处于手动控制模式
|
|
|
+ showLockTime: false, // 是否显示锁定时间弹窗
|
|
|
+ lockTimeType: 'continuous', // 锁定时间类型
|
|
|
+
|
|
|
+ followPhase: false,
|
|
|
+ intersectionData: {},
|
|
|
+ currentRoute: {
|
|
|
+ id: 1, name: '靖远路与北公路交叉口 1', level: '一级', mode: '快进', time: '30s',
|
|
|
+ mainVideo: require('@/assets/videos/video1.mp4'),
|
|
|
+ cornerVideos: { nw: require('@/assets/videos/video1.mp4'), ne: require('@/assets/videos/video2.mp4'), sw: require('@/assets/videos/video2.mp4'), se: require('@/assets/videos/video1.mp4') }
|
|
|
+ },
|
|
|
+ cycleLength: 140,
|
|
|
+ currentSec: 15,
|
|
|
+ mockPhaseData: [
|
|
|
+ // ================= 上轨道 (Track 0) =================
|
|
|
+ // S1阶段 (0-30s): P1 直行
|
|
|
+ [0, 0, 23, 'P1', 30, 'green', 'UP'],
|
|
|
+ [0, 23, 26, '', 3, 'stripe', ''],
|
|
|
+ [0, 26, 29, '', 3, 'yellow', ''],
|
|
|
+ [0, 29, 30, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S2阶段 (30-60s): P2 左转
|
|
|
+ [0, 30, 53, 'P2', 30, 'green', 'TURN_LEFT'],
|
|
|
+ [0, 53, 56, '', 3, 'stripe', ''],
|
|
|
+ [0, 56, 59, '', 3, 'yellow', ''],
|
|
|
+ [0, 59, 60, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S3阶段 (60-110s): P3 侧向左转 (使用向左箭头)
|
|
|
+ [0, 60, 103, 'P3', 50, 'green', 'TURN_LEFT'],
|
|
|
+ [0, 103, 106, '', 3, 'stripe', ''],
|
|
|
+ [0, 106, 109, '', 3, 'yellow', ''],
|
|
|
+ [0, 109, 110, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S4阶段 (110-140s): P4 掉头
|
|
|
+ [0, 110, 133, 'P4', 30, 'green', 'UTURN'],
|
|
|
+ [0, 133, 136, '', 3, 'stripe', ''],
|
|
|
+ [0, 136, 139, '', 3, 'yellow', ''],
|
|
|
+ [0, 139, 140, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // ================= 下轨道 (Track 1) =================
|
|
|
+ // S1阶段 (0-30s): P5 直行
|
|
|
+ [1, 0, 23, 'P5', 30, 'green', 'UP'],
|
|
|
+ [1, 23, 26, '', 3, 'stripe', ''],
|
|
|
+ [1, 26, 29, '', 3, 'yellow', ''],
|
|
|
+ [1, 29, 30, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S2阶段 (30-60s): P6 左转
|
|
|
+ [1, 30, 53, 'P6', 30, 'green', 'TURN_LEFT'],
|
|
|
+ [1, 53, 56, '', 3, 'stripe', ''],
|
|
|
+ [1, 56, 59, '', 3, 'yellow', ''],
|
|
|
+ [1, 59, 60, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S3阶段 (60-110s): P7 侧向右转 (使用向右箭头)
|
|
|
+ [1, 60, 103, 'P7', 50, 'green', 'TURN_RIGHT'],
|
|
|
+ [1, 103, 106, '', 3, 'stripe', ''],
|
|
|
+ [1, 106, 109, '', 3, 'yellow', ''],
|
|
|
+ [1, 109, 110, '', 1, 'red', ''],
|
|
|
+
|
|
|
+ // S4阶段 (110-140s): P8 左转
|
|
|
+ [1, 110, 133, 'P8', 30, 'green', 'TURN_LEFT'],
|
|
|
+ [1, 133, 136, '', 3, 'stripe', ''],
|
|
|
+ [1, 136, 139, '', 3, 'yellow', ''],
|
|
|
+ [1, 139, 140, '', 1, 'red', '']
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 控制方式数据
|
|
|
+ currentMethod: 'temp',
|
|
|
+ currentScheme: 'early_peak',
|
|
|
+ schemeOptions: [
|
|
|
+ { label: '早高峰', value: 'early_peak' },
|
|
|
+ { label: '晚高峰', value: 'evening_peak' },
|
|
|
+ { label: '平峰', value: 'normal' }
|
|
|
+ ],
|
|
|
+ currentLocktime: 50,
|
|
|
+ locktimeOptions: [
|
|
|
+ { label: '50', value: 50 },
|
|
|
+ { label: '100', value: 100 },
|
|
|
+ { label: '300', value: 300 }
|
|
|
+ ],
|
|
|
+ currentStage: '1',
|
|
|
+ // 补充了 time 属性,用于双向绑定输入框的时间
|
|
|
+ currentStageList: [
|
|
|
+ { value: '1', time: 30, img: 'test_img1.png' },
|
|
|
+ { value: '2', time: 30, img: 'test_img1.png' },
|
|
|
+ { value: '3', time: 50, img: 'test_img1.png' },
|
|
|
+ { value: '4', time: 30, img: 'test_img1.png' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ // 监听控制方式切换
|
|
|
+ currentMethod(newVal) {
|
|
|
+ // 需求4:切换步进方案策略时,出现锁定时间弹窗
|
|
|
+ if (newVal === 'step') {
|
|
|
+ this.showLockTime = true;
|
|
|
+ } else {
|
|
|
+ this.showLockTime = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 模拟需求1:根据不同模式,切换对应的控制方案数据 (Mock 逻辑)
|
|
|
+ this.updateSchemeDataByMethod(newVal);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async mounted() {
|
|
|
+ this.intersectionData = await getIntersectionData();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 切换手动控制模式
|
|
|
+ toggleManualMode() {
|
|
|
+ this.isManualMode = !this.isManualMode;
|
|
|
+ if (!this.isManualMode) {
|
|
|
+ // 如果退出手动模式,可选择重置表单状态
|
|
|
+ this.showLockTime = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 模拟:根据控制方式改变下拉方案的数据
|
|
|
+ updateSchemeDataByMethod(method) {
|
|
|
+ if (method === 'system') {
|
|
|
+ this.schemeOptions = [
|
|
|
+ { label: '系统优化方案A', value: 'sys_a' },
|
|
|
+ { label: '系统优化方案B', value: 'sys_b' }
|
|
|
+ ];
|
|
|
+ this.currentScheme = 'sys_a';
|
|
|
+ } else {
|
|
|
+ this.schemeOptions = [
|
|
|
+ { label: '早高峰', value: 'early_peak' },
|
|
|
+ { label: '晚高峰', value: 'evening_peak' },
|
|
|
+ { label: '平峰', value: 'normal' }
|
|
|
+ ];
|
|
|
+ this.currentScheme = 'early_peak';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 取消按钮
|
|
|
+ onCancel() {
|
|
|
+ this.isManualMode = false;
|
|
|
+ this.showLockTime = false;
|
|
|
+ // 可以在此添加回滚初始数据的逻辑
|
|
|
+ },
|
|
|
+
|
|
|
+ // 需求5:点击确认按钮提交 + 表单验证
|
|
|
+ onConfirm() {
|
|
|
+ // 验证1:临时方案必须检查时间是否有效
|
|
|
+ if (this.currentMethod === 'temp') {
|
|
|
+ const isInvalid = this.currentStageList.some(item => !item.time || item.time <= 0);
|
|
|
+ if (isInvalid) {
|
|
|
+ alert('请输入有效的阶段时间 (必须大于0)!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证2:步进方案必须选择锁定类型
|
|
|
+ if (this.currentMethod === 'step') {
|
|
|
+ if (this.lockTimeType === 'timer' && !this.currentLocktime) {
|
|
|
+ alert('请选择解锁时间!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构造提交参数
|
|
|
+ const submitData = {
|
|
|
+ method: this.currentMethod,
|
|
|
+ scheme: this.currentScheme,
|
|
|
+ stages: this.currentMethod === 'temp' ? this.currentStageList : null,
|
|
|
+ lockConfig: this.currentMethod === 'step' ? {
|
|
|
+ type: this.lockTimeType,
|
|
|
+ time: this.lockTimeType === 'timer' ? this.currentLocktime : null
|
|
|
+ } : null
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('提交的数据:', submitData);
|
|
|
+
|
|
|
+ // 提交完成后可根据业务决定是否退出手动模式
|
|
|
+ // this.isManualMode = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.crossing-detail-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: 20px;
|
|
|
+ /* padding: 20px; */
|
|
|
+ height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-panel-left {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 685px;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-panel-right {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+.intersection-video-wrap {
|
|
|
+ width: 100%;
|
|
|
+ height: 300px;
|
|
|
+ flex: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.signal-timing-wrap {
|
|
|
+ flex: 1;
|
|
|
+ --s: 1;
|
|
|
+ width: 100%;
|
|
|
+ height: 80px;
|
|
|
+ min-width: 0;
|
|
|
+ background-color: transparent;
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ padding: calc(var(--s) * 10px);
|
|
|
+}
|
|
|
+
|
|
|
+.header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: calc(var(--s) * 15px);
|
|
|
+ color: #e0e6f1;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.title-area {
|
|
|
+ font-size: calc(var(--s) * 16px);
|
|
|
+}
|
|
|
+
|
|
|
+.main-title {
|
|
|
+ font-size: calc(var(--s) * 18px);
|
|
|
+ font-weight: bold;
|
|
|
+ margin-right: calc(var(--s) * 10px);
|
|
|
+}
|
|
|
+
|
|
|
+.sub-info {
|
|
|
+ font-size: calc(var(--s) * 12px);
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+.checkbox-area {
|
|
|
+ font-size: calc(var(--s) * 12px);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0.7;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.checkbox-area:hover {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.checkbox-mock {
|
|
|
+ width: calc(var(--s) * 14px);
|
|
|
+ height: calc(var(--s) * 14px);
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.5);
|
|
|
+ margin-right: calc(var(--s) * 6px);
|
|
|
+ border-radius: 2px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.checkbox-mock.is-checked {
|
|
|
+ background-color: #4da8ff;
|
|
|
+ border-color: #4da8ff;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ width: 100%;
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 80px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-overlay {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #758599;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/** 控制方法 */
|
|
|
+.control-method {
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.control-label-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ column-gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.control-label {
|
|
|
+ font-size: 28px;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.control-label-wrap span {
|
|
|
+ display: inline-block;
|
|
|
+}
|
|
|
+
|
|
|
+.operation-btn {
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+.operation-btn:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+.operation-btn.is-active {
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+
|
|
|
+.control-method .control-label-wrap {
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.control-scheme {
|
|
|
+ margin-top: 20px;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+.lock-time {
|
|
|
+ width: 40%;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow:
|
|
|
+ inset 0px 0px 10px 0px rgba(88, 146, 255, 0.4),
|
|
|
+ inset 20px 0px 30px -10px rgba(88, 146, 255, 0.15);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+.lock-time-label-wrap {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 8px 8px 0 0;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.lock-time-label {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.lock-time-options {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 10px;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.lock-time-option {}
|
|
|
+
|
|
|
+.lock-time-close {
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.glow-header {
|
|
|
+ background: linear-gradient(180deg,
|
|
|
+ rgba(65, 115, 205, 0.6) 0%,
|
|
|
+ rgba(40, 70, 130, 0.1) 100%);
|
|
|
+
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+}
|
|
|
+
|
|
|
+.current-stage {
|
|
|
+ background-color: rgba(65, 115, 205, 0.2);
|
|
|
+ border: 1px solid #3660a5;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.current-stage-warp {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 32px;
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+.current-stage-label {
|
|
|
+ font-size: 14px;
|
|
|
+ width: 100px;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-img {
|
|
|
+ border-radius: 5px;
|
|
|
+ width: 65px;
|
|
|
+ height: 65px;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-active {
|
|
|
+ background-blend-mode: multiply;
|
|
|
+ background-color: rgba(17, 23, 29, .5);
|
|
|
+}
|
|
|
+
|
|
|
+.stage-input {
|
|
|
+ width: 65px;
|
|
|
+ border: 1px solid rgba(161,190,255,0.7);
|
|
|
+ background-color: transparent;
|
|
|
+ padding: 5px;
|
|
|
+ color: #ffffff;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+/** 按钮 */
|
|
|
+/* 按钮基础通用样式 */
|
|
|
+.btn {
|
|
|
+ display: inline-flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 32px;
|
|
|
+ font-size: 14px;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ transition: all 0.2s ease-in-out;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+/* --- 取消按钮 (幽灵按钮) --- */
|
|
|
+.btn-cancel {
|
|
|
+ background-color: transparent;
|
|
|
+ color: #d1d5db;
|
|
|
+ border: 1px solid rgba(130, 150, 190, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-cancel:hover {
|
|
|
+ color: #ffffff;
|
|
|
+ border-color: rgba(130, 150, 190, 0.8);
|
|
|
+ background-color: rgba(255, 255, 255, 0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-cancel:active {
|
|
|
+ background-color: rgba(255, 255, 255, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+/* --- 确认按钮 (实心主按钮) --- */
|
|
|
+.btn-confirm {
|
|
|
+ background-color: #3b74ff;
|
|
|
+ color: #ffffff;
|
|
|
+ border: 1px solid #3b74ff;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm:hover {
|
|
|
+ background-color: #5a8bff;
|
|
|
+ border-color: #5a8bff;
|
|
|
+ box-shadow: 0 2px 8px rgba(59, 116, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.btn-confirm:active {
|
|
|
+ background-color: #265bed;
|
|
|
+ border-color: #265bed;
|
|
|
+ box-shadow: none;
|
|
|
+}
|
|
|
+
|
|
|
+.button-group {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.button-group>div {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 禁用状态:未点击手动控制时,使表单只读 */
|
|
|
+.form-interactive-area {
|
|
|
+ transition: opacity 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.form-interactive-area.is-disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ pointer-events: none; /* 核心:禁止鼠标事件,无法点击下拉框、单选和输入框 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 当前阶段输入框微调 */
|
|
|
+.stage-item-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-input {
|
|
|
+ width: 65px;
|
|
|
+ border: 1px solid rgba(161,190,255,0.7);
|
|
|
+ background-color: transparent;
|
|
|
+ padding: 5px;
|
|
|
+ color: #ffffff;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-input:disabled {
|
|
|
+ border-color: rgba(255, 255, 255, 0.2);
|
|
|
+ color: rgba(255, 255, 255, 0.5);
|
|
|
+ background-color: rgba(0, 0, 0, 0.2);
|
|
|
+}
|
|
|
+
|
|
|
+.stage-item-wrapper .unit {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 6px;
|
|
|
+ right: 8px;
|
|
|
+ color: #77A1FF;
|
|
|
+ font-size: 12px;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* 弹窗过渡动画 */
|
|
|
+.fade-enter-active, .fade-leave-active {
|
|
|
+ transition: opacity 0.3s, transform 0.3s;
|
|
|
+}
|
|
|
+.fade-enter, .fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-10px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 原有 lock-time 样式调整使其能绝对定位,类似弹窗 */
|
|
|
+.lock-time {
|
|
|
+ margin-top: 15px;
|
|
|
+ width: 60%;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow:
|
|
|
+ inset 0px 0px 10px 0px rgba(88, 146, 255, 0.4),
|
|
|
+ inset 20px 0px 30px -10px rgba(88, 146, 255, 0.15);
|
|
|
+ background-color: rgba(20, 30, 50, 0.9); /* 增加背景色防止透明穿透 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 单选框基础对齐 */
|
|
|
+.lock-time-option label {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+</style>
|