##
zhou zhou
2025-12-23 9c44fd1baaf6f6d854677cf590e974a021840dd2
##
1个文件已修改
570 ■■■■■ 已修改文件
Monitor-APP/pages/home/home.vue 570 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Monitor-APP/pages/home/home.vue
@@ -12,8 +12,8 @@
                <view class="button-right" @click="ejected()"></view>
                <!-- 主视图 -->
                <view class="main">
                    <view class="mian-item">
                        <view class="mian-item-box">
                    <view class="main-item">
                        <view class="main-item-box">
                            <y-box>
                                <view class="box-item">
                                    <text class="item-title">智能大屏显示系统</text>
@@ -23,9 +23,9 @@
                                    <text class="item-title">仓库数据</text>
                                    <text class="item-subTitle">warehouse data</text>
                                    <view class="img-box">
                                        <view style="width: 100%; display: flex" v-for="(item, i) in locList" :key="i">
                                            <view style="display: flex; justify-content: center; align-items: center; width: 48%; font-size: 1.3vw">物料码: {{ item.matnr }}</view>
                                            <view style="display: flex; justify-content: center; align-items: center; width: 48%; font-size: 1.3vw">数量: {{ item.matnrCount }}</view>
                                        <view class="data-row" v-for="(item, i) in locList" :key="i">
                                            <view class="data-cell data-text">物料码: {{ item.matnr }}</view>
                                            <view class="data-cell data-text-lg">数量: {{ item.matnrCount }}</view>
                                        </view>
                                    </view>
                                </view>
@@ -61,8 +61,8 @@
                            </y-box>
                        </view>
                    </view>
                    <view class="mian-item">
                        <view class="mian-item-box">
                    <view class="main-item">
                        <view class="main-item-box">
                            <view style="width: 100%; height: 100%">
                                <view style="height: 35%">
                                    <!-- 折线图 -->
@@ -113,27 +113,21 @@
                                                        :echartsApp="true"
                                                    />
                                                </view>
                                                <view>
                                                <view class="stat-panel">
                                                    <view class="flex-row sub-info">
                                                        <image src="../../static/g1.png" mode="aspectFit"></image>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw">在库</view>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw; font-size: 2vw">
                                                            {{ baseInfo.stockCount }}
                                                        <view class="stat-label">在库</view>
                                                        <view class="stat-label data-text-lg">{{ baseInfo.stockCount }}</view>
                                                        </view>
                                                    </view>
                                                    <view class="flex-row sub-info" style="margin-top: 2vh">
                                                    <view class="flex-row sub-info">
                                                        <image src="../../static/f1.png" mode="aspectFit"></image>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw">空库</view>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw; font-size: 2vw">
                                                            {{ baseInfo.emptyCount }}
                                                        <view class="stat-label">空库</view>
                                                        <view class="stat-label data-text-lg">{{ baseInfo.emptyCount }}</view>
                                                        </view>
                                                    </view>
                                                    <view class="flex-row sub-info" style="margin-top: 2vh">
                                                    <view class="flex-row sub-info">
                                                        <image src="../../static/e1.png" mode="aspectFit"></image>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw">锁定</view>
                                                        <view style="width: 8vw; height: 4vw; line-height: 4vw; font-size: 2vw">
                                                            {{ baseInfo.noneCount }}
                                                        </view>
                                                        <view class="stat-label">锁定</view>
                                                        <view class="stat-label data-text-lg">{{ baseInfo.noneCount }}</view>
                                                    </view>
                                                </view>
                                            </view>
@@ -266,15 +260,31 @@
</template>
<script>
import WebSocketUtil from './uniWebSocket';
// 任务类型常量映射
const IO_TYPE_MAP = {
    101: '全板出库',
    103: '拣料出库',
    107: '盘点出库',
    1: '入库',
    10: '空托入库',
    110: '空托出库',
    104: '并板出库',
    54: '并板入库'
};
export default {
    data() {
        return {
            // 视图状态
            homeViewShow: true,
            infoViewShow: false,
            errorInfoViewShow: false,
            homeMode: [],
            infoMode: [],
            errorInfoMode: [],
            // 基础信息
            baseInfo: {
                xDistance: 1,
                yDistance: 2,
@@ -286,29 +296,33 @@
                emptyCount: '',
                noneCount: ''
            },
            // 图表数据
            chartsData: {
                Line: {
                    categories: [],
                    series: []
                Line: { categories: [], series: [] },
                Pie: { series: [{ data: [] }] }
                },
                Pie: {
                    series: [
                        {
                            data: []
                        }
                    ]
                }
            },
            // 动画与图表
            duration: 300,
            calendar: '',
            ringOpts: {},
            chartsDataLine1: {},
            chartsDataPie2: {},
            // 配置弹窗
            ejectShow: false,
            // 服务器配置
            baseIP: '',
            basePort: '',
            baseLedId: '',
            baseUrl: '',
            wmsUrl: '',
            wmsPort: '',
            getConfigUrl: 'http://127.0.0.1:8088/wcs/monitor/getIpConfig',
            // 任务信息
            infoType: 0,
            infoText: {
                title: '',
@@ -321,23 +335,33 @@
                barcode: ''
            },
            swiperList: [],
            // 连接状态
            timeOut: false,
            times: 0,
            socketClient: null,
            socketUrl: '',
            // 日期时间
            currDate: '',
            locList: [],
            oldHours: '',
            oldMin: '',
            socketClient: null,
            // 版本更新
            version: '',
            socketUrl: '',
            msgType: 'success',
            filename: '',
            dialogContent: '',
            wmsUrl: '',
            wmsPort: '',
            // 视图切换控制
            switchDebounceTimer: null,
            viewSwitchInProgress: false,
            getConfigUrl:"http://127.0.0.1:8088/wcs/monitor/getIpConfig"
            // 定时器ID(用于清理)
            refreshTimerId: null,
            restartTimerId: null,
            gcTimerId: null
        };
    },
    onShow() {
@@ -346,95 +370,111 @@
        plus.navigator.setFullscreen(true);
        // #endif
        this.getVersion();
    },
    mounted() {
        let that = this;
        const BaseIP = uni.getStorageSync('BaseIp');
        const BaseLedId = uni.getStorageSync('BaseLedId');
        const BasePort = uni.getStorageSync('BasePort');
        const BaseCrnId = uni.getStorageSync('BaseCrnId');
        const PROJ = uni.getStorageSync('UPROJ');
        const WMSURL = uni.getStorageSync('wmsUrl');
        const WMSPORT = uni.getStorageSync('wmsPort');
        that.baseUrl = PROJ;
        that.baseIP = BaseIP;
        that.baseLedId = BaseLedId;
        that.basePort = BasePort;
        that.baseCrnId = BaseCrnId;
        that.wmsUrl = WMSURL;
        that.wmsPort = WMSPORT;
        // 使用统一的配置初始化方法
        this.initConfig();
    },
    created() {
        let that = this;
        const BaseIP = uni.getStorageSync('BaseIp');
        const BaseLedId = uni.getStorageSync('BaseLedId');
        const BasePort = uni.getStorageSync('BasePort');
        const BaseCrnId = uni.getStorageSync('BaseCrnId');
        const PROJ = uni.getStorageSync('UPROJ');
        const WMSURL = uni.getStorageSync('wmsUrl');
        const WMSPORT = uni.getStorageSync('wmsPort');
        that.baseUrl = PROJ;
        that.baseIP = BaseIP;
        that.baseLedId = BaseLedId;
        that.basePort = BasePort;
        that.baseCrnId = BaseCrnId;
        that.wmsUrl = WMSURL;
        that.wmsPort = WMSPORT;
        that.getIPConfig();
        that.uniWebSocket();
        // that.webSockerInit()
        // 初始化配置并建立连接
        this.initConfig();
        this.getIPConfig();
        this.uniWebSocket();
    },
    onLoad() {
        setInterval(() => {
        // 数据刷新定时器(每秒)
        this.refreshTimerId = setInterval(() => {
            this.getServerData();
            this.controller();
        }, 1000);
        setInterval(() => {
        // 应用重启定时器(每4小时)
        this.restartTimerId = setInterval(() => {
            setTimeout(() => {
                plus.runtime.restart();
            }, 100);
        }, 1000 * 60 * 60 * 4);
        // 每小时触发一次垃圾回收
        const memoryCleanerId = setInterval(() => {
        // 垃圾回收定时器(每5分钟)
        this.gcTimerId = setInterval(() => {
            // #ifdef APP-PLUS
            if (plus.os.name.toLowerCase() === 'android') {
                // 强制触发 GC
                plus.android.importClass('java.lang.System');
                plus.android.invoke('java.lang.System', 'gc');
                console.log('手动触发 GC');
            }
            // 清理不再需要的大型数据对象
            // #endif
            // 清理大型数据对象
            if (this.oldData && this.oldData.length > 100) {
                this.oldData = [];
            }
        }, 30000 * 10); // 每小时执行一次
        }, 30000 * 10);
    },
    methods: {
        getIPConfig(){
            const that = this
            
                console.log(that.baseIP)
    // 组件卸载时清理资源
    onUnload() {
        // 清理所有定时器
        if (this.refreshTimerId) {
            clearInterval(this.refreshTimerId);
            this.refreshTimerId = null;
        }
        if (this.restartTimerId) {
            clearInterval(this.restartTimerId);
            this.restartTimerId = null;
        }
        if (this.gcTimerId) {
            clearInterval(this.gcTimerId);
            this.gcTimerId = null;
        }
        if (this.switchDebounceTimer) {
            clearTimeout(this.switchDebounceTimer);
            this.switchDebounceTimer = null;
        }
        // 关闭 WebSocket 连接
        if (this.socketClient) {
            this.socketClient.close();
            this.socketClient = null;
        }
    },
    methods: {
        // 统一的配置初始化方法
        initConfig() {
            this.baseIP = uni.getStorageSync('BaseIp') || '';
            this.baseLedId = uni.getStorageSync('BaseLedId') || '';
            this.basePort = uni.getStorageSync('BasePort') || '';
            this.baseCrnId = uni.getStorageSync('BaseCrnId') || '';
            this.baseUrl = uni.getStorageSync('UPROJ') || '';
            this.wmsUrl = uni.getStorageSync('wmsUrl') || '';
            this.wmsPort = uni.getStorageSync('wmsPort') || '';
        },
        // 获取IP配置(带错误处理)
        getIPConfig() {
            const that = this;
                uni.request({
                    url: that.getConfigUrl,                
                    method:'GET',
                timeout: 5000,
                    success(result) {
                        console.log(result)
                        const res = result.data
                        uni.setStorageSync('BaseIp', res.data.ledIp);
                        uni.setStorageSync('BaseLedId', res.data.ledId);
                        uni.setStorageSync('BasePort', res.data.ledPort);
                        uni.setStorageSync('UPROJ', res.data.ledUrl);
                    if (result.data && result.data.data) {
                        const configData = result.data.data;
                        uni.setStorageSync('BaseIp', configData.ledIp || '');
                        uni.setStorageSync('BaseLedId', configData.ledId || '');
                        uni.setStorageSync('BasePort', configData.ledPort || '');
                        uni.setStorageSync('UPROJ', configData.ledUrl || '');
                        console.log('IP配置获取成功');
                    }
                },
                fail(err) {
                    console.error('获取IP配置失败:', err);
                    // 使用本地存储的配置作为后备
                    }
                });
        },
        uniWebSocket() {
            let that = this;
@@ -494,20 +534,7 @@
                that.baseInfo.used = data.used;
                that.baseInfo.usedPr = data.usedPr;
                that.chartsData.Line.categories = [
                    that.getDateFormat(-11),
                    that.getDateFormat(-10),
                    that.getDateFormat(-9),
                    that.getDateFormat(-8),
                    that.getDateFormat(-7),
                    that.getDateFormat(-6),
                    that.getDateFormat(-5),
                    that.getDateFormat(-4),
                    that.getDateFormat(-3),
                    that.getDateFormat(-2),
                    that.getDateFormat(-1),
                    that.getDateFormat(0)
                ];
                that.chartsData.Line.categories = that.generateDateCategories(-11, 0);
                that.chartsData.Line.series = data.rows;
                that.calendar = data.year + '年' + data.month + '月' + data.day + '日 ' + data.hour + ':' + data.minute + ':' + data.second + ' ' + data.week;
                that.currDate = data.year + '/' + data.month + '/' + data.day;
@@ -535,59 +562,63 @@
                    that.infoType = 0;
                }
            } else if (data.type === 'task') {
                if (data.taskList && data.taskList !== '' && data.taskList.length != 0) {
                    if (that.infoType == 2 || that.infoType == 3) {
                        return;
                    }
                    // infoType1:只有拣料等信息
                    that.infoType = 1;
                    if (data.taskList[0].ioType === 101) {
                        that.infoText.title = '全板出库';
                    } else if (data.taskList[0].ioType === 103) {
                        that.infoText.title = '拣料出库';
                    } else if (data.taskList[0].ioType === 107) {
                        that.infoText.title = '盘点出库';
                    } else if (data.taskList[0].ioType === 1) {
                        that.infoText.title = '入库';
                    } else if (data.taskList[0].ioType === 10) {
                        that.infoText.title = '空托入库';
                    } else if (data.taskList[0].ioType === 110) {
                        that.infoText.title = '空托出库';
                    } else if (data.taskList[0].ioType === 104) {
                        that.infoText.title = '并板出库';
                    } else if (data.taskList[0].ioType === 54) {
                        that.infoText.title = '并板入库';
                    }else  {
                        that.infoText.title = data.taskList[0].title;
                    }
                    that.infoText.barcode = data.taskList[0].barcode;
                    that.infoText.workNo = data.taskList[0].workNo;
                    that.infoText.sourceLocNo = data.taskList[0].sourceLocNo;
                    that.infoText.staNo = data.taskList[0].staNo;
                    that.swiperList = data.taskList[0].matDtos;
                this.handleTaskData(data);
                }else if(data.type === 'NoData'){                    
                    that.infoType = 0;
                }
                 else {
                    if (that.infoType == 2 || that.infoType == 3) {
        },
        // 处理任务数据(使用常量映射简化逻辑)
        handleTaskData(data) {
            // 无任务列表时
            if (!data.taskList || data.taskList.length === 0) {
                // 异常信息优先级更高,不切换
                if (this.infoType === 2 || this.infoType === 3) {
                        return;
                    }
                    that.infoType = 0;
                this.infoType = 0;
                return;
                }
            // 异常信息优先级更高,不切换到任务视图
            if (this.infoType === 2 || this.infoType === 3) {
                return;
            }
            const task = data.taskList[0];
            this.infoType = 1;
            // 使用常量映射获取任务类型名称
            this.infoText.title = IO_TYPE_MAP[task.ioType] || task.title || '任务';
            this.infoText.barcode = task.barcode || '';
            this.infoText.workNo = task.workNo || '';
            this.infoText.sourceLocNo = task.sourceLocNo || '';
            this.infoText.staNo = task.staNo || '';
            this.swiperList = task.matDtos || [];
        },
        // 生成日期分类数组(性能优化)
        generateDateCategories(startOffset, endOffset) {
            const categories = [];
            const baseDate = new Date(this.currDate);
            for (let i = startOffset; i <= endOffset; i++) {
                const date = new Date(baseDate);
                date.setDate(date.getDate() + i);
                categories.push(`${date.getMonth() + 1}-${date.getDate()}`);
            }
            return categories;
        },
        getDateFormat(value) {
            var date = new Date(this.currDate); // 获取当前时间
            date.setDate(date.getDate() + value); // 设置天数 -1 天
            var m = date.getMonth() + 1;
            var d = date.getDate();
            var newDate = m + '-' + d;
            return newDate;
            const date = new Date(this.currDate);
            date.setDate(date.getDate() + value);
            return `${date.getMonth() + 1}-${date.getDate()}`;
        },
        getServerData() {
            this.chartsDataLine1 = JSON.parse(JSON.stringify(this.chartsData.Line));
            this.chartsDataPie2 = JSON.parse(JSON.stringify(this.chartsData.Pie));
            // 使用展开运算符浅拷贝(性能优于 JSON 深拷贝)
            this.chartsDataLine1 = { ...this.chartsData.Line };
            this.chartsDataPie2 = { ...this.chartsData.Pie };
        },
        // 控制器
        controller() {
@@ -619,37 +650,53 @@
            }
        },
        showHomeView() {
        // 统一的视图切换方法
        switchToView(viewType) {
            const transitionMode = ['fade', 'slide-bottom'];
            // 根据目标视图隐藏其他视图
            if (viewType !== 'home') {
                this.homeViewShow = false;
                this.homeMode = transitionMode;
            }
            if (viewType !== 'info') {
            this.infoViewShow = false;
                this.infoMode = transitionMode;
            }
            if (viewType !== 'error') {
            this.errorInfoViewShow = false;
            this.infoMode = ['fade', 'slide-bottom'];
            this.errorInfoMode = ['fade', 'slide-bottom'];
                this.errorInfoMode = transitionMode;
            }
            // 延迟显示目标视图
            setTimeout(() => {
                switch(viewType) {
                    case 'home':
                this.homeViewShow = true;
                this.homeMode = ['fade', 'slide-bottom'];
                        this.homeMode = transitionMode;
                        break;
                    case 'info':
                        this.infoViewShow = true;
                        this.infoMode = transitionMode;
                        break;
                    case 'error':
                        this.errorInfoViewShow = true;
                        this.errorInfoMode = transitionMode;
                        break;
                }
            }, this.duration);
        },
        showHomeView() {
            this.switchToView('home');
        },
        showInfoView() {
            this.homeViewShow = false;
            this.errorInfoViewShow = false;
            this.homeMode = ['fade', 'slide-bottom'];
            this.errorInfoMode = ['fade', 'slide-bottom'];
            setTimeout(() => {
                this.infoViewShow = true;
                this.infoMode = ['fade', 'slide-bottom'];
            }, this.duration);
            this.switchToView('info');
        },
        showErrorView() {
            this.homeViewShow = false;
            this.infoViewShow = false;
            this.homeMode = ['fade', 'slide-bottom'];
            this.infoMode = ['fade', 'slide-bottom'];
            setTimeout(() => {
                this.errorInfoViewShow = true;
                this.errorInfoMode = ['fade', 'slide-bottom'];
            }, this.duration);
            this.switchToView('error');
        },
        // 配置
        ejected() {
@@ -692,33 +739,34 @@
                that.getUpdateVersion();
            }, 100);
        },
        // 校验版本
        // 校验版本(增强错误处理)
        getUpdateVersion() {
            let that = this;
            let type = 1;
            if (that.baseUrl == 'http://undefined:undefined/undefined') {
            const that = this;
            const type = 1;
            // 配置验证
            if (!that.baseIP || !that.wmsPort || !that.wmsUrl) {
                console.log('版本检查跳过:配置不完整');
                return;
            }
            const wms = 'http://' + that.baseIP + ':' + that.wmsPort + '/' + that.wmsUrl;
            console.log(wms);
            let url = wms + '/appVersion/checkUpdate/' + that.version + '/' + type;
            const wmsBaseUrl = `http://${that.baseIP}:${that.wmsPort}/${that.wmsUrl}`;
            const url = `${wmsBaseUrl}/appVersion/checkUpdate/${that.version}/${type}`;
            uni.request({
                url: url,
                method: 'GET',
                success(res) {
                    console.log(res);
                    var res = res.data;
                    if (res.data) {
                timeout: 10000,
                success(result) {
                    const res = result.data;
                    if (res && res.data) {
                        that.filename = res.data.path;
                        that.dialogContent = '发现新版本:' + res.data.version + ', 是否立即更新';
                        that.dialogContent = `发现新版本: ${res.data.version}, 是否立即更新`;
                        that.$refs.upVersion.open();
                    } else {
                        uni.showToast({
                            title: res.msg,
                            icon: 'none',
                            position: 'top'
                        });
                    }
                },
                fail(err) {
                    console.error('版本检查失败:', err);
                }
            });
        },
@@ -799,38 +847,99 @@
<style>
@import url('home.css');
/* 列 */
/* ========== 工具类 ========== */
.flex-col {
    display: flex;
    flex-direction: column;
}
/* 行 */
.flex-row {
    display: flex;
    flex-direction: row;
}
.flex-full {
    width: 100%;
    display: flex;
}
.flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}
/* 数据文本样式 */
.data-text {
    font-size: 1.3vw;
    color: rgba(255, 255, 255, 0.9);
}
.data-text-lg {
    font-size: 2vw;
    font-weight: 600;
    color: #00d4ff;
}
.data-text-xl {
    font-size: 3vw;
    font-weight: 700;
}
.stat-label {
    width: 8vw;
    height: 4vw;
    line-height: 4vw;
}
/* 数据行列表 */
.data-row {
    width: 100%;
    display: flex;
    padding: 0.5vw 0;
    border-bottom: 1px solid rgba(0, 212, 255, 0.1);
    transition: background 0.2s ease;
}
.data-row:hover {
    background: rgba(0, 212, 255, 0.05);
}
.data-cell {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 48%;
}
/* 统计面板 */
.stat-panel {
    display: flex;
    flex-direction: column;
    gap: 1.5vh;
}
/* ========== 容器 ========== */
.container {
    width: 100vw;
    min-height: 100vh;
    background-color: #00163e;
    background: linear-gradient(135deg, #00163e 0%, #001a4d 50%, #00163e 100%);
    color: #fff;
    text-align: center;
}
/* 主视图 */
/* ========== 主视图 ========== */
.home-view {
    width: 100vw;
    min-height: 100vh;
    background-image: url(../../static/background.png);
    background-size: 100vw 100vh;
    background-position: center;
}
.home-right {
    width: 50vw;
    height: 89vh;
    /* background-color: cadetblue; */
}
.home-right-box {
@@ -838,6 +947,7 @@
    height: 98%;
}
/* ========== 头部标题 ========== */
.head {
    width: 100vw;
    height: 11vh;
@@ -847,16 +957,23 @@
    align-items: center;
    justify-content: center;
    transform: scale(0.7);
    background: linear-gradient(180deg, rgba(0, 212, 255, 0.1) 0%, transparent 100%);
    text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
    letter-spacing: 0.5vw;
}
/* ========== 时间工具栏 ========== */
.time-tools {
    position: absolute;
    right: 2%;
    top: 2%;
    font-size: 1vw;
    transform: scale(0.8);
    color: rgba(255, 255, 255, 0.8);
    text-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
}
/* ========== 装饰按钮 ========== */
.button-left {
    position: absolute;
    background-image: url(../../static/right.png);
@@ -866,6 +983,13 @@
    width: 13.5%;
    height: 8.5%;
    transform: scaleX(-1);
    opacity: 0.9;
    transition: opacity 0.3s ease;
}
.button-left:hover,
.button-right:hover {
    opacity: 1;
}
.button-right {
@@ -876,17 +1000,30 @@
    left: 65%;
    width: 13.5%;
    height: 8.5%;
    opacity: 0.9;
    transition: opacity 0.3s ease;
}
/* ========== 仓库数据列表 ========== */
.img-box {
    height: 23vw;
    width: 100%;
    /* background-color: #666666; */
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    flex-wrap: wrap;
    font-size: 1vw;
    overflow-y: auto;
    padding: 0.5vw;
}
.img-box::-webkit-scrollbar {
    width: 4px;
}
.img-box::-webkit-scrollbar-thumb {
    background: rgba(0, 212, 255, 0.5);
    border-radius: 2px;
}
.item-img {
@@ -894,7 +1031,6 @@
    margin-top: 5%;
    display: flex;
    flex-direction: row;
    /* background-color: #00ffff; */
    justify-content: flex-start;
    align-items: flex-start;
}
@@ -907,21 +1043,38 @@
    width: 5vw;
}
/* ========== 进度条(增强版)========== */
.progressBar {
    margin-top: 9%;
    /* width: 100%; */
    height: 20%;
    background-color: #233751;
    background: linear-gradient(90deg, #233751 0%, #1a2a3d 100%);
    border-radius: 5vw;
    overflow: hidden;
    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
    position: relative;
}
.progress {
    /* width: 90%; */
    height: 100%;
    background-color: #ff5722;
    max-width: 100%;
    background: linear-gradient(90deg, #ff9800 0%, #ff5722 50%, #f44336 100%);
    border-radius: 5vw;
    transition: width 0.5s ease-out;
    position: relative;
}
.progress::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, transparent 50%);
    border-radius: 5vw;
}
/* ========== 图表区域 ========== */
.charts-box {
    width: 80%;
    height: 24vh;
@@ -931,19 +1084,28 @@
.charts-box-ring {
    width: 60%;
    height: 24vh;
    /* background-color: #00ffff; */
}
/* ========== 库存统计信息 ========== */
.sub-info {
    font-size: 1.5vw;
    padding: 0.5vw 1vw;
    border-radius: 0.5vw;
    background: rgba(0, 212, 255, 0.05);
    transition: background 0.3s ease;
}
.sub-info:hover {
    background: rgba(0, 212, 255, 0.1);
}
.sub-info image {
    width: 3.5vw;
    height: 3.5vw;
    margin-right: 0.5vw;
}
/* 共用 */
/* ========== 共用 ========== */
.main {
    width: 100vw;
    height: 88vh;
@@ -951,7 +1113,7 @@
    display: flex;
}
.mian-item {
.main-item {
    width: 50%;
    height: 100%;
    display: flex;
@@ -960,7 +1122,7 @@
    justify-content: center;
}
.mian-item-box {
.main-item-box {
    width: 98%;
    height: 98%;
}