<template>
    <div class="running-status">
        <el-row :gutter="20">
            <el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="14" class="status-col">
                <!-- 设备模式和OTA升级部分 -->
                <el-row :gutter="20" class="mode-section">
                    <!-- 设备模式 -->
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
                        <el-card class="mode-card" shadow="hover">
                            <div class="mode-header">
                                <i class="el-icon-menu" style="font-size: 30px;color: #409EFF;"></i>
                                <span class="mode-title">
                                    <template v-if="boardOnline">{{ $t('device.running-status.866086-0') }}</template>
                                    <template v-else>设备离线</template>
                                </span>
                            </div>
                            <div class="mode-content">
                                <template v-if="boardOnline">
                                    <span class="title" :style="{ color: statusColor.background }">{{ title }}</span>
                                </template>
                                <template v-else>
                                    <span class="title offline">设备离线</span>
                                </template>
                            </div>
                        </el-card>
                    </el-col>

                    <!-- 设备升级 -->
                    <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
                        <el-card class="mode-card" shadow="hover">
                            <div class="mode-header">
                                <svg-icon icon-class="ota" style="font-size: 30px; color: #409EFF;" />
                                <span class="mode-title">{{ $t('device.running-status.866086-1') }}</span>
                            </div>
                            <div class="mode-content">
                                <el-button type="primary" size="mini" :plain="true" @click="viewVersion()">
                                    {{ $t('device.running-status.866086-44') }}
                                </el-button>
                            </div>
                        </el-card>
                    </el-col>
                </el-row>

                <!-- 编译信息卡片 -->
                <el-card class="mode-card" shadow="hover">
                    <div class="mode-header">
                        <div class="header-left">
                            <i class="el-icon-info"></i>
                            <span class="mode-title">编译信息</span>
                        </div>
                        <el-button type="text" @click="refreshBuildInfo" size="normal">
                            <i class="el-icon-refresh"></i> 刷新
                        </el-button>
                    </div>
                    <div class="mode-content">
                        <div class="info-item" v-loading="buildInfoLoading">
                            <span class="info-label">编译版本：</span>
                            <span class="info-value">{{ buildVersion || '加载中...' }}</span>
                        </div>
                        <div class="info-item" v-loading="buildInfoLoading">
                            <span class="info-label">编译时间：</span>
                            <span class="info-value">{{ buildTime || '加载中...' }}</span>
                        </div>
                    </div>
                </el-card>

                <!-- 基础设置（显卡） -->
                <el-card class="settings-card" shadow="hover">
                    <div slot="header" class="settings-header">
                        <span class="settings-title">基础设置</span>
                    </div>
                    <el-form :model="basicSettings" label-width="100px" style="margin-top: 20px;">
                        <el-form-item label="屏幕开关">
                            <el-switch v-model="basicSettings.screenEnabled" active-color="#13ce66"
                                inactive-color="#ff4949" @change="handleScreenSwitchChange">
                            </el-switch>
                        </el-form-item>
                        <el-form-item label="屏幕亮度">
                            <el-slider v-model="basicSettings.brightness" :min="0" :max="100"
                                :format-tooltip="formatBrightness" @change="handleBrightnessChange" style="width: 80%"
                                :disabled="!basicSettings.screenEnabled">
                            </el-slider>
                        </el-form-item>
                        <!-- <el-form-item>
                            <el-button type="danger" @click="handleClearScreen"
                                :disabled="!basicSettings.screenEnabled">清除屏幕</el-button>
                        </el-form-item> -->
                    </el-form>
                </el-card>

                <!-- 屏幕参数卡片 -->
                <el-card class="settings-card" shadow="hover">
                    <div slot="header" class="settings-header">
                        <span class="settings-title">屏幕参数</span>
                    </div>
                    <el-form :model="screenParams" label-width="120px" style="margin-top: 20px;">
                        <el-form-item label="屏幕模板">
                            <el-select v-model="screenParams.template" @change="handleTemplateChange">
                                <el-option v-for="tpl in screenTemplates" :key="tpl.value" :label="tpl.label"
                                    :value="tpl.value"></el-option>
                            </el-select>
                        </el-form-item>
                        <el-form-item label="OE极性">
                            <el-switch v-model="screenParams.oePolarity" :active-value="1"
                                :inactive-value="0"></el-switch>
                        </el-form-item>
                        <el-form-item label="DATA极性">
                            <el-switch v-model="screenParams.dataPolarity" :active-value="1"
                                :inactive-value="0"></el-switch>
                        </el-form-item>
                        <el-form-item label="屏宽">
                            <el-input-number v-model="screenParams.width" :min="1" :max="10000"></el-input-number>
                        </el-form-item>
                        <el-form-item label="屏高">
                            <el-input-number v-model="screenParams.height" :min="1" :max="10000"></el-input-number>
                        </el-form-item>
                        <el-form-item label="屏幕角度">
                            <el-radio-group v-model="screenParams.angle" @change="drawAddProgramPreview">
                                <el-radio :label="0">0°</el-radio>
                                <el-radio :label="90">90°</el-radio>
                                <el-radio :label="180">180°</el-radio>
                                <el-radio :label="270">270°</el-radio>
                            </el-radio-group>
                        </el-form-item>
                        <el-form-item>
                            <el-button type="primary" @click="handleSendScreenParams">下发参数</el-button>
                        </el-form-item>
                    </el-form>
                </el-card>

                <!-- 音频列表 -->
                <!-- <el-card class="audio-list-card" shadow="hover">
                    <div slot="header" class="audio-list-header">
                        <span class="audio-list-title">音频列表</span>
                        <el-button type="primary" size="mini" icon="el-icon-plus" @click="showAddAudioDialog">
                            添加音频
                        </el-button>
                    </div>
                    <el-table :data="audioList" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }"
                        border>
                        <el-table-column prop="id" label="序号" width="80" align="center">
                        </el-table-column>
                        <el-table-column prop="name" label="音频名称" min-width="150" align="center">
                        </el-table-column>
                        <el-table-column label="操作" width="120" align="center">
                            <template slot-scope="scope">
                                <el-button type="text" icon="el-icon-delete" @click="handleDeleteAudio(scope.row)">
                                </el-button>
                            </template>
</el-table-column>
<template slot="empty">
                            <div style="padding: 20px 0;">
                                <el-empty description="暂无音频数据"></el-empty>
                            </div>
                        </template>
</el-table>
</el-card> -->
            </el-col>

            <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
                <!-- 开机参数卡片（右侧） -->
                <el-card class="settings-card" shadow="hover">
                    <div slot="header" class="settings-header">
                        <span class="settings-title">开机参数</span>
                    </div>
                    <el-form :model="bootSettings" label-width="120px" style="margin-top: 20px;">
                        <el-form-item label="开机界面可选">
                            <el-switch v-model="bootSettings.showBootScreen"
                                @change="handleBootScreenSwitchChange"></el-switch>
                        </el-form-item>
                        <el-form-item label="重置开关">
                            <el-switch v-model="bootSettings.resetEnabled"
                                @change="handleResetSwitchChange"></el-switch>
                        </el-form-item>
                    </el-form>
                </el-card>

                <!-- 传感器阈值设置卡片 -->
                <el-card class="settings-card" shadow="hover">
                    <div slot="header" class="settings-header">
                        <span class="settings-title">传感器阈值设置</span>
                    </div>
                    <el-form :model="sensorThreshold" label-width="120px" style="margin-top: 20px;">
                        <el-form-item label="速度阈值">

                            <el-input-number v-model="sensorThreshold.speed" :min="0" :max="200" :step="1"
                                style="width: 100%">
                            </el-input-number>
                        </el-form-item>
                        <el-form-item label="温度阈值">
                            <el-input-number v-model="sensorThreshold.temp" :min="-50" :max="100" :step="1"
                                style="width: 100%">
                            </el-input-number>
                            <!-- <span style="margin-left: 8px; color: #909399;">°C</span> -->
                        </el-form-item>
                        <el-form-item label="湿度阈值">
                            <el-input-number v-model="sensorThreshold.humi" :min="0" :max="100" :step="1"
                                style="width: 100%">
                            </el-input-number>
                            <!-- <span style="margin-left: 8px; color: #909399;">%</span> -->
                        </el-form-item>
                        <el-form-item>
                            <div style="text-align: center;">
                                <el-button type="primary" @click="handleSendSensorThreshold">下发阈值</el-button>
                            </div>
                        </el-form-item>
                    </el-form>
                </el-card>

                <!-- 设备监测图表-->
                <el-row :gutter="20" v-if="deviceInfo.chartList.length > 0">
                    <el-col :xs="24" :sm="12" :md="12" :lg="24" :xl="12" v-for="(item, index) in deviceInfo.chartList"
                        :key="index">
                        <el-card shadow="hover" style="border-radius: 8px; margin-bottom: 20px">
                            <div ref="map" style="height: 230px; width: 185px; margin: 0 auto; margin-bottom: 15px">
                            </div>
                        </el-card>
                    </el-col>
                </el-row>
            </el-col>
        </el-row>

        <!-- 播放列表 - 占据整行宽度 -->
        <el-row :gutter="20" style="margin-top: 20px;">
            <el-col :xs="22" :sm="22" :md="22" :lg="22" :xl="22">
                <el-card class="default-list-card" shadow="hover">
                    <div slot="header" class="default-list-header">
                        <span class="default-list-title">节目列表</span>
                        <!-- <el-button type="primary" size="mini" icon="el-icon-plus" @click="showAddPlaylistDialog">
                            添加节目
                        </el-button> -->
                        <el-button type="success" size="mini" icon="el-icon-plus" @click="showAddProgramDialog">
                            添加自定义节目
                        </el-button>
                    </div>

                    <!-- 显卡节目列表 -->
                    <div v-if="programList.length > 0" style="margin-bottom: 20px;">
                        <h4 style="margin-bottom: 15px; color: #409EFF;">显卡节目列表</h4>
                        <el-table :data="programList" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }"
                            border>
                            <el-table-column prop="id" label="序号" width="80" align="center">
                            </el-table-column>
                            <el-table-column prop="name" label="节目名称" min-width="120" align="center">
                            </el-table-column>
                            <el-table-column prop="duration" label="时长(秒)" width="100" align="center">
                            </el-table-column>
                            <el-table-column prop="mode" label="模式" width="120" align="center">
                            </el-table-column>
                            <el-table-column prop="zones" label="分区数" width="100" align="center">
                            </el-table-column>
                            <el-table-column prop="content" label="内容" min-width="200" align="center">
                                <template slot-scope="scope">
                                    <el-tooltip :content="scope.row.content" placement="top">
                                        <span>{{ scope.row.content.length > 30 ? scope.row.content.substring(0, 30) +
                                            '...' : scope.row.content }}</span>
                                    </el-tooltip>
                                </template>
                            </el-table-column>
                            <el-table-column label="使能" width="100" align="center">
                                <template slot-scope="scope">
                                    <el-switch v-model="scope.row.enabled" :active-value="1" :inactive-value="0"
                                        @change="handleProgramEnableChange">
                                    </el-switch>
                                </template>
                            </el-table-column>
                            <el-table-column label="操作" width="150" align="center">
                                <template slot-scope="scope">
                                    <el-button type="text" icon="el-icon-edit" @click="editProgram(scope.row)">
                                        编辑
                                    </el-button>
                                    <el-button type="text" icon="el-icon-delete" @click="deleteProgram(scope.row)">
                                        删除
                                    </el-button>
                                </template>
                            </el-table-column>
                        </el-table>
                    </div>

                    <template v-if="programList.length === 0">
                        <div style="padding: 20px 0;">
                            <el-empty description="暂无节目列表数据"></el-empty>
                        </div>
                    </template>
                </el-card>
            </el-col>
        </el-row>

        <!-- 固件版本查看对话框 -->
        <el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openVersion" width="550px"
            append-to-body>
            <el-form ref="firmwareForm" label-width="100px" :model="firmwareParams" :inline="true" :rules="rules">
                <el-form-item :label="$t('device.running-status.866086-38')" prop="firmwareType">
                    <el-select v-model="deviceInfo.firmwareType" :placeholder="$t('firmware.index.222541-51')"
                        style="width: 350px" disabled>
                        <el-option v-for="item in firmwareTypeList" :key="item.value" :label="item.label"
                            :value="item.value"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item :label="$t('device.running-status.866086-39')" prop="">
                    <el-input :placeholder="$t('device.running-status.866086-40')" v-model="deviceInfo.firmwareVersion"
                        style="width: 350px" disabled>
                        <template slot="prepend">Version</template>
                    </el-input>
                </el-form-item>
            </el-form>

            <div slot="footer">
                <el-tooltip effect="dark" :content="$t('device.running-status.866086-41')" placement="top-start">
                    <el-button type="primary" @click="getLatestFirmware" :disabled="device.status !== 3">{{
                        $t('device.running-status.866086-42') }}</el-button>
                </el-tooltip>
                <el-button @click="cancel1">{{ $t('cancel') }}</el-button>
            </div>
        </el-dialog>

        <!-- 添加或修改产品固件对话框 -->
        <el-dialog :title="$t('device.running-status.866086-10')" :visible.sync="openFirmware" width="600px"
            append-to-body>
            <div v-if="!firmware" style="text-align: center; font-size: 16px">
                <i class="el-icon-success" style="color: #67c23a"></i>
                {{ $t('device.running-status.866086-11') }}
            </div>
            <div v-else-if="!compareVersions(deviceInfo.firmwareVersion, firmware.version)"
                style="text-align: center; font-size: 16px">
                <i class="el-icon-warning" style="color: #E6A23C"></i>
                {{ $t('device.running-status.866086-47') }}
            </div>
            <el-descriptions v-else :column="1" border size="large"
                :labelStyle="{ width: '150px', 'font-weight': 'bold' }">
                <template slot="title">
                    <el-link icon="el-icon-success" type="success" :underline="false">{{
                        $t('device.running-status.866086-12') }}</el-link>
                </template>
                <el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName
                }}</el-descriptions-item>
                <el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName
                }}</el-descriptions-item>
                <el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version
                }}</el-descriptions-item>
                <el-descriptions-item :label="$t('device.running-status.866086-16')">
                    <el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{
                        getDownloadUrl(firmware.filePath) }}</el-link>
                </el-descriptions-item>
                <el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark
                }}</el-descriptions-item>
            </el-descriptions>
            <div slot="footer" class="dialog-footer">
                <el-button type="success" @click="otaUpgrade"
                    v-if="firmware && compareVersions(deviceInfo.firmwareVersion, firmware.version)">
                    {{ $t('device.running-status.866086-18') }}
                </el-button>
                <el-button @click="cancel">{{ $t('cancel') }}</el-button>
            </div>
        </el-dialog>





        <!-- 添加/编辑自定义节目对话框 -->
        <el-dialog :title="isEditProgram ? '编辑自定义节目' : '添加自定义节目'" :visible.sync="addProgramDialogVisible" width="700px"
            append-to-body>
            <div class="sticky-canvas-preview">
                <canvas ref="addProgramPreviewCanvas" width="640px" height="240px"
                    style="width:100%;height:240px;border:1px solid #e0e0e0;border-radius:16px;box-shadow:0 2px 8px #e0e0e0;background:#FFFFFF00; margin-bottom: 20px;"></canvas>
            </div>
            <el-form :model="addProgramForm" ref="addProgramFormRef" label-width="60px" size="mini"
                style="margin-bottom:0;">
                <el-form-item label="节目模式" label-width="70px">
                    <el-select v-model="addProgramForm.mode" placeholder="请选择模式" @change="onAddProgramModeChange"
                        style="width:140px">
                        <el-option v-for="(item, idx) in addProgramModes" :key="idx" :label="item" :value="idx" />
                    </el-select>
                    <el-form-item label="节目时长" label-width="70px" style="display:inline-block;margin-left:24px;">
                        <el-input-number v-model="addProgramForm.duration" :min="1" :max="600" style="width:90px" /> 秒
                    </el-form-item>
                </el-form-item>
                <el-form-item label="节目备注" label-width="70px">
                    <el-input v-model="addProgramForm.remark" placeholder="请输入节目备注名称" style="width:100%" />
                </el-form-item>
                <el-divider style="margin:10px 0;" />
                <div v-for="(zone, zIdx) in addProgramForm.zones" :key="zIdx"
                    style="margin-bottom:10px;padding:10px 12px 8px 12px;border:1px solid #f0f0f0;border-radius:8px;background:#fafbfc;">
                    <div style="font-weight:bold;font-size:14px;margin-bottom:6px;color:#409EFF;">分区{{ zIdx + 1 }}</div>
                    <!-- 第一行：类型、常用语句、展示信息/图片路径 -->
                    <el-row :gutter="12" style="margin-bottom:2px;">
                        <el-col :span="6">
                            <el-form-item label="类型" label-width="42px">
                                <el-select v-model="zone.playType" placeholder="类型" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramPlayTypes" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="6" v-if="zone.playType === 0">
                            <el-form-item label="常用" label-width="42px">
                                <el-select v-model="zone.commonPhrase" placeholder="常用语句"
                                    @change="onAddProgramPhraseChange(zIdx)" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramCommonPhrases" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="15" v-if="zone.playType === 0">
                            <el-form-item label="展示信息" label-width="70px">
                                <el-input v-model="zone.displayText" type="textarea" placeholder="请输入展示信息" autosize
                                    style="width:100%" />
                            </el-form-item>
                        </el-col>
                        <el-col :span="19" v-if="zone.playType === 1">
                            <el-form-item label="图片选择" label-width="70px">
                                <el-select v-model="zone.image" placeholder="请选择图片" style="width:100%">
                                    <el-option v-for="item in addProgramTrafficImages" :key="item.value"
                                        :label="item.label" :value="item.value" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <!-- 新增：图片尺寸和颜色 -->
                    <el-row :gutter="12" v-if="zone.playType === 1" style="margin-bottom:2px;">
                        <el-col :span="8">
                            <el-form-item label="图片尺寸" label-width="70px">
                                <el-select v-model="zone.imageSize" placeholder="请选择尺寸" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramImageSizes" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="8">
                            <el-form-item label="图片颜色" label-width="70px">
                                <el-select v-model="zone.imageColor" placeholder="请选择颜色" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramImageColors" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <!-- 第二行：字体、字号、颜色、加粗、拉伸（仅文字） -->
                    <el-row :gutter="12" v-if="zone.playType === 0" style="margin-bottom:2px;">
                        <el-col :span="6">
                            <el-form-item label="字体" label-width="42px">
                                <el-select v-model="zone.font" placeholder="字体" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFonts" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="7">
                            <el-form-item label="字体(英)" label-width="70px">
                                <el-select v-model="zone.fontEn" placeholder="字体(英)" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFontEns" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="字号" label-width="42px">
                                <el-select v-model="zone.fontSize" placeholder="字号" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFontSizes" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="颜色" label-width="42px">
                                <el-select v-model="zone.fontColor" placeholder="颜色" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFontColors" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="6">
                            <el-form-item label="加粗" label-width="42px">
                                <el-select v-model="zone.fontBold" placeholder="加粗" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFontBold" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="6">
                            <el-form-item label="拉伸" label-width="42px">
                                <el-select v-model="zone.fontStretch" placeholder="拉伸" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramFontStretch" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <!-- 第三行：动画特效、速度、停留、水平、垂直 -->
                    <el-row :gutter="8" style="margin-bottom:2px;">
                        <el-col :span="6">
                            <el-form-item label="特效" label-width="42px">
                                <el-select v-model="zone.effect" placeholder="特效" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramEffects" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="速度" label-width="42px">
                                <el-select v-model="zone.speed" placeholder="速度" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramSpeeds" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="停留" label-width="42px">
                                <el-select v-model="zone.stayTime" placeholder="停留" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramStayTimes" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="6">
                            <el-form-item label="水平" label-width="42px">
                                <el-select v-model="zone.hAlign" placeholder="水平" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramHorizontalAlign" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                        <el-col :span="6">
                            <el-form-item label="垂直" label-width="42px">
                                <el-select v-model="zone.vAlign" placeholder="垂直" style="width:100%">
                                    <el-option v-for="(item, idx) in addProgramVerticalAlign" :key="idx" :label="item"
                                        :value="idx" />
                                </el-select>
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <!-- 第四行：X、宽、Y、高 -->
                    <el-row :gutter="8">
                        <el-col :span="5">
                            <el-form-item label="X" label-width="20px">
                                <el-input-number :value="zoneDisplay(zIdx).x" :min="0" style="width:100%"
                                    @input="setZoneDisplay(zIdx, 'x', $event)" />
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="宽" label-width="24px">
                                <el-input-number :value="zoneDisplay(zIdx).width" :min="1" style="width:100%"
                                    @input="setZoneDisplay(zIdx, 'width', $event)" />
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="Y" label-width="20px">
                                <el-input-number :value="zoneDisplay(zIdx).y" :min="0" style="width:100%"
                                    @input="setZoneDisplay(zIdx, 'y', $event)" />
                            </el-form-item>
                        </el-col>
                        <el-col :span="5">
                            <el-form-item label="高" label-width="24px">
                                <el-input-number :value="zoneDisplay(zIdx).height" :min="1" style="width:100%"
                                    @input="setZoneDisplay(zIdx, 'height', $event)" />
                            </el-form-item>
                        </el-col>
                    </el-row>
                </div>
                <div style="text-align: right;margin-top:8px;">
                    <el-button @click="addProgramDialogVisible = false" size="mini">取 消</el-button>
                    <el-button type="primary" @click="submitAddProgram" size="mini">{{ isEditProgram ? '更新' : '确定'
                    }}</el-button>
                </div>
            </el-form>
        </el-dialog>
    </div>
</template>

<script>
import { getLatestFirmware } from '@/api/iot/firmware';
import { serviceInvoke, serviceInvokeReply } from '@/api/iot/runstatus';
import { getOrderControl } from '@/api/iot/control';

export default {
    name: 'running-status',
    props: {
        device: {
            type: Object,
            default: null,
        },
    },
    watch: {
        device: {
            handler(newVal) {
                if (newVal && newVal.deviceId != 0) {
                    this.deviceInfo = newVal;
                    this.updateDeviceStatus(this.deviceInfo);
                    this.$nextTick(function () {
                        this.MonitorChart();
                    });
                    // console.log("物模型", JSON.stringify(this.deviceInfo.thingsModels));

                    if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) {
                        this.deviceInfo.thingsModels = this.device.thingsModels.sort((a, b) => b.order - a.order);
                    }
                    if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
                        this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
                    }
                }
            },
        },
        // 监听自定义节目参数变化，实时刷新预览
        addProgramForm: {
            handler() {
                this.$nextTick(() => {
                    this.prepareAddProgramPreviewAssets();
                    this.resetAddProgramPreviewPage();
                    this.resetAddProgramPreviewAnimState();
                    this.drawAddProgramPreview();
                    this.startAddProgramPreviewAutoPage();
                    this.startAddProgramPreviewAnimLoop();
                });
            },
            deep: true
        },
        addProgramDialogVisible(val) {
            if (val) {
                this.$nextTick(() => {
                    this.prepareAddProgramPreviewAssets();
                    this.resetAddProgramPreviewPage();
                    this.resetAddProgramPreviewAnimState();
                    this.drawAddProgramPreview();
                    this.startAddProgramPreviewAutoPage();
                    this.startAddProgramPreviewAnimLoop();
                });
            } else {
                this.stopAddProgramPreviewAutoPage();
                this.stopAddProgramPreviewAnimLoop();
            }
        },
        screenParams: {
            handler() {
                // 屏幕参数变化时，重置分区、刷新预览
                this.onAddProgramModeChange(this.addProgramForm.mode);
                this.prepareAddProgramPreviewAssets();
                this.resetAddProgramPreviewPage();
                this.resetAddProgramPreviewAnimState();
                this.drawAddProgramPreview();
            },
            deep: true
        },
    },

    data() {
        return {
            title: '设备控制',
            shadowUnEnable: false,
            statusColor: {
                background: '#67C23A',
                color: '#fff',
                maxWidth: '200px',
            },
            firmware: {},
            openFirmware: false,
            loading: true,
            deviceInfo: {
                deviceId: 0,
                serialNumber: '',
                productId: '',
                productName: '',
                status: 0,
                isShadow: 0,
                rssi: 0,
                firmwareVersion: '',
                wirelessVersion: '',
                firmwareType: 1,
                protocolCode: '',
                thingsModels: [],
                chartList: [],
            },
            firmwareParams: {
                firmwareType: '',
                versionInput: '',
            },
            monitorChart: [
                {
                    chart: {},
                    data: {
                        id: '',
                        name: '',
                        value: '',
                    },
                },
            ],
            openVersion: false,
            firmwareTypeList: [
                {
                    label: this.$t('firmware.index.222541-52'),
                    value: 1,
                },
                {
                    label: 'HTTP',
                    value: 2,
                },
            ],
            rules: {
                firmwareType: [
                    {
                        required: true,
                        message: this.$t('device.running-status.866086-43'),
                        trigger: 'blur',
                    },
                ],
            },
            // 基础设置
            basicSettings: {
                screenEnabled: true,
                brightness: 50
            },
            bootSettings: {
                showBootScreen: false,
                resetEnabled: false
            },
            // 传感器阈值设置
            sensorThreshold: {
                speed: 60,
                temp: 30,
                humi: 50
            },

            screenParams: {
                template: '',        // 屏幕模板
                oePolarity: '',      // OE极性
                dataPolarity: '',    // DATA极性
                width: '',           // 屏宽
                height: '',          // 屏高
                angle: ''            // 屏幕角度
            },
            // 新增：自定义节目相关
            addProgramDialogVisible: false,
            isEditProgram: false, // 是否为编辑模式
            editingProgram: null, // 当前编辑的节目
            addProgramForm: {
                mode: 0,
                duration: 10,
                remark: '',
                zones: [
                    {
                        playType: 0,
                        commonPhrase: 0,
                        displayText: '',
                        font: 0,
                        fontEn: 0, // 新增英文字体
                        fontShape: 0,
                        fontSize: 0,
                        fontColor: 0, // 红色（默认）
                        fontBold: 0,
                        fontStretch: 0,
                        image: '',
                        imageSize: 1, // 32x32
                        imageColor: 0, // 红色（默认）
                        effect: 0,
                        speed: 4,
                        stayTime: 5,
                        hAlign: 1,
                        vAlign: 1,
                        x: 0,
                        y: 0,
                        width: 32,
                        height: 64
                    }
                ]
            },
            addProgramModes: ['模式1', '模式2(上下)', '模式3(左右)', '模式4(上中下)', '模式5', '模式6'],
            addProgramPlayTypes: ['文字', '图片'],
            addProgramCommonPhrases: ['自定义', "公安交警正在巡逻",
                "公安交警停车检查",
                "前方事故减速慢行",
                "警察临检请您配合",
                "交警临检请您配合",
                "《《《《《",
                "》》》》》",
                "禁止停车",
                "交通管制禁止通行",
                "公安检查靠边停车",
                "前方事故道路封闭",
                "禁止掉头",
                "雨雪天气注意安全",
                "大雾天气减速慢行",
                "靠右停车接受检查",
                "警察临检靠边停车"],
            addProgramFonts: ['宋体(中)', '黑体(中)', '楷体(中)'],
            addProgramFontShapes: ['圆角(英)', '直角(英)'],
            addProgramFontSizes: ['16px', '24px', '32px'],
            addProgramFontColors: ['红色', '绿色', '黄色'],
            addProgramFontBold: ['不加粗', '加粗'],
            addProgramFontStretch: ['不拉伸', '横向拉伸', '纵向拉伸'],
            addProgramEffects: ['立即显示', '左移', '右移', '上移', '下移', '连续左移', '闪烁换页'],
            addProgramSpeeds: ['1X', '2X', '3X', '4X', '5X'],
            addProgramStayTimes: ['不停顿', '100毫秒', '500毫秒', '1秒', '2秒', '3秒', '4秒', '5秒', '6秒', '8秒', '10秒'],
            addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'],
            addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'],
            addProgramImageSizes: ['16x16', '32x32', '48x48', '64x64'],
            addProgramImageColors: ['红色', '绿色', '黄色'],
            addProgramFontEns: [
                '打字(英)', '长黑(英)', '白斜(英)', '线形(英)', '方斜(英)', '歌德(英)', '黑正(英)', '美术(英)', '手写(英)', '正圆(英)', '时钟(英)圆角(英)'
            ],
            // 自动分页相关
            addProgramPreviewPage: [0], // 每个分区当前页
            addProgramPreviewTimer: [], // 每个分区定时器
            addProgramPreviewAssets: [], // 每个分区的分行分页缓存
            addProgramPreviewAnimState: [], // 每个分区动画状态
            addProgramPreviewAnimFrame: null, // 动画主循环ID
            addProgramTrafficImages: [],
            screenTemplates: [
                { label: '自定义', value: '', params: { oe: '', data: '', w: '', h: '', angle: '' } },
                { label: '伸缩屏', value: 'flex', params: { oe: 1, data: 1, w: 64, h: 32, angle: 270 } },
                { label: '大屏', value: 'big', params: { oe: 1, data: 1, w: 64, h: 32, angle: 180 } },
                { label: '小屏', value: 'small', params: { oe: 1, data: 1, w: 64, h: 16, angle: 0 } }
            ],
            // 编译信息
            buildVersion: '',
            buildTime: '',
            buildInfoLoading: false,
            // 节目列表
            programList: [],
            // 节目使能状态数组
            programEnableList: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // 默认全部开启
            boardOnline: false,
        };
    },
    mounted() {
        this.loadTrafficImages();
        if (this.device && this.device.deviceId) {
            this.handleDeviceChange(this.device);
            this.initDataStatus();
            this.initData();
        }
        // 初始化自定义节目预览
        this.$nextTick(() => {
            this.drawAddProgramPreview();
        });
    },

    methods: {
        loadTrafficImages() {
            try {
                // 使用webpack的require.context动态加载图片
                const files = require.context('@/assets/traffic', false, /\.(png|jpe?g|svg|bmp)$/);
                this.addProgramTrafficImages = files.keys().map(key => {
                    const label = key.replace('./', '');
                    return {
                        label: label, // 例如: '1.png'
                        value: files(key) // Webpack处理后的路径
                    };
                });
            } catch (e) {
                console.error("加载交通标志图片失败, 请确认 'src/assets/traffic' 目录下有图片文件。", e);
                this.addProgramTrafficImages = [];
            }
        },

        // 获取暂停时间（毫秒）
        getPauseTime(stayTimeIndex) {
            const pauseTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000];
            return pauseTimeMap[stayTimeIndex] || 1000;
        },

        // 获取字体大小
        getFontSize(fontSizeIndex) {
            const fontSizeMap = [16, 24, 32];
            return fontSizeMap[fontSizeIndex] || 16;
        },

        // 获取图片编号（根据图片路径提取编号）
        getImageNumber(imagePath) {
            if (!imagePath) return 0;
            // 从图片路径中提取编号，根据显卡.md文档中的图片编号映射
            const imageNameMap = {
                '减速慢行.png': 0, // 注意安全
                '减速让行.png': 1, // 禁止驶入
                '掉头.png': 2, // 禁止掉头
                '禁止停车.png': 3, // 禁止停车
                '禁止通行.png': 4, // 禁止通行
                '限速40.png': 5, // 限速40
                '限速60.png': 6, // 限速60
                '限速80.png': 7, // 限速80
                '限速100.png': 8, // 限速100
                '限速120.png': 9, // 限速120
                '取消限速40.png': 25, // 取消限速40
                '取消限速60.png': 26, // 取消限速60
                '取消限速80.png': 27, // 取消限速80
                '取消限速100.png': 28, // 取消限速100
                '取消限速120.png': 29, // 取消限速120
                // 可以根据实际图片添加更多映射
            };
            const fileName = imagePath.split('/').pop();
            return imageNameMap[fileName] || 0;
        },

        // 获取图片尺寸
        getImageSize(imageSizeIndex) {
            const imageSizeMap = [16, 32, 48, 64];
            return imageSizeMap[imageSizeIndex] || 32;
        },

        // 解析节目名称（去除\0结束符）
        parseProgramName(rem) {
            if (!rem) return '未命名节目';
            return rem.replace(/\0/g, '');
        },

        // 获取模式名称
        getModeName(areaM) {
            const modeNames = ['模式1', '模式2(上下)', '模式3(左右)', '模式4(上中下)', '模式5', '模式6'];
            return modeNames[areaM] || `模式${areaM}`;
        },

        // 解析节目内容
        parseProgramContent(aLst) {
            if (!aLst || aLst.length === 0) return '无内容';

            const contents = [];
            aLst.forEach((area, areaIndex) => {
                if (area.pLst && area.pLst.length > 0) {
                    area.pLst.forEach((item, itemIndex) => {
                        if (item.typ === 0 && item.txt) {
                            // 文本类型
                            const text = item.txt.str ? item.txt.str.replace(/\0/g, '') : '';
                            if (text) {
                                contents.push(`分区${areaIndex + 1}: ${text}`);
                            }
                        } else if (item.typ === 1 && item.img) {
                            // 图片类型
                            const imageName = this.getImageNameByNumber(item.img.num);
                            contents.push(`分区${areaIndex + 1}: 图片${item.img.num}(${imageName})`);
                        }
                    });
                }
            });

            return contents.length > 0 ? contents.join('; ') : '无内容';
        },

        // 根据图片编号获取图片名称
        getImageNameByNumber(num) {
            const imageNameMap = {
                0: '注意安全',
                1: '禁止驶入',
                2: '禁止掉头',
                3: '禁止停车',
                4: '禁止通行',
                5: '限速40',
                6: '限速60',
                7: '限速80',
                8: '限速100',
                9: '限速120',
                25: '取消限速40',
                26: '取消限速60',
                27: '取消限速80',
                28: '取消限速100',
                29: '取消限速120'
            };
            return imageNameMap[num] || `图片${num}`;
        },

        // 查看节目详情
        viewProgram(program) {
            this.$alert(`
                <div style="text-align: left;">
                    <p><strong>节目名称：</strong>${program.name}</p>
                    <p><strong>播放时长：</strong>${program.duration}秒</p>
                    <p><strong>显示模式：</strong>${program.mode}</p>
                    <p><strong>分区数量：</strong>${program.zones}个</p>
                    <p><strong>节目内容：</strong>${program.content}</p>
                </div>
            `, '节目详情', {
                dangerouslyUseHTMLString: true,
                confirmButtonText: '确定'
            });
        },

        // 删除节目
        deleteProgram(program) {
            this.$confirm(`确认删除节目"${program.name}"吗？`, '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList');
                if (progListModel) {
                    try {
                        // 只发送删除指令，不带上整个节目列表
                        progListModel.shadow = 'JSON=' + JSON.stringify({ del_prog: program.order });
                        this.mqttPublish(this.deviceInfo, progListModel).then(() => {
                            this.$message.success('删除指令已发送');
                        }).catch(error => {
                            console.error('发送删除指令失败:', error);
                            this.$message.error('删除失败');
                        });
                    } catch (error) {
                        console.error('构建删除数据失败:', error);
                        this.$message.error('删除失败');
                    }
                } else {
                    this.$message.warning('未找到102#progList物模型');
                }
            }).catch(() => { });
        },

        // 编辑节目
        editProgram(program) {
            this.isEditProgram = true;
            this.editingProgram = program;

            // 解析节目数据并填充表单
            this.fillProgramForm(program.raw);

            // 显示弹窗
            this.addProgramDialogVisible = true;
        },

        // 填充节目表单数据
        fillProgramForm(programData) {
            if (!programData) return;

            // console.log('填充节目表单数据:', JSON.stringify(programData));

            // 重置表单
            this.addProgramForm = {
                mode: programData.areaM || 0,
                duration: programData.dur || 10,
                remark: this.parseProgramName(programData.rem) || '',
                zones: []
            };

            // 解析分区数据
            if (programData.aLst && programData.aLst.length > 0) {
                // console.log('解析分区数据:', JSON.stringify(programData.aLst));
                this.addProgramForm.zones = programData.aLst.map(area => {
                    let x = area.size ? area.size.x : 0;
                    let y = area.size ? area.size.y : 0;
                    let w = area.size ? area.size.l : 32;
                    let h = area.size ? area.size.h : 64;
                    if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
                        [x, y] = [y, x];
                        [w, h] = [h, w];
                    }
                    const zone = {
                        playType: 0,
                        commonPhrase: 0,
                        displayText: '',
                        font: 0,
                        fontEn: 0, // 新增英文字体
                        fontShape: 0,
                        fontSize: 0,
                        fontColor: 0, // 红色（默认）
                        fontBold: 0,
                        fontStretch: 0,
                        image: '',
                        imageSize: 1,
                        imageColor: 0, // 红色（默认）
                        effect: 0,
                        speed: 4,
                        stayTime: 5,
                        hAlign: 1,
                        vAlign: 1,
                        x: x,
                        y: y,
                        width: w,
                        height: h
                    };

                    // 解析播放项
                    if (area.pLst && area.pLst.length > 0) {
                        const item = area.pLst[0]; // 取第一个播放项
                        if (item.typ === 0 && item.txt) {
                            // 文本类型
                            zone.playType = 0;
                            zone.displayText = item.txt.str ? item.txt.str.replace(/\0/g, '') : '';
                            zone.font = item.txt.fCn ? item.txt.fCn - 1 : 0; // 中文字体索引
                            zone.fontEn = item.txt.fEn ? item.txt.fEn - 1 : 0; // 英文字体索引
                            zone.fontSize = item.txt.fS ? this.getFontSizeIndex(item.txt.fS) : 0; // 字体大小索引
                            zone.fontColor = item.txt.col - 1 || 0; // 文本颜色
                            zone.fontBold = item.txt.fW || 0; // 字体加粗
                            zone.fontStretch = item.txt.stch || 0; // 拉伸方向
                            // 调试输出
                            // console.log('编辑节目item.txt.hPos:', item.txt.hPos, 'item.txt.vPos:', item.txt.vPos);
                            // 修正：物模型hPos/vPos为1/2/3，UI下拉框为0/1/2
                            zone.hAlign = typeof item.txt.hPos === 'number' ? (item.txt.hPos - 1) : 0; // 水平对齐（1左，2中，3右）
                            zone.vAlign = typeof item.txt.vPos === 'number' ? (item.txt.vPos - 1) : 0; // 垂直对齐（1顶，2中，3底）
                            // console.log('zone.hAlign:', zone.hAlign, 'zone.vAlign:', zone.vAlign);
                        } else if (item.typ === 1 && item.img) {
                            // 图片类型
                            zone.playType = 1;
                            zone.image = this.getImagePathByNumber(item.img.num);
                            zone.imageSize = this.getImageSizeIndex(item.img.w); // 使用宽度作为尺寸
                            zone.imageColor = item.img.col || 0; // 图片颜色
                        }

                        // 解析动画效果
                        if (item.anim) {
                            zone.effect = item.anim.typ ? item.anim.typ - 1 : 0; // 动画类型需要减1
                            zone.speed = item.anim.spd || 4; // 动画速度
                            zone.stayTime = item.anim.pauseT ? this.getStayTimeIndex(item.anim.pauseT) : 5; // 暂停时间索引
                        }
                    }

                    return zone;
                });
            } else {
                // 如果没有分区数据，创建默认分区
                this.addProgramForm.zones = [{
                    playType: 0,
                    commonPhrase: 0,
                    displayText: '',
                    font: 0,
                    fontEn: 0, // 新增英文字体
                    fontShape: 0,
                    fontSize: 0,
                    fontColor: 0, // 红色（默认）
                    fontBold: 0,
                    fontStretch: 0,
                    image: '',
                    imageSize: 1,
                    imageColor: 0, // 红色（默认）
                    effect: 0,
                    speed: 4,
                    stayTime: 5,
                    hAlign: 1,
                    vAlign: 1,
                    x: 0,
                    y: 0,
                    width: 32,
                    height: 64
                }];
            }

            // 更新预览
            this.$nextTick(() => {
                this.onAddProgramModeChange(this.addProgramForm.mode); // 保证分区布局和模式同步
                this.drawAddProgramPreview();
            });
        },

        // 根据图片编号获取图片路径
        getImagePathByNumber(imageNumber) {
            const imageNumberMap = {
                0: '减速慢行.png',
                1: '减速让行.png',
                2: '掉头.png',
                3: '禁止停车.png',
                4: '禁止通行.png',
                5: '限速40.png',
                6: '限速60.png',
                7: '限速80.png',
                8: '限速100.png',
                9: '限速120.png',
                25: '取消限速40.png',
                26: '取消限速60.png',
                27: '取消限速80.png',
                28: '取消限速100.png',
                29: '取消限速120.png'
            };
            const fileName = imageNumberMap[imageNumber] || '减速慢行.png';
            return this.addProgramTrafficImages.find(img => img.label === fileName)?.value || '';
        },

        // 根据图片尺寸获取尺寸索引
        getImageSizeIndex(imageSize) {
            const sizeMap = [16, 32, 48, 64];
            return sizeMap.indexOf(imageSize) || 1;
        },

        // 根据字体大小获取字体大小索引
        getFontSizeIndex(fontSize) {
            const fontSizeMap = [16, 24, 32];
            return fontSizeMap.indexOf(fontSize) || 0;
        },

        // 根据暂停时间获取停留时间索引
        getStayTimeIndex(pauseTime) {
            const pauseTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000];
            return pauseTimeMap.indexOf(pauseTime) || 5;
        },

        // 根据图片编号获取图片名称
        getImageNameByNumber(imageNumber) {
            const imageNameMap = {
                0: '减速慢行',
                1: '减速让行',
                2: '掉头',
                3: '禁止停车',
                4: '禁止通行',
                5: '限速40',
                6: '限速60',
                7: '限速80',
                8: '限速100',
                9: '限速120',
                25: '取消限速40',
                26: '取消限速60',
                27: '取消限速80',
                28: '取消限速100',
                29: '取消限速120'
            };
            return imageNameMap[imageNumber] || `图片${imageNumber}`;
        },

        // 处理节目使能状态变化
        handleProgramEnableChange() {
            // 构建10个元素的使能状态数组
            const enableArray = new Array(10).fill(0); // 默认全部为0

            // 根据节目列表更新使能状态
            this.programList.forEach((program, index) => {
                if (index < 10) { // 确保不超过10个
                    enableArray[index] = program.enabled;
                }
            });

            this.programEnableList = enableArray;

            // 发送使能状态到设备
            const progEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progEn');
            if (progEnModel) {
                try {
                    const progEnData = {
                        prog_en: this.programEnableList
                    };
                    progEnModel.shadow = 'JSON=' + JSON.stringify(progEnData);
                    this.mqttPublish(this.deviceInfo, progEnModel).then(() => {
                        this.$message.success('节目使能状态已更新');
                    }).catch(error => {
                        console.error('发送节目使能状态失败:', error);
                        this.$message.error('更新失败');
                    });
                } catch (error) {
                    console.error('构建节目使能数据失败:', error);
                    this.$message.error('更新失败');
                }
            } else {
                this.$message.warning('未找到102#progEn物模型');
            }
        },

        // 刷新编译信息
        async refreshBuildInfo() {
            // console.log('programList', JSON.stringify(this.programList))
            try {
                // 发送102#model指令，值为1
                const model = this.deviceInfo.thingsModels.find(m => m.id === '102#model');
                if (model) {
                    model.shadow = '1';
                    await this.mqttPublish(this.deviceInfo, model);
                    this.$message.success('已发送刷新指令，编译信息将自动更新');
                } else {
                    this.$message.warning('未找到102#model物模型');
                }
            } catch (error) {
                console.error('发送刷新指令失败:', error);
                this.$message.error('发送刷新指令失败');
            }
        },

        // 获取编译信息
        getBuildInfo() {
            this.buildInfoLoading = true;
            try {
                // 从物模型102#info获取编译信息
                const infoModel = this.deviceInfo.thingsModels.find(model => model.id === '102#info');
                if (infoModel && infoModel.shadow) {
                    try {
                        const jsonStr = infoModel.shadow.replace('JSON=', '');
                        const data = JSON.parse(jsonStr);
                        this.buildVersion = data.versions || '未知版本';
                        this.buildTime = data.compile_time || '未知时间';
                    } catch (parseError) {
                        console.error('解析编译信息JSON失败:', parseError);
                        this.buildVersion = '未知版本';
                        this.buildTime = '未知时间';
                    }
                } else {
                    // 如果物模型不存在，使用默认值
                    this.buildVersion = '未知版本';
                    this.buildTime = '未知时间';
                }
            } catch (error) {
                console.error('获取编译信息失败:', error);
                this.buildVersion = '未知版本';
                this.buildTime = '未知时间';
            } finally {
                this.buildInfoLoading = false;
            }
        },
        //发送指令
        async mqttPublish(device, model) {
            const command = {};
            command[model.id] = model.shadow;
            const params = {
                deviceId: device.deviceId,
                modelId: model.modelId,
            };
            const response = await getOrderControl(params);
            if (response.code != 200) {
                this.$message({
                    type: 'warning',
                    message: response.msg,
                });
                return;
            }
            const data = {
                serialNumber: device.serialNumber,
                productId: device.productId,
                remoteCommand: command,
                identifier: model.id,
                modelName: model.name,
                isShadow: device.status != 3,
                type: model.type,
            };
            //设备在线状态判断
            if (this.device.status !== 3 && this.device.isShadow !== 1) {
                let title = '';
                if (this.device.status === 1) {
                    title = this.$t('device.device-variable.930930-0');
                } else if (this.device.status === 2) {
                    title = this.$t('device.device-variable.930930-1');
                } else {
                    title = this.$t('device.device-variable.930930-2');
                }
                this.$message({
                    type: 'warning',
                    message: title,
                });
                return;
            }
            if ((this.deviceInfo.protocolCode === 'MODBUS-TCP' || this.deviceInfo.protocolCode === 'MODBUS-RTU') && this.device.status === 3) {
                await serviceInvokeReply(data).then((response) => {
                    if (response.code === 200) {
                        this.$message({
                            type: 'success',
                            message: this.$t('device.running-status.866086-25'),
                        });
                    } else {
                        this.$message.error(response.msg);
                    }
                });
            } else {
                await serviceInvoke(data).then((response) => {
                    if (response.code === 200) {
                        this.$message({
                            type: 'success',
                            message: this.$t('device.running-status.866086-25'),
                        });
                    } else {
                        this.$message.error(response.msg);
                    }
                });
            }
        },

        // 保留原有的设备状态相关方法
        handleDeviceChange(device) {
            if (device && device.deviceId != 0) {
                const { firmwareVersion, wirelessVersion, firmwareType, ...res } = device;
                const data = {
                    version: firmwareType === 1 ? firmwareVersion : wirelessVersion,
                    firmwareType,
                    ...res,
                };
                this.deviceInfo = data;
                this.updateDeviceStatus(this.deviceInfo);
                this.$nextTick(() => {
                    this.MonitorChart();
                });
                if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) {
                    this.deviceInfo.thingsModels = this.deviceInfo.thingsModels.sort((a, b) => b.order - a.order);
                    this.updateBasicSettings(); // 更新基础设置
                    // this.printThingsModels();
                    this.getBuildInfo(); // 获取编译信息
                }
                if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
                    this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
                }
            }
        },

        // 更新基础设置
        updateBasicSettings() {


            if (!this.deviceInfo.thingsModels) return;
            this.updateBoardOnlineStatus();

            // 屏幕开关
            const screenEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#screenEn');
            if (screenEnModel) {
                this.basicSettings.screenEnabled = screenEnModel.shadow === '1';
            }

            // 屏幕亮度
            const luminanceModel = this.deviceInfo.thingsModels.find(model => model.id === '102#luminance');
            if (luminanceModel) {
                this.basicSettings.brightness = parseInt(luminanceModel.shadow) || 50;
            }

            // 开机参数
            const bootEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#bootEn');
            if (bootEnModel) {
                this.bootSettings.showBootScreen = bootEnModel.shadow === '1';
            }
            const factoryEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#factoryEn');
            if (factoryEnModel) {
                this.bootSettings.resetEnabled = factoryEnModel.shadow === '1';
            }

            // 屏幕参数
            const scrParamModel = this.deviceInfo.thingsModels.find(model => model.id === '102#scrParam');
            if (scrParamModel && scrParamModel.shadow) {
                try {
                    const jsonStr = scrParamModel.shadow.replace('JSON=', '');
                    const data = JSON.parse(jsonStr);
                    if (data.scr_param) {
                        this.screenParams.oePolarity = data.scr_param.oe;
                        this.screenParams.dataPolarity = data.scr_param.data;
                        this.screenParams.width = data.scr_param.w;
                        this.screenParams.height = data.scr_param.h;
                        this.screenParams.angle = data.scr_param.angle;
                    }
                } catch (error) {
                    console.error('解析屏幕参数失败:', error);
                }
            }

            // 传感器阈值设置
            const sensorThresholdModel = this.deviceInfo.thingsModels.find(model => model.id === '102#snsrThr');
            if (sensorThresholdModel && sensorThresholdModel.shadow) {
                try {
                    const jsonStr = sensorThresholdModel.shadow.replace('JSON=', '');
                    const data = JSON.parse(jsonStr);
                    if (data.snsr_thr) {
                        this.sensorThreshold.speed = data.snsr_thr.speed || 60;
                        this.sensorThreshold.temp = data.snsr_thr.temp || 30;
                        this.sensorThreshold.humi = data.snsr_thr.humi || 50;
                    }
                } catch (error) {
                    console.error('解析传感器阈值失败:', error);
                }
            }

            // 更新节目列表（如果有102#progList物模型）
            const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList');
            // console.log("progListModel", JSON.stringify(progListModel))
            if (progListModel && progListModel.shadow) {
                try {
                    const jsonStr = progListModel.shadow.replace('JSON=', '');
                    const data = JSON.parse(jsonStr);
                    if (data.prog_list && data.prog_list.length > 0) {
                        // 解析节目列表并更新显示
                        this.programList = data.prog_list.map((program, index) => {
                            return {
                                id: index + 1,
                                order: program.order,
                                name: this.parseProgramName(program.rem),
                                duration: program.dur,
                                mode: this.getModeName(program.areaM),
                                zones: program.aLst ? program.aLst.length : 0,
                                content: this.parseProgramContent(program.aLst),
                                enabled: this.programEnableList[index] || 0, // 使用使能状态数组，默认为0
                                raw: program
                            };
                        });
                        // console.log('解析后的节目列表:', this.programList);
                    } else {
                        this.programList = [];
                    }
                } catch (error) {
                    console.error('解析节目列表失败:', error);
                    this.programList = [];
                }
            }

            // 更新节目使能状态（如果有102#progEn物模型）
            const progEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progEn');
            if (progEnModel && progEnModel.shadow) {
                try {
                    const jsonStr = progEnModel.shadow.replace('JSON=', '');
                    const data = JSON.parse(jsonStr);
                    if (data.prog_en && Array.isArray(data.prog_en)) {
                        // 确保使能状态数组有10个元素
                        this.programEnableList = new Array(10).fill(0);
                        data.prog_en.forEach((enabled, index) => {
                            if (index < 10) {
                                this.programEnableList[index] = enabled;
                            }
                        });

                        // 更新节目列表中的使能状态
                        this.programList.forEach((program, index) => {
                            program.enabled = this.programEnableList[index] || 0;
                        });
                        // console.log('节目使能状态:', this.programEnableList);
                    }
                } catch (error) {
                    console.error('解析节目使能状态失败:', error);
                    // 如果解析失败，初始化为10个0
                    this.programEnableList = new Array(10).fill(0);
                }
                //在线离线
            }
        },

        printThingsModels() {
            console.log('当前物模型数据:', JSON.stringify(this.deviceInfo.thingsModels, null, 2));
            this.$message({
                message: '物模型数据已打印到控制台',
                type: 'success'
            });
        },



        // 保留其他必要的方法
        initData() {
            this.$busEvent.$on('updateData', (params) => {
                this.updateParam(params);
            });
        },

        // 处理设备上报的数据更新
        // updateParam(params) {
        //     console.log(1111111111)
        //     if (!params || !this.deviceInfo.thingsModels) return;

        //     const { serialNumber, productId, data } = params;
        //     if (data && this.deviceInfo.serialNumber === serialNumber) {
        //         // 更新物模型数据
        //         this.deviceInfo.thingsModels.forEach(model => {
        //             if (data[model.id] !== undefined) {
        //                 model.shadow = data[model.id];
        //             }
        //         });

        //         // 更新基础设置
        //         this.updateBasicSettings();
        //     }
        // },


        //更新参数值
        updateParam(params) {
            let { serialNumber, productId, data } = params;
            let isComplete = false;
            data = data.message;
            if (data) {
                for (let j = 0; j < data.length; j++) {
                    for (let k = 0; k < this.deviceInfo.thingsModels.length && !isComplete; k++) {
                        if (this.deviceInfo.thingsModels[k].id == data[j].id) {
                            const variable = this.deviceInfo.thingsModels[k];
                            // 普通类型(小数/整数/字符串/布尔/枚举)
                            if (this.deviceInfo.thingsModels[k].datatype.type == 'decimal' || this.deviceInfo.thingsModels[k].datatype.type == 'integer') {
                                variable.shadow = Number(data[j].value);
                            } else {
                                variable.shadow = data[j].value;
                            }
                        }
                        if (this.deviceInfo.thingsModels[k].datatype.type == 'object') {
                            // 对象类型
                            for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.params.length; n++) {
                                if (this.deviceInfo.thingsModels[k].datatype.params[n].id == data[j].id) {
                                    this.deviceInfo.thingsModels[k].datatype.params[n].shadow = data[j].value;
                                    isComplete = true;
                                    break;
                                }
                            }
                        } else if (this.deviceInfo.thingsModels[k].datatype.type == 'array') {
                            // 数组类型
                            if (this.deviceInfo.thingsModels[k].datatype.arrayType == 'object') {
                                // 1.对象类型数组,id为数组中一个元素,例如：array_01_gateway_temperature
                                if (String(data[j].id).indexOf('array_') == 0) {
                                    for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
                                        for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
                                            if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == data[j].id) {
                                                this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
                                                isComplete = true;
                                                break;
                                            }
                                        }
                                        if (isComplete) {
                                            break;
                                        }
                                    }
                                } else {
                                    // 2.对象类型数组，例如：gateway_temperature,消息ID添加前缀后匹配
                                    for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayParams.length; n++) {
                                        for (let m = 0; m < this.deviceInfo.thingsModels[k].datatype.arrayParams[n].length; m++) {
                                            let index = n > 9 ? String(n) : '0' + k;
                                            let prefix = 'array_' + index + '_';
                                            if (this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].id == prefix + data[j].id) {
                                                this.deviceInfo.thingsModels[k].datatype.arrayParams[n][m].shadow = data[j].value;
                                            }
                                        }
                                    }
                                }
                            } else {
                                // 整数、小数和字符串类型数组
                                for (let n = 0; n < this.deviceInfo.thingsModels[k].datatype.arrayModel.length; n++) {
                                    if (this.deviceInfo.thingsModels[k].datatype.arrayModel[n].id == data[j].id) {
                                        this.deviceInfo.thingsModels[k].datatype.arrayModel[n].shadow = data[j].value;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    // 图表数据
                    for (let k = 0; k < this.deviceInfo.chartList.length; k++) {
                        if (this.deviceInfo.chartList[k].id.indexOf('array_') == 0) {
                            // 数组类型匹配,例如：array_00_gateway_temperature
                            if (this.deviceInfo.chartList[k].id == data[j].id) {
                                this.deviceInfo.chartList[k].shadow = data[j].value;
                                // 更新图表
                                for (let m = 0; m < this.monitorChart.length; m++) {
                                    if (data[j].id == this.monitorChart[m].data.id) {
                                        let data = [
                                            {
                                                value: this.deviceInfo.chartList[k].shadow,
                                                name: this.monitorChart[m].data.name,
                                            },
                                        ];
                                        this.monitorChart[m].chart.setOption({
                                            series: [
                                                {
                                                    data: data,
                                                },
                                            ],
                                        });
                                        break;
                                    }
                                }
                            }
                        } else {
                            // 普通类型匹配
                            if (this.deviceInfo.chartList[k].id == data[j].id) {
                                this.deviceInfo.chartList[k].shadow = data[j].value;
                                // 更新图表
                                for (let m = 0; m < this.monitorChart.length; m++) {
                                    if (data[j].id == this.monitorChart[m].data.id) {
                                        let data = [
                                            {
                                                value: this.deviceInfo.chartList[k].shadow,
                                                name: this.monitorChart[m].data.name,
                                            },
                                        ];
                                        this.monitorChart[m].chart.setOption({
                                            series: [
                                                {
                                                    data: data,
                                                },
                                            ],
                                        });
                                        break;
                                    }
                                }
                            }
                        }
                        if (isComplete) {
                            break;
                        }
                    }
                }
            }
            this.updateBasicSettings();
            this.getBuildInfo(); // 更新编译信息
        },






        initDataStatus() {
            this.$busEvent.$on('updateStatus', (status) => {
                this.updateStatus(status);
            });
        },

        updateStatus(status) {
            let { serialNumber, productId, data } = status;
            if (data) {
                if (this.deviceInfo.serialNumber == serialNumber) {
                    this.deviceInfo.status = data.status;
                    this.deviceInfo.isShadow = data.isShadow;
                    this.deviceInfo.rssi = data.rssi;
                    this.updateDeviceStatus(this.deviceInfo);
                }
            }
        },

        updateDeviceStatus(device) {
            if (device.status == 3) {
                this.statusColor.background = '#12d09f';
                this.title = this.$t('device.running-status.866086-26');
                this.shadowUnEnable = false;
            } else {
                if (device.isShadow == 1) {
                    this.statusColor.background = '#486FF2';
                    this.title = this.$t('device.running-status.866086-27');
                    this.shadowUnEnable = false;
                } else {
                    this.statusColor.background = '#909399';
                    this.title = this.$t('device.running-status.866086-28');
                    this.shadowUnEnable = true;
                }
            }
            this.$emit('statusEvent', this.deviceInfo.status);
        },

        // 保留固件更新相关方法
        viewVersion() {
            this.openVersion = true;
            this.firmwareParams.firmwareType = 1;
            this.firmwareParams.versionInput = '';
            this.handleVersionInputChange();
        },

        //选择改变
        handleVersionInputChange() {
            if (this.firmwareParams.firmwareType == 1) {
                this.firmwareParams.versionInput = 'Version' + this.device.firmwareVersion;
            } else {
                this.firmwareParams.versionInput = 'Version' + this.device.wirelessVersion;
            }
        },
        cancel1() {
            this.openVersion = false;
        },

        // 添加版本比较方法
        compareVersions(version1, version2) {
            // 移除可能的'Version'前缀
            version1 = String(version1).replace('Version', '').trim();
            version2 = String(version2).replace('Version', '').trim();

            const v1Parts = version1.split('.').map(Number);
            const v2Parts = version2.split('.').map(Number);

            for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
                const v1 = v1Parts[i] || 0;
                const v2 = v2Parts[i] || 0;

                if (v1 < v2) return true;
                if (v1 > v2) return false;
            }
            return false;
        },

        /** 获取最新固件 */
        getLatestFirmware() {
            const { deviceId, firmwareType } = this.deviceInfo;
            if (!deviceId || !firmwareType) {
                this.$message.error('设备信息不完整');
                return;
            }
            getLatestFirmware(deviceId, firmwareType).then((response) => {
                if (response.code === 200) {
                    if (response.data) {
                        this.firmware = response.data;
                        // 添加版本比较的调试信息
                        console.log('当前版本:', this.deviceInfo.firmwareVersion);
                        console.log('新版本:', this.firmware.version);
                        console.log('版本比较结果:', this.compareVersions(this.deviceInfo.firmwareVersion, this.firmware.version));
                        this.openFirmware = true;
                    } else {
                        this.$message.info('当前已是最新版本');
                        this.openFirmware = true;
                    }
                } else {
                    this.$message.error(response.msg || '获取固件信息失败');
                }
            }).catch(error => {
                console.error('获取固件信息失败:', error);
                this.$message.error('获取固件信息失败');
            });
        },
        /** 设备升级 */
        otaUpgrade() {
            // OTA升级
            let topic = '/' + this.deviceInfo.productId + '/' + this.deviceInfo.serialNumber + '/ota/get';
            let message = '{"version":' + this.firmware.version + ',"downloadUrl":"' + this.getDownloadUrl(this.firmware.filePath) + '"}';
            // 发布
            this.$mqttTool
                .publish(topic, message, this.$t('device.running-status.866086-31'))
                .then((res) => {
                    this.$modal.notifySuccess(res);
                })
                .catch((res) => {
                    this.$modal.notifyError(res);
                });
            this.openFirmware = false;
        },

        cancel() {
            this.openFirmware = false;
        },

        /** 获取下载路径 */
        getDownloadUrl(path) {
            return window.location.origin + process.env.VUE_APP_BASE_API + path;
        },

        MonitorChart() {
            for (let i = 0; i < this.deviceInfo.chartList.length; i++) {
                this.monitorChart[i] = {
                    chart: this.$echarts.init(this.$refs.map[i]),
                    data: {
                        id: this.deviceInfo.chartList[i].id,
                        name: this.deviceInfo.chartList[i].name,
                        value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
                    },
                };
                var option;
                option = {
                    tooltip: {
                        formatter: ' {b} <br/> {c}' + this.deviceInfo.chartList[i].datatype.unit,
                    },
                    series: [
                        {
                            name: this.deviceInfo.chartList[i].datatype.type,
                            type: 'gauge',
                            min: this.deviceInfo.chartList[i].datatype.min,
                            max: this.deviceInfo.chartList[i].datatype.max,
                            colorBy: 'data',
                            splitNumber: 10,
                            radius: '100%',
                            splitLine: {
                                distance: 4,
                            },
                            axisLabel: {
                                fontSize: 10,
                                distance: 10,
                            },
                            axisTick: {
                                distance: 4,
                            },
                            axisLine: {
                                lineStyle: {
                                    width: 8,
                                    color: [
                                        [0.2, '#409EFF'],
                                        [0.8, '#12d09f'],
                                        [1, '#F56C6C'],
                                    ],
                                    opacity: 0.3,
                                },
                            },
                            pointer: {
                                icon: 'triangle',
                                length: '60%',
                                width: 7,
                            },
                            progress: {
                                show: true,
                                width: 8,
                            },
                            detail: {
                                valueAnimation: true,
                                formatter: '{value}' + ' ' + this.deviceInfo.chartList[i].datatype.unit,
                                offsetCenter: [0, '80%'],
                                fontSize: 20,
                            },
                            data: [
                                {
                                    value: this.deviceInfo.chartList[i].shadow ? this.deviceInfo.chartList[i].shadow : this.deviceInfo.chartList[i].datatype.min,
                                    name: this.deviceInfo.chartList[i].name,
                                },
                            ],
                            title: {
                                offsetCenter: [0, '115%'],
                                fontSize: 16,
                            },
                        },
                    ],
                };
                option && this.monitorChart[i].chart.setOption(option);
            }
        },

        // 录音相关方法
        floatTo16BitPCM(input) {
            const output = new Int16Array(input.length);
            for (let i = 0; i < input.length; i++) {
                let s = Math.max(-1, Math.min(1, input[i]));
                output[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
            }
            return output;
        },
        async startRecording() {
            if (this.isRecording) return;
            try {
                const stream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                        sampleRate: 44100,
                        channelCount: 2,
                        sampleSize: 16,
                        autoGainControl: false,
                        noiseSuppression: false,
                        echoCancellation: false
                    }
                });
                const Mp3Encoder = window.lamejs.Mp3Encoder;
                this._mp3Encoder = new Mp3Encoder(2, 44100, 128);
                this._audioChunks = [];
                this._audioContext = new AudioContext({ sampleRate: 44100 });
                this._sourceNode = this._audioContext.createMediaStreamSource(stream);
                this._processorNode = this._audioContext.createScriptProcessor(4096, 2, 2);
                this._stream = stream;
                this._processorNode.onaudioprocess = (event) => {
                    const leftChannel = event.inputBuffer.getChannelData(0);
                    const rightChannel = event.inputBuffer.getChannelData(1);
                    // 修正：Float32转Int16
                    const left = this.floatTo16BitPCM(leftChannel);
                    const right = this.floatTo16BitPCM(rightChannel);
                    const mp3Buffer = this._mp3Encoder.encodeBuffer(left, right);
                    if (mp3Buffer.length > 0) {
                        this._audioChunks.push(mp3Buffer);
                    }
                };
                this._sourceNode.connect(this._processorNode);
                this._processorNode.connect(this._audioContext.destination);
                this.isRecording = true;
                this.recordingStatus = '正在录音...';
                this.recordingTime = 0;
                this.timer = setInterval(() => this.recordingTime++, 1000);
            } catch (error) {
                console.error('录音失败:', error);
                this.$message.error(`录音失败: ${error.message}`);
            }
        },

        stopRecording() {
            if (!this.isRecording) return;
            clearInterval(this.timer);
            if (this._sourceNode) this._sourceNode.disconnect();
            if (this._processorNode) this._processorNode.disconnect();
            if (this._stream) this._stream.getTracks().forEach(track => track.stop());
            // 刷新编码器缓冲区
            if (this._mp3Encoder) {
                const lastChunk = this._mp3Encoder.flush();
                if (lastChunk.length > 0) this._audioChunks.push(lastChunk);
            }
            // 生成 MP3 文件
            const mp3Blob = new Blob(this._audioChunks, { type: 'audio/mp3' });
            this.audioUrl = URL.createObjectURL(mp3Blob);
            this.hasRecording = true;
            this.recordingStatus = '录音完成';
            this.audioChunks = this._audioChunks;
            this.isRecording = false;
        },

        formatTime(seconds) {
            const minutes = Math.floor(seconds / 60);
            const remainingSeconds = seconds % 60;
            return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
        },

        reRecord() {
            // 释放之前的音频URL
            if (this.audioUrl) {
                URL.revokeObjectURL(this.audioUrl);
                this.audioUrl = null;
            }
            this.hasRecording = false;
            this.audioChunks = [];
            this.recordingStatus = '准备就绪';
            this.recordingTime = 0;  // 重置录音时长
        },

        async uploadRecording() {
            if (!this.hasRecording) return;

            try {
                const audioBlob = new Blob(this.audioChunks, { type: 'audio/mp3' });
                const formData = new FormData();
                formData.append('file', audioBlob, `recording_${Date.now()}.mp3`);

                // 强制使用 HTTP 协议，因为接口只支持 HTTP
                const uploadUrl = 'https://iot-xcwl.cn/common/upload/audio';
                // const uploadUrl = 'http://1.94.62.14:8080/common/upload/audio';

                // 调用上传接口
                const response = await fetch(uploadUrl, {
                    method: 'POST',
                    headers: {
                        'Authorization': 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImU3MWM2OTg4LTNlMzMtNDYyMy05M2M3LWE4YzZmMTNlMjZkZSJ9.wgsL8b3WDmyuesG8JTA3LcNFp2FigkB90h6Inwxt7OFadH6rc5np5TjAyU1pzU2_b5cmG8BYXMEdAqEdJzoDcA'
                    },
                    body: formData
                });

                if (response.ok) {
                    const result = await response.json();
                    if (result.code === 200) {
                        this.$message.success('上传成功');
                        this.recordings.unshift({
                            name: `录音_${this.formatTime(this.recordingTime)}`,
                            time: new Date().toLocaleString()
                        });

                        // 提取返回的 URL 并通过 MQTT 下发到 103#onlinePlay
                        if (result.url) {
                            const onlinePlayModel = this.deviceInfo.thingsModels.find(model => model.id === '103#onlinePlay');
                            if (onlinePlayModel) {
                                // 构建包含 interrupt 参数的 JSON 数据
                                const playData = 'JSON=' + JSON.stringify({
                                    online_play: {
                                        url: "http://1.94.62.14:8080" + result.resourcePath,
                                    },
                                    interrupt: 98
                                });
                                onlinePlayModel.shadow = playData;
                                await this.mqttPublish(this.deviceInfo, onlinePlayModel);
                                this.$message.success('音频已下发到设备');
                            } else {
                                this.$message.warning('未找到 103#onlinePlay 物模型');
                            }
                        }

                        // 重置录音状态
                        this.reRecord();
                    } else {
                        this.$message.error(result.msg || '上传失败');
                    }
                } else {
                    this.$message.error('上传失败，请检查网络连接');
                }
            } catch (error) {
                // 处理混合内容错误
                if (error.message && error.message.includes('Mixed Content')) {
                    this.$message.error('由于安全策略，无法在 HTTPS 页面访问 HTTP 接口。请联系管理员配置接口支持 HTTPS。');
                } else {
                    this.$message.error('上传失败');
                }
                console.error('上传错误:', error);
            }
        },

        deleteRecording(index) {
            this.recordings.splice(index, 1);
        },

        handleAudioSwitchChange(val) {
            const playEnModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playEn');
            if (playEnModel) {
                playEnModel.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, playEnModel);
            }
        },

        showAddAudioDialog() {
            this.addAudioDialogVisible = true;
            this.newAudio = {
                remark: '',
                per: 0,
                spd: 5,
                pit: 5,
                vol: 5,
                tex_utf8: '',
                filename: ''
            };
        },







        formatBrightness(val) {
            return val + '%';
        },
        handleScreenSwitchChange(val) {
            // 占位物模型ID: '102#screenEn'
            const screenEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#screenEn');
            if (screenEnModel) {
                screenEnModel.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, screenEnModel);
            }
        },
        handleBrightnessChange(val) {
            // 占位物模型ID: '102#brightness'
            const brightnessModel = this.deviceInfo.thingsModels.find(model => model.id === '102#luminance');
            if (brightnessModel) {
                brightnessModel.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, brightnessModel);
            }
        },
        handleClearScreen() {
            // 占位物模型ID: '102#clearScreen'，下发清除指令
            const clearScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '102#clearScreen');
            if (clearScreenModel) {
                clearScreenModel.shadow = '1';
                this.mqttPublish(this.deviceInfo, clearScreenModel);
            } else {
                this.$message.warning('未找到清除屏幕物模型');
            }
        },
        handleBootScreenSwitchChange(val) {
            // 占位物模型ID: '102#bootScreen'
            const bootScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '102#bootEn');
            if (bootScreenModel) {
                bootScreenModel.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, bootScreenModel);
            }
        },
        handleResetSwitchChange(val) {
            // 占位物模型ID: '102#reset'
            const resetModel = this.deviceInfo.thingsModels.find(model => model.id === '102#factoryEn');
            if (resetModel) {
                resetModel.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, resetModel);
            }
        },
        handleTemplateChange(val) {
            const template = this.screenTemplates.find(t => t.value === val);
            if (template) {
                const params = template.params;
                this.$set(this.screenParams, 'oePolarity', params.oe);
                this.$set(this.screenParams, 'dataPolarity', params.data);
                this.$set(this.screenParams, 'width', params.w);
                this.$set(this.screenParams, 'height', params.h);
                this.$set(this.screenParams, 'angle', params.angle);
                this.drawAddProgramPreview(); // 切换模板时强制刷新预览
            }
        },
        handleOePolarityChange(val) {
            // 占位物模型ID: '102#oePolarity'
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#oePolarity');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        handleDataPolarityChange(val) {
            // 占位物模型ID: '102#dataPolarity'
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#dataPolarity');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        handleWidthChange(val) {
            // 占位物模型ID: '102#width'
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#width');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        handleHeightChange(val) {
            // 占位物模型ID: '102#height'
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#height');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        handleAngleChange(val) {
            // 占位物模型ID: '102#angle'
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#angle');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        handleSendScreenParams() {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#scrParam');
            if (model) {
                const param = {
                    scr_param: {
                        oe: this.screenParams.oePolarity,
                        data: this.screenParams.dataPolarity,
                        w: this.screenParams.width,
                        h: this.screenParams.height,
                        angle: this.screenParams.angle
                    }
                };
                model.shadow = 'JSON=' + JSON.stringify(param);
                this.mqttPublish(this.deviceInfo, model);
                this.$message.success('参数已下发');
            } else {
                this.$message.warning('未找到屏幕参数物模型');
            }
        },

        // 下发传感器阈值
        handleSendSensorThreshold() {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#snsrThr');
            if (model) {
                const param = {
                    snsr_thr: {
                        speed: this.sensorThreshold.speed,
                        temp: this.sensorThreshold.temp,
                        humi: this.sensorThreshold.humi
                    }
                };
                model.shadow = 'JSON=' + JSON.stringify(param);
                this.mqttPublish(this.deviceInfo, model);
                this.$message.success('传感器阈值已下发');
            } else {
                this.$message.warning('未找到传感器阈值物模型');
            }
        },
        showAddProgramDialog() {
            this.isEditProgram = false;
            this.editingProgram = null;
            this.addProgramDialogVisible = true;
            this.resetAddProgramForm();
        },
        resetAddProgramForm() {
            // 使用当前屏幕参数
            const screenW = this.screenParams.width || 32;
            const screenH = this.screenParams.height || 64;
            this.addProgramForm = {
                mode: 0,
                duration: 10,
                remark: '',
                zones: [
                    {
                        playType: 0,
                        commonPhrase: 0,
                        displayText: '',
                        font: 0,
                        fontEn: 0, // 新增英文字体
                        fontShape: 0,
                        fontSize: 0,
                        fontColor: 0, // 红色（默认）
                        fontBold: 0,
                        fontStretch: 0,
                        image: '',
                        imageSize: 1,
                        imageColor: 0, // 红色（默认）
                        effect: 0,
                        speed: 4,
                        stayTime: 5,
                        hAlign: 1,
                        vAlign: 1,
                        x: 0,
                        y: 0,
                        width: screenW,
                        height: screenH
                    }
                ]
            };
        },
        onAddProgramModeChange(val) {
            // 根据模式调整分区数量和布局
            const isSwap = this.screenParams.angle === 90 || this.screenParams.angle === 270;
            let actualMode = val;
            //神经逻辑，反人类逻辑
            if (isSwap) {
                if (val === 1) actualMode = 2; // 上下变左右
                else if (val === 2) actualMode = 1; // 左右变上下
                else if (val === 3) actualMode = 2; // 上中下变左右
            }
            const zoneCounts = [1, 2, 2, 3, 1, 1];
            const count = zoneCounts[actualMode] || 1;
            // 使用当前屏幕参数
            const screenW = this.screenParams.width || 32;
            const screenH = this.screenParams.height || 64;
            const newZones = [];
            for (let i = 0; i < count; i++) {
                const templateZone = {
                    playType: 0,
                    commonPhrase: 0,
                    displayText: '',
                    font: 0,
                    fontEn: 0, // 新增英文字体
                    fontShape: 0,
                    fontSize: 0,
                    fontColor: 0, // 红色（默认）
                    fontBold: 0,
                    fontStretch: 0,
                    image: '',
                    imageSize: 1,
                    imageColor: 0, // 红色（默认）
                    effect: 0,
                    speed: 4,
                    stayTime: 5,
                    hAlign: 1,
                    vAlign: 1,
                    x: 0,
                    y: 0,
                    width: screenW,
                    height: screenH
                };
                switch (actualMode) {
                    case 1: // 上下
                        templateZone.height = Math.floor(screenH / count);
                        templateZone.y = i * templateZone.height;
                        if (i === count - 1) templateZone.height = screenH - templateZone.y;
                        break;
                    case 2: // 左右
                        templateZone.width = Math.floor(screenW / count);
                        templateZone.x = i * templateZone.width;
                        if (i === count - 1) templateZone.width = screenW - templateZone.x;
                        break;
                    case 3: // 上中下
                        templateZone.height = Math.floor(screenH / count);
                        templateZone.y = i * templateZone.height;
                        if (i === count - 1) templateZone.height = screenH - templateZone.y;
                        break;
                }
                if (this.addProgramForm.zones[i]) {
                    newZones.push({
                        ...this.addProgramForm.zones[i],
                        x: templateZone.x,
                        y: templateZone.y,
                        width: templateZone.width,
                        height: templateZone.height
                    });
                } else {
                    newZones.push(templateZone);
                }
            }
            this.addProgramForm.zones = newZones;
        },
        onAddProgramPhraseChange(zIdx) {
            const idx = this.addProgramForm.zones[zIdx].commonPhrase;
            if (idx > 0) {
                this.addProgramForm.zones[zIdx].displayText = this.addProgramCommonPhrases[idx];
            }
        },
        submitAddProgram() {
            // 根据显卡.md文档格式组装节目参数
            const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList');
            if (progListModel) {
                try {
                    // 计算 order
                    let order;
                    if (this.isEditProgram && this.editingProgram) {
                        order = this.editingProgram.order;
                    } else {
                        // 新建，找未用的最小 order
                        const usedOrders = this.programList.map(p => p.order);
                        order = 0;
                        while (usedOrders.includes(order) && order < 10) {
                            order++;
                        }
                    }

                    // 构建节目对象
                    const programObj = {
                        order: order,
                        rem: (this.addProgramForm.remark || `自定义节目${Date.now()}`) + '\0', // 节目备注，需要包含\0
                        dur: this.addProgramForm.duration, // 播放时长(秒)
                        areaM: this.addProgramForm.mode, // 分区模式
                        aLst: this.addProgramForm.zones.map(zone => {
                            // 构建分区数据
                            let x = zone.x, y = zone.y, w = zone.width, h = zone.height;
                            // if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
                            //     [x, y] = [y, x];
                            //     [w, h] = [h, w];
                            // }
                            const area = {
                                size: {
                                    x: x, // 左下角X坐标
                                    y: y, // 左下角Y坐标
                                    l: w, // 宽度
                                    h: h  // 高度
                                },
                                pLst: []
                            };

                            // 根据播放类型构建播放项
                            if (zone.playType === 0) {
                                // 文本类型
                                const textItem = {
                                    typ: 0,
                                    anim: {
                                        typ: zone.effect + 1, // 动画类型映射
                                        spd: zone.speed, // 动画速度
                                        pauseT: this.getPauseTime(zone.stayTime), // 暂停时间
                                        playT: 2000 // 播放时间
                                    },
                                    txt: {
                                        str: zone.displayText + '\0', // 文本内容，需要包含\0
                                        col: zone.fontColor + 1, // 文本颜色
                                        fS: this.getFontSize(zone.fontSize), // 字体大小
                                        fCn: zone.font + 1, // 中文字体
                                        fEn: zone.fontEn + 1, // 英文字体
                                        fW: zone.fontBold, // 字体加粗
                                        stch: zone.fontStretch, // 拉伸方向
                                        hPos: zone.hAlign + 1, // 水平对齐（1左，2中，3右）
                                        vPos: zone.vAlign + 1 // 垂直对齐（1顶，2中，3底）
                                    }
                                };
                                area.pLst.push(textItem);
                            } else if (zone.playType === 1) {
                                // 图片类型
                                const imageItem = {
                                    typ: 1,
                                    anim: {
                                        typ: zone.effect + 1,
                                        spd: zone.speed,
                                        pauseT: this.getPauseTime(zone.stayTime),
                                        playT: 2000
                                    },
                                    img: {
                                        num: this.getImageNumber(zone.image), // 图片编号
                                        col: zone.imageColor, // 图片颜色
                                        data: '', // 预留字段
                                        w: this.getImageSize(zone.imageSize), // 图片宽度
                                        h: this.getImageSize(zone.imageSize) // 图片高度
                                    }
                                };
                                area.pLst.push(imageItem);
                            }

                            return area;
                        })
                    };

                    // 只下发当前节目
                    const programData = {
                        del_prog: 0, // 不删除节目
                        prog_list: [programObj]
                    };

                    progListModel.shadow = 'JSON=' + JSON.stringify(programData);
                    this.mqttPublish(this.deviceInfo, progListModel).then(() => {
                        this.addProgramDialogVisible = false;
                        this.$message.success(this.isEditProgram ? '自定义节目更新成功' : '自定义节目添加成功');
                    }).catch(error => {
                        console.error('发送节目数据失败:', error);
                        this.$message.error(this.isEditProgram ? '自定义节目更新失败' : '自定义节目添加失败');
                    });
                } catch (error) {
                    console.error('构建节目数据失败:', error);
                    this.$message.error(this.isEditProgram ? '自定义节目更新失败' : '自定义节目添加失败');
                }
            } else {
                this.$message.warning('未找到102#progList物模型');
            }
        },
        // 画面预览渲染
        drawAddProgramPreview() {
            const canvas = this.$refs.addProgramPreviewCanvas;
            if (!canvas) return;
            const ctx = canvas.getContext('2d');

            // 获取当前屏幕参数
            let screenW = this.screenParams.width || 32;
            let screenH = this.screenParams.height || 64;
            // 如果角度为90或270，渲染时宽高互换
            const angle = this.screenParams.angle;
            let swapWH = angle === 90 || angle === 270;
            if (swapWH) {
                [screenW, screenH] = [screenH, screenW];
            }

            // 获取实际渲染尺寸
            const renderWidth = 640; // 调大预览宽度
            const renderHeight = 240; // 调大预览高度

            // 设置高清渲染
            const devicePixelRatio = window.devicePixelRatio || 1;
            canvas.width = renderWidth * devicePixelRatio;
            canvas.height = renderHeight * devicePixelRatio;
            canvas.style.width = `${renderWidth}px`;
            canvas.style.height = `${renderHeight}px`;
            ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置变换
            ctx.scale(devicePixelRatio, devicePixelRatio);

            // 清空整个画布为透明
            ctx.clearRect(0, 0, renderWidth, renderHeight);

            // 计算缩放比例（基于screenParams的LED屏幕尺寸）
            const scale = Math.min(
                renderWidth / screenW,
                renderHeight / screenH
            );

            // 居中偏移量
            const offsetX = (renderWidth - screenW * scale) / 2;
            const offsetY = (renderHeight - screenH * scale) / 2;

            // 绘制LED屏幕区域（黑色背景）
            ctx.fillStyle = '#000';
            ctx.fillRect(offsetX, offsetY, screenW * scale, screenH * scale);

            // 遍历所有分区
            this.addProgramForm.zones.forEach((zone, zIdx) => {
                const asset = this.addProgramPreviewAssets[zIdx];
                if (!asset) return;

                // 计算分区在画布中的位置（角度为90或270时，x/y/width/height互换）
                let zoneX, zoneY, zoneWidth, zoneHeight;
                if (swapWH) {
                    zoneX = offsetX + (zone.y || 0) * scale;
                    zoneY = offsetY + (zone.x || 0) * scale;
                    zoneWidth = (zone.height || 0) * scale;
                    zoneHeight = (zone.width || 0) * scale;
                } else {
                    zoneX = offsetX + (zone.x || 0) * scale;
                    zoneY = offsetY + (zone.y || 0) * scale;
                    zoneWidth = (zone.width || 0) * scale;
                    zoneHeight = (zone.height || 0) * scale;
                }

                // 绘制分区边框（蓝色虚线）
                ctx.save();
                ctx.strokeStyle = '#409EFF';
                ctx.lineWidth = 1.5;
                ctx.setLineDash([5, 3]);
                ctx.strokeRect(zoneX, zoneY, zoneWidth, zoneHeight);
                ctx.restore();

                // 裁剪区域，防止动画溢出
                ctx.save();
                ctx.beginPath();
                ctx.rect(zoneX, zoneY, zoneWidth, zoneHeight);
                ctx.clip();

                // 获取动画状态
                const state = this.addProgramPreviewAnimState[zIdx];

                if (zone.playType === 0 && asset.isText) {
                    // 文本类型分区
                    const page = this.addProgramPreviewPage[zIdx] || 0;
                    const pageLines = asset.pages[page] || [];

                    // 设置字体样式
                    const colors = ['#FF0000', '#00FF00', '#FFFF00'];
                    ctx.fillStyle = colors[zone.fontColor] || '#000000';
                    ctx.textBaseline = 'top';
                    // 这里根据zone.fontEn选择英文字体
                    const fontFamilies = [
                        'SimSun, 宋体, Songti SC, serif',
                        'SimHei, 黑体, Heiti SC, sans-serif',
                        'KaiTi, 楷体, Kaiti SC, serif'
                    ];
                    const enFontMap = [
                        'Courier New', 'Arial Black', 'Arial Italic', 'Lucida Console', 'Impact', 'Gothic', 'Arial Narrow', 'Comic Sans MS', 'Brush Script MT', 'Century Gothic', 'Times New Roman'
                    ];
                    let fontFamily;
                    if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) {
                        fontFamily = enFontMap[zone.fontEn] || fontFamilies[0];
                    } else {
                        fontFamily = fontFamilies[zone.font] || fontFamilies[0];
                    }
                    ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${fontFamily}`;

                    // 计算行高
                    const lineHeight = asset.scaledFontSize * 1.2;

                    // 计算文本起始位置（考虑垂直对齐）
                    let startY = zoneY;
                    const verticalAlign = zone.vAlign;
                    const totalHeight = pageLines.length * lineHeight;

                    if (verticalAlign === 1) { // 垂直居中
                        startY = zoneY + (zoneHeight - totalHeight) / 2;
                    } else if (verticalAlign === 2) { // 底部对齐
                        startY = zoneY + zoneHeight - totalHeight;
                    } else { // 顶部对齐
                        startY = zoneY;
                    }

                    // 绘制每一行文本
                    pageLines.forEach((line, lineIdx) => {
                        // 计算水平位置（考虑水平对齐）
                        const textWidth = ctx.measureText(line).width;
                        let startX = zoneX;

                        if (zone.hAlign === 1) { // 水平居中
                            startX = zoneX + (zoneWidth - textWidth) / 2;
                        } else if (zone.hAlign === 2) { // 右对齐
                            startX = zoneX + zoneWidth - textWidth;
                        } else { // 左对齐
                            startX = zoneX;
                        }

                        // 计算动画偏移
                        const effect = zone.effect;
                        let animOffsetX = 0;
                        let animOffsetY = 0;
                        if (state) {
                            if (effect === 1 || effect === 2 || effect === 5) animOffsetX = state.currentX;
                            if (effect === 3 || effect === 4) animOffsetY = state.currentY;
                        }

                        ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY);
                    });
                } else if (zone.playType === 1 && asset.isImage) {
                    // 图片类型分区
                    if (asset.loaded && asset.img.complete) {
                        const img = asset.img;

                        // 获取用户选择的目标尺寸
                        const sizeValues = [16, 32, 48, 64];
                        const targetSize = sizeValues[zone.imageSize] || 32;

                        // 根据原始宽高比计算缩放后的尺寸
                        let newWidth, newHeight;
                        if (img.width > img.height) {
                            newWidth = targetSize;
                            newHeight = (img.height / img.width) * targetSize;
                        } else {
                            newWidth = (img.width / img.height) * targetSize;
                            newHeight = targetSize;
                        }

                        // 创建临时画布用于调整尺寸和颜色
                        const tempCanvas = document.createElement('canvas');
                        tempCanvas.width = newWidth;
                        tempCanvas.height = newHeight;
                        const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });

                        // 绘制图片到临时画布（实现缩放）
                        tempCtx.drawImage(img, 0, 0, newWidth, newHeight);

                        // 获取像素数据并应用颜色
                        const imageData = tempCtx.getImageData(0, 0, newWidth, newHeight);
                        const data = imageData.data;
                        // 修正颜色顺序：0=红色, 1=绿色, 2=黄色
                        const colors = [
                            [255, 0, 0],    // 红色
                            [0, 255, 0],    // 绿色
                            [255, 255, 0]   // 黄色
                        ];
                        const selectedColor = colors[zone.imageColor];
                        if (selectedColor) {
                            for (let i = 0; i < data.length; i += 4) {
                                // 优化线条识别：通过计算灰度并使用更宽松的阈值来捕捉完整的笔画
                                const grayscale = (data[i] + data[i + 1] + data[i + 2]) / 3;
                                const isLine = grayscale < 128 && data[i + 3] > 50;

                                if (isLine) {
                                    // 将线条着色
                                    data[i] = selectedColor[0];     // R
                                    data[i + 1] = selectedColor[1]; // G
                                    data[i + 2] = selectedColor[2]; // B
                                    data[i + 3] = 255;              // 确保线条完全不透明
                                } else {
                                    // 将背景设为透明
                                    data[i + 3] = 0;
                                }
                            }
                        }
                        // 将处理后的图像数据放回临时画布
                        tempCtx.putImageData(imageData, 0, 0);

                        // 计算在主画布上绘制的最终尺寸和位置
                        const finalWidth = newWidth * scale;
                        const finalHeight = newHeight * scale;
                        const dX = zoneX + (zoneWidth - finalWidth) / 2;
                        const dY = zoneY + (zoneHeight - finalHeight) / 2;

                        // 将着色和缩放后的临时画布内容绘制到主画布
                        ctx.drawImage(tempCanvas, dX, dY, finalWidth, finalHeight);

                    } else {
                        // Image not loaded yet, draw placeholder
                        ctx.fillStyle = '#222';
                        ctx.fillRect(zoneX + 4, zoneY + 4, zoneWidth - 8, zoneHeight - 8);

                        ctx.fillStyle = '#fff';
                        ctx.font = `${Math.round(12 * scale)}px Arial`;
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'middle';
                        ctx.fillText('加载中...', zoneX + zoneWidth / 2, zoneY + zoneHeight / 2);
                    }
                }

                // 恢复裁剪区域
                ctx.restore();
            });
        },
        prepareAddProgramPreviewAssets() {
            const canvas = this.$refs.addProgramPreviewCanvas;
            if (!canvas) return;

            // 动态获取屏幕参数
            let screenW = this.screenParams.width || 32;
            let screenH = this.screenParams.height || 64;
            // 如果角度为90或270，渲染时宽高互换
            const angle = this.screenParams.angle;
            const swapWH = angle === 90 || angle === 270;
            if (swapWH) {
                [screenW, screenH] = [screenH, screenW];
            }
            const renderWidth = 640; // 与实际渲染宽度匹配
            const scale = Math.min(renderWidth / screenW, 240 / screenH);

            this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => {
                if (zone.playType === 1 && zone.image) {
                    console.log('加载图片', zone.image);
                    // 图片类型，异步加载图片
                    const img = new window.Image();
                    // img.crossOrigin = 'Anonymous';
                    const asset = { img, isImage: true, loaded: false };
                    img.onload = () => {
                        asset.loaded = true;
                        this.drawAddProgramPreview();
                    };
                    img.onerror = (e) => {
                        console.error("Preview image failed to load:", e);
                    };
                    img.src = zone.image;
                    return asset;
                }
                if (zone.playType !== 0 || !zone.displayText) {
                    return { pages: [[]], isText: false };
                }

                // 获取原始字体大小（像素值）
                const fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px');

                // 计算缩放后的字体大小
                const scaledFontSize = Math.max(10, Math.round(fontSize * scale * 0.8)); // 添加0.8缩放系数

                const fontFamily = [
                    'SimSun, 宋体, Songti SC, serif',
                    'SimHei, 黑体, Heiti SC, sans-serif',
                    'KaiTi, 楷体, Kaiti SC, serif'
                ];
                const fontWeight = zone.fontBold ? 'bold' : 'normal';

                // 创建临时canvas测量文本
                const measureCanvas = document.createElement('canvas');
                const measureCtx = measureCanvas.getContext('2d');
                const fontFamilies2 = [
                    'SimSun, 宋体, Songti SC, serif',
                    'SimHei, 黑体, Heiti SC, sans-serif',
                    'KaiTi, 楷体, Kaiti SC, serif'
                ];
                const enFontMap2 = [
                    'Courier New', 'Arial Black', 'Arial Italic', 'Lucida Console', 'Impact', 'Gothic', 'Arial Narrow', 'Comic Sans MS', 'Brush Script MT', 'Century Gothic', 'Times New Roman'
                ];
                let fontFamily2;
                if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) {
                    fontFamily2 = enFontMap2[zone.fontEn] || fontFamilies2[0];
                } else {
                    fontFamily2 = fontFamilies2[zone.font] || fontFamilies2[0];
                }
                measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamily2}`;

                // 分行逻辑
                const zoneRenderWidth = swapWH ? zone.height : zone.width;
                const maxWidth = zoneRenderWidth * scale - 4; // 预留4px边距
                let currentLine = '';
                const lines = [];

                for (const char of zone.displayText) {
                    const testLine = currentLine + char;
                    const metrics = measureCtx.measureText(testLine);

                    if (metrics.width > maxWidth) {
                        if (currentLine) lines.push(currentLine);
                        currentLine = char;
                    } else {
                        currentLine = testLine;
                    }
                }

                if (currentLine) lines.push(currentLine);

                // 计算每页最大行数
                const zoneRenderHeight = swapWH ? zone.width : zone.height;
                const lineHeight = scaledFontSize * 1.2;
                const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight));

                // 分页
                const pages = [];
                for (let i = 0; i < lines.length; i += maxLines) {
                    pages.push(lines.slice(i, i + maxLines));
                }

                if (pages.length === 0) pages.push([]);

                // 新增：计算总宽高和区域宽高，供动画用
                const totalWidth = Math.max(...lines.map(line => measureCtx.measureText(line).width), 0);
                const totalHeight = lines.length * lineHeight;
                return {
                    pages,
                    isText: true,
                    scaledFontSize,
                    fontFamily: fontFamily2,
                    fontWeight,
                    lineHeight,
                    totalWidth,
                    totalHeight,
                    zoneRenderWidth: zoneRenderWidth * scale,
                    zoneRenderHeight: zoneRenderHeight * scale
                };
            });
        },
        resetAddProgramPreviewPage() {
            this.addProgramPreviewPage = this.addProgramForm.zones.map(() => 0);
        },
        stopAddProgramPreviewAutoPage() {
            if (this.addProgramPreviewTimer && this.addProgramPreviewTimer.length) {
                this.addProgramPreviewTimer.forEach(timer => timer && clearInterval(timer));
            }
            this.addProgramPreviewTimer = [];
        },
        startAddProgramPreviewAutoPage() {
            this.stopAddProgramPreviewAutoPage();
            this.addProgramPreviewTimer = this.addProgramForm.zones.map((zone, zIdx) => {
                const asset = this.addProgramPreviewAssets[zIdx];
                if (!asset || !asset.isText) return null;
                const totalPages = asset.pages.length;
                if (totalPages <= 1) return null;
                // 停留时间映射
                const stayTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000];
                let stayMs = 1000;
                if (zone.stayTime >= 0 && zone.stayTime < stayTimeMap.length) {
                    stayMs = stayTimeMap[zone.stayTime];
                }
                if (stayMs === 0) stayMs = 1000;
                return setInterval(() => {
                    this.addProgramPreviewPage[zIdx] = (this.addProgramPreviewPage[zIdx] + 1) % totalPages;
                    this.drawAddProgramPreview();
                }, stayMs);
            });
        },
        getZoneLinesAndMax(zone) {
            // 计算分区内容分行和每页最大行数
            const canvas = this.$refs.addProgramPreviewCanvas;
            if (!canvas) return { lines: [], maxLines: 1 };
            const ctx = canvas.getContext('2d');
            const screenW = 32, screenH = 64;
            const canvasW = canvas.width, canvasH = canvas.height;
            const scale = Math.min(canvasW / screenW, canvasH / screenH);
            let fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px');
            let scaledFontSize = Math.max(10, Math.round(fontSize * scale));
            const fontFamily = [
                'SimSun, 宋体, Songti SC, serif',
                'SimHei, 黑体, Heiti SC, sans-serif',
                'KaiTi, 楷体, Kaiti SC, serif'
            ];
            const fontWeight = zone.fontBold ? 'bold' : 'normal';
            ctx.font = `${fontWeight} ${scaledFontSize}px ${fontFamily[zone.font]}`;
            let lines = [];
            let text = zone.displayText;
            let zw = zone.width * scale;
            let zh = zone.height * scale;
            while (text && text.length > 0) {
                let fitLen = 0;
                let testStr = '';
                for (let i = 1; i <= text.length; i++) {
                    testStr = text.slice(0, i);
                    if (ctx.measureText(testStr).width > zw - 4) {
                        break;
                    }
                    fitLen = i;
                }
                if (fitLen === 0) fitLen = 1;
                lines.push(text.slice(0, fitLen));
                text = text.slice(fitLen);
            }
            const maxLines = Math.floor((zh - 4) / scaledFontSize);
            return { lines, maxLines };
        },
        resetAddProgramPreviewAnimState() {
            // 初始化每个分区动画状态
            this.addProgramPreviewAnimState = this.addProgramForm.zones.map((zone, idx) => {
                return {
                    currentX: 0,
                    currentY: 0,
                    currentPage: this.addProgramPreviewPage[idx] || 0,
                    pageTimer: null,
                    effect: zone.effect,
                };
            });
        },
        stopAddProgramPreviewAnimLoop() {
            if (this.addProgramPreviewAnimFrame) {
                cancelAnimationFrame(this.addProgramPreviewAnimFrame);
                this.addProgramPreviewAnimFrame = null;
            }
        },
        startAddProgramPreviewAnimLoop() {
            this.stopAddProgramPreviewAnimLoop();
            const loop = () => {
                this.updateAddProgramPreviewAnimState();
                this.drawAddProgramPreview();
                this.addProgramPreviewAnimFrame = requestAnimationFrame(loop);
            };
            this.addProgramPreviewAnimFrame = requestAnimationFrame(loop);
        },
        updateAddProgramPreviewAnimState() {
            // 动画主循环，更新每个分区的动画状态
            const renderWidth = 640;
            const renderHeight = 240;
            // const scale = Math.min(renderWidth / 32, renderHeight / 64);
            // 使用与 prepareAddProgramPreviewAssets 中相同的 scale 计算方式
            let screenW = this.screenParams.width || 32;
            let screenH = this.screenParams.height || 64;
            const angle = this.screenParams.angle;
            const swapWH = angle === 90 || angle === 270;
            if (swapWH) {
                [screenW, screenH] = [screenH, screenW];
            }
            const scale = Math.min(renderWidth / screenW, renderHeight / screenH);


            this.addProgramForm.zones.forEach((zone, idx) => {
                const asset = this.addProgramPreviewAssets[idx];
                const state = this.addProgramPreviewAnimState[idx];

                if (!asset || !state || !asset.isText) return;

                const effect = zone.effect;
                const page = this.addProgramPreviewPage[idx] || 0;
                const pageLines = asset.pages[page] || [];
                const lineHeight = asset.lineHeight;
                // const totalHeight = pageLines.length * lineHeight; // 原始计算
                // const totalWidth = Math.max(...pageLines.map(line => measureCtx.measureText(line).width), 0); // 原始计算
                // 使用 prepareAddProgramPreviewAssets 中预先计算好的值
                const totalHeight = asset.totalHeight;
                const totalWidth = asset.totalWidth;


                // 动画速度
                const speedMap = [1, 2, 3, 4, 5];
                const animSpeed = (speedMap[zone.speed] || 3) * scale * 0.5; // 像素/帧

                // 区域宽高 (使用预先计算并缩放的值)
                const zoneWidth = asset.zoneRenderWidth;
                const zoneHeight = asset.zoneRenderHeight;

                // 暂停时间 (毫秒)
                const pauseMs = this.getPauseTime(zone.stayTime);

                // 确保初始状态值存在
                if (typeof state.isPausing !== 'boolean') state.isPausing = false;
                if (typeof state.pauseStart !== 'number') state.pauseStart = 0;
                if (typeof state.pausePhase !== 'string') state.pausePhase = 'start'; // 'start' | 'move'


                switch (effect) {
                    case 1: // 左移
                        // 确保初始 state.currentX 值存在
                        if (typeof state.currentX !== 'number') state.currentX = 0;
                        // 确保初始 pausePhase 值存在
                        if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move'

                        // 初始位置停留阶段
                        if (!state.isPausing && state.pausePhase === 'start') {
                            state.isPausing = true;
                            state.pauseStart = Date.now();
                        }

                        // 检查初始停留是否结束
                        if (state.isPausing && state.pausePhase === 'start') {
                            if (Date.now() - state.pauseStart >= pauseMs) {
                                state.isPausing = false;
                                state.pausePhase = 'move';
                            }
                            // 移动阶段
                        } else if (!state.isPausing && state.pausePhase === 'move') {
                            // --- 强制多移动的逻辑 ---
                            // !!! 核心修改点 !!!
                            // 原判断: if (state.currentX + totalWidth > 0)
                            // 新判断: 计算从起始位置(0)到当前位置已经移动的距离 (-state.currentX)
                            //        并与一个我们认为足够"移出"的距离进行比较
                            const pixelsMoved = -state.currentX; // 从 X=0 开始，向左移动了多远
                            const distanceToTriggerReset = zoneWidth + 100; // <--- 关键：移出区域宽度 + 额外距离
                            // 例如：区域宽 32 + 额外 100 = 132
                            // 你可以调整这个 100 为你觉得合适的值

                            if (pixelsMoved < distanceToTriggerReset) {
                                // 移动距离还不够，继续移动
                                state.currentX -= animSpeed;
                            } else {
                                // 移动距离足够了（强制认为已经移出），立即重置
                                // console.log(`Zone ${idx} - Left Move: Forcefully reset after moving ${pixelsMoved.toFixed(2)} pixels.`); // 调试信息
                                state.currentX = 0; // 回到起始位置
                                state.pausePhase = 'start'; // 回到初始停留阶段
                            }
                            // --- 强制多移动逻辑结束 ---
                        }
                        // Y轴不移动
                        state.currentY = 0;
                        break;


                    case 2: // 右移
                        // 确保初始 state.currentX 值存在
                        if (typeof state.currentX !== 'number') state.currentX = 0; // 通常从左侧开始向右移
                        // 确保初始 pausePhase 值存在
                        if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move'

                        // 初始位置停留阶段
                        if (!state.isPausing && state.pausePhase === 'start') {
                            state.isPausing = true;
                            state.pauseStart = Date.now();
                        }

                        // 检查初始停留是否结束
                        if (state.isPausing && state.pausePhase === 'start') {
                            if (Date.now() - state.pauseStart >= pauseMs) {
                                state.isPausing = false;
                                state.pausePhase = 'move';
                            }
                            // 移动阶段
                        } else if (!state.isPausing && state.pausePhase === 'move') {
                            // --- 修正后的逻辑 ---
                            // 判断是否还可以继续向右移动
                            // 条件：当前文本左边缘距离完全移出右边界还有距离 (currentX < zoneWidth)
                            if (state.currentX < zoneWidth) {
                                state.currentX += animSpeed;
                                // 可选：边界检查
                                // if (state.currentX > zoneWidth) {
                                //    state.currentX = zoneWidth; // 精确设置到边界
                                // }
                            } else {
                                // 内容完全移出右侧，立即重置，准备下一轮
                                // 不再进入 'end' 停留阶段
                                state.currentX = 0; // 回到左侧起始位置
                                state.pausePhase = 'start'; // 回到初始停留阶段
                            }
                            // --- 修正结束 ---
                        }
                        // Y轴不移动
                        state.currentY = 0;
                        break;

                    case 3: // 上移
                        // 确保初始 state.currentY 值存在
                        if (typeof state.currentY !== 'number') state.currentY = 0;
                        // 确保初始 pausePhase 值存在
                        if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move'

                        // 初始位置停留阶段
                        if (!state.isPausing && state.pausePhase === 'start') {
                            state.isPausing = true;
                            state.pauseStart = Date.now();
                        }

                        // 检查初始停留是否结束
                        if (state.isPausing && state.pausePhase === 'start') {
                            if (Date.now() - state.pauseStart >= pauseMs) {
                                state.isPausing = false;
                                state.pausePhase = 'move';
                            }
                            // 移动阶段
                        } else if (!state.isPausing && state.pausePhase === 'move') {
                            // --- 修正后的逻辑 ---
                            // 判断是否还可以继续向上移动
                            // 条件：当前文本底边距离完全移出顶边还有距离 (currentY + totalHeight > 0)
                            if (state.currentY + totalHeight > 0) {
                                state.currentY -= animSpeed;
                                // 可选：边界检查
                                // if (state.currentY + totalHeight < 0) {
                                //     state.currentY = -totalHeight;
                                // }
                            } else {
                                // 内容完全移出顶部，立即重置，准备下一轮
                                // 不再进入 'end' 停留阶段
                                state.currentY = 0; // 回到顶部起始位置
                                state.pausePhase = 'start'; // 回到初始停留阶段
                            }
                            // --- 修正结束 ---
                        }
                        // X轴不移动
                        state.currentX = 0;
                        break;

                    case 4: // 下移
                        // 确保初始 state.currentY 值存在
                        if (typeof state.currentY !== 'number') state.currentY = 0; // 通常从顶部开始向下移
                        // 确保初始 pausePhase 值存在
                        if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move'

                        // 初始位置停留阶段
                        if (!state.isPausing && state.pausePhase === 'start') {
                            state.isPausing = true;
                            state.pauseStart = Date.now();
                        }

                        // 检查初始停留是否结束
                        if (state.isPausing && state.pausePhase === 'start') {
                            if (Date.now() - state.pauseStart >= pauseMs) {
                                state.isPausing = false;
                                state.pausePhase = 'move';
                            }
                            // 移动阶段
                        } else if (!state.isPausing && state.pausePhase === 'move') {
                            // --- 修正后的逻辑 ---
                            // 判断是否还可以继续向下移动
                            // 条件：当前文本顶边距离完全移出底边还有距离 (currentY < zoneHeight)
                            if (state.currentY < zoneHeight) {
                                state.currentY += animSpeed;
                                // 可选：边界检查
                                // if (state.currentY > zoneHeight) {
                                //    state.currentY = zoneHeight;
                                // }
                            } else {
                                // 内容完全移出底部，立即重置，准备下一轮
                                // 不再进入 'end' 停留阶段
                                state.currentY = 0; // 回到顶部起始位置
                                state.pausePhase = 'start'; // 回到初始停留阶段
                            }
                            // --- 修正结束 ---
                        }
                        // X轴不移动
                        state.currentX = 0;
                        break;

                    case 5: // 连续左移 (立即显示 -> 左移 -> 立即显示 -> ...)
                        // 确保初始 state.currentX 值存在
                        if (typeof state.currentX !== 'number') state.currentX = 0;
                        // 确保初始 pausePhase 值存在
                        if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move'

                        // 初始立即显示阶段 (或一轮结束后的显示阶段)
                        if (!state.isPausing && state.pausePhase === 'start') {
                            // 立即显示阶段，不移动，停留 pauseMs 时间
                            state.isPausing = true;
                            state.pauseStart = Date.now();
                        }

                        // 检查立即显示阶段是否结束
                        if (state.isPausing && state.pausePhase === 'start') {
                            if (Date.now() - state.pauseStart >= pauseMs) {
                                state.isPausing = false;
                                state.pausePhase = 'move'; // 进入移动阶段
                            }
                            // 移动阶段 (左移)
                        } else if (!state.isPausing && state.pausePhase === 'move') {
                            // --- 修正后的逻辑 (连续左移的移动逻辑与普通左移一致) ---
                            // 判断是否还可以继续向左移动
                            if (state.currentX > -totalWidth) {
                                state.currentX -= animSpeed;
                                // 可选边界检查
                                // if (state.currentX < -totalWidth) {
                                //     state.currentX = -totalWidth;
                                // }
                            } else {
                                // 内容完全移出，进入下一个立即显示阶段
                                // 连续左移的效果是在这里停留 pauseMs 后再显示下一轮
                                state.isPausing = true;
                                state.pauseStart = Date.now();
                                state.currentX = 0; // 回到起始位置显示
                                state.pausePhase = 'start'; // 回到 'start' 阶段进行显示停留
                            }
                            // --- 修正结束 ---
                        }
                        state.currentY = 0; // Y轴不移动
                        break;

                    case 6: // 闪烁换页
                        // 由自动翻页定时器控制，此处不处理动画位移
                        state.currentX = 0;
                        state.currentY = 0;
                        break;

                    default: // 立即显示 (effect === 0) 或其他未知效果
                        state.currentX = 0;
                        state.currentY = 0;
                        // 可以考虑添加立即显示的停留逻辑，如果需要的话
                        // if (!state.isPausing && state.pausePhase === 'start') {
                        //     state.isPausing = true;
                        //     state.pauseStart = Date.now();
                        // }
                        // if (state.isPausing && state.pausePhase === 'start') {
                        //      if (Date.now() - state.pauseStart >= pauseMs) {
                        //          state.isPausing = false;
                        //          state.pausePhase = 'end'; // 或者重置为 'start'
                        //      }
                        // }
                        // state.pausePhase = 'start'; // 重置状态以便下次循环
                        break;
                }
            });
        },
        measureTextWidth(text, asset) {
            // 用于动画宽度测量
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${asset.fontFamily}`;
            return ctx.measureText(text).width;
        },
        // ================= 显卡物模型适配方法 BEGIN =================
        // 屏幕总开关
        handleScreenSwitchChange(val) {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#screenEn');
            if (model) {
                model.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 开机界面开关
        handleBootEnChange(val) {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#bootEn');
            if (model) {
                model.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 恢复出厂
        handleFactoryEnChange(val) {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#factoryEn');
            if (model) {
                model.shadow = val ? '1' : '0';
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 屏幕亮度
        handleLuminanceChange(val) {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#luminance');
            if (model) {
                model.shadow = val.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 屏参设置
        handleScrParamChange(param) {
            // param: { w, h, oe, data, angle }
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#scrParam');
            if (model) {
                model.shadow = JSON.stringify({ scr_param: param });
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 节目列表
        handleProgListChange(progList) {
            // progList: [{order, rem, dur, areaM, aLst: [...] }]
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#progList');
            if (model) {
                model.shadow = JSON.stringify({ prog_list: progList });
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 节目开关列表
        handleProgEnChange(progEnArr) {
            // progEnArr: [1,0,1,1,0]
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#progEn');
            if (model) {
                model.shadow = JSON.stringify(progEnArr);
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 删除节目
        handleDelProg(index) {
            // index: 0=删第1条, -1=全部清空
            const model = this.deviceInfo.thingsModels.find(m => m.id === 'del_prog');
            if (model) {
                model.shadow = index.toString();
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 传感器阈值
        handleSnsrThrChange(thr) {
            // thr: { speed: 60, temp: 30, humi: 50 }
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#snsrThr');
            if (model) {
                model.shadow = JSON.stringify({ snsr_thr: thr });
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // 触发一次全量上报
        handleRequestModelReport() {
            const model = this.deviceInfo.thingsModels.find(m => m.id === '102#model');
            if (model) {
                model.shadow = '1';
                this.mqttPublish(this.deviceInfo, model);
            }
        },
        // ================= 显卡物模型适配方法 END =================
        // 获取分区显示用的坐标和尺寸，index为分区下标
        zoneDisplay(zIdx) {
            const zone = this.addProgramForm.zones[zIdx];
            if (!zone) return { x: 0, y: 0, width: 0, height: 0 };
            // 已去除角度为90或270时的xy/宽高对调逻辑，UI显示与实际参数一致
            return {
                x: zone.x,
                y: zone.y,
                width: zone.width,
                height: zone.height
            };
        },
        // 设置分区的物理坐标，UI输入时不再自动对调
        setZoneDisplay(zIdx, key, value) {
            const zone = this.addProgramForm.zones[zIdx];
            if (!zone) return;
            // 已去除角度为90或270时的xy/宽高对调逻辑，UI输入与实际参数一致
            zone[key] = value;
        },
        updateBoardOnlineStatus() {
            if (!this.deviceInfo || !this.deviceInfo.thingsModels) {
                this.boardOnline = false;
                return;
            }
            const onlineModel = this.deviceInfo.thingsModels.find(m => m.id === '1#onlineBoard');
            if (!onlineModel || !(onlineModel.shadow || onlineModel.value)) {  // 先检查 shadow
                this.boardOnline = false;
                return;
            }
            try {
                const valueStr = onlineModel.shadow || onlineModel.value;  // 优先使用 shadow
                const jsonStr = valueStr.replace(/^JSON=/, '');
                const data = JSON.parse(jsonStr);
                const onlineArr = data.online || [];
                this.boardOnline = onlineArr.includes(102); // 显卡编号
            } catch (e) {
                this.boardOnline = false;
            }
            // console.log("测试是否受到消息", JSON.stringify(this.boardOnline));
            // console.log("数据来源：", onlineModel.shadow ? "shadow" : "value");

        }
    },
    computed: {
        // ...原有computed
    },
};
</script>

<style lang="scss" scoped>
.running-status {
    padding: 20px;

    .status-col {
        .title {
            line-height: 28px;
            font-size: 16px;
        }
    }

    .mode-section {
        margin-bottom: 30px;
    }

    .mode-card {
        margin-bottom: 20px;
        transition: all 0.3s;
        padding: 20px;

        &:hover {
            transform: translateY(-5px);
        }

        .mode-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 15px;

            .header-left {
                display: flex;
                align-items: center;

                i,
                .svg-icon {
                    font-size: 30px;
                    margin-right: 8px;
                    color: #409EFF;
                }

                .mode-title {
                    font-size: 16px;
                    font-weight: bold;
                    color: #303133;
                }
            }
        }

        .mode-content {
            text-align: center;
            padding: 10px 0;

            .title {
                font-size: 16px;
                font-weight: bold;
            }

            .info-item {
                display: flex;
                align-items: center;
                justify-content: flex-start;
                margin-bottom: 15px;
                padding: 8px 12px;
                background: #f8f9fa;
                border-radius: 6px;
                border-left: 3px solid #409EFF;
                position: relative;
                min-height: 40px;

                .info-label {
                    font-size: 14px;
                    color: #606266;
                    margin-right: 12px;
                    min-width: 80px;
                }

                .info-value {
                    font-size: 14px;
                    color: #303133;
                    font-weight: 600;
                    flex: 1;
                }

                &.el-loading-parent--relative {
                    .el-loading-mask {
                        background-color: rgba(255, 255, 255, 0.8);
                        border-radius: 6px;
                    }
                }
            }
        }
    }

    .settings-card,
    .audio-list-card,
    .default-list-card {
        margin-bottom: 20px;
        transition: all 0.3s;

        &:hover {
            transform: translateY(-5px);
        }

        .settings-header,
        .audio-list-header,
        .default-list-header {
            display: flex;
            justify-content: space-between;
            align-items: center;

            .settings-title,
            .audio-list-title,
            .default-list-title {
                font-size: 16px;
                font-weight: bold;
                color: #303133;
            }
        }
    }

    .el-table {
        margin-top: 15px;
    }

    .el-slider {
        margin-top: 10px;
    }
}

.voice-control-card {
    margin-bottom: 20px;
    transition: all 0.3s;

    &:hover {
        transform: translateY(-5px);
    }

    .voice-control-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        .voice-control-title {
            font-size: 16px;
            font-weight: bold;
            color: #303133;
        }
    }

    .voice-control-content {
        padding: 20px;
        text-align: center;

        .recorder-status {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 20px;

            .status-indicator {
                width: 60px;
                height: 60px;
                border-radius: 50%;
                background: #f4f4f5;
                display: flex;
                align-items: center;
                justify-content: center;
                margin-bottom: 10px;
                transition: all 0.3s;

                i {
                    font-size: 30px;
                    color: #909399;
                }

                &.recording {
                    background: #fef0f0;
                    animation: pulse 1.5s infinite;

                    i {
                        color: #f56c6c;
                    }
                }
            }

            .status-text {
                font-size: 14px;
                color: #606266;
            }
        }

        .timer-display {
            font-size: 24px;
            font-weight: bold;
            color: #303133;
            margin-bottom: 20px;
        }

        .control-buttons {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 20px;

            .el-button {
                width: 50px;
                height: 50px;
                font-size: 20px;
            }
        }

        .recording-preview {
            margin-top: 20px;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 8px;

            .preview-title {
                font-size: 14px;
                color: #606266;
                margin-bottom: 10px;
                text-align: left;
            }

            .audio-player {
                display: flex;
                flex-direction: column;
                align-items: center;
                gap: 10px;

                audio {
                    width: 100%;
                    height: 40px;
                }

                .preview-controls {
                    display: flex;
                    justify-content: center;
                    margin-top: 5px;

                    .el-button {
                        padding: 8px 15px;

                        i {
                            margin-right: 5px;
                        }
                    }
                }
            }
        }

        .recording-list {
            text-align: left;
            border-top: 1px solid #ebeef5;
            padding-top: 15px;

            .list-title {
                font-size: 14px;
                color: #606266;
                margin-bottom: 10px;
            }

            .recording-item {
                display: flex;
                align-items: center;
                padding: 8px 0;
                border-bottom: 1px solid #ebeef5;

                .recording-name {
                    flex: 1;
                    font-size: 14px;
                    color: #303133;
                }

                .recording-time {
                    font-size: 12px;
                    color: #909399;
                    margin-right: 10px;
                }
            }
        }
    }
}

@keyframes pulse {
    0% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.4);
    }

    70% {
        transform: scale(1.1);
        box-shadow: 0 0 0 10px rgba(245, 108, 108, 0);
    }

    100% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
    }
}

.radar-settings {
    margin-top: -20px;
    padding: 15px;
    background-color: #f8f9fa;
    border-radius: 4px;

    .speed-range {
        display: flex;
        align-items: center;
        gap: 10px;

        .el-input-number {
            width: 120px;
        }

        .speed-separator {
            color: #606266;
            font-size: 16px;
        }

        .speed-unit {
            color: #606266;
            margin-left: 5px;
        }
    }
}

.time-range {
    display: flex;
    flex-direction: column;
    gap: 10px;

    .time-header {
        margin-bottom: 5px;
    }

    .time-pickers {
        display: flex;
        align-items: center;
        width: 100%;

        .time-separator {
            margin: 0 10px;
        }
    }
}

.weekday-select {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

// 自定义表格滚动条样式
.el-table {

    // 设置表格容器的滚动条样式
    &::-webkit-scrollbar {
        width: 12px;
        height: 12px;
    }

    &::-webkit-scrollbar-track {
        background: #f1f1f1;
        border-radius: 6px;
    }

    &::-webkit-scrollbar-thumb {
        background: #c1c1c1;
        border-radius: 6px;

        &:hover {
            background: #a8a8a8;
        }
    }

    // 表格内部的滚动条样式
    .el-table__body-wrapper {
        &::-webkit-scrollbar {
            width: 28px;
            height: 28px;
        }

        &::-webkit-scrollbar-track {
            background: #f5f5f5;
            border-radius: 29px;
        }

        &::-webkit-scrollbar-thumb {
            background: #bfbfbf;
            border-radius: 29px;
        }
    }
}

// 全局滚动条样式（影响所有滚动条）
::-webkit-scrollbar {
    width: 12px;
    height: 12px;
}

::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 6px;
}

::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 6px;

    &:hover {
        background: #a8a8a8;
    }
}

/* 弹窗内canvas预览区固定顶部 */
.sticky-canvas-preview {
    position: sticky;
    top: 0;
    z-index: 10;
    background: #fff;
    padding-top: 24px;
    padding-bottom: 16px;
    box-shadow: 0 2px 8px #e0e0e0;
    border-radius: 16px;
    margin-bottom: 20px;
}

.board-status-single {
    margin-top: 6px;
    font-size: 15px;

    span {
        font-weight: 600;

        &.online {
            color: #67C23A;
        }

        &.offline {
            color: #bbb;
        }
    }
}

.title.offline {
    color: #bbb;
}
</style>