1
0
forked from dyf/dyf-vue-ui
This commit is contained in:
liub
2025-10-10 12:02:54 +08:00
13 changed files with 292 additions and 89 deletions

View File

@ -6,8 +6,8 @@ VITE_APP_ENV = 'development'
# 开发环境 # 开发环境
#VITE_APP_BASE_API = 'https://fuyuanshen.com/backend' #VITE_APP_BASE_API = 'https://fuyuanshen.com/backend'
# VITE_APP_BASE_API = 'https://www.cnxhyc.com/jq' VITE_APP_BASE_API = 'https://www.cnxhyc.com/jq'
VITE_APP_BASE_API = 'http://192.168.110.56:8000' #VITE_APP_BASE_API = 'http://192.168.2.34:8000'
#代永飞接口 #代永飞接口
# VITE_APP_BASE_API = 'http://457102h2d6.qicp.vip:24689' # VITE_APP_BASE_API = 'http://457102h2d6.qicp.vip:24689'

View File

@ -42,7 +42,8 @@ export interface DeviceDetail {
name: string; // 姓名 name: string; // 姓名
code: string; // ID身份证/工号) code: string; // ID身份证/工号)
}; };
chargeState: string chargeState: string;
alarmStatus:number
} }
// 定义灯光模式的类型接口 // 定义灯光模式的类型接口
export interface LightMode { export interface LightMode {

View File

@ -5,6 +5,7 @@ export interface deviceQuery extends PageQuery {
deviceType: string; deviceType: string;
deviceStatus: string; deviceStatus: string;
bluetoothName?: string; // 蓝牙名称查询字段 bluetoothName?: string; // 蓝牙名称查询字段
onlineStatus?: string;
} }
export interface deviceForm { export interface deviceForm {

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.99 18">
<path id="_矢量_42" d="M17.82,5.81L9.5.08c-.16-.11-.38-.11-.54,0L.2,5.81c-.13.08-.2.21-.2.35v11.41c0,.24.21.43.46.43h5.54c.25,0,.46-.19.46-.43v-2.48c0-1.42,1.17-2.65,2.68-2.69,1.57-.04,2.85,1.13,2.85,2.58v2.59c0,.24.21.43.46.43h5.08c.25,0,.46-.19.46-.43V6.15c0-.14-.06-.26-.18-.35h.01Z"/>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View File

@ -1,13 +1,16 @@
<template> <template>
<div v-if="!item.hidden"> <div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item, item.children) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow"> <template
v-if="hasOneShowingChild(item, item.children) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<span @click="handleMenuClick(onlyOneChild, $event)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" /> <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
<template #title> <template #title>
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span> <span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
</template> </template>
</el-menu-item> </el-menu-item>
</span>
</app-link> </app-link>
</template> </template>
@ -17,14 +20,8 @@
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span> <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
</template> </template>
<sidebar-item <sidebar-item v-for="(child, index) in item.children" :key="child.path + index" :is-nest="true" :item="child"
v-for="(child, index) in item.children" :base-path="resolvePath(child.path)" class="nest-menu" />
:key="child.path + index"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu> </el-sub-menu>
</div> </div>
</template> </template>
@ -77,7 +74,23 @@ const hasOneShowingChild = (parent: RouteRecordRaw, children?: RouteRecordRaw[])
return false; return false;
}; };
const router = useRouter();
// 处理菜单点击,完全控制跳转行为
const handleMenuClick = (route, event) => {
console.log(route, 'route');
if (route.meta.openInNewTab) {
// 完全阻止默认行为和事件冒泡
event.preventDefault();
event.stopPropagation();
console.log('Opening in new tab:', route);
const resolvedRoute = router.resolve({
name: route.name || route.path
});
const fullUrl = new URL(resolvedRoute.href, window.location.origin).href;
window.open(fullUrl, '_blank');
} else {
}
};
const resolvePath = (routePath: string, routeQuery?: string): any => { const resolvePath = (routePath: string, routeQuery?: string): any => {
if (isExternal(routePath)) { if (isExternal(routePath)) {

View File

@ -43,7 +43,7 @@ export const constantRoutes: RouteRecordRaw[] = [
path: "/homeIndex", path: "/homeIndex",
name: "HomeIndex", name: "HomeIndex",
component: () => import("@/views/homeIndex/index.vue"), component: () => import("@/views/homeIndex/index.vue"),
meta: {title: '数据大屏', icon: 'dashboard', preload: true, keepAlive: true }, meta: { title: '数据大屏', icon: '首页1.1', preload: true, keepAlive: true, openInNewTab: true },
}, },
{ {
path: '', path: '',
@ -54,7 +54,7 @@ export const constantRoutes: RouteRecordRaw[] = [
path: '/index', path: '/index',
component: () => import('@/views/index.vue'), component: () => import('@/views/index.vue'),
name: 'Index', name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true, keepAlive: false } meta: { title: '首页', icon: '首页1.1', affix: true, keepAlive: false }
} }
] ]
}, },

View File

@ -6,7 +6,7 @@
<div>设备型号{{ deviceDetail.deviceImei }}</div> <div>设备型号{{ deviceDetail.deviceImei }}</div>
<div class="device-status">设备状态 <div class="device-status">设备状态
<span :class="{ online: deviceDetail.onlineStatus === 1, offline: deviceDetail?.onlineStatus === 0 }"> <span :class="{ online: deviceDetail.onlineStatus === 1, offline: deviceDetail?.onlineStatus === 0 }">
{{ deviceDetail.onlineStatus === 1 ? "在线" : "离线" }} {{ deviceDetail.onlineStatus === 1 ? '在线' : (deviceDetail.onlineStatus === 2 ? '故障' : '离线') }}
</span> </span>
</div> </div>
<div>电量{{ deviceDetail.batteryPercentage || 0 }}%</div> <div>电量{{ deviceDetail.batteryPercentage || 0 }}%</div>
@ -15,6 +15,14 @@
<!-- 主体内容区域 --> <!-- 主体内容区域 -->
<div class="content-wrapper"> <div class="content-wrapper">
<el-row :gutter="20" class="content-row" :class="deviceDetail.alarmStatus == 1 ? '' : 'displayNone'" >
<el-col :lg="24" :xs="24">
<div class="staticRwo" :class="deviceDetail.alarmStatus == 1 ? '' : 'displayNone'"
@click="showClose()">
设备强制报警中!
</div>
</el-col>
</el-row>
<!-- 第一行灯光模式 + 灯光亮度强制报警位置信息 --> <!-- 第一行灯光模式 + 灯光亮度强制报警位置信息 -->
<el-row :gutter="20" class="content-row"> <el-row :gutter="20" class="content-row">
<el-col :lg="16" :xs="24"> <el-col :lg="16" :xs="24">
@ -50,7 +58,7 @@
:loading-text="lightModesLoading ? '保存中...' : '保存'"> {{ :loading-text="lightModesLoading ? '保存中...' : '保存'"> {{
lightModesLoading ? '保存中' : '保存' }}</el-button> lightModesLoading ? '保存中' : '保存' }}</el-button>
</div> </div>
<el-button type="danger" class="alarm-btn" @click="forceAlarm" :loading="forceAlarmLoading" <el-button type="danger" class="alarm-btn" @click="forceAlarm" :loading="forceAlarmLoading" v-if="deviceDetail.alarmStatus === 0 || deviceDetail.alarmStatus === null"
:loading-text="forceAlarmLoading ? '报警中...' : '强制报警'" > {{ :loading-text="forceAlarmLoading ? '报警中...' : '强制报警'" > {{
forceAlarmLoading ? '报警中' : '强制报警' }}</el-button> forceAlarmLoading ? '报警中' : '强制报警' }}</el-button>
</div> </div>
@ -248,7 +256,8 @@ const deviceDetail = ref<DeviceDetail & { typeName: string }>({
address: '', address: '',
sendMsg: '', sendMsg: '',
chargeState: '0', chargeState: '0',
typeName: '' typeName: '',
alarmStatus: 0
}); });
// 保留原有的操作中标志位 // 保留原有的操作中标志位
const isUpdatingStatus = ref(false); const isUpdatingStatus = ref(false);
@ -357,7 +366,7 @@ const handleLaserClick = async () => {
laserMode.value.switchStatus = !targetStatus; laserMode.value.switchStatus = !targetStatus;
} }
} catch (error: any) { } catch (error: any) {
proxy?.$modal.msgError(error.msg) ; // proxy?.$modal.msgError(error.msg);
// 恢复之前的状态 // 恢复之前的状态
laserMode.value.switchStatus = !laserMode.value.switchStatus; laserMode.value.switchStatus = !laserMode.value.switchStatus;
} finally { } } finally { }
@ -420,11 +429,56 @@ const saveBtn = () => {
} else { } else {
lightModesLoading.value = false lightModesLoading.value = false
proxy?.$modal.msgError(res.msg); //proxy?.$modal.msgError(res.msg);
} }
}) })
} }
// 解除报警
const showClose = async () => {
try {
await proxy?.$modal.confirm('确定要对该设备解除报警?', '提示');
// 2. 准备请求数据
const batchId = generateShortId();
let data = {
deviceIds: [route.params.deviceId],
typeName: deviceDetail.value.typeName,
deviceImeiList: [deviceDetail.value.deviceImei],
batchId: batchId,
instructValue: '0', //强制报警1解除报警0
}
const registerRes = await api.sendAlarmMessage(data);
if (registerRes.code !== 200) {
proxy?.$modal.msgWarning(registerRes.msg)
return
}
// 4. 获取设备状态
let deviceImei = deviceDetail.value.deviceImei
const statusRes = await getDeviceStatus({
functionMode: 2,
batchId,
typeName: 'FunctionAccessBatchStatusRule',
deviceImei,
interval: 500
},
api.deviceRealTimeStatus
);
// 只有当状态为'OK'时才显示成功弹窗
if (statusRes.data.functionAccess === 'OK') {
proxy?.$modal.msgSuccess(statusRes.msg);
await getList();
}
} catch (error: any) {
}
}
// 强制报警 // 强制报警
const forceAlarm = async () => { const forceAlarm = async () => {
try { try {
@ -458,10 +512,12 @@ const forceAlarm = async () => {
// 只有当状态为'OK'时才显示成功弹窗 // 只有当状态为'OK'时才显示成功弹窗
if (statusRes.data.functionAccess === 'OK') { if (statusRes.data.functionAccess === 'OK') {
proxy?.$modal.msgSuccess(statusRes.msg); proxy?.$modal.msgSuccess(statusRes.msg);
await getList();
} }
} catch (error: any) { } catch (error: any) {
proxy?.$modal.msgWarning(error.msg) // proxy?.$modal.msgWarning(error.msg)
forceAlarmLoading.value = false;
} finally { } finally {
forceAlarmLoading.value = false; forceAlarmLoading.value = false;
@ -588,6 +644,10 @@ const handleDeviceMessage = (msg: any) => {
if (deviceDetail.value.batteryPercentage < 20 && Number(deviceDetail.value.chargeState) == 0) { if (deviceDetail.value.batteryPercentage < 20 && Number(deviceDetail.value.chargeState) == 0) {
centerDialogVisible.value = true centerDialogVisible.value = true
} }
break;
case 7:
deviceDetail.value.alarmStatus = deviceState[1];
break; break;
default: default:
// 其他类型消息(不处理,仅打印) // 其他类型消息(不处理,仅打印)
@ -644,6 +704,7 @@ onUnmounted(() => {
box-sizing: border-box; box-sizing: border-box;
padding: 15px; padding: 15px;
} }
.device-page { .device-page {
.header-bar { .header-bar {
border-radius: 8px; border-radius: 8px;
@ -948,4 +1009,23 @@ onUnmounted(() => {
width: 52px; width: 52px;
height: 28px; height: 28px;
} }
.staticRwo {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 34, 96, 0.1);
background: white;
border: 1px solid #ebeef5;
height: auto;
line-height: 36px;
box-sizing: border-box;
text-indent: 15px;
color: #ff0000;
font-weight: bold;
font-size: 17px;
margin-bottom: 5px;
}
.displayNone {
display: none !important;
}
</style> </style>

View File

@ -130,6 +130,12 @@
<div class="normal red" v-if="scope.row.bindingStatus == 0">未绑定</div> <div class="normal red" v-if="scope.row.bindingStatus == 0">未绑定</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="报警状态" align="center" prop="alarmStatus">
<template #default="scope">
<div class="normal red" v-if="scope.row.alarmStatus == 1">报警中</div>
<div class="normal green" v-if="scope.row.alarmStatus == 0">未报警</div>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width"> <el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">

View File

@ -562,7 +562,7 @@ function SaveMultiData() {
// return api.uploadBoot(formData); // return api.uploadBoot(formData);
// } // }
// 上传开机画面根据类型适配不同的上传接口其他类型暂且默认670 // 上传开机画面根据类型适配不同的上传接口其他类型暂且默认670
function updaeLogo(ids, file, deviceType?: number,) { function updaeLogo(ids, file, deviceType?: number) {
const selectedRows = getSelectionRows(grid); const selectedRows = getSelectionRows(grid);
let realDeviceType = 670; // 默认670 let realDeviceType = 670; // 默认670
if (selectedRows.length > 0) { if (selectedRows.length > 0) {

View File

@ -131,7 +131,11 @@
<div>{{ scope.row.deviceImei }} {{ scope.row.deviceMac }}</div> <div>{{ scope.row.deviceImei }} {{ scope.row.deviceMac }}</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="报警地点" align="center" prop="location" show-overflow-tooltip/> <el-table-column label="报警地点" align="center" prop="location" show-overflow-tooltip>
<template #default="scope">
<div>{{ scope.row.location && scope.row.location !== '[]' ? scope.row.location : '无' }}</div>
</template>
</el-table-column>
<el-table-column label="报警事项" align="center" prop="deviceAction"> <el-table-column label="报警事项" align="center" prop="deviceAction">
<template #default="scope"> <template #default="scope">
<el-tag type="danger" v-if="scope.row.deviceAction == 0">强制报警</el-tag> <el-tag type="danger" v-if="scope.row.deviceAction == 0">强制报警</el-tag>

View File

@ -12,7 +12,7 @@
<el-form-item label="设备MAC" prop="deviceMac"> <el-form-item label="设备MAC" prop="deviceMac">
<el-input v-model="queryParams.deviceMac" placeholder="请输入设备MAC" clearable /> <el-input v-model="queryParams.deviceMac" placeholder="请输入设备MAC" clearable />
</el-form-item> </el-form-item>
<el-form-item label="请输入设备IMEI" prop="deviceImei"> <el-form-item label="设备IMEI" prop="deviceImei">
<el-input v-model="queryParams.deviceImei" placeholder="请输入设备IMEI" clearable /> <el-input v-model="queryParams.deviceImei" placeholder="请输入设备IMEI" clearable />
</el-form-item> </el-form-item>
<el-form-item label="设备类型" prop="deviceType"> <el-form-item label="设备类型" prop="deviceType">
@ -27,6 +27,13 @@
<el-option label="失效" value="0" /> <el-option label="失效" value="0" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="在线状态" prop="onlineStatus">
<el-select v-model="queryParams.onlineStatus" placeholder="在线状态" style="margin-left: 10px">
<el-option label="在线" value="1" />
<el-option label="离线" value="0" />
<el-option label="故障" value="2" />
</el-select>
</el-form-item>
<el-form-item label="创建时间"> <el-form-item label="创建时间">
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
@ -88,7 +95,7 @@
<el-popover placement="right" trigger="click"> <el-popover placement="right" trigger="click">
<template #reference> <template #reference>
<img v-if="scope.row.devicePic" :src="scope.row.devicePic" <img v-if="scope.row.devicePic" :src="scope.row.devicePic"
style="width: 40px; height: 40px; cursor: pointer; object-fit: contain" style="width: 50px; height: 50px; cursor: pointer; object-fit: contain"
class="hover:opacity-80 transition-opacity" /> class="hover:opacity-80 transition-opacity" />
<img v-else src="@/assets/index/IMG.png" alt="" style="width: 40px; height: 40px;"> <img v-else src="@/assets/index/IMG.png" alt="" style="width: 40px; height: 40px;">
</template> </template>
@ -110,7 +117,7 @@
<el-table-column prop="onlineStatus" label="设备状态"> <el-table-column prop="onlineStatus" label="设备状态">
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.onlineStatus === 1 ? 'success' : 'info'"> <el-tag :type="scope.row.onlineStatus === 1 ? 'success' : 'info'">
{{ scope.row.onlineStatus === 1 ? '在线' : '离线' }} {{ scope.row.onlineStatus === 1 ? '在线' : (scope.row.onlineStatus === 2 ? '故障' : '离线') }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
@ -451,7 +458,8 @@ const initData: PageData<deviceForm, deviceQuery> = {
deviceMac: '', deviceMac: '',
deviceImei: '', deviceImei: '',
deviceType: '', deviceType: '',
deviceStatus: '' deviceStatus: '',
onlineStatus:''
}, },
rules: { rules: {
deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }], deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
@ -711,17 +719,17 @@ const httpRequestImg = (parm): Promise<any> => {
return Promise.resolve(); return Promise.resolve();
}; };
const beforeUpload = (file) => { const beforeUpload = (file) => {
const isLt2M = file.size / 1024 / 1024 < 2; //const isLt2M = file.size / 1024 / 1024 < 2;
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'; const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJPG) { if (!isJPG) {
ElMessage.warning('请上传jpg、png格式大小不超过2M的照片'); ElMessage.warning('请上传jpg、png格式');
return false; return false;
} }
if (!isLt2M) { // if (!isLt2M) {
ElMessage.warning('大小不超过2M的照片片'); // ElMessage.warning('大小不超过2M的照片片');
return false; // return false;
} // }
return isJPG && isLt2M; return isJPG;
}; };
// 文件上传状态改变时触发 // 文件上传状态改变时触发
const fileUploadChange = (files, fileList) => { const fileUploadChange = (files, fileList) => {

View File

@ -4,9 +4,10 @@
<div class="btn_mounth" :class="{ cur: activeTab == 'month' }" @click="switchTab('month')">近一月</div> <div class="btn_mounth" :class="{ cur: activeTab == 'month' }" @click="switchTab('month')">近一月</div>
<div class="btn_mounth" :class="{ cur: activeTab === 'halfYear' }" @click="switchTab('halfYear')">近半年</div> <div class="btn_mounth" :class="{ cur: activeTab === 'halfYear' }" @click="switchTab('halfYear')">近半年</div>
</div> </div>
<div ref="chartContainerRef" class="chartContainer" :class="{ 'show-scroll': showScroll }">
<div ref="chartRef" class="chartRef"></div> <div ref="chartRef" class="chartRef"></div>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -14,19 +15,46 @@ import * as echarts from 'echarts';
import { getDeviceUsageFrequency } from '@/api/homeIndex/index'; import { getDeviceUsageFrequency } from '@/api/homeIndex/index';
const chartRef = ref<HTMLDivElement | null>(null); const chartRef = ref<HTMLDivElement | null>(null);
const chartContainerRef = ref<HTMLDivElement | null>(null);
const activeTab = ref('month'); const activeTab = ref('month');
let myChart: echarts.ECharts | null = null; // 保存图表实例 const showScroll = ref(false); // 控制是否显示滚动条
let dataTimer: NodeJS.Timeout | null = null; // 数据更新定时器 let myChart: echarts.ECharts | null = null;
let dataTimer: NodeJS.Timeout | null = null;
// 根据天数获取数据并更新图表 // 根据天数获取数据并更新图表
const fetchDataAndUpdate = (days: number) => { const fetchDataAndUpdate = (days: number) => {
getDeviceUsageFrequency({ days }).then((res) => { getDeviceUsageFrequency({ days }).then((res) => {
if (res.code === 200 && res.data && myChart) { if (res.code === 200 && res.data && myChart) {
//(转换为图表所需格式 // 模拟数据(根据需求调整数量
const dataCount = activeTab.value === 'month' ? 8 : 25; // 一月8条半年25条
const mockData = Array.from({ length: dataCount }, (_, index) => ({
deviceName: `设备${index + 1}`,
frequency: Math.floor(Math.random() * 100)
}));
const chartData = res.data.map(item => ({ const chartData = res.data.map(item => ({
name: item.deviceName, name: item.deviceName,
value: item.frequency value: item.frequency
})); }));
const scrollThreshold = 20;
showScroll.value = chartData.length > scrollThreshold;
// 动态计算图表高度
const baseItemHeight = 20;
const minHeight = 200;
const maxHeight = 600;
let chartHeight;
if (showScroll.value) {
chartHeight = Math.min(chartData.length * baseItemHeight, maxHeight);
} else {
chartHeight = Math.max(chartData.length * baseItemHeight, minHeight);
}
if (chartRef.value) {
chartRef.value.style.height = `${chartHeight}px`;
}
// 更新图表 // 更新图表
myChart.setOption({ myChart.setOption({
yAxis: { yAxis: {
@ -36,21 +64,30 @@ const fetchDataAndUpdate = (days: number) => {
data: chartData.map(item => item.value) data: chartData.map(item => item.value)
}] }]
}); });
// 数据更新后,重新调整图表尺寸
setTimeout(() => {
if (myChart) {
myChart.resize();
}
}, 0);
} }
}).catch(err => { }).catch(err => {
console.error('获取数据失败', err); console.error(err);
}); });
}; };
// 切换标签逻辑 // 切换标签逻辑
const switchTab = (tab: string) => { const switchTab = (tab: string) => {
activeTab.value = tab; activeTab.value = tab;
// 根据标签切换天数(近一月=30天近半年=180天
const days = tab === 'month' ? 30 : 180; const days = tab === 'month' ? 30 : 180;
fetchDataAndUpdate(days); fetchDataAndUpdate(days);
// 切换标签后重新启动定时器
startDataTimer(); startDataTimer();
// 重置滚动位置
if (chartContainerRef.value) {
chartContainerRef.value.scrollTop = 10;
}
}; };
// 开始数据定时器 // 开始数据定时器
@ -58,7 +95,6 @@ const startDataTimer = () => {
if (dataTimer) { if (dataTimer) {
clearInterval(dataTimer); clearInterval(dataTimer);
} }
// 每300秒5分钟更新一次数据
dataTimer = setInterval(() => { dataTimer = setInterval(() => {
const days = activeTab.value === 'month' ? 30 : 180; const days = activeTab.value === 'month' ? 30 : 180;
fetchDataAndUpdate(days); fetchDataAndUpdate(days);
@ -81,18 +117,19 @@ const initChart = () => {
const option = { const option = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'shadow' } // 柱状图建议使用阴影指示器 axisPointer: { type: 'shadow' }
}, },
grid: { grid: {
left: '5%', left: '5%',
right: '10%', right: '5%',
bottom: '3%', bottom: '2%',
top: '20%', top: '12%',
containLabel: true containLabel: true
}, },
yAxis: { yAxis: {
type: 'category', type: 'category',
data: [], // 初始空数据 inverse: true,
data: [],
axisLine: { axisLine: {
lineStyle: { lineStyle: {
color: '#1e3a8a', color: '#1e3a8a',
@ -100,26 +137,37 @@ const initChart = () => {
} }
}, },
axisLabel: { axisLabel: {
color: '#DEEFFF' color: '#DEEFFF',
fontSize: 12
},
axisTick: {
alignWithLabel: true
} }
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
axisLine: { show: false }, axisLine: { show: false },
axisLabel: { show: false }, axisLabel: { show: false },
splitLine: { show: false } splitLine: {
show: true,
lineStyle: {
color: 'rgba(30, 58, 138, 0.3)',
type: 'dashed'
}
}
}, },
series: [{ series: [{
name: '使用频次', name: '使用频次',
type: 'bar', type: 'bar',
data: [], // 初始空数据 data: [],
barWidth: '9px', barWidth: '12px',
stack: 'total', stack: 'total',
label: { label: {
show: true, show: true,
position: 'right', position: 'right',
valueAnimation: true, valueAnimation: true,
color: '#DEEFFF' color: '#DEEFFF',
fontSize: 11
}, },
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient( color: new echarts.graphic.LinearGradient(
@ -130,15 +178,14 @@ const initChart = () => {
] ]
), ),
borderRadius: 4 borderRadius: 4
} },
barGap: '30%',
barCategoryGap: '40%'
}] }]
}; };
myChart.setOption(option); myChart.setOption(option);
// 初始化数据
fetchDataAndUpdate(30); fetchDataAndUpdate(30);
// 启动定时器
startDataTimer(); startDataTimer();
}; };
@ -155,11 +202,8 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
// 清除定时器
clearDataTimer(); clearDataTimer();
// 移除事件监听
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
// 销毁图表实例
if (myChart) { if (myChart) {
myChart.dispose(); myChart.dispose();
myChart = null; myChart = null;
@ -170,7 +214,8 @@ onUnmounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
.vchartPage { .vchartPage {
margin-top: 4.9vh; margin-top: 4.9vh;
position: relative; // 确保按钮定位正确 position: relative;
height: 100%;
} }
.btn_mounth_box { .btn_mounth_box {
@ -191,7 +236,7 @@ onUnmounted(() => {
color: #fff; color: #fff;
font-size: 0.8vw; font-size: 0.8vw;
cursor: pointer; cursor: pointer;
margin-left: 0.5vw; // 按钮间距 margin-left: 0.5vw;
} }
.cur { .cur {
@ -199,8 +244,49 @@ onUnmounted(() => {
background-size: 100% 100%; background-size: 100% 100%;
} }
.chartContainer {
width: 100%;
height: auto;
overflow-y: hidden;
overflow-x: hidden;
position: relative;
// margin-top: 2vh;
transition: all 0.3s ease;
// 当需要显示滚动条时的样式
&.show-scroll {
height: 24vh;
overflow-y: auto;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(7, 104, 212, 0.8);
border-radius: 4px;
border: 2px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-thumb:hover {
background-color: rgba(7, 104, 212, 1);
}
&::-webkit-scrollbar-track {
background-color: rgba(30, 58, 138, 0.1);
border-radius: 4px;
}
&::-webkit-scrollbar-track:hover {
background-color: rgba(30, 58, 138, 0.2);
}
}
}
.chartRef { .chartRef {
width: 100%; width: 100%;
height: 24vh; min-height: 100px;
box-sizing: border-box;
} }
</style> </style>

View File

@ -16,7 +16,7 @@
<div class="item-cell alarm-event"> <div class="item-cell alarm-event">
{{ getEventName(item.deviceAction) }} {{ getEventName(item.deviceAction) }}
</div> </div>
<div class="item-cell loaction">{{ item.location }}</div> <div class="item-cell loaction"> {{ item.location && item.location !== '[]' ? item.location : '无' }}</div>
</div> </div>
<div v-for="(item, index) in displayData" :key="`second-${getKey(item, index)}`" class="alarm-item"> <div v-for="(item, index) in displayData" :key="`second-${getKey(item, index)}`" class="alarm-item">
<div class="item-cell">{{ item.startTime }}</div> <div class="item-cell">{{ item.startTime }}</div>
@ -25,7 +25,7 @@
<div class="item-cell alarm-event"> <div class="item-cell alarm-event">
{{ getEventName(item.deviceAction) }} {{ getEventName(item.deviceAction) }}
</div> </div>
<div class="item-cell loaction">{{ item.location }}</div> <div class="item-cell loaction"> {{ item.location && item.location !== '[]' ? item.location : '无' }}</div>
</div> </div>
</div> </div>
</div> </div>