| | |
| | | ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] |
| | | ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] |
| | | ElDialog: typeof import('element-plus/es')['ElDialog'] |
| | | ElDivider: typeof import('element-plus/es')['ElDivider'] |
| | | ElDrawer: typeof import('element-plus/es')['ElDrawer'] |
| | | ElEmpty: typeof import('element-plus/es')['ElEmpty'] |
| | | ElForm: typeof import('element-plus/es')['ElForm'] |
| | |
| | | ElHeader: typeof import('element-plus/es')['ElHeader'] |
| | | ElIcon: typeof import('element-plus/es')['ElIcon'] |
| | | ElImage: typeof import('element-plus/es')['ElImage'] |
| | | ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] |
| | | ElInput: typeof import('element-plus/es')['ElInput'] |
| | | ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] |
| | | ElLink: typeof import('element-plus/es')['ElLink'] |
| | | ElMain: typeof import('element-plus/es')['ElMain'] |
| | | ElMenu: typeof import('element-plus/es')['ElMenu'] |
| | | ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] |
| | |
| | | ElOption: typeof import('element-plus/es')['ElOption'] |
| | | ElPagination: typeof import('element-plus/es')['ElPagination'] |
| | | ElPopover: typeof import('element-plus/es')['ElPopover'] |
| | | ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] |
| | | ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] |
| | | ElRow: typeof import('element-plus/es')['ElRow'] |
| | | ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |
| | | ElSelect: typeof import('element-plus/es')['ElSelect'] |
| | | ElSpace: typeof import('element-plus/es')['ElSpace'] |
| | | ElStep: typeof import('element-plus/es')['ElStep'] |
| | | ElSteps: typeof import('element-plus/es')['ElSteps'] |
| | | ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] |
| | | ElSwitch: typeof import('element-plus/es')['ElSwitch'] |
| | | ElTable: typeof import('element-plus/es')['ElTable'] |
| | |
| | | ElTag: typeof import('element-plus/es')['ElTag'] |
| | | ElText: typeof import('element-plus/es')['ElText'] |
| | | ElTooltip: typeof import('element-plus/es')['ElTooltip'] |
| | | ElTree: typeof import('element-plus/es')['ElTree'] |
| | | ElUpload: typeof import('element-plus/es')['ElUpload'] |
| | | FormCol: typeof import('./src/components/layout/FormCol.vue')['default'] |
| | | FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default'] |
| | | FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default'] |
| | |
| | | FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default'] |
| | | FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default'] |
| | | FYTable: typeof import('./src/components/table/FYTable.vue')['default'] |
| | | IEpDownload: typeof import('~icons/ep/download')['default'] |
| | | IEpInfoFilled: typeof import('~icons/ep/info-filled')['default'] |
| | | ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default'] |
| | | ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default'] |
| | | ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default'] |
| | |
| | | const ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] |
| | | const ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] |
| | | const ElDialog: typeof import('element-plus/es')['ElDialog'] |
| | | const ElDivider: typeof import('element-plus/es')['ElDivider'] |
| | | const ElDrawer: typeof import('element-plus/es')['ElDrawer'] |
| | | const ElEmpty: typeof import('element-plus/es')['ElEmpty'] |
| | | const ElForm: typeof import('element-plus/es')['ElForm'] |
| | |
| | | const ElHeader: typeof import('element-plus/es')['ElHeader'] |
| | | const ElIcon: typeof import('element-plus/es')['ElIcon'] |
| | | const ElImage: typeof import('element-plus/es')['ElImage'] |
| | | const ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] |
| | | const ElInput: typeof import('element-plus/es')['ElInput'] |
| | | const ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] |
| | | const ElLink: typeof import('element-plus/es')['ElLink'] |
| | | const ElMain: typeof import('element-plus/es')['ElMain'] |
| | | const ElMenu: typeof import('element-plus/es')['ElMenu'] |
| | | const ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] |
| | |
| | | const ElOption: typeof import('element-plus/es')['ElOption'] |
| | | const ElPagination: typeof import('element-plus/es')['ElPagination'] |
| | | const ElPopover: typeof import('element-plus/es')['ElPopover'] |
| | | const ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] |
| | | const ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] |
| | | const ElRow: typeof import('element-plus/es')['ElRow'] |
| | | const ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] |
| | | const ElSelect: typeof import('element-plus/es')['ElSelect'] |
| | | const ElSpace: typeof import('element-plus/es')['ElSpace'] |
| | | const ElStep: typeof import('element-plus/es')['ElStep'] |
| | | const ElSteps: typeof import('element-plus/es')['ElSteps'] |
| | | const ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] |
| | | const ElSwitch: typeof import('element-plus/es')['ElSwitch'] |
| | | const ElTable: typeof import('element-plus/es')['ElTable'] |
| | |
| | | const ElTag: typeof import('element-plus/es')['ElTag'] |
| | | const ElText: typeof import('element-plus/es')['ElText'] |
| | | const ElTooltip: typeof import('element-plus/es')['ElTooltip'] |
| | | const ElTree: typeof import('element-plus/es')['ElTree'] |
| | | const ElUpload: typeof import('element-plus/es')['ElUpload'] |
| | | const FormCol: typeof import('./src/components/layout/FormCol.vue')['default'] |
| | | const FYBgTaskCard: typeof import('./src/components/bg-task/FYBgTaskCard.vue')['default'] |
| | | const FYBgTaskDialog: typeof import('./src/components/bg-task/FYBgTaskDialog.vue')['default'] |
| | |
| | | const FYReconfrimButton: typeof import('./src/components/button/FYReconfrimButton.vue')['default'] |
| | | const FYSearchBar: typeof import('./src/components/search-option/FYSearchBar.vue')['default'] |
| | | const FYTable: typeof import('./src/components/table/FYTable.vue')['default'] |
| | | const IEpDownload: typeof import('~icons/ep/download')['default'] |
| | | const IEpInfoFilled: typeof import('~icons/ep/info-filled')['default'] |
| | | const ItemDevice: typeof import('./src/components/list-item/ItemDevice.vue')['default'] |
| | | const ItemMonitorObj: typeof import('./src/components/list-item/ItemMonitorObj.vue')['default'] |
| | | const ItemScene: typeof import('./src/components/list-item/ItemScene.vue')['default'] |
| | |
| | | <!-- </el-scrollbar> --> |
| | | <!-- 商标 --> |
| | | <el-row ref="subTitleRef" class="sub-title" justify="center"> |
| | | <el-space>{{ collapse ? '' : subTitle }}</el-space> |
| | | <!-- <el-space>{{ collapse ? '' : subTitle }}</el-space> --> |
| | | </el-row> |
| | | </el-scrollbar> |
| | | </el-aside> |
| | |
| | | return { |
| | | // collapse: false, |
| | | menuHeight: '80vh', |
| | | title: '油烟智能监测与监管', |
| | | title: '餐饮油烟智能监测监管', |
| | | subTitle: '©上海飞羽环保科技有限公司', |
| | | appIcon: AppIcon, |
| | | } |
| | |
| | | name: '环信码', |
| | | children: [ |
| | | { |
| | | path: '/index/inspection/scene-info', |
| | | icon: 'solar:shop-2-line-duotone', |
| | | name: '店铺管理', |
| | | }, |
| | | { |
| | | path: '/index/analysis/auto-evalution', |
| | | icon: 'solar:pie-chart-3-line-duotone', |
| | | name: '自动评估', |
| | |
| | | { |
| | | path: '/index/analysis/huanxincode-manage', |
| | | icon: 'solar:archive-down-minimlistic-line-duotone', |
| | | name: '环信码', |
| | | name: '评估排名', |
| | | }, |
| | | { |
| | | path: '/index/inspection/report-manage', |
| | | icon: 'solar:folder-favourite-bookmark-line-duotone', |
| | | name: '评估报告', |
| | | }, |
| | | { |
| | | path: '/index/inspection/scene-info', |
| | | icon: 'solar:shop-2-line-duotone', |
| | | name: '店铺管理', |
| | | }, |
| | | |
| | | // { |
| | | // path: '/index/analysis/data-product', |
| | | // icon: 'solar:document-add-line-duotone', |
| | |
| | | |
| | | // 先添加指定的店铺 |
| | | specifiedShops.forEach((name, index) => { |
| | | // 随机生成在线状态(80%概率在线) |
| | | const isOnline = Math.random() < 0.8 |
| | | // 随机生成异常状态(只有在线时才可能有异常) |
| | | // 0: 油烟浓度超标, 1: 供电异常, 2: 设备或网络异常, 3: 无异常 |
| | | const exceptionStatus = isOnline ? Math.floor(Math.random() * 4) : 2 // 离线时默认为设备或网络异常 |
| | | |
| | | shops.push({ |
| | | shop: { |
| | | name: name, |
| | |
| | | longitude: 121.45 + Math.random() * 0.1, |
| | | ringCodeLevel: ringCodeLevels[Math.floor(Math.random() * ringCodeLevels.length)], |
| | | ringCodePublishTime: '2023-03-16 10:00:00', |
| | | isOnline: isOnline, |
| | | exceptionStatus: exceptionStatus, |
| | | }, |
| | | recentData: generateRecentData(), |
| | | }) |
| | |
| | | const suffix = nameSuffixes[Math.floor(Math.random() * nameSuffixes.length)] |
| | | const randomName = `${prefix}${cuisine}${suffix}${i}` |
| | | |
| | | // 随机生成在线状态(80%概率在线) |
| | | const isOnline = Math.random() < 0.8 |
| | | // 随机生成异常状态(只有在线时才可能有异常) |
| | | // 0: 油烟浓度超标, 1: 供电异常, 2: 设备或网络异常, 3: 无异常 |
| | | const exceptionStatus = isOnline ? Math.floor(Math.random() * 4) : 2 // 离线时默认为设备或网络异常 |
| | | |
| | | shops.push({ |
| | | shop: { |
| | | name: randomName, |
| | |
| | | longitude: 121.41 + Math.random() * 0.1, |
| | | ringCodeLevel: ringCodeLevels[Math.floor(Math.random() * ringCodeLevels.length)], |
| | | ringCodePublishTime: '2023-03-16 10:00:00', |
| | | isOnline: isOnline, |
| | | exceptionStatus: exceptionStatus, |
| | | }, |
| | | recentData: generateRecentData(), |
| | | }) |
| | |
| | | import { useToolboxStore } from '@/stores/toolbox' |
| | | import util from './util' |
| | | import * as echarts from 'echarts' |
| | | import exceedIcon from '@/assets/exceed.png' |
| | | import exceptionIcon from '@/assets/exception.png' |
| | | import offlineIcon from '@/assets/offline.png' |
| | | |
| | | const toolboxStore = useToolboxStore() |
| | | |
| | | var _massMarks = undefined |
| | | |
| | | // 环信码等级和对应颜色 |
| | | const ringCodeLevelColors = [ |
| | | '#52c41a', // 绿色 |
| | | '#faad14', // 黄色 |
| | | '#f5222d', // 红色 |
| | | ] |
| | | // 状态图标配置 |
| | | const statusIcons = { |
| | | exceed: exceedIcon, // 油烟浓度超标 |
| | | exception: exceptionIcon, // 供电异常 |
| | | offline: offlineIcon, // 设备或网络异常 |
| | | online: createCustomMarkerOnline(), // 在线状态 |
| | | offlineStatus: createCustomMarkerOffline(), // 离线状态 |
| | | } |
| | | |
| | | /** |
| | | * 绘制海量点标记 |
| | |
| | | * @param {number} shops[].shop.longitude 经度 |
| | | * @param {string} shops[].shop.ringCodeLevel 最新环信码等级 |
| | | * @param {string} shops[].shop.ringCodePublishTime 最新环信码发布时间 |
| | | * @param {boolean} shops[].shop.isOnline 在线状态 |
| | | * @param {number} shops[].shop.exceptionStatus 异常状态(0: 油烟浓度超标, 1: 供电异常, 2: 设备或网络异常, 3: 无异常) |
| | | * @param {Array} shops[].recentData 近1小时的监测数据 |
| | | * @param {string} shops[].recentData[].sampleTime 数据采样时间 |
| | | * @param {number} shops[].recentData[].oilSmokeConcentration 油烟浓度 |
| | |
| | | */ |
| | | function drawMassMarks(shops) { |
| | | // 配置样式 |
| | | const massMarksStyle = ringCodeLevelColors.map((color, index) => ({ |
| | | url: createCustomMarker(color), |
| | | const massMarksStyle = [ |
| | | { |
| | | url: statusIcons.exceed, |
| | | size: new AMap.Size(20, 20), |
| | | anchor: new AMap.Pixel(10, 10), |
| | | })) |
| | | }, |
| | | { |
| | | url: statusIcons.exception, |
| | | size: new AMap.Size(20, 20), |
| | | anchor: new AMap.Pixel(10, 10), |
| | | }, |
| | | { |
| | | url: statusIcons.offline, |
| | | size: new AMap.Size(20, 20), |
| | | anchor: new AMap.Pixel(10, 10), |
| | | }, |
| | | { |
| | | url: statusIcons.online, |
| | | size: new AMap.Size(32, 32), |
| | | anchor: new AMap.Pixel(10, 10), |
| | | }, |
| | | { |
| | | url: statusIcons.offlineStatus, |
| | | size: new AMap.Size(32, 32), |
| | | anchor: new AMap.Pixel(10, 10), |
| | | }, |
| | | ] |
| | | // 准备海量点数据 |
| | | const massMarksData = shops.map((shop, index) => { |
| | | // 根据环信码等级获取颜色 |
| | | const color = getColorByRingCodeLevel(shop.shop.ringCodeLevel) |
| | | // 根据异常状态和在线状态获取样式索引 |
| | | let styleIndex = 3 // 默认在线状态 |
| | | |
| | | if (shop.shop.exceptionStatus !== undefined) { |
| | | switch (shop.shop.exceptionStatus) { |
| | | case 0: // 油烟浓度超标 |
| | | styleIndex = 0 |
| | | break |
| | | case 1: // 供电异常 |
| | | styleIndex = 1 |
| | | break |
| | | case 2: // 设备或网络异常 |
| | | styleIndex = 2 |
| | | break |
| | | case 3: // 无异常,根据在线状态决定 |
| | | styleIndex = shop.shop.isOnline ? 3 : 4 |
| | | break |
| | | default: |
| | | styleIndex = shop.shop.isOnline ? 3 : 4 |
| | | } |
| | | } else { |
| | | // 没有异常状态时,根据在线状态决定 |
| | | styleIndex = shop.shop.isOnline ? 3 : 4 |
| | | } |
| | | |
| | | return { |
| | | id: index, |
| | | name: shop.shop.name, |
| | | lnglat: [shop.shop.longitude, shop.shop.latitude], |
| | | style: shop.shop.ringCodeLevel, // 样式索引,对应 massMarksStyle |
| | | style: styleIndex, // 样式索引,对应 massMarksStyle |
| | | extData: shop, // 存储完整的店铺信息 |
| | | } |
| | | }) |
| | |
| | | } |
| | | |
| | | /** |
| | | * 根据异常状态获取中文 |
| | | * @param {number} status 异常状态 |
| | | * @returns {string} 中文表示 |
| | | */ |
| | | function getExceptionStatusText(status) { |
| | | switch (status + '') { |
| | | case '0': |
| | | return '油烟浓度超标' |
| | | case '1': |
| | | return '供电异常' |
| | | case '2': |
| | | return '设备或网络异常' |
| | | case '3': |
| | | return '无异常' |
| | | default: |
| | | return '未知状态' |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据异常状态获取颜色 |
| | | * @param {number} status 异常状态 |
| | | * @returns {string} 颜色值 |
| | | */ |
| | | function getColorByExceptionStatus(status) { |
| | | switch (status + '') { |
| | | case '0': |
| | | return '#f5222d' // 油烟浓度超标 - 红色 |
| | | case '1': |
| | | return '#faad14' // 供电异常 - 黄色 |
| | | case '2': |
| | | return '#8c8c8c' // 设备或网络异常 - 灰色 |
| | | case '3': |
| | | return '#52c41a' // 无异常 - 绿色 |
| | | default: |
| | | return '#8c8c8c' // 灰色 |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 创建自定义标记 |
| | | * @param {string} color 标记颜色 |
| | | * @returns {string} 标记的SVG URL |
| | |
| | | } |
| | | |
| | | /** |
| | | * 创建在线状态的SVG标记(油烟监测设备形象) |
| | | * @returns {string} 标记的SVG URL |
| | | */ |
| | | function createCustomMarkerOnline() { |
| | | const svg = ` |
| | | <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> |
| | | <!-- 设备主体 - 圆角矩形 --> |
| | | <rect x="5" y="8" width="22" height="16" rx="3" fill="#52c41a" stroke="white" stroke-width="2"/> |
| | | |
| | | <!-- 设备顶部 - 弧形 --> |
| | | <path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#389e0d"/> |
| | | |
| | | <!-- 设备底部 - 弧形 --> |
| | | <path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#389e0d"/> |
| | | |
| | | <!-- 设备显示屏 --> |
| | | <rect x="8" y="11" width="16" height="10" rx="2" fill="white"/> |
| | | |
| | | <!-- 显示屏数据 --> |
| | | <path d="M11 14 L21 14" stroke="#52c41a" stroke-width="1.5"/> |
| | | <path d="M11 17 L18 17" stroke="#52c41a" stroke-width="1.5"/> |
| | | <path d="M11 20 L15 20" stroke="#52c41a" stroke-width="1.5"/> |
| | | |
| | | <!-- 设备天线 --> |
| | | <line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/> |
| | | <circle cx="16" cy="3" r="1.5" fill="white"/> |
| | | |
| | | <!-- 设备指示灯 --> |
| | | <circle cx="27" cy="16" r="3" fill="#ffffff"/> |
| | | <circle cx="27" cy="16" r="1.5" fill="#52c41a"/> |
| | | |
| | | <!-- 装饰线条 --> |
| | | <line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/> |
| | | <line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/> |
| | | </svg> |
| | | ` |
| | | return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))) |
| | | } |
| | | |
| | | /** |
| | | * 创建离线状态的SVG标记(油烟监测设备形象) |
| | | * @returns {string} 标记的SVG URL |
| | | */ |
| | | function createCustomMarkerOffline() { |
| | | const svg = ` |
| | | <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> |
| | | <!-- 设备主体 - 圆角矩形 --> |
| | | <rect x="5" y="8" width="22" height="16" rx="3" fill="#8c8c8c" stroke="white" stroke-width="2"/> |
| | | |
| | | <!-- 设备顶部 - 弧形 --> |
| | | <path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#666666"/> |
| | | |
| | | <!-- 设备底部 - 弧形 --> |
| | | <path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#666666"/> |
| | | |
| | | <!-- 设备显示屏 --> |
| | | <rect x="8" y="11" width="16" height="10" rx="2" fill="white"/> |
| | | |
| | | <!-- 显示屏无数据 - 交叉线 --> |
| | | <line x1="11" y1="12" x2="21" y2="22" stroke="#8c8c8c" stroke-width="2"/> |
| | | <line x1="11" y1="22" x2="21" y2="12" stroke="#8c8c8c" stroke-width="2"/> |
| | | |
| | | <!-- 设备天线 --> |
| | | <line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/> |
| | | <circle cx="16" cy="3" r="1.5" fill="white"/> |
| | | |
| | | <!-- 设备指示灯 --> |
| | | <circle cx="27" cy="16" r="3" fill="#ffffff"/> |
| | | <circle cx="27" cy="16" r="1.5" fill="#8c8c8c"/> |
| | | |
| | | <!-- 装饰线条 --> |
| | | <line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/> |
| | | <line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/> |
| | | </svg> |
| | | ` |
| | | return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))) |
| | | } |
| | | |
| | | /** |
| | | * 显示店铺信息窗口 |
| | | * @param {Object} shop 店铺对象 |
| | | */ |
| | | function showShopInfoWindow(shop) { |
| | | // 准备信息窗口内容 |
| | | // const content = ` |
| | | // <div style="padding: 10px; max-width: 300px;"> |
| | | // <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3> |
| | | // <div style="font-size: 14px; line-height: 1.5;"> |
| | | // <p><strong>地址:</strong>${shop.shop.address}</p> |
| | | // <p><strong>环信码等级:</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${shop.shop.ringCodeLevel}</span></p> |
| | | // <p><strong>环信码发布时间:</strong>${shop.shop.ringCodePublishTime}</p> |
| | | // <h4 style="margin: 10px 0 5px 0; color: #666;">近1小时监测数据</h4> |
| | | // <div style="max-height: 150px; overflow-y: auto;"> |
| | | // ${shop.recentData |
| | | // .map( |
| | | // (item) => ` |
| | | // <div style="padding: 5px; border-bottom: 1px solid #f0f0f0;"> |
| | | // <p><strong>采样时间:</strong>${item.sampleTime}</p> |
| | | // <p><strong>油烟浓度:</strong>${item.oilSmokeConcentration} mg/m³</p> |
| | | // <p><strong>净化器电流:</strong>${item.purifierCurrent} A</p> |
| | | // <p><strong>风机电流:</strong>${item.fanCurrent} A</p> |
| | | // </div> |
| | | // `, |
| | | // ) |
| | | // .join('')} |
| | | // </div> |
| | | // </div> |
| | | // </div> |
| | | // ` |
| | | // 获取在线状态文本 |
| | | const onlineStatusText = shop.shop.isOnline ? '在线' : '离线' |
| | | // 获取异常状态文本 |
| | | const exceptionStatusText = getExceptionStatusText(shop.shop.exceptionStatus) |
| | | // 获取异常状态颜色 |
| | | const exceptionStatusColor = getColorByExceptionStatus(shop.shop.exceptionStatus) |
| | | |
| | | // 根据状态获取对应的图标 |
| | | let statusIcon = statusIcons.online // 默认在线图标 |
| | | if (shop.shop.exceptionStatus !== undefined) { |
| | | switch (shop.shop.exceptionStatus) { |
| | | case 0: |
| | | statusIcon = statusIcons.exceed |
| | | break |
| | | case 1: |
| | | statusIcon = statusIcons.exception |
| | | break |
| | | case 2: |
| | | statusIcon = statusIcons.offline |
| | | break |
| | | case 3: |
| | | statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus |
| | | break |
| | | default: |
| | | statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus |
| | | } |
| | | } else { |
| | | statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus |
| | | } |
| | | |
| | | // 根据在线状态获取图标 |
| | | const onlineIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus |
| | | |
| | | // 根据异常状态获取图标 |
| | | let exceptionIcon = statusIcons.online // 默认在线图标 |
| | | if (shop.shop.exceptionStatus !== undefined) { |
| | | switch (shop.shop.exceptionStatus) { |
| | | case 0: |
| | | exceptionIcon = statusIcons.exceed |
| | | break |
| | | case 1: |
| | | exceptionIcon = statusIcons.exception |
| | | break |
| | | case 2: |
| | | exceptionIcon = statusIcons.offline |
| | | break |
| | | case 3: |
| | | exceptionIcon = statusIcons.online |
| | | break |
| | | default: |
| | | exceptionIcon = statusIcons.online |
| | | } |
| | | } |
| | | |
| | | const content = ` |
| | | <div style="padding: 10px; width: 400px;"> |
| | | <h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3> |
| | | <div style="font-size: 14px; line-height: 1.5;"> |
| | | <p><strong>地址:</strong>${shop.shop.address}</p> |
| | | <p><strong>在线状态:</strong><span style="color: ${shop.shop.isOnline ? '#52c41a' : '#8c8c8c'}">${onlineStatusText}</span> </p> |
| | | <p><strong>异常状态:</strong><span style="color: ${exceptionStatusColor}">${exceptionStatusText}</span></p> |
| | | <p><strong>环信码等级:</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${getRingCodeLevelText(shop.shop.ringCodeLevel)}</span></p> |
| | | <p><strong>环信码发布时间:</strong>${shop.shop.ringCodePublishTime}</p> |
| | | <h4 style="margin: 10px 0 5px 0; color: #666;">近1小时监测数据</h4> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 任务完成率 --> |
| | | <!-- 巡查点次 --> |
| | | <div class="metric-card"> |
| | | <div class="card-header"> |
| | | <div class="card-title">任务完成率</div> |
| | | <div class="card-title">巡查点次</div> |
| | | <div class="card-icon task-icon"> |
| | | <svg |
| | | width="20" |
| | |
| | | </svg> |
| | | </div> |
| | | </div> |
| | | <div class="card-value">{{ metrics.taskCompletionRate }}%</div> |
| | | <div class="card-value">{{ metrics.inspectionPoints }}</div> |
| | | <div class="card-trend"> |
| | | <span |
| | | class="trend-arrow" |
| | | :class="{ |
| | | up: metrics.taskCompletionRateTrend > 0, |
| | | down: metrics.taskCompletionRateTrend < 0, |
| | | up: metrics.inspectionPointsTrend > 0, |
| | | down: metrics.inspectionPointsTrend < 0, |
| | | }" |
| | | > |
| | | {{ metrics.taskCompletionRateTrend > 0 ? '↑' : '↓' }} |
| | | {{ metrics.inspectionPointsTrend > 0 ? '↑' : '↓' }} |
| | | </span> |
| | | <span class="trend-text">{{ Math.abs(metrics.taskCompletionRateTrend) }}%</span> |
| | | <span class="trend-text">{{ Math.abs(metrics.inspectionPointsTrend) }}</span> |
| | | <span class="trend-label">{{ getCompareLabel() }}</span> |
| | | </div> |
| | | </div> |
| | |
| | | <!-- 设备状态饼图 --> |
| | | <div class="device-status-chart"> |
| | | <canvas id="deviceStatusChart"></canvas> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 地图图例 --> |
| | | <div class="map-legend"> |
| | | <div class="legend-header"> |
| | | <h4>图例</h4> |
| | | </div> |
| | | <div class="legend-items"> |
| | | <div class="legend-item"> |
| | | <img src="@/assets/exceed.png" alt="油烟浓度超标" class="legend-icon" /> |
| | | <span class="legend-text">油烟浓度超标</span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <img src="@/assets/exception.png" alt="供电异常" class="legend-icon" /> |
| | | <span class="legend-text">供电异常</span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <img src="@/assets/offline.png" alt="设备或网络异常" class="legend-icon" /> |
| | | <span class="legend-text">设备或网络异常</span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <img |
| | | src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI1IiB5PSI4IiB3aWR0aD0iMjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjNTJjNDFhIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSA4IFEgMTYgMyAyNyA4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiMzODllMGQiLz48cGF0aCBkPSJNNSAyNCBRIDE2IDI5IDI3IDI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjEwIiByeD0iMiIgZmlsbD0id2hpdGUiLz48cGF0aCBkPSJNMTIgMTQgTCAyMSAxNCIgc3Ryb2tlPSIjNTJjNDFhIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxwYXRoIGQ9Ik0xMiAxNyBMIDE4IDE3IiBzdHJva2U9IiM1MmM0MWEiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTEyIDIwIEwgMTUgMjAiIHN0cm9rZT0iIzUyYzQxYSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iMTYiIHkxPSI4IiB4Mj0iMTYiIHkyPSIzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMyIgcj0iMS41IiBmaWxsPSJ3aGl0ZSIvPjxjaXJjbGUgY3g9IjI3IiBjeT0iMTYiIHI9IjMiIGZpbGw9IiNmZmZmZmYiLz48Y2lyY2xlIGN4PSIyNyIgY3k9IjE2IiByPSIxLjUiIGZpbGw9IiM1MmM0MWEiLz48bGluZSB4MT0iNSIgeTE9IjEzIiB4Mj0iNiIgeTI9IjEzIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxsaW5lIHgxPSI1IiB5MT0iMTkiIHgyPSI2IiB5Mj0iMTkiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PC9zdmc+" |
| | | alt="在线状态" |
| | | class="legend-icon" |
| | | /> |
| | | <span class="legend-text">在线状态</span> |
| | | </div> |
| | | <div class="legend-item"> |
| | | <img |
| | | src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSI1IiB5PSI4IiB3aWR0aD0iMjIiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjOGM4YzhjIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiLz48cGF0aCBkPSJNNSA4IFEgMTYgMyAyNyA4IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cGF0aCBkPSJNNSAyNCBRIDE2IDI5IDI3IDI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9IiM2NjY2NjYiLz48cmVjdCB4PSI4IiB5PSIxMSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjEwIiByeD0iMiIgZmlsbD0id2hpdGUiLz48bGluZSB4MT0iMTEiIHkxPSIxMiIgeDI9IjIxIiB5Mj0iMjIiIHN0cm9rZT0iIzhjOGM4YyIgc3Ryb2tlLXdpZHRoPSIyIi8+PGxpbmUgeDE9IjExIiB5MT0iMjIiIHgyPSIyMSIgeTI9IjEyIiBzdHJva2U9IiM4YzhjOGMiIHN0cm9rZS13aWR0aD0iMiIvPjxsaW5lIHgxPSIxNiIgeTE9IjgiIHgyPSIxNiIgeTI9IjMiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PGNpcmNsZSBjeD0iMTYiIGN5PSIzIiByPSIxLjUiIGZpbGw9IndoaXRlIi8+PGNpcmNsZSBjeD0iMjciIGN5PSIxNiIgcj0iMyIgZmlsbD0iI2ZmZmZmZiIvPjxjaXJjbGUgY3g9IjI3IiBjeT0iMTYiIHI9IjEuNSIgZmlsbD0iIzhjOGM4YyIvPjxsaW5lIHgxPSI1IiB5MT0iMTMiIHgyPSI2IiB5Mj0iMTMiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41Ii8+PGxpbmUgeDE9IjUiIHkxPSIxOSIgeDI9IjYiIHkyPSIxOSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48L3N2Zz4=" |
| | | alt="离线状态" |
| | | class="legend-icon" |
| | | /> |
| | | <span class="legend-text">离线状态</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 巡查情况统计卡片 --> |
| | |
| | | onlineRateTrend: 2, |
| | | purifierEfficiency: 85, |
| | | purifierEfficiencyTrend: -3, |
| | | taskCompletionRate: 78, |
| | | taskCompletionRateTrend: 10, |
| | | inspectionPoints: 350, |
| | | inspectionPointsTrend: 50, |
| | | }, |
| | | overview: { |
| | | totalShops: 245, |
| | |
| | | onlineRateTrend: Math.floor(Math.random() * 10) - 5, |
| | | purifierEfficiency: Math.floor(Math.random() * 30) + 70, |
| | | purifierEfficiencyTrend: Math.floor(Math.random() * 10) - 5, |
| | | taskCompletionRate: Math.floor(Math.random() * 40) + 60, |
| | | taskCompletionRateTrend: Math.floor(Math.random() * 15) - 7, |
| | | inspectionPoints: Math.floor(Math.random() * 100) + 300, |
| | | inspectionPointsTrend: Math.floor(Math.random() * 100) - 50, |
| | | } |
| | | |
| | | // 更新巡查统计数据 |
| | |
| | | animation: blink 1s infinite; |
| | | } |
| | | |
| | | /* 地图图例样式 */ |
| | | .map-legend { |
| | | position: absolute; |
| | | bottom: 4px; |
| | | right: 4px; |
| | | width: 200px; |
| | | background-color: rgba(255, 255, 255, 0.9); |
| | | border-radius: 8px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | padding: 16px; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .legend-header { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .legend-header h4 { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #262626; |
| | | margin: 0; |
| | | text-align: center; |
| | | } |
| | | |
| | | .legend-items { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .legend-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | font-size: 12px; |
| | | color: #4e5969; |
| | | } |
| | | |
| | | .legend-icon { |
| | | width: 24px; |
| | | height: 24px; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | | /* @media (max-width: 1200px) { |
| | | .top-cards { |
| | |
| | | |
| | | // 用户管理相关 |
| | | const users = ref([ |
| | | { id: 1, username: 'admin', name: '管理员', role: 'admin', status: 'active' }, |
| | | { id: 2, username: 'user1', name: '用户1', role: 'user', status: 'active' }, |
| | | { |
| | | id: 3, |
| | | username: 'restaurant1', |
| | | name: '店铺管理员1', |
| | | id: 1, |
| | | username: 'fuxiaojie', |
| | | name: '付小姐在成都', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { id: 2, username: 'jike', name: '吉刻联盟', role: 'restaurant_admin', status: 'active' }, |
| | | { id: 3, username: 'jiazaitala', name: '家在塔啦', role: 'restaurant_admin', status: 'active' }, |
| | | { id: 4, username: 'langlailiao', name: '狼来了', role: 'restaurant_admin', status: 'active' }, |
| | | { id: 5, username: 'lekaisai', name: '乐凯撒星游店', role: 'restaurant_admin', status: 'active' }, |
| | | { |
| | | id: 6, |
| | | username: 'xinyuan', |
| | | name: '馨远美食小镇(哈尼美食广场)', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { id: 7, username: 'bangyuehan', name: '棒约翰', role: 'restaurant_admin', status: 'active' }, |
| | | { id: 8, username: 'nangtang', name: '弄堂咪道', role: 'restaurant_admin', status: 'active' }, |
| | | { |
| | | id: 9, |
| | | username: 'yangji', |
| | | name: '杨记齐齐哈尔烤肉', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { |
| | | id: 10, |
| | | username: 'rensheng', |
| | | name: '上海稔传餐饮管理有限公司(人生一串)', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { id: 11, username: 'yuanjia', name: '缘家', role: 'restaurant_admin', status: 'active' }, |
| | | { |
| | | id: 12, |
| | | username: 'quansheng', |
| | | name: '泉盛餐饮(上海)有限公司(食其家)', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { id: 13, username: 'fengmao', name: '丰茂烤串', role: 'restaurant_admin', status: 'active' }, |
| | | { |
| | | id: 14, |
| | | username: 'taihuang', |
| | | name: '上海泰煌餐饮管理有限公司(泰煌鸡)', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |
| | | { |
| | | id: 15, |
| | | username: 'chenxi', |
| | | name: '徐汇区辰熙餐馆(小铁君串烧居酒屋)', |
| | | role: 'restaurant_admin', |
| | | status: 'active', |
| | | }, |