<template>
    <div class="crossing-detail-panel" :class="{ 'is-dual': isDual }">
        <!-- content-row：单图模式 display:contents 透明（保持原 row 布局），双图模式显形为 row 容器 -->
        <div class="content-row">
            <div class="detail-panel-left">
                <div class="intersection-video-wrap">
                    <IntersectionMapVideos :mapData="intersectionData" />
                </div>
                <!-- 单图模式：相位图保持在左侧栏内 -->
                <div v-if="!isDual" class="signal-timing-wrap">
                    <div class="header">
                        <div class="title-area">
                            <span class="main-title">方案状态</span>
                            <span class="sub-info">(周期: {{ cycleLength }} 相位差: {{ phaseDiff }} 协调时间: {{ coordTime }})</span>
                        </div>
                    </div>
                    <SignalTimingChart :cycleLength="cycleLength" :currentTime="currentSec"
                        :phaseData="mockPhaseData" :showScanLine="dataReady" :autoScan="dataReady"
                        @scan-tick="onScanTick" />
                </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 control-chips-row">
                            <div class="control-chips">
                                <!-- 手动开关 -->
                                <button type="button" class="chip chip-manual"
                                    :class="{ 'is-active': isManualMode }" @click="toggleManualMode">
                                    {{ isManualMode ? '解除手动' : '手动控制' }}
                                </button>
                                <span class="chip-divider"></span>

                                <!-- 紧急模式：关灯 / 黄闪 / 全红 -->
                                <button type="button" class="chip chip-mode-off"
                                    :class="{ 'is-active': currentMethod === 'lights_off' }"
                                    :disabled="!isManualMode" @click="setControlMethod('lights_off')">关灯</button>
                                <button type="button" class="chip chip-mode-yellow"
                                    :class="{ 'is-active': currentMethod === 'yellow_flash' }"
                                    :disabled="!isManualMode" @click="setControlMethod('yellow_flash')">黄闪</button>
                                <button type="button" class="chip chip-mode-red"
                                    :class="{ 'is-active': currentMethod === 'all_red' }"
                                    :disabled="!isManualMode" @click="setControlMethod('all_red')">全红</button>
                                <span class="chip-divider"></span>

<!-- 配时动作：弹窗 -->
                                <button type="button" class="chip chip-action"
                                    :disabled="!isManualMode" @click="openTempSchemeDialog">临时修改</button>
                                <button type="button" class="chip chip-action"
                                    :disabled="!isManualMode" @click="openSchemeDialog">修改方案</button>
                            </div>
                        </div>
                    </div>

                    <div class="form-interactive-area">
                        <div class="form-editable-area" :class="{ 'is-disabled': !isManualMode }">
                            <div class="control-scheme">
                                <div class="current-stage" ref="stageGrid">
                                    <div v-for="(item, index) in currentStageList" :key="index"
                                        class="stage-item-wrapper">
                                        <div class="phase-box" :class="{ 'is-active': item.value === currentStage }"
                                            @click="onStageClick(item.value)">
                                            <PhaseDiagram
                                                v-if="item.icons && item.icons.length"
                                                :icons="item.icons"
                                                :no="item.no || item.value"
                                                :arrow-color="item.arrowColor"
                                                :arrow-colors="item.arrowColors || {}"
                                                :bg-color="item.bgColor"
                                                :number-color="item.numberColor"
                                            />
                                            <img v-else :src="item.img" alt="stage" class="phase-image" />
                                        </div>

                                        <div class="bottom-controls">
                                            <div class="input-unit-wrapper">
                                                <input type="number" v-model.number="item.time" class="stage-input"
                                                    :disabled="!canEditStage"
                                                    :title="canEditStage ? '修改阶段时间' : '当前控制方式不可修改'" />
                                                <span class="unit">s</span>
                                            </div>
                                            <span class="percent">{{ stagePercent(item.time) }}</span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- 方案圆饼图: 从 .form-interactive-area 抽出, 作为 .form-group 直接子节点,
                         脱离 current-stage 的滚动上下文, 保证 4 阶段时圆饼图始终可见 -->
                    <div class="donut-row">
                        <div class="donut-item">
                            <div class="donut-title">实时方案（执行方案3）</div>
                            <PlanDonutChart :chartData="realtimeDonutData"
                                :centerValue="String(realtimeRemaining)" centerLabel="剩余时长" :showTotal="true"
                                :totalValue="cycleLength" :scale="panelScale" />
                        </div>
                        <div class="donut-item">
                            <div class="donut-title">下周期方案</div>
                            <PlanDonutChart :chartData="nextCycleDonutData" :centerValue="String(cycleLength)"
                                centerLabel="总时长" :showTotal="false" :scale="panelScale" />
                        </div>
                    </div>

                    <div class="button-group" v-show="isManualMode && currentMethod !== 'step'">
                        <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><!-- /.content-row -->

        <!-- 双图模式：相位图作为整宽底部行（跨左右两栏） -->
        <div v-if="isDual" class="signal-timing-wrap is-dual">
            <div class="header">
                <div class="title-area">
                    <span class="main-title">方案状态</span>
                    <span class="sub-info">(周期: {{ cycleLength }} 相位差: {{ phaseDiff }} 协调时间: {{ coordTime }})</span>
                </div>
            </div>
            <div class="timing-row timing-row-live">
                <div class="row-label">
                    本周期 实时<span v-if="thisCycle"> · {{ thisCycle.schemeName }}</span>
                </div>
                <div class="row-chart">
                    <SignalTimingChart :cycleLength="thisCycle.cycleLength" :currentTime="currentSec"
                        :phaseData="thisCycle.phaseData" :showScanLine="dataReady" :showScanLineLabel="dataReady"
                        :clipToActive="true" :compactScanLine="true" :autoScan="dataReady"
                        @scan-tick="onScanTick" />
                </div>
            </div>
            <div class="timing-row timing-row-last">
                <div class="row-label">
                    上周期 方案
                    <span v-if="lastCycle"> · 实际 {{ lastCycle.actualDuration }}s / 计划 {{ lastCycle.cycleLength }}s</span>
                </div>
                <div class="row-chart">
                    <SignalTimingChart v-if="lastCycle" :cycleLength="lastCycle.cycleLength" :currentTime="0"
                        :phaseData="lastCycle.phaseData" :showScanLine="false" :showScanLineLabel="false" />
                    <div v-else class="empty-placeholder">暂无上周期数据</div>
                </div>
            </div>
        </div>

        <!-- 步进锁定时间弹窗 -->
        <transition name="fade">
            <div class="lock-time-overlay" v-if="showLockTime" @click.self="showLockTime = false">
                <div class="lock-time-dialog">
                    <div class="lock-time-header">
                        <span class="lock-time-title">锁定时间</span>
                        <span class="lock-time-close" @click="showLockTime = false">✕</span>
                    </div>
                    <div class="lock-time-divider"></div>
                    <div class="lock-time-body">
                        <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" size="auto" @click.native.prevent />
                                    秒解锁
                                </label>
                            </div>
                        </div>
                        <div class="lock-time-actions">
                            <button type="button" class="btn btn-cancel"
                                @click="onLockTimeCancel">取消</button>
                            <button type="button" class="btn btn-confirm"
                                @click="onLockTimeConfirm">确认</button>
                        </div>
                    </div>
                </div>
            </div>
        </transition>
    </div>
</template>

<script>
import SignalTimingChart from '@/components/ui/SignalTimingChart.vue';
import IntersectionMapVideos from '@/components/ui/IntersectionMapVideos.vue';
import DropdownSelect from '@/components/ui/DropdownSelect.vue';
import PlanDonutChart from '@/components/ui/PlanDonutChart.vue';
import PhaseDiagram from '@/components/ui/PhaseDiagram.vue';

import { apiGetCrossingDetailData, apiSaveCrossingTempScheme, apiSaveCrossingScheme } from '@/api';

export default {
    name: 'CrossingPanel',
    components: {
        SignalTimingChart,
        IntersectionMapVideos,
        DropdownSelect,
        PlanDonutChart,
        PhaseDiagram,
    },
    props: {
        preloadedData: { type: Object, default: null },
        iconMode: { type: String, default: 'simple' }  // 'default' | 'simple'
    },
    // dialogManager 由 DashboardLayout 顶层 provide；用于"临时修改 / 修改方案"弹窗的打开与关闭
    inject: {
        dialogManager: { default: null },
    },
    data() {
        return {
            startDate: '2026-04-13',
            startTime: '14:03:06',
            endDate: '2026-04-13',
            endTime: '15:03:06',
            duration: null,
            period: null,
            // 核心状态控制
            isManualMode: false, // 是否处于手动控制模式
            showLockTime: false, // 是否显示锁定时间弹窗
            lockTimeType: 'continuous', // 锁定时间类型

            dataReady: false,
            followPhase: false,
            intersectionData: {},
            currentRoute: {},
            cycleLength: 140,
            currentSec: 0,
            phaseDiff: 0,
            coordTime: 0,
            mockPhaseData: [],
            panelScale: 1,

            // 控制方式数据
            controlMethodOptions: [],
            currentMethod: 'temp',
            currentScheme: 'early_peak',
            schemeOptions: [],
            // 实时方案圆饼图数据（从 phaseData 动态生成）
            realtimeDonutData: [],
            // 下周期方案圆饼图数据
            nextCycleDonutData: [],
            // 实时方案剩余时长
            realtimeRemaining: 0,
            // 各阶段基础数据（从 phaseData 解析）
            phaseStages: [],
            currentLocktime: 50,
            locktimeOptions: [],
            currentStage: '1',
            // 补充了 time 属性，用于双向绑定输入框的时间
            currentStageList: [],

            // 双相位图模式数据（仅当后端返回 thisCycle/lastCycle 时启用）
            thisCycle: null,
            lastCycle: null,
        }
    },
    computed: {
        // 双相位图模式：后端提供了 thisCycle 时启用，否则保持单图行为
        isDual() {
            return !!(this.thisCycle && this.thisCycle.phaseData && this.thisCycle.phaseData.length);
        },
        // 黄闪、关灯、全红时禁用控制方案
        isSchemeDisabled() {
            return ['yellow_flash', 'lights_off', 'all_red'].includes(this.currentMethod);
        },
        // 定周期、中心计划、感应控制、临时方案可编辑当前阶段
        canEditStage() {
            return ['fixed', 'system', 'sensor', 'temp'].includes(this.currentMethod);
        },
        // 时长选项：30, 60, 90 ... 300
        durationOptions() {
            const list = [];
            for (let i = 30; i <= 300; i += 30) list.push(i);
            return list;
        }
    },
    watch: {
        // 监听控制方式切换
        currentMethod(newVal) {
            // 切换到步进时不自动弹出锁定时间，等用户点击阶段再弹
            this.showLockTime = false;
            if (newVal === 'step') {
                this.syncLocktimeByStage();
            }

            // 模拟需求1：根据不同模式，切换对应的控制方案数据 (Mock 逻辑)
            this.updateSchemeDataByMethod(newVal);
        },
        // 阶段列表变化时重挂 ResizeObserver 到新的首个元素 (max-height 跟着重算)
        currentStageList() {
            this.$nextTick(() => this._observeFirstStageItem());
        },
        // 当前阶段切到第 5+ 个时, 自动滚到可见
        currentStage() {
            this.$nextTick(() => this._scrollCurrentStageIntoView());
        },
    },
    mounted() {
        this.initScaleObserver();
        if (this.preloadedData) {
            this.applyData(this.preloadedData);
        } else {
            this.loadData();
        }
    },
    beforeDestroy() {
        if (this._ro) this._ro.disconnect();
        if (this._stageRO) this._stageRO.disconnect();
        if (this._stageRaf) cancelAnimationFrame(this._stageRaf);
    },
    methods: {
        // 点击阶段：切换选中，步进模式下同时弹出锁定时间
        onStageClick(value) {
            this.currentStage = value;
            if (this.currentMethod === 'step') {
                this.showLockTime = true;
                this.syncLocktimeByStage();
            }
        },
        // 根据当前选中阶段同步锁定时间选项
        syncLocktimeByStage() {
            const stage = this.currentStageList.find(s => s.value === this.currentStage);
            if (stage && stage.locktimeOptions) {
                this.locktimeOptions = stage.locktimeOptions;
                // 如果当前值不在新选项中，重置为第一个
                const hasValue = this.locktimeOptions.some(o => o.value === this.currentLocktime);
                if (!hasValue) {
                    this.currentLocktime = this.locktimeOptions[0]?.value || null;
                }
            }
        },
        stagePercent(time) {
            const total = this.currentStageList.reduce((s, item) => s + (item.time || 0), 0);
            if (!total) return '0%';
            return Math.round(time / total * 100) + '%';
        },
        onScanTick(activeTime) {
            if (!this.mockPhaseData || this.mockPhaseData.length === 0) return;
            // 只看第一轨道(trackIdx=0)的相位
            const phase = this.mockPhaseData.find(p => p[0] === 0 && activeTime >= p[1] && activeTime < p[2]);
            if (!phase) return;

            const type = phase[5];       // green/stripe/yellow/red
            const iconValue = phase[6];  // 如 "STRAIGHT_DOWN,STRAIGHT_UP"
            const direction = phase[7];  // ns/ew
            const phaseName = phase[3];  // P1/P2 等
            const endTime = phase[2];
            const remaining = Math.max(0, Math.round(endTime - activeTime));

            const nsGreen = (type === 'green' && direction === 'ns');
            const ewGreen = (type === 'green' && direction === 'ew');

            // 从图标值解析当前允许的行驶方向类型
            // STRAIGHT→S, TURN_*_LEFT→L, *_UTURN→U
            let activeArrowTypes = [];
            if ((nsGreen || ewGreen) && iconValue) {
                const icons = iconValue.split(',');
                icons.forEach(ic => {
                    if (ic.includes('UTURN')) activeArrowTypes.push('U');
                    if (ic.includes('TURN') && !ic.includes('UTURN')) activeArrowTypes.push('L');
                    if (ic.includes('STRAIGHT')) activeArrowTypes.push('S');
                });
                // 去重
                activeArrowTypes = [...new Set(activeArrowTypes)];
            }

            // 人行道全红判断：只有 P1/P3 绿灯期间人行道才有绿灯，其余时段全红
            const pedAllRed = !(type === 'green' && (phaseName === 'P1' || phaseName === 'P3'));

            this.$set(this.intersectionData, 'signals', {
                pedAllRed,
                ns: {
                    phaseName: nsGreen ? ({ P1: '南北直行', P2: '南北左转' }[phaseName] || '南北') : (this.intersectionData.signals?.ns?.phaseName || '南北'),
                    time: remaining,
                    isGreen: nsGreen,
                    activeArrowTypes: nsGreen ? activeArrowTypes : []
                },
                ew: {
                    phaseName: ewGreen ? ({ P3: '东西直行', P4: '东西左转' }[phaseName] || '东西') : (this.intersectionData.signals?.ew?.phaseName || '东西'),
                    time: remaining,
                    isGreen: ewGreen,
                    activeArrowTypes: ewGreen ? activeArrowTypes : []
                }
            });

            // 更新实时方案圆饼图的已走时长
            this.updateRealtimeDonut(activeTime);

            // 双相位图模式：检测周期 wrap-around，触发 refetch 让后端把刚结束的周期作为 lastCycle 返回
            if (this.isDual) {
                if (this._prevTick != null && activeTime < this._prevTick - 1) {
                    this.refetchDetail();
                }
                this._prevTick = activeTime;
            }
        },
        async refetchDetail() {
            if (this._refetching) return;
            this._refetching = true;
            try {
                const nodeId = this.$attrs.id || this.id;
                const data = await apiGetCrossingDetailData(nodeId, { iconMode: this.iconMode });
                if (data) this.applyData(data);
            } finally {
                this._refetching = false;
            }
        },
        // 从 phaseData 解析4个阶段的绿灯时长，构建圆饼图数据
        buildDonutFromPhaseData() {
            const phaseData = this.mockPhaseData || [];
            const stageColors = ['#3b82f6', '#a855f7', '#14b8a6', '#f59e0b'];
            const stageLabels = ['1-南北直行', '2-南北左转', '3-东西直行', '4-东西左转'];

            // 提取 track 0 的绿灯相位（每阶段的第一个 green）
            const greenPhases = phaseData.filter(p => p[0] === 0 && p[5] === 'green');
            const stages = greenPhases.slice(0, 4).map((p, i) => {
                const total = p[8] || Math.floor(this.cycleLength / 4);
                return {
                    label: stageLabels[i] || `阶段${i + 1}`,
                    value: total,           // 阶段总时长
                    start: p[1],            // 阶段开始时间（从绿灯起始）
                    end: p[1] + total,      // 阶段结束时间
                    color: stageColors[i]
                };
            });
            this.phaseStages = stages;

            // 实时方案：已走时长 + 4个阶段
            this.realtimeDonutData = [
                { label: '已走时长', value: 0, color: '#8892a0' },
                ...stages.map(s => ({ label: s.label, value: s.value, color: s.color }))
            ];
            this.realtimeRemaining = this.cycleLength;

            // 下周期方案：4个阶段，初始数据与实时方案相同
            this.nextCycleDonutData = stages.map(s => ({
                label: s.label,
                value: s.value,
                color: s.color
            }));
        },
        // 根据扫描线时间更新实时方案圆饼图
        updateRealtimeDonut(activeTime) {
            if (this.phaseStages.length === 0) return;
            const elapsed = Math.round(activeTime);
            const remaining = Math.max(0, this.cycleLength - elapsed);
            this.realtimeRemaining = remaining;

            // 计算各阶段的剩余时长
            const stageValues = this.phaseStages.map(s => {
                if (activeTime >= s.end) return 0;            // 阶段已完成
                if (activeTime < s.start) return s.value;     // 阶段未开始
                return Math.max(0, Math.round(s.end - activeTime)); // 阶段进行中
            });

            this.realtimeDonutData = [
                { label: '已走时长', value: elapsed, color: '#8892a0' },
                ...this.phaseStages.map((s, i) => ({
                    label: s.label,
                    value: stageValues[i],
                    color: s.color
                }))
            ];
        },
        // 观察首个 stage-item-wrapper, 算出 2 行高度(含行间 gap) 写到 CSS 变量。
        // grid 的 height 锁在 2 行高度: 不足 8 个时下排留白; 超 8 个时按行滚动。
        // 防 ResizeObserver loop 警告: rAF 推到下一帧 + 值比较切断"测量→撑大→再测"循环。
        _observeFirstStageItem() {
            const grid = this.$refs.stageGrid;
            if (!grid) return;
            if (this._stageRO) {
                this._stageRO.disconnect();
                this._stageRO = null;
            }
            const first = grid.firstElementChild;
            if (!first) return;
            const schedule = () => {
                if (this._stageRaf) return;
                this._stageRaf = requestAnimationFrame(() => {
                    this._stageRaf = 0;
                    const f = grid.firstElementChild;
                    if (!f) return;
                    const rowH = f.offsetHeight;
                    if (rowH <= 0) return;
                    const cs = window.getComputedStyle(grid);
                    const gap = parseFloat(cs.rowGap || cs.gap || '0') || 0;
                    const reservedH = rowH * 2 + gap;
                    if (this._lastRowH === rowH && this._lastReservedH === reservedH) return;
                    this._lastRowH = rowH;
                    this._lastReservedH = reservedH;
                    grid.style.setProperty('--stage-row-h', rowH + 'px');
                    grid.style.setProperty('--stage-reserved-h', reservedH + 'px');
                });
            };
            this._stageRO = new ResizeObserver(schedule);
            this._stageRO.observe(first);
            schedule();
        },
        // 当前阶段切换后, 若它在第二行/之后, 自动滚到可见
        _scrollCurrentStageIntoView() {
            const grid = this.$refs.stageGrid;
            if (!grid) return;
            const idx = this.currentStageList.findIndex(s => s.value === this.currentStage);
            const el = idx >= 0 ? grid.children[idx] : null;
            if (el && typeof el.scrollIntoView === 'function') {
                el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
            }
        },
        initScaleObserver() {
            const ro = new ResizeObserver(entries => {
                const { width } = entries[0].contentRect;
                const s = Math.min(width / 1315, 1);
                this.$el.style.setProperty('--s', s);
                this.panelScale = s;
            });
            ro.observe(this.$el);
            this._ro = ro;
        },
        async loadData() {
            const nodeId = this.$attrs.id || this.id;
            const data = await apiGetCrossingDetailData(nodeId, { iconMode: this.iconMode });
            if (data) {
                this.applyData(data);
            }
        },
        // 给每个 stage 派生 icons (PhaseDiagram 渲染用):
        // 优先用后端直接给的 icons[], 否则从 direction 字符串拆 token。
        // 缺失时 PhaseDiagram 不渲染, 模板内 fallback 到旧的 <img :src="item.img">。
        _withIcons(stages) {
            return (stages || []).map(item => ({
                ...item,
                icons: item.icons || (item.direction || '').split(',').filter(Boolean),
            }));
        },
        applyData(data) {
            this.currentRoute = data.currentRoute || {};
            this.intersectionData = data.intersectionData || {};
            this.mockPhaseData = data.phaseData || [];
            this.cycleLength = data.cycleLength || 140;
            this.currentSec = data.currentTime || 0;
            this.phaseDiff = data.phaseDiff || 0;
            this.coordTime = data.coordTime || 0;
            this.currentStageList = this._withIcons(data.stageList);
            // 双相位图字段：后端没返回时为 null，UI 自动退化为单图模式
            this.thisCycle = data.thisCycle || null;
            this.lastCycle = data.lastCycle || null;
            this.buildDonutFromPhaseData();
            this.$nextTick(() => {
                this.dataReady = true;
            });
            this.schemeOptions = data.schemeOptions || [];
            if (data.currentScheme) this.currentScheme = data.currentScheme;
            if (data.controlMethodOptions) this.controlMethodOptions = data.controlMethodOptions;
            if (data.currentMethod) this.currentMethod = data.currentMethod;
            if (data.locktimeOptions) this.locktimeOptions = data.locktimeOptions;
            if (data.startDate) this.startDate = data.startDate;
            if (data.startTime) this.startTime = data.startTime;
            if (data.endDate) this.endDate = data.endDate;
            if (data.endTime) this.endTime = data.endTime;
            if (data.duration) this.duration = data.duration;
            if (data.period) this.period = data.period;
        },
        // 切换手动控制模式
        toggleManualMode() {
            this.isManualMode = !this.isManualMode;
            if (!this.isManualMode) {
                // 如果退出手动模式，可选择重置表单状态
                this.showLockTime = false;
            }
        },
        // 控制方式 chip 按钮点击：切 currentMethod 触发现有 watch（line ~272）逻辑
        setControlMethod(value) {
            if (!this.isManualMode) return;
            this.currentMethod = value;
        },

        // ====== 配时方案编辑弹窗 ======

        // 当前路口在弹窗里用的稳定 ID（不同路口独立弹窗，同一路口同一类型互斥）
        _crossingId() {
            return this.currentRoute?.id || this.$attrs.id || this.id || 'unknown';
        },
        _tempDialogId() { return `scheme-edit-temp-${this._crossingId()}`; },
        _schemeDialogId() { return `scheme-edit-permanent-${this._crossingId()}`; },

        // "临时修改"按钮：打开带时间区段的弹窗（同时关掉"修改方案"避免互相覆盖）
        openTempSchemeDialog() {
            if (!this.isManualMode || !this.dialogManager) return;
            // 互斥：先关掉对面弹窗（DashboardLayout provide 的方法名是 closeDialog，不是 handleDialogClose）
            this.dialogManager.closeDialog(this._schemeDialogId());

            const id = this._tempDialogId();
            this.dialogManager.openDialog({
                id,
                title: '修改临时配时方案',
                component: 'SchemeStageEditDialog',
                width: 720,
                height: 480,
                center: true,
                draggable: true,
                resizable: true,
                noPadding: true,         // 内容组件自管 padding
                showClose: true,
                data: {
                    dialogId: id,
                    showTimeRange: true,
                    payload: {
                        stages: JSON.parse(JSON.stringify(this.currentStageList || [])),
                        timeRange: {
                            startDate: this.startDate, startTime: this.startTime,
                            endDate: this.endDate, endTime: this.endTime,
                            duration: this.duration, period: this.period,
                        },
                        isFixedCycle: false,
                    },
                    onSave: (updated) => this.onTempSchemeSave(updated),
                    onCancel: null,
                },
            });
        },

        // "修改方案"按钮：打开不带时间区段的弹窗
        openSchemeDialog() {
            if (!this.isManualMode || !this.dialogManager) return;
            // 互斥：先关掉对面弹窗
            this.dialogManager.closeDialog(this._tempDialogId());

            const id = this._schemeDialogId();
            this.dialogManager.openDialog({
                id,
                title: '修改配时方案',
                component: 'SchemeStageEditDialog',
                width: 660,
                height: 420,
                center: true,
                draggable: true,
                resizable: true,
                noPadding: true,
                showClose: true,
                data: {
                    dialogId: id,
                    showTimeRange: false,
                    payload: {
                        stages: JSON.parse(JSON.stringify(this.currentStageList || [])),
                        isFixedCycle: false,
                    },
                    onSave: (updated) => this.onSchemeSave(updated),
                    onCancel: null,
                },
            });
        },

        async onTempSchemeSave(payload) {
            const id = this._crossingId();
            // 调后端保存（mock 当前直接 200，真实后端可能返回 schemeId / appliedAt 等）
            try {
                await apiSaveCrossingTempScheme(id, payload);
            } catch (e) {
                console.warn('[CrossingDetailPanel] saveTempScheme failed:', e);
            }
            // 本地写回（不阻塞 UI）
            if (Array.isArray(payload.stages)) this.currentStageList = this._withIcons(payload.stages);
            const tr = payload.timeRange || {};
            if (tr.startDate !== undefined) this.startDate = tr.startDate;
            if (tr.startTime !== undefined) this.startTime = tr.startTime;
            if (tr.endDate !== undefined) this.endDate = tr.endDate;
            if (tr.endTime !== undefined) this.endTime = tr.endTime;
            if (tr.duration !== undefined) this.duration = tr.duration;
            if (tr.period !== undefined) this.period = tr.period;
            this.buildDonutFromPhaseData();
            this.$emit('confirm', { method: 'temp', scheme: this.currentScheme, stages: this.currentStageList });
        },

        async onSchemeSave(payload) {
            const id = this._crossingId();
            try {
                await apiSaveCrossingScheme(id, { ...payload, schemeId: this.currentScheme });
            } catch (e) {
                console.warn('[CrossingDetailPanel] saveScheme failed:', e);
            }
            if (Array.isArray(payload.stages)) this.currentStageList = this._withIcons(payload.stages);
            this.buildDonutFromPhaseData();
            this.$emit('confirm', { method: this.currentMethod, scheme: this.currentScheme, stages: this.currentStageList });
        },

        // 模拟：根据控制方式改变下拉方案的数据
        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;
        },

        // 步进锁定时间弹窗：取消（不退出手动控制）
        onLockTimeCancel() {
            this.showLockTime = false;
        },

        // 步进锁定时间弹窗：确认（不退出手动控制）
        onLockTimeConfirm() {
            this.showLockTime = false;
            const submitData = {
                method: 'step',
                stage: this.currentStage,
                lockTimeType: this.lockTimeType,
                locktime: this.lockTimeType === 'timer' ? this.currentLocktime : null,
            };
            console.log('步进指令：', submitData);
            this.$emit('confirm', submitData);
        },

        // 需求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' ? {
                    stage: this.currentStage,
                    type: this.lockTimeType,
                    time: this.lockTimeType === 'timer' ? this.currentLocktime : null
                } : null
            };

            console.log('提交的数据：', submitData);

            // 提交完成后可根据业务决定是否退出手动模式
            // this.isManualMode = false;
        }
    }
}
</script>

<style scoped>
.crossing-detail-panel {
    --s: 1;
    position: relative;
    display: flex;
    flex-direction: row;
    gap: clamp(4px, calc(var(--s) * 12px), 12px);
    height: 100%;
    min-height: 0;
    overflow: hidden;
}

/* 双图模式：根节点变 column，content-row 变 row 容器，相位图作为整宽底部行 */
.crossing-detail-panel.is-dual {
    flex-direction: column;
    gap: clamp(4px, calc(var(--s) * 8px), 8px);
}

/* 单图模式：content-row 透明，子节点直接挂到根的 row 布局上（保持原行为） */
.content-row {
    display: contents;
}

/* 双图模式：content-row 显形为 row，承载视频+表单 */
.crossing-detail-panel.is-dual .content-row {
    display: flex;
    flex-direction: row;
    gap: clamp(4px, calc(var(--s) * 12px), 12px);
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    width: 100%;
}

/* ===== 左侧：还原原始固定 55% 占比 ===== */
.detail-panel-left {
    display: flex;
    flex-direction: column;
    flex: 0 0 55%;
    min-height: 0;
    min-width: 0;
}

/* ===== 右侧：flex 列容器 + 滚动兜底 ===== */
.detail-panel-right {
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
}

.intersection-video-wrap {
    width: 100%;
    min-height: 0;
    flex: 2;
}

.signal-timing-wrap {
    flex: 0 0 auto;
    min-height: 0;
    height: clamp(95px, calc(var(--s) * 166px), 166px);
    width: 100%;
    min-width: 0;
    background-color: transparent;
    box-sizing: border-box;
    position: relative;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding: clamp(3px, calc(var(--s) * 10px), 10px);
}

/* 双相位图模式：整宽底部行 */
.signal-timing-wrap.is-dual {
    height: clamp(100px, calc(var(--s) * 170px), 170px);
    padding: 0 clamp(3px, calc(var(--s) * 10px), 10px);
}
/* 双图模式下 header 紧贴下方相位图，避免无意义空白 */
.signal-timing-wrap.is-dual .header {
    margin-bottom: 0;
    line-height: 1;
    padding: 2px 0 0;
}
.signal-timing-wrap.is-dual .main-title {
    font-size: clamp(10px, calc(var(--s) * 14px), 14px);
}
.signal-timing-wrap.is-dual .sub-info {
    font-size: clamp(9px, calc(var(--s) * 12px), 12px);
}
.signal-timing-wrap.is-dual .timing-row {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.signal-timing-wrap.is-dual .row-label {
    flex: 0 0 auto;
    font-size: clamp(8px, calc(var(--s) * 11px), 11px);
    color: #9ca3af;
    padding: 0 4px;
    line-height: 1;
    margin-bottom: 2px;
    /* 强制单行 + 省略号：多窗口窄屏下"上周期 实际 / 计划" 这类长文本曾换成 2 行，
       挤压自己 row 内 chart 高度并视觉撑出上一条 chart 的 canvas 边界 */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.signal-timing-wrap.is-dual .row-chart {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    position: relative;
    /* 防御性：ECharts 极端情况下 label/markLine 可能溢出 canvas 边界，加裁切兜底 */
    overflow: hidden;
}
.signal-timing-wrap.is-dual .empty-placeholder {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6b7280;
    font-size: clamp(10px, calc(var(--s) * 12px), 12px);
}

.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: clamp(2px, calc(var(--s) * 6px), 15px);
    color: #e0e6f1;
    flex-shrink: 0;
}

.title-area {
    font-size: clamp(9px, calc(var(--s) * 16px), 16px);
}

.main-title {
    font-size: clamp(10px, calc(var(--s) * 18px), 18px);
    font-weight: bold;
    margin-right: clamp(4px, calc(var(--s) * 10px), 10px);
}

.sub-info {
    font-size: clamp(8px, calc(var(--s) * 12px), 12px);
    opacity: 0.8;
}

.checkbox-area {
    font-size: clamp(8px, calc(var(--s) * 12px), 12px);
    display: flex;
    align-items: center;
    cursor: pointer;
    opacity: 0.7;
    user-select: none;
}

.checkbox-area:hover {
    opacity: 1;
}

.checkbox-mock {
    width: clamp(10px, calc(var(--s) * 14px), 14px);
    height: clamp(10px, calc(var(--s) * 14px), 14px);
    border: 1px solid rgba(255, 255, 255, 0.5);
    margin-right: clamp(3px, calc(var(--s) * 6px), 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: 50px;
    overflow: hidden;
}

.loading-overlay {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #758599;
    font-size: 14px;
}

/* ===== 右侧表单内层容器 ===== */
.detail-right-form {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.form-group {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/** 控制方法 */
.control-method {
    color: #ffffff;
    flex-shrink: 0;
}

.control-label-wrap {
    display: flex;
    align-items: center;
    margin-bottom: clamp(4px, calc(var(--s) * 10px), 20px);
    column-gap: clamp(4px, calc(var(--s) * 10px), 20px);
	margin-top: clamp(4px, calc(var(--s) * 20px), 20px);
}

.control-label {
    font-size: clamp(12px, calc(var(--s) * 22px), 28px);
    color: #ffffff;
    white-space: nowrap;
}

.control-label-wrap span {
    display: inline-block;
}

.operation-btn {
    font-size: clamp(10px, calc(var(--s) * 16px), 16px);
    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: flex-start;
}
.control-chips-row {
    flex-wrap: wrap;
    gap: clamp(4px, calc(var(--s) * 8px), 10px);
}

/* ===== 新版控制方式 chip 按钮组 ===== */
.control-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: clamp(4px, calc(var(--s) * 6px), 8px);
}
.chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    outline: none;
    cursor: pointer;
    user-select: none;
    color: #ffffff;
    font-size: clamp(10px, calc(var(--s) * 14px), 14px);
    line-height: 1;
    padding: clamp(4px, calc(var(--s) * 6px), 8px) clamp(8px, calc(var(--s) * 14px), 16px);
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.06);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
    transition: filter 0.15s, opacity 0.15s, transform 0.05s, box-shadow 0.15s;
    white-space: nowrap;
}
.chip:hover:not(:disabled) {
    filter: brightness(1.15);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.32);
}
.chip:active:not(:disabled) {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.chip:focus-visible:not(:disabled) {
    box-shadow: 0 0 0 2px #ffffff, 0 1px 2px rgba(0, 0, 0, 0.2);
}
.chip:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
.chip.is-active {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55) inset, 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* 颜色 token（参考图） */
.chip-manual { background: #ff6b6b; }                  /* 解除手动 / 手动控制 */
.chip-mode-off { background: #4dd4ac; }                /* 关灯 */
.chip-mode-yellow { background: #fbbf24; color: #1f2937; } /* 黄闪：背景亮，文字反深色 */
.chip-mode-red { background: #ef4444; }                /* 全红 */
.chip-action { background: #3b82f6; }                  /* 临时修改 / 修改方案 */
.chip-action:hover:not(:disabled) { background: #2563eb; }
.chip-divider {
    width: 1px;
    height: clamp(14px, calc(var(--s) * 20px), 22px);
    background: rgba(127, 182, 255, 0.25);
    margin: 0 clamp(2px, calc(var(--s) * 4px), 6px);
}

.control-scheme {
    margin-top: clamp(4px, calc(var(--s) * 10px), 20px);
    /* 设置 font-size 供 DropdownSelect size="auto" 继承 */
    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
}

.control-scheme.is-disabled {
    opacity: 0.4;
    pointer-events: none;
}

.lock-time {
    width: 80%;
    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-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.45);
    z-index: 200;
    border-radius: inherit;
}

/* 步进锁定时间弹窗 */
.lock-time-dialog {
    background: linear-gradient(135deg, rgba(10, 25, 60, 0.97) 0%, rgba(20, 40, 90, 0.97) 100%);
    border: 1px solid rgba(161, 190, 255, 0.3);
    border-radius: 6px;
    min-width: 220px;
    max-width: 320px;
    width: 70%;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
}

.lock-time-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: clamp(4px, calc(var(--s) * 8px), 10px);
    border-radius: 6px 6px 0 0;
    color: #ffffff;
    background: linear-gradient(180deg,
            rgba(65, 115, 205, 0.6) 0%,
            rgba(40, 70, 130, 0.1) 100%);
    backdrop-filter: blur(10px);
}

.lock-time-title {
    font-size: clamp(10px, calc(var(--s) * 16px), 16px);
    color: #ffffff;
}

.lock-time-close {
    cursor: pointer;
    color: #ffffff;
}

.lock-time-body {
    padding: 0;
}

.lock-time-options {
    display: flex;
    flex-direction: column;
    row-gap: clamp(4px, calc(var(--s) * 10px), 10px);
    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
    padding: clamp(4px, calc(var(--s) * 10px), 10px);
    color: #ffffff;
}

.lock-time-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 8px 12px;
    border-top: 1px solid rgba(161, 190, 255, 0.15);
}

.lock-time-option {
    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
}

/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.25s;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}

/* 4 列固定网格; 始终锁 2 行 + 行间 gap (不足 8 阶段时第 2 行留白); 超 8 滚动 */
.current-stage {
    margin-bottom: clamp(4px, calc(var(--s) * 10px), 20px);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    /* 关键: grid 默认 align-content/items: stretch 会把内容拉伸填满 min-height,
       与 _observeFirstStageItem 测量形成"测量→撑大→再测→更撑大"循环。
       锁住行集合贴顶 + 行高按内容定, 切断循环。 */
    grid-auto-rows: max-content;
    align-content: start;
    align-items: start;
    gap: clamp(8px, calc(var(--s) * 18px), 24px);
    color: #ffffff;
    /* --stage-reserved-h 由 JS 测量首个 stage 后写入 (2 行高 + 行间 gap)。
       不给 fallback: 未写入前 var() 整体无效, min/max-height 退到 initial(0/none) */
    min-height: var(--stage-reserved-h);
    max-height: var(--stage-reserved-h);
    overflow-y: auto;
    overflow-x: hidden;
    /* Firefox 滚动条 */
    scrollbar-width: thin;
    scrollbar-color: rgba(161, 190, 255, 0.45) rgba(255, 255, 255, 0.04);
}

/* WebKit / Blink (Chrome / Edge / Electron) 滚动条样式 */
.current-stage::-webkit-scrollbar {
    width: 6px;
}
.current-stage::-webkit-scrollbar-track {
    background: rgba(255, 255, 255, 0.04);
    border-radius: 3px;
}
.current-stage::-webkit-scrollbar-thumb {
    background: rgba(161, 190, 255, 0.45);
    border-radius: 3px;
}
.current-stage::-webkit-scrollbar-thumb:hover {
    background: rgba(161, 190, 255, 0.75);
}

.stage-input {
    width: 100%;
    min-width: 0;
    border: 1px solid rgba(161, 190, 255, 0.7);
    background-color: transparent;
    padding: clamp(2px, calc(var(--s) * 5px), 5px);
    color: #ffffff;
    text-align: center;
}

.phase-box {
    position: relative;
    width: 100%;
    /* 尺寸上限由 .stage-item-wrapper 的 --item-max-w 控制, 与 bottom-controls 同宽 */
    max-width: var(--item-max-w);
    margin: 0 auto;
    aspect-ratio: 1 / 1;
    background: #E6F0FF;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.3s ease;
    box-sizing: border-box;
    overflow: hidden;
}

.phase-image {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.phase-box::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(30, 106, 255, 0.5);
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}

.phase-box.is-active::after {
    opacity: 1;
}

/** 按钮 */
.btn {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    height: clamp(22px, calc(var(--s) * 36px), 36px);
    padding: 0 clamp(10px, calc(var(--s) * 32px), 32px);
    font-size: clamp(9px, calc(var(--s) * 14px), 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;
    flex-shrink: 0;
    /* margin-top: auto: 在 .form-group(flex column) 里把按钮推到底, 即使 form 内容不满也贴底 */
    margin-top: auto;
    /* sticky 兜底: 内容溢出 .detail-panel-right 时(16/32 阶段且面板较矮), 滚动也能见 */
    position: sticky;
    bottom: 0;
    z-index: 5;
    padding-top: clamp(4px, calc(var(--s) * 10px), 20px);
}

.button-group>div {
    display: flex;
    gap: clamp(4px, calc(var(--s) * 8px), 12px);
}

/* 禁用状态 */
.form-interactive-area {
    transition: opacity 0.3s;
    /* donut-row 抽出去后, 内部只剩 current-stage (本身已锁 2 行高 + 内部滚动),
       不再需要 flex:1 撑高; auto 高度让 donut 紧贴 current-stage 下方, 不留空隙 */
    flex: 0 0 auto;
    min-height: 0;
    /* current-stage 内部已经有 overflow-y: auto, 这里不再二次滚动 */
    overflow: visible;
}

.form-editable-area.is-disabled {
    opacity: 0.6;
    pointer-events: none;
}

.DropdownSelect-is-disabled{
	opacity: 0.6;
}

/* 当前阶段输入框微调 */
.stage-item-wrapper {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: clamp(2px, calc(var(--s) * 4px), 6px);
    position: relative;
    /* phase-box 和 bottom-controls 共用同一份上限宽, 保证 svg 框与下面的输入框等宽对齐 */
    --item-max-w: clamp(60px, calc(var(--s) * 100px), 110px);
}

.bottom-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(4px, calc(var(--s) * 6px), 8px);
    /* 输入框和百分比的间距 */
    /* 与 phase-box 同宽: 共用 --item-max-w + 居中 */
    width: 100%;
    max-width: var(--item-max-w);
    margin: 0 auto;
    box-sizing: border-box;
}

/* 新增包裹层的相对定位 */
.input-unit-wrapper {
    position: relative;
    display: inline-block;
}

.stage-input {
    width: 100%;
    min-width: 0;
    border: 1px solid rgba(161, 190, 255, 0.7);
    background-color: transparent;
    padding: clamp(2px, calc(var(--s) * 5px), 5px);
    /* 给右侧留出空间，防止数字过长被 s 挡住 */
    padding-right: clamp(10px, calc(var(--s) * 16px), 16px);
    color: #ffffff;
    text-align: center;
    border-radius: 4px;
	font-size: clamp(9px, calc(var(--s) * 13px), 13px);
}

/* 修改 s 单位的定位方式为垂直居中 */
.input-unit-wrapper .unit {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: clamp(3px, calc(var(--s) * 6px), 8px);
    color: #77A1FF;
    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
    pointer-events: none;
}

/* 微调百分比的间距，让排版更紧凑 */
.stage-item-wrapper .percent {
    color: rgba(255, 255, 255, 0.5);
    font-size: clamp(9px, calc(var(--s) * 13px), 13px);
    white-space: nowrap;
}

.stage-input {
    width: 100%;
    min-width: 0;
    border: 1px solid rgba(161, 190, 255, 0.7);
    background-color: transparent;
    padding: clamp(2px, calc(var(--s) * 5px), 5px);
    font-size: clamp(9px, calc(var(--s) * 14px), 14px);
    color: #ffffff;
    text-align: center;
    border-radius: 4px;
    -moz-appearance: textfield;
}

.stage-input::-webkit-outer-spin-button,
.stage-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.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);
}

/* 弹窗过渡动画 */

/* lock-time 弹窗补充样式 */
.lock-time {
    margin-top: clamp(4px, calc(var(--s) * 10px), 15px);
    background-color: rgba(20, 30, 50, 0.9);
}

/* 单选框基础对齐 */
.lock-time-option label {
    display: flex;
    align-items: center;
    gap: clamp(3px, calc(var(--s) * 6px), 8px);
    cursor: pointer;
}

/* ===== 方案圆饼图左右布局 ===== */
.donut-row {
    display: flex;
    gap: clamp(4px, calc(var(--s) * 16px), 16px);
    width: 100%;
	margin-top: clamp(4px, calc(var(--s) * 20px), 20px);
}

.donut-item {
    flex: 1;
    min-width: 0;
}

.donut-title {
    font-size: clamp(12px, calc(var(--s) * 22px), 28px);
    color: #ffffff;
    margin-bottom: 4px;
}

/* ===== 时间表单栏布局 ===== */
.time-form-bar {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: clamp(3px, calc(var(--s) * 5px), 6px);
    margin-bottom: clamp(4px, calc(var(--s) * 10px), 20px);
}

/* 第二行：带标签的项各占 2 列（共 4 列，两项铺满一行） */
.time-form-bar .form-item-labeled {
    grid-column: span 2;
    display: flex;
    align-items: center;
    gap: clamp(3px, calc(var(--s) * 6px), 8px);
}

.time-form-bar .form-label {
    white-space: nowrap;
    flex-shrink: 0;
    color: rgba(200, 220, 255, 0.65);
    font-size: clamp(9px, calc(var(--s) * 12px), 13px);
}

.time-form-bar .form-item-labeled .el-select {
    flex: 1;
    min-width: 0;
}

/* ===== ElementUI 深色主题适配 ===== */

/* 覆盖 el-date-editor 固定宽度，让其跟随 grid 布局 */
.time-form-bar>>>.el-date-editor.el-input,
.time-form-bar>>>.el-date-editor.el-input__inner,
.time-form-bar>>>.el-select {
    width: 100%;
}

/* 输入框样式 */
.time-form-bar>>>.el-input__inner {
    background-color: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(161, 190, 255, 0.35);
    color: #e0e6f1;
    font-size: clamp(9px, calc(var(--s) * 11px), 11px);
    height: clamp(22px, calc(var(--s) * 28px), 28px);
    line-height: clamp(22px, calc(var(--s) * 28px), 28px);
    padding-left: clamp(22px, calc(var(--s) * 26px), 26px);
    padding-right: clamp(4px, calc(var(--s) * 6px), 6px);
}

.time-form-bar>>>.el-input__inner::placeholder {
    color: rgba(255, 255, 255, 0.3);
}

.time-form-bar>>>.el-input__inner:hover {
    border-color: rgba(161, 190, 255, 0.6);
}

.time-form-bar>>>.el-input__inner:focus {
    border-color: #3b74ff;
}

/* 图标颜色、尺寸跟随缩放 */
.time-form-bar>>>.el-input__prefix,
.time-form-bar>>>.el-input__suffix {
    color: rgba(255, 255, 255, 0.4);
}

.time-form-bar>>>.el-input__icon {
    font-size: clamp(9px, calc(var(--s) * 12px), 12px);
    line-height: clamp(22px, calc(var(--s) * 28px), 28px);
    width: clamp(18px, calc(var(--s) * 22px), 22px);
}
</style>