forked from dyf/dyf-vue-ui
Merge branch 'main' of http://47.107.152.87:3000/liubiao/dyf-vue-ui
This commit is contained in:
@ -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'
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
4
src/assets/icons/svg/home.svg
Normal file
4
src/assets/icons/svg/home.svg
Normal 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 |
@ -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)) {
|
||||||
|
|||||||
@ -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 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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,8 +58,8 @@
|
|||||||
: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>
|
||||||
<div class="content-card_gps">
|
<div class="content-card_gps">
|
||||||
@ -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;
|
||||||
@ -586,8 +642,12 @@ const handleDeviceMessage = (msg: any) => {
|
|||||||
deviceDetail.value.batteryRemainingTime = deviceState[5]; //续航时间
|
deviceDetail.value.batteryRemainingTime = deviceState[5]; //续航时间
|
||||||
// getList(); // 重新获取设备详情
|
// getList(); // 重新获取设备详情
|
||||||
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:
|
||||||
// 其他类型消息(不处理,仅打印)
|
// 其他类型消息(不处理,仅打印)
|
||||||
@ -638,12 +698,13 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.p-2{
|
.p-2 {
|
||||||
background: rgba(247, 248, 252, 1);
|
background: rgba(247, 248, 252, 1);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
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>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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) => {
|
||||||
@ -924,7 +932,7 @@ const handleImportSuccess = (response: any) => {
|
|||||||
importResult.value.isShow = true;
|
importResult.value.isShow = true;
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
console.log(response.data,'response.data');
|
console.log(response.data, 'response.data');
|
||||||
|
|
||||||
importResult.value.succeed = response.data.successCount;
|
importResult.value.succeed = response.data.successCount;
|
||||||
importResult.value.errorSun = response.data.failureCount;
|
importResult.value.errorSun = response.data.failureCount;
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -182,8 +227,8 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn_mounth {
|
.btn_mounth {
|
||||||
width:4.5vw;
|
width: 4.5vw;
|
||||||
height:4.5vh;
|
height: 4.5vh;
|
||||||
background: url(@/assets/homeIndex/btn.png) no-repeat;
|
background: url(@/assets/homeIndex/btn.png) no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user