32 Commits

Author SHA1 Message Date
dhr
b6fabc0c4c 1011 2025-10-11 09:59:06 +08:00
dhr
0022ca0d01 0930 2025-09-30 17:57:19 +08:00
dhr
fe0ffbdf11 0929 2025-09-29 19:56:24 +08:00
dhr
7645cba791 0929 2025-09-29 18:49:04 +08:00
dhr
f58efb0e08 0929 2025-09-29 17:17:42 +08:00
dhr
db9e2e55ea 0929 2025-09-29 15:18:50 +08:00
dhr
6079814962 0928 2025-09-28 20:12:49 +08:00
Teo
fd4e05a802 Merge branch 'tcy' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 19:00:18 +08:00
tcy
af65455d33 style(dzt.vue): 调整已完成文本的字体大小以改善显示效果 2025-09-28 18:59:14 +08:00
Teo
d1c090b855 Merge branch 'ljx' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 18:58:57 +08:00
ljx
ed25998d61 修改 2025-09-28 18:58:39 +08:00
Teo
6003bcbe32 Merge branch 'dhr' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 18:55:19 +08:00
dhr
8cd3ed3f8c 0928 2025-09-28 18:54:52 +08:00
Teo
16003cff02 Merge branch 'tcy' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 18:54:20 +08:00
Teo
e9a60e978f 合并 2025-09-28 18:54:18 +08:00
tcy
63d17eea3c fix: 更新开发环境API地址并调整系统图文字大小
调整系统图中所有文字元素的字体大小以改善显示效果
2025-09-28 18:36:26 +08:00
dhr
9407ad5446 0928 2025-09-28 18:03:50 +08:00
dhr
3606ab7cf8 0928 2025-09-28 17:31:02 +08:00
Teo
11f9433ba7 合并 2025-09-28 17:29:25 +08:00
Teo
b6ec72acee Merge branch 'lx' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 17:28:16 +08:00
Teo
3fa5b39fc3 Merge branch 'dhr' of http://192.168.110.2:3000/taoge_xiaodi/maintenance_system 2025-09-28 17:27:19 +08:00
dhr
4a31c7d028 0928 2025-09-28 17:23:00 +08:00
ljx
744b7a6d97 提交 2025-09-28 17:19:42 +08:00
dhr
3f07f7afe3 0926 2025-09-26 20:32:14 +08:00
dhr
6b9bfb66b1 0925 2025-09-25 20:03:08 +08:00
tcy
33831ecad3 fix: 修复分页请求和预置点添加功能
在spjk.vue中添加isflow参数确保分页请求正确
在presetAdd.vue中使用ElLoading替代原有loading实现更好的用户体验
2025-09-24 20:04:56 +08:00
tcy
d68f537537 Merge branch 'lx' of http://xny.yj-3d.com:3000/taoge_xiaodi/maintenance_system into tcy 2025-09-24 17:53:36 +08:00
tcy
b7c716509d fix(devicePreset): 修改删除预置位接口参数及调用方式
refactor(securitySurveillance): 添加项目ID参数到监控列表请求
style(camera): 注释掉未使用的设备操作按钮代码
2025-09-24 17:52:51 +08:00
dhr
9913a7854c 0924 2025-09-24 16:37:09 +08:00
tcy
64c538775f feat(securitySurveillance): 实现首页大屏数据展示和设备状态动态更新
- 新增获取首页大屏数据的API接口
- 在安全监控页面添加数据获取逻辑并传递给子组件
- 更新设备状态组件显示实时在线/离线数据
- 优化视频监控组件播放器初始化和销毁逻辑
- 调整API接口路径和参数格式
- 移除无用代码和注释
2025-09-24 16:31:18 +08:00
dhr
80cca114a9 0922 2025-09-23 20:36:47 +08:00
ljx
033c6bcbfa 大屏 2025-09-23 15:17:35 +08:00
73 changed files with 51887 additions and 4132 deletions

View File

@ -5,7 +5,7 @@ VITE_APP_TITLE = 新能源场站智慧运维平台
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
# 开发环境 # 开发环境
VITE_APP_BASE_API = 'http://192.168.110.149:18899' VITE_APP_BASE_API = 'http://192.168.110.210:18899'
# 应用访问路径 例如使用前缀 /admin/ # 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/' VITE_APP_CONTEXT_PATH = '/'

View File

@ -29,6 +29,7 @@
"axios": "1.8.4", "axios": "1.8.4",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"echarts": "5.6.0", "echarts": "5.6.0",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0", "echarts-liquidfill": "^3.1.0",
"element-plus": "2.9.8", "element-plus": "2.9.8",
"ezuikit-js": "^8.1.10", "ezuikit-js": "^8.1.10",
@ -95,4 +96,4 @@
"Safari >= 14", "Safari >= 14",
"Firefox >= 78" "Firefox >= 78"
] ]
} }

View File

@ -10,7 +10,7 @@ import { DevicePresetVO, DevicePresetForm, DevicePresetQuery } from '@/api/camer
export const listDevicePreset = (query?: DevicePresetQuery): AxiosPromise<DevicePresetVO[]> => { export const listDevicePreset = (query?: DevicePresetQuery): AxiosPromise<DevicePresetVO[]> => {
return request({ return request({
url: '/camera/devicePreset/list', url: '/ops/devicePreset/list',
method: 'get', method: 'get',
params: query params: query
}); });
@ -22,7 +22,7 @@ export const listDevicePreset = (query?: DevicePresetQuery): AxiosPromise<Device
*/ */
export const getDevicePreset = (id: string | number): AxiosPromise<DevicePresetVO> => { export const getDevicePreset = (id: string | number): AxiosPromise<DevicePresetVO> => {
return request({ return request({
url: '/camera/devicePreset/' + id, url: '/ops/devicePreset/' + id,
method: 'get' method: 'get'
}); });
}; };
@ -33,7 +33,7 @@ export const getDevicePreset = (id: string | number): AxiosPromise<DevicePresetV
*/ */
export const addDevicePreset = (data: DevicePresetForm) => { export const addDevicePreset = (data: DevicePresetForm) => {
return request({ return request({
url: '/camera/devicePreset', url: '/ops/devicePreset',
method: 'post', method: 'post',
data: data data: data
}); });
@ -45,7 +45,7 @@ export const addDevicePreset = (data: DevicePresetForm) => {
*/ */
export const updateDevicePreset = (data: DevicePresetForm) => { export const updateDevicePreset = (data: DevicePresetForm) => {
return request({ return request({
url: '/camera/devicePreset', url: '/ops/devicePreset',
method: 'put', method: 'put',
data: data data: data
}); });
@ -55,10 +55,11 @@ export const updateDevicePreset = (data: DevicePresetForm) => {
* 删除摄像头预置位 * 删除摄像头预置位
* @param id * @param id
*/ */
export const delDevicePreset = (id: string | number | Array<string | number>) => { export const delDevicePreset = (data: any) => {
return request({ return request({
url: '/camera/devicePreset/' + id, url: '/ops/devicePreset/delYzd',
method: 'delete' method: 'delete',
data: [data]
}); });
}; };
/** /**
@ -67,7 +68,7 @@ export const delDevicePreset = (id: string | number | Array<string | number>) =>
*/ */
export const callDevicePreset = (data: DevicePresetForm) => { export const callDevicePreset = (data: DevicePresetForm) => {
return request({ return request({
url: '/camera/devicePreset/call', url: '/ops/devicePreset/callYzd',
method: 'post', method: 'post',
data: data data: data
}); });

33
src/api/large/index.ts Normal file
View File

@ -0,0 +1,33 @@
import request from '@/utils/request';
// 查询图表总数据
export function getPowerStationOverview() {
return request({
url: '/ops/ginlong/api/getPowerStationOverview',
method: 'get'
});
}
//能源收益
export function getStationMonthOverview(params: any) {
return request({
url: '/ops/ginlong/api/getStationMonthOverview',
method: 'get',
params
});
}
//能源收益
export function getInverterListOverview(params: any) {
return request({
url: '/ops/ginlong/api/getInverterListOverview',
method: 'get',
params
});
}
//警告
export function getAlarmListOverview(params?: any) {
return request({
url: '/ops/ginlong/api/getAlarmListOverview',
method: 'get',
params
});
}

View File

@ -14,3 +14,10 @@ export function getMonitoringList(data) {
data data
}) })
} }
// 获取首页大屏数据
export function getHomeScreenData() {
return request({
url: '/ops/monitoriing/getMonitoringDp',
method: 'get',
})
}

View File

@ -29,10 +29,11 @@ export const treeselect = (params?: any): AxiosPromise<MenuTreeOption[]> => {
}; };
// 根据角色ID查询菜单下拉树结构 // 根据角色ID查询菜单下拉树结构
export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMenuTree> => { export const roleMenuTreeselect = (roleId: string | number, params?: any): AxiosPromise<RoleMenuTree> => {
return request({ return request({
url: '/system/menu/roleMenuTreeselect/' + roleId, url: '/system/menu/roleMenuTreeselect/' + roleId,
method: 'get' method: 'get',
params
}); });
}; };

View File

@ -47,3 +47,11 @@ export const uploadbaoxiu = (data) => {
data: data data: data
}); });
}; };
export const baoxiuRecord = (data) => {
return request({
url: '/ops/report/record',
method: 'get',
params: data
});
};

View File

@ -0,0 +1,57 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const gongdanlist = (query) => {
return request({
url: '/ops/order/list',
method: 'get',
params: query
});
};
//新增待办事项
export const addgongdan = (data) => {
return request({
url: '/ops/order',
method: 'post',
data: data
});
};
//修改待办事项
export const updategongdan = (data) => {
return request({
url: '/ops/order',
method: 'put',
data: data
});
};
//删除待办事项
export function delgongdan(ids) {
return request({
url: `/ops/order/${ids}`, // 拼接ids作为路径参数
method: 'delete'
});
}
export const gongdanDetail = (id) => {
return request({
url: `/ops/order/${id}`,
method: 'get'
});
};
export const uploadgongdan = (data) => {
return request({
url: '/resource/oss/upload',
method: 'post',
data: data
});
};
export const gongdanRecord = (data) => {
return request({
url: '/ops/order/record',
method: 'get',
params: data
});
};

View File

@ -0,0 +1,57 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const jiedianlist = (query) => {
return request({
url: '/ops/node/list',
method: 'get',
params: query
});
};
//新增待办事项
export const addjiedian = (data) => {
return request({
url: '/ops/node',
method: 'post',
data: data
});
};
//修改待办事项
export const updatejiedian = (data) => {
return request({
url: '/ops/node',
method: 'put',
data: data
});
};
//删除待办事项
export function deljiedian(ids) {
return request({
url: `/ops/node/${ids}`, // 拼接ids作为路径参数
method: 'delete'
});
}
export const jiedianDetail = (id) => {
return request({
url: `/ops/node/${id}`,
method: 'get'
});
};
export const uploadjiedian = (data) => {
return request({
url: '/resource/oss/upload',
method: 'post',
data: data
});
};
export const jiedianRecord = (data) => {
return request({
url: '/ops/node/record',
method: 'get',
params: data
});
};

View File

@ -0,0 +1,57 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const qiangxiulist = (query) => {
return request({
url: '/ops/repair/list',
method: 'get',
params: query
});
};
//新增待办事项
export const addqiangxiu = (data) => {
return request({
url: '/ops/repair',
method: 'post',
data: data
});
};
//修改待办事项
export const updateqiangxiu = (data) => {
return request({
url: '/ops/repair',
method: 'put',
data: data
});
};
//删除待办事项
export function delqiangxiu(ids) {
return request({
url: `/ops/repair/${ids}`, // 拼接ids作为路径参数
method: 'delete'
});
}
export const qiangxiuDetail = (id) => {
return request({
url: `/ops/repair/${id}`,
method: 'get'
});
};
export const uploadqiangxiu = (data) => {
return request({
url: '/resource/oss/upload',
method: 'post',
data: data
});
};
export const qiangxiuRecord = (data) => {
return request({
url: '/ops/repair/record',
method: 'get',
params: data
});
};

View File

@ -39,3 +39,11 @@ export const syrenwuDetail = (id) => {
method: 'get' method: 'get'
}); });
}; };
export const syrenwujilu = (data) => {
return request({
url: '/ops/testTask/record',
method: 'get',
params: data
});
};

View File

@ -34,7 +34,7 @@ export const delxunjian = (ids) => {
//查询人员 //查询人员
export const xunjianUserlist = (query) => { export const xunjianUserlist = (query) => {
return request({ return request({
url: '/ops/constructionUser/list', url: '/system/user/list',
method: 'get', method: 'get',
params: query params: query
}); });

27252
src/assets/china.json Normal file

File diff suppressed because it is too large Load Diff

7522
src/assets/cq.json Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

BIN
src/assets/large/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/large/income.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

BIN
src/assets/large/power.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

BIN
src/assets/large/right1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
src/assets/large/right2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

BIN
src/assets/large/right3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

BIN
src/assets/large/right4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

BIN
src/assets/large/right5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

BIN
src/assets/large/right6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

BIN
src/assets/large/right7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

BIN
src/assets/large/right8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

BIN
src/assets/large/right9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

BIN
src/assets/large/secure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -0,0 +1,86 @@
// 选择框样式
.el-select {
.el-select__wrapper {
background: transparent !important;
box-shadow: none !important;
border: 0.1px solid rgba(24, 177, 219, 0.3) !important;
}
.el-select__placeholder {
color: rgba(255, 255, 255, 0.9) !important;
}
}
.el-popper {
background: transparent !important;
border: 1px solid rgba(24, 177, 219, 0.3) !important;
.el-popper__arrow:before {
background: rgba(10, 79, 84, 0.5) !important;
border: 1px solid rgba(10, 79, 84, 1) !important;
right: 0;
display: none !important;
}
.el-select-dropdown__item {
color: rgba(255, 255, 255, 0.9) !important;
}
.is-hovering {
background: rgba(10, 79, 84, 1) !important;
}
}
// 日期组件样式
.el-input__wrapper {
display: inline-flex;
flex-grow: 1;
align-items: center;
justify-content: center;
padding: 1px 11px;
background-color: transparent !important;
background-image: none;
// border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
// cursor: text;
// transition: var(--el-transition-box-shadow);
// transform: translate3d(0, 0, 0);
box-shadow: none !important;
border: 0.1px solid rgba(24, 177, 219, 0.3) !important;
}
.el-input__inner {
color: #fff !important;
}
.el-date-table-cell__text {
color: #fff !important;
}
.el-date-picker {
/* --el-datepicker-text-color: var(--el-text-color-regular); */
--el-datepicker-off-text-color: var(--el-text-color-placeholder);
--el-datepicker-header-text-color: #fff !important;
--el-datepicker-icon-color: #fff !important;
/* --el-datepicker-border-color: var(--el-disabled-border-color); */
/* --el-datepicker-inner-border-color: var(--el-border-color-light); */
/* --el-datepicker-inrange-bg-color: var(--el-border-color-extra-light); */
/* --el-datepicker-inrange-hover-bg-color: var(--el-border-color-extra-light); */
/* --el-datepicker-active-color: var(--el-color-primary); */
--el-datepicker-hover-text-color: #fff !important;
}
.el-date-picker__header-label {
color: #fff !important;
}
.el-picker-panel {
color: #fff !important;
background: rgba(10, 79, 84, 0.85) !important;
// border-radius: var(--el-border-radius-base);
// line-height: 30px;
}

View File

@ -0,0 +1,169 @@
<template>
<div ref="echartBox" class="echarts"></div>
</template>
<script setup lang="ts">
import china from '@/assets/china.json';
import cq from '@/assets/cq.json';
import { ref, onMounted, watchEffect, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts/core';
import {
BarChart, // 柱状图
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
LineChart, // 折线图
LineSeriesOption,
PieChart, // 饼图
PieSeriesOption,
PictorialBarChart,
MapChart,
ScatterChart,
EffectScatterChart,
LinesChart
} from 'echarts/charts';
import {
// 组件类型的定义后缀都为 ComponentOption
// 标题
TitleComponent,
TitleComponentOption,
// 提示框
TooltipComponent,
TooltipComponentOption,
// 直角坐标系
GridComponent,
GridComponentOption,
// 图例
LegendComponent,
LegendComponentOption,
// 数据集组件
DatasetComponent,
DatasetComponentOption,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
DataZoomComponent,
DataZoomComponentOption,
// 极坐标
PolarComponent,
PolarComponentOption,
MarkLineComponentOption,
MarkLineComponent,
// MarkPoint
MarkPointComponent,
MarkPointComponentOption,
// VisualMap
VisualMapComponent,
VisualMapComponentOption,
// GeoComponent
GeoComponent,
GeoComponentOption
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import 'echarts-gl';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = echarts.ComposeOption<
| BarSeriesOption
| LineSeriesOption
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| LegendComponentOption
| DataZoomComponentOption
| PolarComponentOption
| MarkLineComponentOption
| MarkPointComponentOption
| VisualMapComponentOption
| GeoComponentOption
>;
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
DataZoomComponent,
PolarComponent,
MarkLineComponent,
MarkPointComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
BarChart,
LineChart,
PieChart,
VisualMapComponent,
PictorialBarChart,
GeoComponent,
MapChart,
ScatterChart,
EffectScatterChart,
LinesChart
]);
const props = defineProps({
option: {
type: Object,
default: () => {
return null;
}
}
});
const emit = defineEmits(['echartsEvent']);
const echartBox = ref(null);
let chart!: echarts.ECharts;
const setChart = (option: ECOption): void => {
if (!props.option || !echartBox.value) {
return;
}
chart.resize();
chart.setOption(option);
};
const resetChart = (): void => {
const option = chart.getOption();
if (!option || !echartBox.value) {
return;
}
chart.resize();
};
onMounted(() => {
(echarts as any).registerMap('china', { geoJSON: china });
(echarts as any).registerMap('cq', { geoJSON: cq });
chart = echarts.init(echartBox.value as any);
emit('echartsEvent', chart);
setChart(props.option);
// 界面拉伸后重设
window.addEventListener('resize', () => {
resetChart();
});
});
watchEffect(() => {
if (chart) {
chart.clear();
}
setChart(props.option);
});
onBeforeUnmount(() => {
if (chart) {
chart.dispose();
}
});
</script>
<style scoped lang="scss">
.echarts {
width: 100%;
height: 100%;
pointer-events: all;
}
</style>

View File

@ -62,6 +62,11 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/error/401.vue'), component: () => import('@/views/error/401.vue'),
hidden: true hidden: true
}, },
{
path: '/largeScreen',
component: () => import('@/views/largeScreen/index.vue'),
hidden: true
},
{ {
path: '', path: '',
component: Layout, component: Layout,
@ -92,9 +97,7 @@ export const constantRoutes: RouteRecordRaw[] = [
]; ];
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载
export const dynamicRoutes: RouteRecordRaw[] = [ export const dynamicRoutes: RouteRecordRaw[] = [];
];
/** /**
* 创建路由 * 创建路由

View File

@ -316,3 +316,58 @@ export const removeClass = (ele: HTMLElement, cls: string) => {
export const isExternal = (path: string) => { export const isExternal = (path: string) => {
return /^(https?:|http?:|mailto:|tel:)/.test(path); return /^(https?:|http?:|mailto:|tel:)/.test(path);
}; };
/**
* 获取步骤状态对应的样式类
* @param {string|number} status - 步骤状态码
* @returns {string} 样式类名
*/
export const getStatusClass = (status: string | number): string => {
// 处理可能的数字输入
const statusStr = status?.toString() || '';
const statusClassMap: Record<string, string> = {
'1': 'status-pending',
'2': 'status-executing',
'3': 'status-completed',
'4': 'status-delayed',
'5': 'status-failed'
};
return statusClassMap[statusStr] || 'status-unknown';
};
/**
* 格式化日期时间(用于步骤条)
* @param {string} dateTime - 日期时间字符串
* @returns {string} 格式化后的日期时间
*/
export const formatDateTime = (dateTime: string): string => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateTime;
}
};
/**
* 获取步骤状态文本
* @param {string|number} status - 步骤状态码
* @returns {string} 状态文本
*/
export const getStepStatusText = (status: string | number): string => {
const statusStr = status?.toString() || '';
const statusMap: Record<string, string> = {
'1': '待执行',
'2': '执行中',
'3': '失败',
'4': '已延期',
'5': '已完成'
};
return statusMap[statusStr] || '未知状态';
};

View File

@ -23,12 +23,18 @@ export const globalHeaders = () => {
}; };
}; };
// 设置默认请求头
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers['Accept'] = 'application/json, text/plain, */*';
axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID; axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
// 创建 axios 实例 // 创建 axios 实例
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000 timeout: 50000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
'Accept': 'application/json, text/plain, */*'
}
}); });
// 请求拦截器 // 请求拦截器

View File

@ -1,6 +1,7 @@
<template> <template>
<div class="system-busPresettingBit-add"> <div class="system-busPresettingBit-add">
<el-dialog v-model="isShowDialog" width="1250px" :close-on-click-modal="false" :destroy-on-close="true"> <el-dialog v-model="isShowDialog" width="1250px" :close-on-click-modal="false" :destroy-on-close="true"
@close="closeDialog">
<template #header> <template #header>
<div <div
v-drag="['.system-busPresettingBit-add .el-dialog', '.system-busPresettingBit-add .el-dialog__header']"> v-drag="['.system-busPresettingBit-add .el-dialog', '.system-busPresettingBit-add .el-dialog__header']">
@ -55,8 +56,10 @@
import { ref, onBeforeUnmount, getCurrentInstance, nextTick } from 'vue'; import { ref, onBeforeUnmount, getCurrentInstance, nextTick } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus'; import { ElMessageBox, ElMessage } from 'element-plus';
import { listDevicePreset, addDevicePreset, updateDevicePreset, delDevicePreset, callDevicePreset } from '@/api/devicePreset'; import { listDevicePreset, addDevicePreset, updateDevicePreset, delDevicePreset, callDevicePreset } from '@/api/devicePreset';
import EZUIKit from 'ezuikit-js'; import { getToken } from '@/api/securitySurveillance/index.js';
import EZUIKit from 'ezuikit-js';
import { ca } from 'element-plus/es/locale/index.mjs';
const emit = defineEmits(['update']); const emit = defineEmits(['update']);
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
@ -110,6 +113,12 @@ function addPre() {
inputErrorMessage: '请输入预置点名称' inputErrorMessage: '请输入预置点名称'
}) })
.then(({ value }) => { .then(({ value }) => {
// 加载动画
const loading = ElLoading.service({
lock: true,
text: '添加中',
background: 'rgba(0, 0, 0, 0.7)',
})
formData.value.presetName = value; formData.value.presetName = value;
addDevicePreset(formData.value) addDevicePreset(formData.value)
.then(() => { .then(() => {
@ -117,7 +126,8 @@ function addPre() {
busPresettingBitList(); busPresettingBitList();
}) })
.finally(() => { .finally(() => {
loading.value = false; // loading.value = false;
loading.close();
}); });
}) })
.catch(() => { }); .catch(() => { });
@ -127,8 +137,8 @@ function addPre() {
function videoPlay(obj: any) { function videoPlay(obj: any) {
console.log('objobjobj', obj); console.log('objobjobj', obj);
getAccessToken().then((res: any) => { getToken().then((res: any) => {
if (res.code == 200 && obj.deviceSerial) { if (res.msg == "ok" && obj.deviceSerial) {
flvPlayer.value = new EZUIKit.EZUIKitPlayer({ flvPlayer.value = new EZUIKit.EZUIKitPlayer({
audio: '0', audio: '0',
id: 'video-container', id: 'video-container',
@ -199,7 +209,12 @@ function handleDelete(row: any) {
deviceSerial: row.deviceSerial, deviceSerial: row.deviceSerial,
ids: id ids: id
}; };
delDevicePreset(id).then((res: any) => { delDevicePreset({
id: row.id,
deviceSerial: row.deviceSerial,
channelNo: "1",
presetIndex: row.presetIndex
}).then((res: any) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('删除成功'); ElMessage.success('删除成功');
busPresettingBitList(); busPresettingBitList();
@ -211,7 +226,12 @@ function handleDelete(row: any) {
// 调用 // 调用
function handleDebug(row: any) { function handleDebug(row: any) {
callDevicePreset(row.id).then((res: any) => { callDevicePreset([{
deviceSerial: row.deviceSerial,
presetIndex: row.presetIndex,
channelNo: "1",
id: row.id
}]).then((res: any) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('调用成功'); ElMessage.success('调用成功');
} }
@ -245,6 +265,7 @@ function resetForm() {
} }
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (flvPlayer.value) { if (flvPlayer.value) {
flvPlayer.value.destroy().then((data: any) => { flvPlayer.value.destroy().then((data: any) => {
console.log('promise 获取 数据', data); console.log('promise 获取 数据', data);

View File

@ -91,7 +91,7 @@
><el-icon><Plus /></el-icon>新增</el-button ><el-icon><Plus /></el-icon>新增</el-button
> >
</el-col> --> </el-col> -->
<el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button type="success" :disabled="single" @click="handleUpdate(null)" <el-button type="success" :disabled="single" @click="handleUpdate(null)"
v-auth="'api/v1/system/ys7Devices/edit'"><el-icon> v-auth="'api/v1/system/ys7Devices/edit'"><el-icon>
<Edit /> <Edit />
@ -102,13 +102,13 @@
v-auth="'api/v1/system/ys7Devices/delete'"><el-icon> v-auth="'api/v1/system/ys7Devices/delete'"><el-icon>
<Delete /> <Delete />
</el-icon>删除</el-button> </el-icon>删除</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="warning" :disabled="multiple" @click="onLinkProject(null)" <el-button type="warning" :disabled="multiple" @click="onLinkProject(null)"
v-auth="'api/v1/system/ys7Devices/add'"><el-icon> v-auth="'api/v1/system/ys7Devices/add'"><el-icon>
<Link /> <Link />
</el-icon>设备分配</el-button> </el-icon>设备分配</el-button>
</el-col> </el-col>-->
</el-row> </el-row>
</div> </div>
<el-table v-loading="loading" :data="tableData.data" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="tableData.data" @selection-change="handleSelectionChange">
@ -137,7 +137,7 @@
{{ scope.row.projectName ? scope.row.projectName : '未分配' }} {{ scope.row.projectName ? scope.row.projectName : '未分配' }}
</template> </template>
</el-table-column> --> </el-table-column> -->
<el-table-column label="备注" align="center" prop="remark" min-width="100px" /> <!-- <el-table-column label="备注" align="center" prop="remark" min-width="100px" /> -->
<!-- <el-table-column label="创建时间" align="center" prop="deviceCreateTime" min-width="100px"> <!-- <el-table-column label="创建时间" align="center" prop="deviceCreateTime" min-width="100px">
<template #default="scope"> <template #default="scope">
<span>{{ proxy.parseTime(scope.row.deviceCreateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> <span>{{ proxy.parseTime(scope.row.deviceCreateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
@ -145,7 +145,7 @@
</el-table-column> --> </el-table-column> -->
<el-table-column label="操作" align="center" class-name="small-padding" min-width="160px" fixed="right"> <el-table-column label="操作" align="center" class-name="small-padding" min-width="160px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button type="primary" link @click="handleUpdate(scope.row)" <!-- <el-button type="primary" link @click="handleUpdate(scope.row)"
v-auth="'api/v1/system/ys7Devices/edit'"><el-icon> v-auth="'api/v1/system/ys7Devices/edit'"><el-icon>
<EditPen /> <EditPen />
</el-icon>修改</el-button> </el-icon>修改</el-button>
@ -156,7 +156,7 @@
<el-button type="primary" link @click="onLinkProject(scope.row)" <el-button type="primary" link @click="onLinkProject(scope.row)"
v-auth="'api/v1/system/ys7Devices/delete'"><el-icon> v-auth="'api/v1/system/ys7Devices/delete'"><el-icon>
<Link /> <Link />
</el-icon>设备分配</el-button> </el-icon>设备分配</el-button> -->
<el-button type="primary" link @click="addPreset(scope.row)"><el-icon> <el-button type="primary" link @click="addPreset(scope.row)"><el-icon>
<Plus /> <Plus />
</el-icon>添加预置位</el-button> </el-icon>添加预置位</el-button>
@ -243,15 +243,18 @@ const resetQuery = (formEl: FormInstance | undefined) => {
const ys7DevicesList = () => { const ys7DevicesList = () => {
loading.value = true; loading.value = true;
getMonitoringList({ getMonitoringList({
pageStart: 1, pageStart: state.tableData.param.pageNum,
pageSize: 10 pageSize: state.tableData.param.pageSize,
isflow: false
}).then((res: any) => { }).then((res: any) => {
let list = res.data ?? []; let list = res.data.object ?? [];
state.tableData.data = list.map((item) => { state.tableData.data = list.map((item) => {
item.enctyptLoading = false; item.enctyptLoading = false;
return item; return item;
}); });
state.tableData.total = res.total; state.tableData.total = Number(res.data.sum);
console.log(state.tableData);
loading.value = false; loading.value = false;
}); });
}; };
@ -270,53 +273,53 @@ const handleSelectionChange = (selection: any[]) => {
}; };
// 新增 // 新增
const handleAdd = () => { // const handleAdd = () => {
addRef.value.openDialog(); // addRef.value.openDialog();
}; // };
// 编辑 // // 编辑
const handleUpdate = (row?: Ys7DeviceVO) => { // const handleUpdate = (row?: Ys7DeviceVO) => {
if (!row) { // if (!row) {
row = state.tableData.data.find((item) => item.id === state.ids[0])!; // row = state.tableData.data.find((item) => item.id === state.ids[0])!;
} // }
editRef.value.openDialog(toRaw(row)); // editRef.value.openDialog(toRaw(row));
}; // };
// 删除 // 删除
const handleDelete = (row?: Ys7DeviceVO) => { // const handleDelete = (row?: any) => {
let msg = row ? `此操作将永久删除数据,是否继续?` : '你确定要删除所选数据?'; // let msg = row ? `此操作将永久删除数据,是否继续?` : '你确定要删除所选数据?';
let id = row ? [row.id] : state.ids; // let id = row ? [row.id] : state.ids;
if (id.length === 0) { // if (id.length === 0) {
ElMessage.error('请选择要删除的数据。'); // ElMessage.error('请选择要删除的数据。');
return; // return;
} // }
ElMessageBox.confirm(msg, '提示', { // ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确认', // confirmButtonText: '确认',
cancelButtonText: '取消', // cancelButtonText: '取消',
type: 'warning' // type: 'warning'
}) // })
.then(() => { // .then(() => {
delYs7Device(id).then(() => { // delYs7Device(id).then(() => {
ElMessage.success('删除成功'); // ElMessage.success('删除成功');
ys7DevicesList(); // ys7DevicesList();
}); // });
}) // })
.catch(() => { }); // .catch(() => { });
}; // };
// 绑定项目 // 绑定项目
const onLinkProject = (row?: Ys7DeviceVO) => { // const onLinkProject = (row?: Ys7DeviceVO) => {
let serials = row ? [row.deviceSerial] : state.ids; // let serials = row ? [row.deviceSerial] : state.ids;
if (serials.length === 0) { // if (serials.length === 0) {
ElMessage.error('请选择要绑定项目的设备'); // ElMessage.error('请选择要绑定项目的设备');
return; // return;
} // }
let info = { serials, row }; // let info = { serials, row };
bindProRef.value.openDialog(toRaw(info)); // bindProRef.value.openDialog(toRaw(info));
}; // };
// 添加预置位 // 添加预置位
const addPreset = (row: any) => { const addPreset = (row: any) => {
@ -324,38 +327,38 @@ const addPreset = (row: any) => {
}; };
// 开关加密 // 开关加密
const encryptChange = (row: Ys7DeviceVO | any) => { // const encryptChange = (row: any) => {
row.enctyptLoading = true; // row.enctyptLoading = true;
// const action = row.videoEncrypted === 0 ? 1 : 0; // // const action = row.videoEncrypted === 0 ? 1 : 0;
console.log(row.videoEncrypted); // console.log(row.videoEncrypted);
toggleEncrypt({ videoEncrypted: row.videoEncrypted, id: row.id }) // toggleEncrypt({ videoEncrypted: row.videoEncrypted, id: row.id })
.then(() => { // .then(() => {
proxy?.$modal.msgSuccess(row.videoEncrypted === 0 ? '关闭成功' : '开启成功'); // proxy?.$modal.msgSuccess(row.videoEncrypted === 0 ? '关闭成功' : '开启成功');
}) // })
.finally(() => { // .finally(() => {
row.enctyptLoading = false; // row.enctyptLoading = false;
ys7DevicesList(); // ys7DevicesList();
}); // });
}; // };
//监听项目id刷新数据 //监听项目id刷新数据
const listeningProject = watch( // const listeningProject = watch(
() => currentProject.value?.id, // () => currentProject.value?.id,
(nid, oid) => { // (nid, oid) => {
tableData.value.param.projectId = nid; // tableData.value.param.projectId = nid;
initTableData(); // initTableData();
} // }
); // );
// 页面加载 // 页面加载
onMounted(() => { onMounted(() => {
initTableData(); initTableData();
}); });
onUnmounted(() => { // onUnmounted(() => {
listeningProject(); // listeningProject();
}); // });
// 暴露变量 // 暴露变量
const { tableData, projectList } = toRefs(state); const { tableData, projectList } = toRefs(state);

View File

@ -0,0 +1,318 @@
<template>
<div class="centerPage">
<div class="centerPage_list">
<div class="card">
<div class="title">今日总发电量</div>
<div class="value">
<span style="color: rgba(29, 214, 255, 1)">{{ data?.dayEnergy ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1); font-size: 12px">kMh</span>
<div class="icon">
<img src="@/assets/large/center4.png" style="width: 16px; height: 16px" alt="" />
</div>
</div>
<div class="compare" v-if="Number(data?.dayEnergy) - Number(data?.dayEnergyOld) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.dayEnergy) - Number(data?.dayEnergyOld) > 0"><Top /></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span>{{ Number(data?.dayEnergy) - Number(data?.dayEnergyOld) > 0 ? '新增' : '减少' }}</span>
<span>{{ (Math.abs(Number(data?.dayEnergy) - Number(data?.dayEnergyOld)) / Number(data?.dayEnergy)) * 100 }} %</span>
</div>
<div class="compare" v-else></div>
<div class="target">目标: 14,200 kWh</div>
</div>
<div class="card">
<div class="title">发电效率</div>
<div class="value">
<span style="color: rgba(0, 227, 150, 1)">{{ data?.generateElectricity ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1); font-size: 12px">%</span>
<div class="icon">
<img src="@/assets/large/center3.png" style="width: 16px; height: 16px" alt="" />
</div>
</div>
<div class="compare" v-if="Number(data?.generateElectricity) - Number(data?.generateElectricityOld) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.generateElectricity) - Number(data?.generateElectricityOld) > 0"><Top /></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span>{{ Number(data?.generateElectricity) - Number(data?.generateElectricityOld) > 0 ? '新增' : '减少' }}</span>
<span
>{{
(Math.abs(Number(data?.generateElectricity) - Number(data?.generateElectricityOld)) / Number(data?.generateElectricity)) * 100
}}
%</span
>
</div>
<div class="compare" v-else></div>
<div class="target">基准: 90.0%</div>
</div>
<div class="card">
<div class="title">设备健康度</div>
<div class="value">
<span style="color: rgba(54, 207, 201, 1)">{{ data?.health ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1); font-size: 12px">%</span>
<div class="icon">
<img src="@/assets/large/center2.png" style="width: 16px; height: 16px" alt="" />
</div>
</div>
<div class="compare" v-if="Number(data?.health) - Number(data?.healthOld) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.health) - Number(data?.healthOld) > 0"><Top /></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span>{{ Number(data?.health) - Number(data?.healthOld) > 0 ? '新增' : '减少' }}</span>
<span>{{ (Math.abs(Number(data?.health) - Number(data?.healthOld)) / Number(data?.health)) * 100 }} %</span>
</div>
<div class="compare" v-else></div>
<div class="target">检测: 24分钟前</div>
</div>
<div class="card">
<div class="title">CO2减排量</div>
<div class="value">
<span style="color: rgba(179, 0, 255, 1)">{{ data?.powerStationAvoidedCo2 ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1); font-size: 12px"></span>
<div class="icon">
<img src="@/assets/large/center1.png" style="width: 16px; height: 16px" alt="" />
</div>
</div>
<div class="compare" v-if="Number(data?.powerStationAvoidedCo2) - Number(data?.powerStationAvoidedCo2Old) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.powerStationAvoidedCo2) - Number(data?.powerStationAvoidedCo2Old) > 0"
><Top
/></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span>{{ Number(data?.powerStationAvoidedCo2) - Number(data?.powerStationAvoidedCo2Old) > 0 ? '新增' : '减少' }}</span>
<span
>{{
(Math.abs(Number(data?.powerStationAvoidedCo2) - Number(data?.powerStationAvoidedCo2Old)) / Number(data?.powerStationAvoidedCo2)) * 100
}}
%</span
>
</div>
<div class="compare" v-else></div>
<div class="target">目标: 12560</div>
</div>
</div>
<div class="centerPage_map">
<div ref="mapRef" class="map-container" style="width: 100%; height: 98%" />
</div>
</div>
</template>
<script setup lang="ts">
import { getPowerStationOverview } from '@/api/large';
import * as echarts from 'echarts';
import china from '@/assets/china.json';
const data = ref<any>({});
// 地图容器引用
const mapRef = ref<HTMLDivElement | null>(null);
// ECharts实例
let myChart: any = null;
// 响应窗口大小变化
const handleResize = () => {
if (myChart) {
myChart.resize();
}
};
// 初始化地图
const initEcharts = () => {
if (!mapRef.value) {
console.error('未找到地图容器元素');
return;
}
// 注册地图
echarts.registerMap('china', china as any);
// 地图数据
const mapData: any = [{ name: '田东县', value: 1, itemStyle: { color: '#fff' } }];
// 散点数据
// 散点数据 - 使用图片标记并调整名称位置
const scatterData: any[] = [
{
name: '田东光伏智慧生态工地开发项目',
value: [107.15, 23.63],
// 使用图片作为标记(注意:需替换为你的图片实际路径)
symbol: 'diamond',
// 标记颜色
itemStyle: {
color: '#0166d6'
},
// 图片标记大小(宽, 高)
symbolSize: [20, 20],
// 名称样式设置
label: {
show: true,
formatter: '{b}', // 显示名称
position: 'top', // 名称在图片上方
color: '#fff',
fontSize: 12,
// 可选:添加文字背景以增强可读性
backgroundColor: 'rgba(3, 26, 52, 0.7)',
padding: [3, 6],
borderRadius: 3
}
}
];
// 初始化新实例,强制清除缓存
myChart = echarts.init(mapRef.value, null, {
renderer: 'canvas', // 明确指定渲染器
useDirtyRect: false // 禁用脏矩形渲染,避免样式缓存
});
// 配置项
const option: any = {
roam: true, // 关键配置:允许鼠标滚轮缩放和拖拽平移
geo: {
type: 'map',
map: 'china',
zoom: 5,
center: [107.15, 23.63],
label: {
show: false,
color: '#fff'
},
itemStyle: {
areaColor: '#031a34', // 地图区域底色
borderColor: '#1e3a6e', // 区域边框颜色
borderWidth: 1
}
},
tooltip: {
trigger: 'item',
formatter: function (params: any) {
return params.name + (params.value ? `${params.value}` : '');
}
},
series: [
{
type: 'map',
map: 'china',
geoIndex: 0,
// 关键在series级别定义emphasis优先级最高
emphasis: {
areaColor: '#fff', // 强制设置hover颜色
label: {
show: true,
color: '#fff'
},
itemStyle: {
areaColor: '#02417e' // 重复设置确保生效
}
},
// 确保没有使用默认样式
select: {
itemStyle: {
areaColor: '#02417e'
}
},
data: mapData
},
{
type: 'scatter',
coordinateSystem: 'geo',
data: scatterData
}
]
};
// 设置配置项
myChart.setOption(option);
};
// 组件挂载时初始化
onMounted(() => {
// 确保DOM渲染完成
nextTick(() => {
initEcharts();
window.addEventListener('resize', handleResize);
});
});
// 组件卸载时清理
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (myChart) {
myChart.dispose();
myChart = null;
}
});
const getDataList = () => {
getPowerStationOverview().then((res) => {
console.log(res);
if (res.code == 200) {
data.value = res.data;
}
});
};
getDataList();
</script>
<style scoped lang="scss">
.centerPage {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0 10px 10px 10px;
box-sizing: border-box;
.centerPage_list {
width: 100%;
height: 20%;
padding: 0 0px 10px 0px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 13px;
.card {
width: 220px;
background: rgba(12, 30, 53, 0.4); /* 深色背景,模拟科技感 */
color: #fff;
border-radius: 8px;
padding: 16px;
font-family: sans-serif;
}
.title {
font-size: 14px;
margin-bottom: 8px;
opacity: 0.8;
}
.value {
font-size: 24px;
// font-weight: bold;
display: flex;
align-items: flex-end;
}
.value span {
margin-right: 15px;
}
.icon {
width: 40px;
height: 40px;
background: rgba(29, 214, 255, 0.1);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
}
.compare {
font-size: 12px;
margin-top: 4px;
color: #4ee44e; /* 绿色标识增长 */
padding-top: 20px;
display: flex;
align-items: center;
// justify-content: center;
}
.target {
font-size: 12px;
margin-top: 4px;
opacity: 0.8;
}
}
.centerPage_map {
width: 100%;
height: 80%;
}
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<div class="header">
<div class="header_left">
<div class="header_left_img">
<img src="@/assets/large/secure.png" style="width: 100%; height: 100%" />
</div>
<div style="font-size: 12px; padding-left: 10px">安全生产天数</div>
<div class="header_left_text">
1,235
<span style="font-size: 12px"></span>
</div>
</div>
<div class="title">
<div class="title_text">智慧运维管理平台</div>
<div>Intelligent Operations Management Platform</div>
</div>
<div class="right">
<div class="top-bar">
<!-- 左侧天气图标 + 日期文字 -->
<div class="left-section">
<img src="@/assets/large/weather.png" alt="天气图标" />
<span>
<span>多云 9°/18°</span>
<span style="padding-left: 20px"> {{ week[date.week] }} {{ date.ymd }}</span>
</span>
</div>
<!-- 分割线 -->
<div class="divider">
<div class="top-block"></div>
<div class="bottom-block"></div>
</div>
<!-- 右侧管理系统图标 + 文字 -->
<div class="right-section">
<img src="@/assets/large/setting.png" alt="设置图标" />
<span>管理系统</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const date = ref({
ymd: '',
hms: '',
week: 0
});
const setTime = () => {
let date1 = new Date();
let year: any = date1.getFullYear();
let month: any = date1.getMonth() + 1;
let day: any = date1.getDate();
let hours: any = date1.getHours();
if (hours < 10) {
hours = '0' + hours;
}
let minutes: any = date1.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let seconds: any = date1.getSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
date.value.ymd = year + '-' + month + '-' + day;
date.value.hms = hours + ':' + minutes + ':' + seconds;
date.value.week = date1.getDay();
};
// 添加定时器,每秒更新一次时间
const timer = setInterval(setTime, 1000);
// 组件卸载时清除定时器
onUnmounted(() => {
clearInterval(timer);
});
</script>
<style scoped lang="scss">
.header {
width: 100%;
height: 80px;
box-sizing: border-box;
padding: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
color: #fff;
}
.header_left {
display: flex;
align-items: center;
.header_left_img {
width: 48px;
height: 48px;
box-sizing: border-box;
// padding-right: 10px;
}
.header_left_text {
font-weight: 500;
text-shadow: 0px 1.24px 6.21px rgba(25, 179, 250, 1);
}
}
.title {
color: #fff;
font-family: 'Rang_men_zheng_title', sans-serif;
text-align: center;
}
.title > div:first-child {
/* 第一个子元素的样式 */
font-size: 38px;
// font-weight: 300;
}
.title > div:last-child {
/* 最后一个子元素的样式 */
font-size: 14px;
letter-spacing: 0.3em; /* 调整这个值来控制间距大小 */
}
.right {
width: 100%;
height: 100%;
display: flex;
}
/* 顶部栏容器Flex 水平布局 + 垂直居中 */
.top-bar {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
// background-color: #1e2128;
color: #fff;
padding: 8px 16px;
font-size: 14px;
}
/* 左侧区域(天气 + 日期):自身也用 Flex 水平排列,确保元素在一行 */
.left-section {
display: flex;
align-items: center;
// margin-right: auto; /* 让右侧元素(管理系统)居右 */
}
.left-section img {
width: 32px;
height: 32px;
margin-right: 8px; /* 图标与文字间距 */
}
/* 分割线(视觉分隔,可根据需求调整样式) */
.divider {
display: grid;
grid-template-rows: 1fr 1fr;
height: 100%; /* 根据需要调整高度 */
padding: 15px 10px;
}
.divider .top-block {
width: 3px;
height: 7px;
background: #19b5fb;
align-self: start;
}
.divider .bottom-block {
width: 3px;
height: 7px;
background: #19b5fb;
align-self: end;
}
/* 右侧区域(管理系统):图标 + 文字水平排列 */
.right-section {
display: flex;
align-items: center;
font-family: 'Rang_men_zheng_title', sans-serif;
font-size: 20px;
}
.right-section img {
width: 20px;
height: 20px;
margin-right: 6px; /* 图标与文字间距 */
}
</style>

View File

@ -0,0 +1,616 @@
<template>
<div class="left_page">
<div class="power">
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/power.png" alt="" />
</div>
<div class="left_title_text">电站总览</div>
</div>
</div>
<div class="left_title_list">
<div class="left_title_item">
<div>总装机容量</div>
<div>
<span style="font-size: 24px; color: rgba(29, 214, 255, 1); padding-right: 10px">{{ data?.capacity ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1)">MW</span>
</div>
<div style="display: flex; align-items: center" v-if="Number(data?.capacity) - Number(data.capacityOld) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.capacity) - Number(data.capacityOld) > 0"><Top /></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span style="letter-spacing: 0.1em; color: rgba(0, 227, 150, 1)"
>{{ (Math.abs(Number(data?.capacity) - Number(data.capacityOld)) / Number(data?.capacity)) * 100 }}%较上月</span
>
</div>
<div v-else></div>
</div>
<div class="left_title_item">
<div>光伏板数量</div>
<div>
<span style="font-size: 24px; color: rgba(29, 214, 255, 1); padding-right: 10px">{{ data?.module ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1)"></span>
</div>
<div style="display: flex; align-items: center">
<!-- <el-icon><Top /></el-icon>
<span style="letter-spacing: 0.1em; color: rgba(0, 227, 150, 1)">2.4%较上月</span> -->
<span style="letter-spacing: 0.1em; color: rgba(156, 163, 175, 1)">- -</span>
</div>
</div>
<div class="left_title_item">
<div>升压站数量</div>
<div>
<span style="font-size: 24px; color: rgba(29, 214, 255, 1); padding-right: 10px">{{ data?.operatingRate ?? '0' }}</span>
<span style="color: rgba(156, 163, 175, 1)"></span>
</div>
<div style="display: flex; align-items: center" v-if="Number(data?.operatingRate) - Number(data?.operatingRateOld) != 0">
<el-icon color="rgba(0, 227, 150, 1)" v-if="Number(data?.operatingRate) - Number(data?.operatingRateOld) > 0"><Top /></el-icon>
<el-icon color="rgba(255, 77, 79, 1)" v-else><Bottom /></el-icon>
<span style="letter-spacing: 0.1em; color: rgba(0, 227, 150, 1)"
>{{ Math.abs(Number(data?.operatingRate) - Number(data?.operatingRateOld)) }}{{
Number(data?.operatingRate) - Number(data?.operatingRateOld) > 0 ? '新增' : '减少'
}}</span
>
</div>
<div v-else></div>
</div>
</div>
</div>
<div style="box-sizing: border-box; padding: 0 10px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; margin-top: 5px">
<div class="inverter">
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/monitor.png" alt="" />
</div>
<div class="left_title_text">逆变器监控</div>
</div>
</div>
<div class="selectTime">
<div class="tab-container">
<div class="tab active" @click="switchTab(1)"></div>
<div class="tab" @click="switchTab(2)"></div>
<div class="tab" @click="switchTab(3)"></div>
<!-- <div class="tab" @click="switchTab(4)"></div> -->
</div>
<el-date-picker
v-model="value1"
type="date"
placeholder="请选择"
size="small"
style="width: 120px"
@change="changeTime"
value-format="YYYY-MM-DD"
v-if="active == 1"
/>
<el-date-picker
v-model="value2"
type="month"
placeholder="请选择"
size="small"
style="width: 120px"
@change="changeTime"
value-format="YYYY-MM"
v-if="active == 2"
/>
<el-date-picker
v-model="value3"
type="year"
placeholder="请选择"
size="small"
style="width: 120px"
@change="changeTime"
value-format="YYYY"
v-if="active == 3"
/>
</div>
<div class="bix">运行状态</div>
<div class="chart-container">
<div ref="chart" style="width: 100%; height: 50px"></div>
</div>
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/Inversion.png" alt="" />
</div>
<div class="left_title_text1">逆变器运行曲线</div>
</div>
</div>
<div class="date_select">
<el-select v-model="value" clearable placeholder="请选择" style="width: 120px; margin-left: 20px" size="small">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<div class="brokenLine">
<EchartBoxTwo :option="lineOption" ref="lineChart"></EchartBoxTwo>
</div>
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/income.png" alt="" />
</div>
<div class="left_title_text">能源收益分析</div>
</div>
</div>
<div class="income">
<EchartBoxTwo :option="barOption" ref="barChart"></EchartBoxTwo>
</div>
<div class="income_list">
<div style="display: flex; justify-content: space-between">
<div style="width: 50%">累计收益</div>
<div style="width: 50%; color: rgba(29, 214, 255, 1)">{{ Number(data2.allInCome).toFixed(2) }}</div>
</div>
<div style="display: flex">
<div style="width: 50%">本月收益</div>
<div style="width: 50%; color: rgba(0, 227, 150, 1)">{{ Number(data2.monthInCome).toFixed(2) }}</div>
</div>
<div style="display: flex">
<div style="width: 50%">度电成本</div>
<div style="width: 50%; color: rgba(54, 207, 201, 1)">{{ Number(data2.price).toFixed(2) }}</div>
</div>
<div style="display: flex">
<div style="width: 50%">预计年收入</div>
<div style="width: 50%; color: rgba(179, 0, 255, 1)">{{ Number(data2.yearInCome).toFixed(2) }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import EchartBoxTwo from '@/components/EchartBox/index.vue';
import { formatDate } from '@/utils/index';
import { getLineOption, getBarOptions } from './optionList';
import { getPowerStationOverview, getStationMonthOverview, getInverterListOverview } from '@/api/large/index';
// 直接在组件内部定义数据
const chartData = ref({
normal: '', // 正常设备数量
abnormal: '', // 异常设备数量
fault: '' // 故障设备数量
});
const value1: any = ref('');
const value2: any = ref('');
const value3: any = ref('');
const value = ref('1');
const options = [
{
value: '1',
label: '交流有功功率'
}
];
const active: any = ref('1');
const data = ref<any>({});
const getDataList = () => {
getPowerStationOverview().then((res) => {
if (res.code == 200) {
data.value = res.data;
}
});
};
getDataList();
const changeTime = () => {
getEnergyData();
getInverterData();
};
const data2 = ref<any>({});
const getEnergyData = () => {
let date: any;
if (active.value == 2) {
date = value2.value;
value3.value = '';
value1.value = '';
} else if (active.value == 3) {
date = value3.value;
value1.value = '';
value2.value = '';
}
const today = new Date();
const formattedDate = `${today.getFullYear()}-${today.getMonth() + 1}`;
const params = {
type: active.value == 1 ? 2 : active.value,
date: date ? date : formattedDate
};
getStationMonthOverview(params).then((res) => {
if (res.code == 200) {
getTurnoverList(res.data.data);
data2.value = res.data;
}
});
};
const data3 = ref<any>({});
const getInverterData = () => {
let date: any;
if (active.value == 1) {
date = value1.value;
value2.value = '';
value3.value = '';
} else if (active.value == 2) {
date = value2.value;
value3.value = '';
value1.value = '';
} else if (active.value == 3) {
date = value3.value;
value1.value = '';
value2.value = '';
}
const today = new Date();
const formattedDate = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
const params = {
type: active.value,
date: date ? date : formattedDate
};
getInverterListOverview(params).then((res) => {
if (res.code == 200) {
pedestrianFlow(res.data.data);
chartData.value.fault = res.data.fault ?? 0;
chartData.value.normal = res.data.normal ?? 0;
chartData.value.abnormal = res.data.offline ?? 0;
renderChart();
}
});
};
const switchTab = (tabNumber: number) => {
const tabs = document.querySelectorAll('.tab');
tabs.forEach((tab) => tab.classList.remove('active'));
// 给对应数值的标签添加active类索引=数值-1
tabs[tabNumber - 1].classList.add('active');
// 可以根据数值执行不同的操作
active.value = tabNumber;
// getInverterData();
};
const chart = ref<HTMLElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const totalAll = ref(0);
// 计算百分比数据处理0值不占位
const calculatePercentages = () => {
const { normal, abnormal, fault } = chartData.value;
const total = Number(normal) + Number(abnormal) + Number(fault);
totalAll.value = total;
if (total === 0) {
return {
normal: 0,
abnormal: 0,
fault: 0
};
}
return {
normal: Number(normal) ?? 0,
abnormal: Number(abnormal) ?? 0,
fault: Number(fault) ?? 0
};
};
const lineOption = ref({});
const barOption = ref({});
const pedestrianFlow = (data?: any) => {
const xData = data.map((item) => item.time);
const yData = data.map((item) => item.content);
const lineData = {
xLabel: xData,
line1: yData
// line2: ['20', '50', '12', '65', '30', '60']
};
lineOption.value = getLineOption(lineData);
};
const getTurnoverList = (data?: any) => {
const xData = data.map((item) => item.time);
const yData = data.map((item) => {
// 先将content转换为数字再调用toFixed
const num = Number(item.content);
return isNaN(num) ? 0 : Number(num.toFixed(2));
});
const barData = {
name: xData,
value: yData
};
barOption.value = getBarOptions(barData);
};
// 初始化图表
const initChart = () => {
if (!chart.value) return;
chartInstance = echarts.init(chart.value);
getEnergyData();
getInverterData();
// pedestrianFlow();
// getTurnoverList();
};
// 渲染图表逆变器柱状图
const renderChart = () => {
// if (!chartInstance) return;
const percentages = calculatePercentages();
const option: echarts.EChartsOption = {
tooltip: {
trigger: 'item',
formatter: (params: any) => {
return `${params.marker} ${params.seriesName}: ${params.value}`;
}
},
legend: {
orient: 'horizontal',
right: 10,
top: 0,
data: [
{ name: '正常', icon: 'circle' },
{ name: '异常', icon: 'circle' },
{ name: '故障', icon: 'circle' }
],
textStyle: {
color: '#fff',
fontSize: 12
}
},
grid: {
left: '-3%',
right: '3%',
top: '30px',
bottom: '3%'
// containLabel: true
},
xAxis: {
type: 'value',
max: totalAll.value,
axisLabel: {
formatter: '{value}%',
show: false
},
splitLine: {
show: false
},
axisLine: { show: false }, // 隐藏轴线
axisTick: { show: false } // 隐藏刻度
},
yAxis: {
type: 'category',
show: false,
data: ['设备状态分布']
},
series: [
{
name: '正常',
type: 'bar',
stack: 'total',
data: [percentages.normal],
barWidth: 10,
itemStyle: {
color: 'rgba(0, 227, 150, 1)'
},
label: {
show: false,
position: 'insideLeft',
// formatter: `{c}%`,
color: '#fff',
fontWeight: 'bold'
}
},
{
name: '异常',
type: 'bar',
stack: 'total',
data: [percentages.abnormal],
barWidth: 10,
itemStyle: {
color: 'rgba(255, 171, 0, 1)'
},
label: {
show: false,
position: 'inside',
// formatter: `{c}%`,
color: '#fff',
fontWeight: 'bold'
}
},
{
name: '故障',
type: 'bar',
stack: 'total',
data: [percentages.fault],
barWidth: 10,
itemStyle: {
color: 'rgba(255, 77, 79, 1)'
},
label: {
show: false,
position: 'insideRight',
// formatter: `{c}%`,
color: '#fff',
fontWeight: 'bold'
}
}
]
};
chartInstance.setOption(option);
};
const lineChart = ref();
onMounted(() => {
initChart();
window.addEventListener('resize', () => chartInstance?.resize());
});
</script>
<style scoped lang="scss">
.left_page {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 0 10px 0 10px;
}
.power {
width: 100%;
// height: 20%;
box-sizing: border-box;
padding: 0 10px 10px 10px;
border: 1px solid rgba(29, 214, 255, 0.1);
border-radius: 10px;
.left_title_list {
width: 100%;
// padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.left_title_item {
width: 30%;
height: 100%;
display: flex;
flex-direction: column;
// align-items: center;
justify-content: space-between;
background-color: rgba(29, 214, 255, 0.1);
border-radius: 10px;
padding: 10px;
box-sizing: border-box;
:deep(.el-icon .top-icon) {
font-weight: bold;
}
}
.left_title_item > div:first-child {
/* 第一个子元素的样式 */
font-size: 12px;
padding-bottom: 5px;
}
.left_title_item > div:nth-child(2) {
/* 第二个子元素的样式 */
padding-bottom: 5px;
/* 添加其他需要的样式 */
}
.left_title_item > div:last-child {
/* 第一个子元素的样式 */
font-size: 12px;
}
}
}
.left_title {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
.left_title_img {
height: 20px;
width: 20px;
}
.left_title_text {
font-size: 20px;
font-family: 'Rang_men_zheng_title', sans-serif;
display: flex;
align-items: flex-end;
margin-left: 15px;
padding-top: 2px;
box-sizing: border-box;
}
.left_title_text1 {
font-size: 14px;
display: flex;
align-items: flex-end;
margin-left: 15px;
padding-top: 2px;
box-sizing: border-box;
color: #fff;
}
}
.tab-container {
display: flex;
// gap: 4px;
font-size: 12px;
margin-right: 20px;
}
.tab {
padding: 4px;
border: 0.1px solid rgba(10, 79, 84, 1);
// border-radius: 6px;
cursor: pointer;
background-color: transparent;
// font-family: Arial, sans-serif;
transition: all 0.2s ease;
}
.tab.active {
background-color: #3b93fd;
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.tab:hover:not(.active) {
background-color: #3b93fd;
}
img {
width: 100%;
height: 100%;
}
.inverter {
width: 100%;
position: relative;
// height: 10%;
.selectTime {
position: absolute;
right: 0;
top: 12px;
display: flex;
align-items: center;
}
.bix {
position: absolute;
font-size: 12px;
color: rgba(156, 163, 175, 1);
top: 50px;
}
.date_select {
position: absolute;
top: 100px;
right: 0;
z-index: 9;
}
}
.chart-container {
width: 100%;
height: 50px;
}
.brokenLine {
width: 100%;
height: 23vh;
// margin-top: 10px;
}
.income {
width: 100%;
height: 24vh;
// margin-top: 20px;
}
.income_list {
width: 100%;
height: 7vh;
display: grid;
grid-template-columns: repeat(2, 1fr);
align-items: center; /* 垂直居中 */
// grid-gap: 10px;
// background-color: rgba(29, 214, 255, 0.1);
// border-radius: 10px;
padding: 0 10px;
box-sizing: border-box;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,735 @@
import * as echarts from 'echarts/core';
// import { PictorialBarChart } from 'echarts/charts'
// 客流量图
export const getOption = (xData: any, yData: any) => {
const data = {
xData,
yData
};
const maxData = Math.ceil(Math.max(...data.yData));
const barData = data.yData.map((item) => {
return maxData;
});
const option = {
grid: {
top: '10%',
left: '8%',
right: '5%',
bottom: '20%'
// containLabel: true
},
xAxis: {
type: 'category',
data: data.xData,
axisLine: {
show: false
},
axisTick: {
show: true
},
axisLabel: {
textStyle: {
color: '#fff'
}
}
},
yAxis: {
show: true,
type: 'value',
max: maxData,
splitLine: {
show: true,
lineStyle: {
type: 'solid',
color: 'rgba(73, 169, 191, 0.2)'
}
}
},
tooltip: {
trigger: 'axis',
backgroundColor: '',
textStyle: {
color: '#fff'
}
},
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
series: [
{
name: '柱图',
type: 'bar',
// barWidth: '10%',
data: barData,
tooltip: {
show: false
},
barGap: '-50%',
itemStyle: {
normal: {
color: 'rgba(73, 169, 191, 0.2)'
}
}
},
{
name: '客单价',
type: 'line',
showAllSymbol: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: {
normal: {
color: 'rgba(217, 231, 255, 0.3)',
shadowColor: 'rgba(0, 0, 0, .3)',
shadowBlur: 0
// shadowOffsetY: 5,
// shadowOffsetX: 5,
}
},
itemStyle: {
color: 'rgba(224, 194, 22, 1)',
borderWidth: 0,
shadowBlur: 0
},
label: {
show: false, // 显示数据标签
color: 'rgba(255, 208, 59, 1)'
},
data: data.yData
}
]
};
return option;
};
// 上菜分析图
export const getOption2 = (data: any) => {
const maxData = Math.max(...data.yData);
const option = {
// backgroundColor: "#38445E",
grid: {
left: '10%',
top: '13%',
bottom: '16%',
right: '10%'
},
xAxis: {
data: data.xData,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: 'rgba(255, 129, 109, 0.1)',
width: 1 //这里是为了突出显示加上的
}
},
axisLabel: {
textStyle: {
color: '#999',
fontSize: 12
}
}
},
yAxis: [
{
splitNumber: 2,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: 'rgba(255, 129, 109, 0.1)',
width: 1 //这里是为了突出显示加上的
}
},
axisLabel: {
textStyle: {
color: '#999'
}
},
splitArea: {
areaStyle: {
color: 'rgba(255,255,255,.5)'
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(255,255,255,.5)',
width: 0.5,
type: 'dashed'
}
}
}
],
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
tooltip: {
trigger: 'axis', // 设置为 'item',表示鼠标悬浮在图形上时显示 tooltip
// formatter: function (params) {
// return `订单数: ${params.data}` // 显示鼠标悬浮项的数量
// },
backgroundColor: '', // 设置提示框的背景颜色
textStyle: {
color: '#fff' // 设置文字颜色
// fontSize: 14 // 设置文字大小
}
},
series: [
{
name: '订单数',
type: 'pictorialBar',
barCategoryGap: '0%',
symbol: 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z',
label: {
show: false,
position: 'top',
distance: 15,
color: 'rgba(255, 235, 59, 1)',
// fontWeight: "bolder",
fontSize: 16
},
itemStyle: {
normal: {
// color: {
// type: "linear",
// x: 0,
// y: 0,
// x2: 0,
// y2: 1,
// colorStops: [
// {
// offset: 0,
// color: "rgba(232, 94, 106, .8)", // 0% 处的颜色
// },
// {
// offset: 1,
// color: "rgba(232, 94, 106, .1)", // 100% 处的颜色
// },
// ],
// global: false, // 缺省为 false
// },
color: function (params: any) {
if (params.data === maxData) {
return 'rgba(255, 219, 103, 0.6)';
} else {
return 'rgba(239, 244, 255, 0.45)';
}
}
},
emphasis: {
opacity: 1
}
},
data: data.yData,
z: 10
}
]
};
return option;
};
//食堂周报图
export const getLineOption = (lineData: any) => {
const maxData = Math.ceil(Math.max(...lineData.line1));
const option = {
backgroundColor: '',
tooltip: {
trigger: 'axis',
backgroundColor: 'transparent',
color: '#7ec7ff',
textStyle: {
color: '#fff'
},
borderColor: '#7ec7ff'
},
// legend: {
// align: 'left',
// right: '5%',
// top: '1%',
// type: 'plain',
// textStyle: {
// color: '#fff',
// fontSize: 12
// },
// // icon:'rect',
// itemGap: 15,
// itemWidth: 18,
// data: [
// {
// name: '上周销售量'
// },
// {
// name: '本周销售量'
// }
// ]
// },
grid: {
top: '12%',
left: '1%',
right: '3%',
bottom: '12%',
containLabel: true
},
xAxis: {
type: 'category',
data: lineData.xLabel,
axisLine: {
show: false
},
axisTick: {
show: true
},
axisLabel: {
textStyle: {
color: '#fff'
}
}
},
yAxis: {
show: true,
type: 'value',
max: maxData,
splitLine: {
show: true,
lineStyle: {
type: 'solid',
color: 'rgba(73, 169, 191, 0.2)'
}
}
},
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
series: [
{
name: '逆变器功率',
type: 'line',
symbol: 'circle', // 默认是空心圆(中间是白色的),改成实心圆
showAllSymbol: false,
symbolSize: 0,
smooth: true,
lineStyle: {
width: 1,
color: 'rgba(80, 164, 225, 1)', // 线条颜色
borderColor: 'rgba(0,0,0,.4)'
},
itemStyle: {
color: 'rgba(80, 164, 225, 1)',
borderWidth: 2,
show: false
},
tooltip: {
show: true
},
areaStyle: {
//线性渐变前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是true则该四个值是绝对像素位置。
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(80, 164, 225, 0.4)'
},
{
offset: 1,
color: 'rgba(80, 164, 225, 0)'
}
],
false
),
shadowColor: 'rgba(25,163,223, 0.5)', //阴影颜色
shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。
},
data: lineData.line1
}
]
};
return option;
};
//#endregion
// 菜品销售图
export const getDishesOption = (data?: any) => {
const res = data;
const dataIndex = 1;
const option = {
xAxis: {
type: 'value',
axisTick: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
}
},
yAxis: {
type: 'category',
axisTick: {
show: false
},
axisLabel: {
margin: 10 // 增大标签与轴线间距
},
width: 60, // 增大Y轴宽度
data: res.name,
axisLine: {
lineStyle: {
color: '#93C9C3'
}
}
},
grid: {
top: '5%', // 设置网格区域与容器之间的边距
bottom: '5%', // 同理
left: '5%',
containLabel: true
},
series: [
{
type: 'bar',
data: res.ratio,
barMaxWidth: 25,
itemStyle: {
barBorderRadius: 3,
color: 'rgba(12, 242, 216, 0.2)'
},
label: {
show: false
}
},
{
type: 'bar',
data: res.data,
barGap: '-100%',
barMaxWidth: 25,
itemStyle: {
barBorderRadius: 3,
color: function (params: any) {
if (params.data <= 300) {
return new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ color: 'rgba(252, 105, 0, 1)', offset: 0 },
{ color: 'rgba(250, 42, 42, 1)', offset: 1 }
]);
} else {
return new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ color: 'rgba(73, 169, 191, 1)', offset: 0 },
{ color: 'rgba(108, 248, 236, 1)', offset: 1 }
]);
}
}
},
label: {
show: true,
position: [200, -15],
formatter: function (params: any) {
if (params.data <= 300) {
return `{a| ${params.value}g/${res.ratio[params.dataIndex]}g}`;
} else {
return `{b| ${params.value}g/${res.ratio[params.dataIndex]}g}`;
}
},
rich: {
a: {
color: 'rgba(255, 78, 51, 1)'
},
b: {
color: 'rgba(255, 235, 59, 1)'
}
}
}
}
]
};
return option;
};
// 菜品库存图
export const getInventoryOption = () => {
const res = {
data: [2800, 300, 3900, 3000, 2450, 2670, 3320],
name: ['麻辣牛肉', '水煮肉片', '酸菜鱼', '辣子鸡丁', '烧白', '冬瓜排骨汤', '清炒油麦菜'],
ratio: [4000, 4000, 4000, 4000, 4000, 4000, 4000]
},
dataIndex = 1;
const option = {
xAxis: {
type: 'value',
axisTick: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
}
},
yAxis: {
type: 'category',
show: false,
axisTick: {
show: false
},
axisLabel: {
margin: 10 // 增大标签与轴线间距
},
width: 20, // 增大Y轴宽度
data: res.name,
axisLine: {
show: false,
lineStyle: {
color: '#93C9C3'
}
}
},
grid: {
top: '5%', // 设置网格区域与容器之间的边距
bottom: '5%', // 同理
left: '5%',
containLabel: true
},
series: [
{
type: 'bar',
data: res.ratio,
barMaxWidth: 6,
itemStyle: {
barBorderRadius: 3,
color: 'rgba(12, 242, 216, 0.2)'
},
label: {
show: true,
position: [0, -15],
fontSize: 14,
color: '#fff',
formatter: function (params: any) {
return params.name;
}
// rich: {
// a: {
// color: "rgba(255, 78, 51, 1)",
// },
// b: {
// color: "rgba(255, 235, 59, 1)",
// },
// },
}
},
{
type: 'bar',
data: res.data,
barGap: '-100%',
barMaxWidth: 6,
itemStyle: {
barBorderRadius: 0,
color: function (params: any) {
if (params.dataIndex === dataIndex) {
return new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ color: 'rgba(255, 78, 51, 1)', offset: 0 },
{ color: 'rgba(252, 105, 0, 0)', offset: 1 }
]);
} else {
return new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ color: 'rgba(242, 224, 27, 1)', offset: 0 },
{ color: 'rgba(236, 227, 127, 0.55)', offset: 0.5 },
{ color: 'rgba(230, 229, 227, 0.1)', offset: 1 }
]);
}
}
},
label: {
show: true,
position: [200, -15],
formatter: function (params: any) {
if (params.dataIndex === dataIndex) {
return `{a| ${params.value}g}`;
} else {
return `{b| ${params.value}g}`;
}
},
rich: {
a: {
color: 'rgba(255, 78, 51, 1)'
},
b: {
color: 'rgba(255, 235, 59, 1)'
}
}
}
}
]
};
return option;
};
export const getBarOptions = (data: any) => {
const option = {
backgroundColor: '',
grid: {
left: '7%',
top: '10%',
bottom: '23%',
right: '2%'
},
tooltip: {
show: true,
backgroundColor: '',
trigger: 'axis',
formatter: '{b0}{c0}万元',
textStyle: {
color: '#fff'
}
// borderColor: 'rgba(252, 217, 18, 1)'
},
xAxis: [
{
type: 'category',
data: data.name,
axisLine: {
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)'
}
},
axisLabel: {
textStyle: {
color: '#999',
fontSize: 12
}
},
axisTick: {
// show: true,
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)',
type: 'dashed'
}
}
}
],
yAxis: [
{
axisLabel: {
formatter: function (value) {
if (value >= 1000) {
value = (value / 1000).toFixed(1) + 'k'; // 大于等于1000的数字显示为1k、2.5k等
}
return value;
},
color: 'rgba(255, 255, 255, 0.8)'
},
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)'
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(108, 128, 151, 0.3)',
type: 'dashed'
}
}
}
],
dataZoom: [
{
// show: true,
start: 0,
end: 30,
bottom: 2, // 下滑块距离x轴底部的距离
height: 23
},
{
type: 'inside'
}
],
series: [
{
type: 'bar',
data: data.value,
stack: '合并',
barWidth: '15',
itemStyle: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(0, 111, 255, 0)' // 0% 处的颜色
},
{
offset: 0.7,
color: 'rgba(0, 111, 255, 0.5)' // 0% 处的颜色
},
{
offset: 1,
color: 'rgba(0, 111, 255, 1)' // 100% 处的颜色
}
],
false
)
},
label: {
show: true,
formatter: '{c}',
position: 'top',
color: '#fff',
fontSize: 10
// padding: 5
}
}
// {
// type: 'bar',
// stack: '合并',
// data: topData,
// barWidth: '15',
// itemStyle: {
// color: 'rgba(252, 217, 18, 1)'
// }
// }
]
};
return option;
};

View File

@ -0,0 +1,442 @@
<template>
<div class="rightPage">
<div class="alarm-container">
<!-- 顶部标题栏 -->
<div class="header">
<img src="@/assets/large/right1.png" style="width: 17px; height: 18px" alt="" />
<span class="title">告警信息中心</span>
<!-- <el-badge :value="unhandledCount" class="unhandled-badge" type="danger"> {{ unhandledCount }}条未处理 </el-badge> -->
<span class="jgao">{{ alarmData.length }}条信息未处理</span>
</div>
<!-- 告警卡片列表可循环渲染这里演示单条 -->
<div class="alarm_list">
<el-card class="alarm-card" shadow="hover" v-for="(item, index) in alarmData" :key="index">
<div class="card-header">
<img src="@/assets/large/right2.png" style="width: 15px; height: 15px" alt="" />
<span class="card-title">{{ item.alarmMsg }}</span>
<span class="time">{{ formatDate(item.alarmBeginTime) }}</span>
</div>
<div class="card-content">
{{ item.advice }}
</div>
<div class="card-footer">
<el-tag type="danger" size="small">紧急</el-tag>
<el-tag type="danger" size="small">处理</el-tag>
</div>
</el-card>
</div>
</div>
<div class="overview">
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/right4.png" alt="" />
</div>
<div class="left_title_text">项目概述</div>
</div>
</div>
<div class="overview_content">
<div>项目名称田东光伏智慧生态工地开发项目</div>
<div>项目位置广西壮族自治区百色市田东县平马镇东宁东路97号百通</div>
<div>项目位置广西壮族自治区百色市田东县平马镇东宁东路97号百通</div>
<div>占地面积约10000亩</div>
<div>土地性质城镇住宅用地兼容商业用地容积率2.5</div>
<div>建设单位这里是建设单位的名称</div>
<div>项目类型集中式光伏电站</div>
<div>总装机容量200MW</div>
</div>
</div>
<div class="monitor">
<div class="left_title">
<div style="display: flex; align-items: center">
<div class="left_title_img">
<img src="@/assets/large/right3.png" alt="" />
</div>
<div class="left_title_text">设备状态监控</div>
</div>
</div>
<div class="stats-container">
<div class="container_item" v-for="(item, index) in deviceStats" :key="index">
<div class="container_item_one">
<div class="container_item_one_box">
<div class="box_img">
<img src="@/assets/large/right6.png" style="width: 20px; height: 20px" />
</div>
<div class="box_text">
<div>{{ item.name }}</div>
<div style="font-size: 12px">{{ item.total }}</div>
</div>
</div>
<div class="card-right">
<div class="progress-top">
<span
class="progress-percent"
:class="{
green1: item.rate >= 99, // 可根据需求调整颜色规则
orange1: item.rate < 99 && item.rate >= 90
}"
>{{ item.rate }}%</span
>
</div>
<div class="progress-bg">
<div
class="progress-fg"
:style="{ width: item.rate + '%' }"
:class="{
green: item.rate >= 99, // 可根据需求调整颜色规则
orange: item.rate < 99 && item.rate >= 90
}"
></div>
</div>
</div>
</div>
<div class="container_item_two">
<div>正常{{ item.normal }}</div>
<div>异常{{ item.abnormal }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getAlarmListOverview } from '@/api/large';
import { formatDate } from '@/utils/index';
const alarmData: any = ref({});
const deviceStats = ref([
{
name: '光伏组件',
icon: '../../../assets/large/right5.png', // 示例图标
total: '25,680',
unit: '块',
rate: 99.2,
normal: '25,472',
abnormal: 208
},
{
name: '逆变器',
icon: '@/assets/large/right6.png',
total: '1,246',
unit: '台',
rate: 98.6,
normal: '1,230',
abnormal: 16
},
{
name: '汇流箱',
icon: '@/assets/large/right7.png',
total: '128',
unit: '台',
rate: 100,
normal: '128',
abnormal: 0
},
{
name: '变压器',
icon: '@/assets/large/right8.png',
total: '32',
unit: '台',
rate: 96.8,
normal: '31',
abnormal: 1
},
{
name: '通信设备',
icon: '@/assets/large/right9.png',
total: '246',
unit: '台',
rate: 95.2,
normal: '234',
abnormal: 12
}
]);
const getAlarm = () => {
getAlarmListOverview().then((res) => {
console.log(res);
alarmData.value = res.data;
});
};
getAlarm();
</script>
<style scoped lang="scss">
.rightPage {
width: 100%;
height: 100%;
}
.alarm-container {
border: 1px solid #1e2b3d; /* 深色背景模拟,可替换成项目背景 */
border-radius: 8px;
color: #fff;
// box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 10px;
}
/* 顶部标题栏 */
.header {
display: flex;
align-items: center;
}
.title {
font-size: 16px;
font-weight: 500;
color: #fff;
margin-left: 8px;
}
.unhandled-badge {
margin-left: auto; /* 右对齐 */
}
.jgao {
font-size: 12px;
color: #f56c6c;
background: rgba(255, 77, 79, 0.2);
padding: 5px 6px;
border-radius: 10px;
margin-left: auto; /* 右对齐 */
}
.alarm_list {
width: 100%;
padding: 5px 0;
height: 14vh;
overflow-y: auto; /* 垂直方向超出时显示滚动条 */
}
// 滚动条优化
.alarm_list::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.alarm_list::-webkit-scrollbar-thumb {
background-color: #0ff !important;
border-radius: 5px;
}
.alarm_list::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.2);
}
/* 告警卡片 */
.alarm-card {
background: rgba(12, 30, 53, 0.3);
color: #fff;
border: none;
border-radius: 8px;
border: 1px solid #f56c6c;
margin-top: 10px;
}
.card-header {
display: flex;
align-items: center;
// justify-content: space-between;
margin-bottom: 12px;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #f56c6c;
margin-left: 10px;
}
.time {
font-size: 12px;
color: #909399;
margin-left: auto; /* 右对齐 */
}
.card-content {
font-size: 13px;
color: #dcdfe6;
margin-bottom: 12px;
line-height: 1.6;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.left_title {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
.left_title_img {
height: 20px;
width: 20px;
}
.left_title_text {
font-size: 20px;
font-family: 'Rang_men_zheng_title', sans-serif;
display: flex;
align-items: flex-end;
margin-left: 15px;
padding-top: 2px;
box-sizing: border-box;
}
.left_title_text1 {
font-size: 14px;
display: flex;
align-items: flex-end;
margin-left: 15px;
padding-top: 2px;
box-sizing: border-box;
color: #fff;
}
}
img {
width: 100%;
height: 100%;
}
.overview {
width: 100%;
height: 28vh;
padding: 10px;
border-radius: 10px;
border: 1px solid #1e2b3d;
margin-top: 20px;
.overview_content {
height: 80%;
width: 100%;
font-size: 14px;
line-height: 30px;
overflow-y: auto; /* 垂直方向超出时显示滚动条 */
}
// 滚动条优化
.overview_content::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.overview_content::-webkit-scrollbar-thumb {
background-color: #0ff !important;
border-radius: 5px;
}
.overview_content::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.2);
}
}
.monitor {
width: 100%;
height: 39vh;
border: 1px solid #1e2b3d;
margin-top: 20px;
padding: 10px;
border-radius: 10px;
.stats-container {
width: 100%; /* 可根据实际场景调整宽度 */
height: 87%;
padding: 10px;
border-radius: 8px;
box-sizing: border-box;
overflow-y: auto; /* 垂直方向超出时显示滚动条 */
.container_item {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.container_item_one {
width: 100%;
display: flex;
justify-content: space-between;
.container_item_one_box {
width: 50%;
display: flex;
.box_img {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(12, 30, 53, 0.6);
display: flex;
justify-content: center;
align-items: center;
}
.box_text {
color: rgba(156, 163, 175, 1);
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 10px;
// align-items: center;
}
}
/* 右侧区域:进度条 + 数据 */
.card-right {
display: flex;
margin-left: 10px;
justify-content: space-between;
align-items: center;
}
.progress-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-right: 6px;
font-size: 14px;
}
.progress-percent {
font-weight: bold;
}
.abnormal {
color: #ff9900; /* 异常数据颜色 */
}
.progress-bg {
height: 6px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
width: 100px;
text-align: right;
}
.progress-fg {
height: 100%;
width: 100px;
transition: width 0.3s;
}
/* 进度条颜色区分(可扩展更多规则) */
.green {
background-color: #28a745;
}
.orange {
background-color: #ffc107;
}
.green1 {
color: #28a745;
}
.orange1 {
color: #ffc107;
}
}
}
.container_item_two {
width: 90%;
height: 100%;
display: flex;
justify-content: space-between;
padding: 10px 0;
margin-left: auto;
color: rgba(156, 163, 175, 1);
font-size: 12px;
}
}
// 滚动条优化
.stats-container::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.stats-container::-webkit-scrollbar-thumb {
background-color: #0ff;
border-radius: 5px;
}
.stats-container::-webkit-scrollbar-track {
background-color: rgba(0, 255, 255, 0.2);
}
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<div class="large-screen">
<Header />
<div class="nav">
<div class="nav_left">
<leftPage />
</div>
<div class="nav_center">
<centerPage />
</div>
<div class="nav_right">
<rightPage />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Header from './components/header.vue';
import leftPage from './components/leftPage.vue';
import centerPage from './components/centerPage.vue';
import rightPage from './components/rightPage.vue';
import '@/assets/styles/element.scss';
</script>
<style scoped lang="scss">
.large-screen {
width: 100vw;
height: 100vh;
background: url('@/assets/large/bg.png') no-repeat;
background-size: 100% 100%;
background-color: rgba(4, 7, 17, 1);
}
.nav {
width: 100%;
height: calc(100vh - 80px);
box-sizing: border-box;
// padding: 10px;
display: grid;
grid-template-columns: 1fr 2fr 1fr;
color: #fff;
}
</style>

View File

@ -64,9 +64,9 @@
<!-- 主开关 --> <!-- 主开关 -->
<g :class="{ 'switch-closed': devices[0].status === 'closed' }"> <g :class="{ 'switch-closed': devices[0].status === 'closed' }">
<circle cx="200" cy="200" r="15" :fill="devices[0].status === 'closed' ? '#10b981' : '#ef4444'" /> <circle cx="200" cy="200" r="15" :fill="devices[0].status === 'closed' ? '#10b981' : '#ef4444'" />
<text x="200" y="250" text-anchor="middle" font-size="12">主开关</text> <text x="200" y="250" text-anchor="middle" font-size="5">主开关</text>
<text x="200" y="270" text-anchor="middle" font-size="10" fill="#666">220kV</text> <text x="200" y="270" text-anchor="middle" font-size="4" fill="#666">220kV</text>
<text x="200" y="285" text-anchor="middle" font-size="10" <text x="200" y="285" text-anchor="middle" font-size="4"
:fill="devices[0].status === 'closed' ? '#10b981' : '#ef4444'"> :fill="devices[0].status === 'closed' ? '#10b981' : '#ef4444'">
{{ devices[0].statusText }} {{ devices[0].statusText }}
</text> </text>
@ -78,15 +78,15 @@
<line x1="315" y1="190" x2="345" y2="190" stroke="#666" stroke-width="2" /> <line x1="315" y1="190" x2="345" y2="190" stroke="#666" stroke-width="2" />
<line x1="315" y1="210" x2="345" y2="210" stroke="#666" stroke-width="2" /> <line x1="315" y1="210" x2="345" y2="210" stroke="#666" stroke-width="2" />
<line x1="315" y1="230" x2="345" y2="230" stroke="#666" stroke-width="2" /> <line x1="315" y1="230" x2="345" y2="230" stroke="#666" stroke-width="2" />
<text x="330" y="285" text-anchor="middle" font-size="10" fill="#10b981">已完成</text> <text x="330" y="285" text-anchor="middle" font-size="5" fill="#10b981">已完成</text>
</g> </g>
<!-- 母线开关 --> <!-- 母线开关 -->
<g :class="{ 'switch-closed': devices[1].status === 'closed' }"> <g :class="{ 'switch-closed': devices[1].status === 'closed' }">
<circle cx="440" cy="200" r="15" :fill="devices[1].status === 'closed' ? '#10b981' : '#ef4444'" /> <circle cx="440" cy="200" r="15" :fill="devices[1].status === 'closed' ? '#10b981' : '#ef4444'" />
<text x="440" y="250" text-anchor="middle" font-size="12">母线开关</text> <text x="440" y="250" text-anchor="middle" font-size="5">母线开关</text>
<text x="440" y="270" text-anchor="middle" font-size="10" fill="#666">110kV</text> <text x="440" y="270" text-anchor="middle" font-size="4" fill="#666">110kV</text>
<text x="440" y="285" text-anchor="middle" font-size="10" <text x="440" y="285" text-anchor="middle" font-size="4"
:fill="devices[1].status === 'closed' ? '#10b981' : '#ef4444'"> :fill="devices[1].status === 'closed' ? '#10b981' : '#ef4444'">
{{ devices[1].statusText }} {{ devices[1].statusText }}
</text> </text>
@ -98,8 +98,8 @@
<!-- 馈线开关A --> <!-- 馈线开关A -->
<g :class="{ 'switch-closed': devices[2].status === 'closed' }"> <g :class="{ 'switch-closed': devices[2].status === 'closed' }">
<circle cx="600" cy="100" r="15" :fill="devices[2].status === 'closed' ? '#10b981' : '#ef4444'" /> <circle cx="600" cy="100" r="15" :fill="devices[2].status === 'closed' ? '#10b981' : '#ef4444'" />
<text x="600" y="60" text-anchor="middle" font-size="12">馈线开关A</text> <text x="600" y="60" text-anchor="middle" font-size="5">馈线开关A</text>
<text x="600" y="80" text-anchor="middle" font-size="10" <text x="600" y="80" text-anchor="middle" font-size="5"
:fill="devices[2].status === 'closed' ? '#10b981' : '#ef4444'"> :fill="devices[2].status === 'closed' ? '#10b981' : '#ef4444'">
{{ devices[2].statusText }} {{ devices[2].statusText }}
</text> </text>
@ -107,14 +107,14 @@
<!-- 负载A --> <!-- 负载A -->
<circle cx="680" cy="100" r="20" fill="#3b82f6" stroke="#1e40af" stroke-width="2" /> <circle cx="680" cy="100" r="20" fill="#3b82f6" stroke="#1e40af" stroke-width="2" />
<text x="680" y="105" text-anchor="middle" fill="white" font-size="12">负载A</text> <text x="680" y="105" text-anchor="middle" fill="white" font-size="5">负载A</text>
<text x="680" y="135" text-anchor="middle" font-size="10" fill="#10b981">已完成</text> <text x="680" y="135" text-anchor="middle" font-size="5" fill="#10b981">已完成</text>
<!-- 馈线开关B --> <!-- 馈线开关B -->
<g :class="{ 'switch-closed': devices[3].status === 'closed' }"> <g :class="{ 'switch-closed': devices[3].status === 'closed' }">
<circle cx="600" cy="300" r="15" :fill="devices[3].status === 'closed' ? '#10b981' : '#ef4444'" /> <circle cx="600" cy="300" r="15" :fill="devices[3].status === 'closed' ? '#10b981' : '#ef4444'" />
<text x="600" y="340" text-anchor="middle" font-size="12">馈线开关B</text> <text x="600" y="340" text-anchor="middle" font-size="5">馈线开关B</text>
<text x="600" y="360" text-anchor="middle" font-size="10" <text x="600" y="360" text-anchor="middle" font-size="5"
:fill="devices[3].status === 'closed' ? '#10b981' : '#ef4444'"> :fill="devices[3].status === 'closed' ? '#10b981' : '#ef4444'">
{{ devices[3].statusText }} {{ devices[3].statusText }}
</text> </text>
@ -123,8 +123,8 @@
<!-- 保护开关 --> <!-- 保护开关 -->
<g :class="{ 'switch-closed': devices[4].status === 'closed' }"> <g :class="{ 'switch-closed': devices[4].status === 'closed' }">
<circle cx="720" cy="300" r="15" :fill="devices[4].status === 'closed' ? '#10b981' : '#ef4444'" /> <circle cx="720" cy="300" r="15" :fill="devices[4].status === 'closed' ? '#10b981' : '#ef4444'" />
<text x="720" y="340" text-anchor="middle" font-size="12">保护开关</text> <text x="720" y="340" text-anchor="middle" font-size="5">保护开关</text>
<text x="720" y="360" text-anchor="middle" font-size="10" <text x="720" y="360" text-anchor="middle" font-size="5"
:fill="devices[4].status === 'closed' ? '#10b981' : '#ef4444'"> :fill="devices[4].status === 'closed' ? '#10b981' : '#ef4444'">
{{ devices[4].statusText }} {{ devices[4].statusText }}
</text> </text>

View File

@ -9,13 +9,13 @@
<el-col :span="12"> <el-col :span="12">
<div class="item"> <div class="item">
<div class="status">在线</div> <div class="status">在线</div>
<div class="count" style="color: rgba(0, 184, 122, 1);">56</div> <div class="count" style="color: rgba(0, 184, 122, 1);">{{ data?.sumOnLine || 0 }}</div>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<div class="item"> <div class="item">
<div class="status">离线</div> <div class="status">离线</div>
<div class="count" style="color: rgba(102, 102, 102, 1);">10</div> <div class="count" style="color: rgba(102, 102, 102, 1);">{{ data?.sumOffLine || 0 }}</div>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -146,4 +146,11 @@
} }
} }
</style> </style>
<script setup></script> <script setup>
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
</script>

View File

@ -70,7 +70,7 @@
<template v-else> <template v-else>
<el-col :span="8" v-for="(item, index) in videoList" :key="index" class="video-wrapper"> <el-col :span="8" v-for="(item, index) in videoList" :key="index" class="video-wrapper">
<!-- 视频容器 --> <!-- 视频容器 -->
<div class="item" :id="`smallVideo-${index + 1}`" ref="smallVideoRef"> <div class="item" :id="`smallVideo-${index + 1}`" :ref="el => smallVideoRefs[index] = el">
<!-- <div class="title">{{ item.name }}</div> --> <!-- <div class="title">{{ item.name }}</div> -->
</div> </div>
<!-- 按钮放在最外层与视频容器同级 --> <!-- 按钮放在最外层与视频容器同级 -->
@ -85,7 +85,7 @@
</el-row> </el-row>
<el-row v-if="!isExpanded"> <el-row v-if="!isExpanded">
<div class="pagination" v-if="activeTab !== 'record'"> <div class="pagination" v-if="activeTab !== 'record'">
<el-pagination layout="prev, pager, next, jumper, sizes" :total="totalRecords" <el-pagination layout="prev, pager, next, jumper" :total="totalRecords"
v-model:current-page="pageStart" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]" v-model:current-page="pageStart" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]"
@current-change="handlePageChange" @size-change="handleSizeChange"></el-pagination> @current-change="handlePageChange" @size-change="handleSizeChange"></el-pagination>
</div> </div>
@ -101,7 +101,7 @@ import EZUIKit from 'ezuikit-js';
// import TitleComponent from '@/components/TitleComponent'; // import TitleComponent from '@/components/TitleComponent';
import { getToken, getMonitoringList } from '@/api/securitySurveillance/index.js'; import { getToken, getMonitoringList } from '@/api/securitySurveillance/index.js';
import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue'; import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue';
import { useUserStore } from '@/store/modules/user';
const activeIndex = ref(0); // 初始选中第一个视频 const activeIndex = ref(0); // 初始选中第一个视频
const isExpanded = ref(true); // 初始为扩展 const isExpanded = ref(true); // 初始为扩展
const accessToken = ref('') const accessToken = ref('')
@ -110,7 +110,8 @@ const pageSize = ref(4); // 默认请求4个视频扩展布局
const totalRecords = ref(0); const totalRecords = ref(0);
const activeTab = ref('live'); const activeTab = ref('live');
const bigVideoRef = ref<HTMLDivElement>(null); const bigVideoRef = ref<HTMLDivElement>(null);
const smallVideoRef = ref<HTMLDivElement>(null); const smallVideoRefs = ref<Array<HTMLDivElement | null>>([]); // 使用数组存储多个视频容器引用
const currentProject = computed(() => useUserStore().selectedProject);
const videoList = ref([]); const videoList = ref([]);
// 存储第二页的数据,用于处理扩展视图右边视频不足的情况 // 存储第二页的数据,用于处理扩展视图右边视频不足的情况
@ -119,13 +120,26 @@ const nextPageVideoList = ref([]);
const hasUsedNextPageData = ref(false); const hasUsedNextPageData = ref(false);
const StructureEZUIKitPlayer = (item: any, index: number, isBig = false) => { const StructureEZUIKitPlayer = (item: any, index: number, isBig = false) => {
// 添加输入参数的安全检查
if (!item || typeof item !== 'object') {
console.error('无效的视频项数据:', item);
return;
}
const containerId = isBig ? 'bigVideo' : isExpanded.value ? `smallVideo-expanded-${index + 1}` : `smallVideo-${index + 1}`; const containerId = isBig ? 'bigVideo' : isExpanded.value ? `smallVideo-expanded-${index + 1}` : `smallVideo-${index + 1}`;
const container = document.getElementById(containerId); const container = document.getElementById(containerId);
// 先销毁旧的播放器实例(如果存在) // 先销毁旧的播放器实例(如果存在)
if (item.player) { if (item.player) {
try { try {
item.player.destroy(); // 添加安全检查确保destroy方法存在
if (typeof item.player.destroy === 'function') {
// 尝试移除所有事件监听器
if (item.player.off) {
item.player.off('*');
}
item.player.destroy();
}
} }
catch (error) { catch (error) {
console.error('销毁播放器失败:', error); console.error('销毁播放器失败:', error);
@ -133,18 +147,24 @@ const StructureEZUIKitPlayer = (item: any, index: number, isBig = false) => {
item.player = null; item.player = null;
} }
if (container) { if (container && accessToken.value && item.deviceSerial) {
item.player = new EZUIKit.EZUIKitPlayer({ try {
audio: '0', item.player = new EZUIKit.EZUIKitPlayer({
id: containerId, audio: '0',
accessToken: accessToken.value, id: containerId,
url: `ezopen://open.ys7.com/${item.deviceSerial}/1.hd.live`, accessToken: accessToken.value,
template: "pcLive", url: `ezopen://open.ys7.com/${item.deviceSerial}/1.hd.live`,
width: container.clientWidth, template: "pcLive",
height: container.clientHeight, width: container.clientWidth,
plugin: ['talk'] height: container.clientHeight,
}); plugin: ['talk']
});
} catch (error) {
console.error('创建播放器失败:', error);
item.player = null;
}
} else {
console.error(`创建播放器失败,缺少必要条件: container=${!!container}, accessToken=${!!accessToken.value}, deviceSerial=${!!item.deviceSerial}`);
} }
}; };
@ -157,21 +177,26 @@ const getTokenData = async () => {
const getMonitoringListData = async () => { const getMonitoringListData = async () => {
// 根据当前视图类型设置请求数量 // 根据当前视图类型设置请求数量
const currentPageSize = isExpanded.value ? 4 : 9; const currentPageSize = isExpanded.value ? 4 : 9;
const { data } = await getMonitoringList({ const { data: { object, sum }, } = await getMonitoringList({
pageStart: pageStart.value, pageStart: pageStart.value,
pageSize: currentPageSize pageSize: currentPageSize,
isflow: true,
projectId: currentProject.value?.id,
}) })
// totalRecords.value = data.total totalRecords.value = Number(sum)
videoList.value = data // 确保object是数组如果不是则使用空数组
videoList.value = Array.isArray(object) ? object : []
} }
// 获取下一页视频数据 // 获取下一页视频数据
const getNextPageData = async () => { const getNextPageData = async () => {
const { data } = await getMonitoringList({ const { data: { object, sum } } = await getMonitoringList({
pageStart: pageStart.value + 1, pageStart: pageStart.value + 1,
isflow: true,
pageSize: 3 // 只需要3个视频 pageSize: 3 // 只需要3个视频
}) })
nextPageVideoList.value = data; // 确保object是数组如果不是则使用空数组
nextPageVideoList.value = Array.isArray(object) ? object : [];
// 标记已经使用了下一页的数据 // 标记已经使用了下一页的数据
hasUsedNextPageData.value = true; hasUsedNextPageData.value = true;
} }
@ -193,37 +218,62 @@ const initVideo = async () => {
bigVideoRef.value.innerHTML = ''; bigVideoRef.value.innerHTML = '';
} }
// 清理所有小视频容器的内容
smallVideoRefs.value.forEach(ref => {
if (ref) {
ref.innerHTML = '';
}
});
// 确保videoList是数组如果不是则使用空数组
const safeVideoList = Array.isArray(videoList.value) ? videoList.value : [];
// 确保nextPageVideoList是数组如果不是则使用空数组
const safeNextPageVideoList = Array.isArray(nextPageVideoList.value) ? nextPageVideoList.value : [];
if (isExpanded.value) { if (isExpanded.value) {
// 扩展布局初始化大视频和右侧3个小视频 // 扩展布局初始化大视频和右侧3个小视频
StructureEZUIKitPlayer(videoList.value[activeIndex.value], 0, true); // 安全检查确保activeIndex在有效范围内
const safeActiveIndex = Math.min(Math.max(activeIndex.value, 0), safeVideoList.length - 1);
if (safeVideoList.length > 0 && safeVideoList[safeActiveIndex] && typeof safeVideoList[safeActiveIndex] === 'object') {
StructureEZUIKitPlayer(safeVideoList[safeActiveIndex], 0, true);
}
// 检查当前视频后面是否有足够的视频 // 检查当前视频后面是否有足够的视频
const remainingVideos = videoList.value.length - activeIndex.value - 1; const remainingVideos = safeVideoList.length - safeActiveIndex - 1;
if (remainingVideos >= 3) { if (remainingVideos >= 3) {
// 当前页后面有足够的视频,直接使用当前页的数据 // 当前页后面有足够的视频,直接使用当前页的数据
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const displayIndex = activeIndex.value + i + 1; const videoIndex = safeActiveIndex + 1 + i;
StructureEZUIKitPlayer(videoList.value[displayIndex], i); if (safeVideoList[videoIndex] && typeof safeVideoList[videoIndex] === 'object') {
// 修复索引使用i而不是i+1确保正确对应smallVideo-expanded-1, 2, 3
StructureEZUIKitPlayer(safeVideoList[videoIndex], i);
}
} }
// 重置已使用下一页数据的标记
hasUsedNextPageData.value = false;
} else { } else {
// 当前页后面视频不足3个需要获取下一页的数据 // 当前页后面视频不足3个需要获取下一页的数据
await getNextPageData(); await getNextPageData();
// 重新获取安全的视频列表
const updatedSafeNextPageVideoList = Array.isArray(nextPageVideoList.value) ? nextPageVideoList.value : [];
// 使用当前页后面的视频和下一页的前几个视频 // 使用当前页后面的视频和下一页的前几个视频
let displayCount = 0; let displayCount = 0;
// 先显示当前页后面的视频 // 先显示当前页后面的视频
for (let i = activeIndex.value + 1; i < videoList.value.length && displayCount < 3; i++) { for (let i = safeActiveIndex + 1; i < safeVideoList.length && displayCount < 3; i++) {
StructureEZUIKitPlayer(videoList.value[i], displayCount); if (safeVideoList[i] && typeof safeVideoList[i] === 'object') {
displayCount++; // 修复索引使用displayCount而不是displayCount+1
StructureEZUIKitPlayer(safeVideoList[i], displayCount);
displayCount++;
}
} }
// 再显示下一页的视频补充到3个 // 再显示下一页的视频补充到3个
for (let i = 0; i < nextPageVideoList.value.length && displayCount < 3; i++) { for (let i = 0; i < updatedSafeNextPageVideoList.length && displayCount < 3; i++) {
StructureEZUIKitPlayer(nextPageVideoList.value[i], displayCount); if (updatedSafeNextPageVideoList[i] && typeof updatedSafeNextPageVideoList[i] === 'object') {
displayCount++; // 修复索引使用displayCount而不是displayCount+1
StructureEZUIKitPlayer(updatedSafeNextPageVideoList[i], displayCount);
displayCount++;
}
} }
} }
} else { } else {
@ -234,9 +284,13 @@ const initVideo = async () => {
await getMonitoringListData(); await getMonitoringListData();
hasUsedNextPageData.value = false; hasUsedNextPageData.value = false;
} }
// 初始化所有视频 // 初始化所有视频,添加更严格的类型检查
videoList.value.forEach((item, index) => { safeVideoList.forEach((item, index) => {
StructureEZUIKitPlayer(item, index); if (item && typeof item === 'object') {
StructureEZUIKitPlayer(item, index);
} else {
console.warn(`跳过无效的视频项: ${index}`);
}
}); });
} }
} }
@ -244,37 +298,53 @@ const initVideo = async () => {
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
pageStart.value = page; pageStart.value = page;
// 这里可以添加分页逻辑 // 这里可以添加分页逻辑
getData()
} }
const handleSizeChange = (size: number) => { const handleSizeChange = (size: number) => {
pageSize.value = size; // 根据当前视图类型设置合适的页面大小
// 扩展视图固定为4普通视图固定为9
pageSize.value = isExpanded.value ? 4 : 9;
pageStart.value = 1; pageStart.value = 1;
// 这里可以添加分页逻辑 getData()
} }
// 清理所有播放器实例 // 清理所有播放器实例
const cleanupPlayers = () => { const cleanupPlayers = () => {
// 清理当前页视频的播放器
videoList.value.forEach(item => { videoList.value.forEach(item => {
if (item.player) { if (item && item.player && typeof item.player.destroy === 'function') {
try { try {
// 尝试移除所有事件监听器
if (item.player.off) {
item.player.off('*');
}
// 销毁播放器实例
item.player.destroy(); item.player.destroy();
} }
catch (error) { catch (error) {
console.error('销毁播放器失败:', error); console.error('销毁播放器失败:', error);
} }
// 确保播放器引用被清除
item.player = null; item.player = null;
} }
}); });
// 清理下一页视频的播放器 // 清理下一页视频的播放器
nextPageVideoList.value.forEach(item => { nextPageVideoList.value.forEach(item => {
if (item.player) { if (item && item.player && typeof item.player.destroy === 'function') {
try { try {
// 尝试移除所有事件监听器
if (item.player.off) {
item.player.off('*');
}
// 销毁播放器实例
item.player.destroy(); item.player.destroy();
} }
catch (error) { catch (error) {
console.error('销毁下一页视频播放器失败:', error); console.error('销毁下一页视频播放器失败:', error);
} }
// 确保播放器引用被清除
item.player = null; item.player = null;
} }
}); });
@ -287,15 +357,32 @@ watch(isExpanded, async (newValue, oldValue) => {
// 从扩展视图切换到普通视图需要重新请求9个视频 // 从扩展视图切换到普通视图需要重新请求9个视频
if (newValue === false && oldValue === true) { if (newValue === false && oldValue === true) {
// 同步更新页面大小为9
pageSize.value = 9;
// 清理所有播放器实例
cleanupPlayers();
// 等待DOM更新
await nextTick();
// 重新请求9个视频数据 // 重新请求9个视频数据
await getData(); await getMonitoringListData();
// 再次等待DOM更新
await nextTick();
// 初始化视频
initVideo();
// 恢复保存的activeIndex值 // 恢复保存的activeIndex值
activeIndex.value = currentActiveIndex; activeIndex.value = currentActiveIndex;
} }
// 从普通视图切换到扩展视图,不需要重新请求数据 // 从普通视图切换到扩展视图,不需要重新请求数据
else if (newValue === true && oldValue === false) { else if (newValue === true && oldValue === false) {
// 但是需要重新初始化视频布局 // 同步更新页面大小为4
pageSize.value = 4;
// 清理所有播放器实例
cleanupPlayers();
// 等待DOM更新完成后再初始化视频
await nextTick(); await nextTick();
// 再次等待确保DOM完全渲染
await new Promise(resolve => setTimeout(resolve, 100));
// 初始化视频
initVideo(); initVideo();
} }
}); });

View File

@ -35,9 +35,11 @@
<div class="box" style="height: 100%;display: flex;"> <div class="box" style="height: 100%;display: flex;">
<div class="left" <div class="left"
style="display: flex;flex-direction: column;height: 100%;justify-content: space-around;padding: 15px;"> style="display: flex;flex-direction: column;height: 100%;justify-content: space-around;padding: 15px;">
<div style="color: rgba(102, 102, 102, 1);">今日录像时长</div> <div style="color: rgba(102, 102, 102, 1);">今日设备情况</div>
<div><span <div><span
style="font-size: 30px;font-weight: 400;letter-spacing: 0px;line-height: 36px;color: rgba(0, 0, 0, 1);font-weight: bold;">54</span> style="font-size: 30px;font-weight: 400;letter-spacing: 0px;line-height: 36px;color: rgba(0, 0, 0, 1);font-weight: bold;">{{
data?.sumMon
|| 0}}</span>
<span <span
style="font-size: 12px;font-weight: 400;letter-spacing: 0px;line-height: 17.38px;color: rgba(154, 154, 154, 1);"> style="font-size: 12px;font-weight: 400;letter-spacing: 0px;line-height: 17.38px;color: rgba(154, 154, 154, 1);">
@ -48,14 +50,14 @@
style="width: 63px;height: 18px;border-radius: 16px;background: rgba(0, 184, 122, .3);text-align: center;"> style="width: 63px;height: 18px;border-radius: 16px;background: rgba(0, 184, 122, .3);text-align: center;">
<span <span
style="font-size: 12px;font-weight: 400;letter-spacing: 0;line-height: 17.38px;color: rgba(0, 184, 122, 1);"> style="font-size: 12px;font-weight: 400;letter-spacing: 0;line-height: 17.38px;color: rgba(0, 184, 122, 1);">
正常<span>53</span> 正常<span>{{ data?.sumOnLine || 0 }}</span>
</span> </span>
</div> </div>
<div <div
style="width: 63px;height: 18px;border-radius: 16px;background: rgba(227, 39, 39, .3);text-align: center;margin-left: 10px;"> style="width: 63px;height: 18px;border-radius: 16px;background: rgba(227, 39, 39, .3);text-align: center;margin-left: 10px;">
<span <span
style="font-size: 12px;font-weight: 400;letter-spacing: 0;line-height: 17.38px;color: rgba(0, 184, 122, 1);color: red;"> style="font-size: 12px;font-weight: 400;letter-spacing: 0;line-height: 17.38px;color: rgba(0, 184, 122, 1);color: red;">
异常<span>53</span> 异常<span>{{ data?.sumOffLine || 0 }}</span>
</span> </span>
</div> </div>
<!-- <div>异常<span>1</span></div> --> <!-- <div>异常<span>1</span></div> -->
@ -153,4 +155,11 @@
margin-right: 30px; margin-right: 30px;
} }
</style> </style>
<script setup></script> <script setup>
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
</script>

View File

@ -25,7 +25,7 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row style="margin-top: 20px;"> <el-row style="margin-top: 20px;">
<Top /> <Top :data="data" />
</el-row> </el-row>
<el-row style="margin-top: 20px;" :gutter="25"> <el-row style="margin-top: 20px;" :gutter="25">
<el-col :span="18"> <el-col :span="18">
@ -37,7 +37,7 @@
</el-row> </el-row>
<el-row style="margin-top: 20px;"> <el-row style="margin-top: 20px;">
<el-col> <el-col>
<Sbzt /> <Sbzt :data="data" />
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
@ -55,4 +55,11 @@ import Top from "./components/top"
import Spjk from "./components/spjk" import Spjk from "./components/spjk"
import Spgl from "./components/spgl"; import Spgl from "./components/spgl";
import Sbzt from "./components/sbzt"; import Sbzt from "./components/sbzt";
import { getHomeScreenData } from "@/api/securitySurveillance";
const data = ref(null)
onMounted(() => {
getHomeScreenData().then(res => {
data.value = res.data
})
})
</script> </script>

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-inspection"> <div class="operation-inspection">
<!-- 导航标签 --> <!-- 导航标签 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 子选项卡 --> <!-- 子选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -48,8 +48,8 @@
</el-select> </el-select>
</div> </div>
<div class="filter-actions"> <div class="filter-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button> <el-button type="primary" icon="Search" class="search-btn" @click="handleSearch"> 搜索 </el-button>
<el-button type="primary" icon="el-icon-plus" class="create-btn" @click="handleCreate">手动创建计划</el-button> <el-button type="primary" icon="Plus" class="create-btn" @click="handleCreate">手动创建计划</el-button>
</div> </div>
</div> </div>
@ -312,43 +312,101 @@
v-model="detailDialogVisible" v-model="detailDialogVisible"
title="巡检计划详情" title="巡检计划详情"
width="800px" width="800px"
class="detail-dialog" :before-close="handleCloseDetailDialog"
center class="custom-experiment-dialog"
:show-close="true"
custom-class="beautified-detail-dialog"
> >
<div class="detail-content"> <div class="task-detail-container">
<div class="detail-header"> <!-- 基础信息区 -->
<h3 class="detail-title">{{ detailData.planName || '巡检计划' }}</h3> <div class="detail-card">
<el-tag :type="detailData.status === '1' ? 'success' : 'info'" class="detail-status-tag"> <h3 class="card-title">基础信息</h3>
{{ detailData.status === '1' ? '启用' : detailData.status === '2' ? '停用' : '-' }} <div class="card-content">
</el-tag> <div class="info-row">
<div class="info-item">
<span class="info-label">计划名称</span>
<span class="info-value">{{ detailData.planName || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">状态</span>
<span class="info-value task-status">
<el-tag :type="detailData.status === '1' ? 'success' : 'info'">
{{ detailData.status === '1' ? '启用' : detailData.status === '2' ? '停用' : '-' }}
</el-tag>
</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">计划类型</span>
<span class="info-value">{{ getPlanTypeText(detailData.planType) || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">巡检对象</span>
<span class="info-value">{{ getObjectTypeText(detailData.objectType) || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">巡检频率</span>
<span class="info-value">{{ detailData.inspectionFrequency || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">负责人</span>
<span class="info-value">{{ detailData.nickName || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">开始日期</span>
<span class="info-value">{{ formatDate(detailData.beginTime) || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">结束日期</span>
<span class="info-value">{{ formatDate(detailData.endTime) || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">计划开始时间</span>
<span class="info-value">{{ detailData.planBeginTime || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">持续时间</span>
<span class="info-value">{{ detailData.duration || '-' }}分钟</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">巡检项</span>
<div class="info-value">
<span v-for="(item, index) in detailData.itemVoList" :key="item.id" class="inspection-item-tag">
{{ item.name }}
<span v-if="index < detailData.itemVoList.length - 1" class="item-separator"></span>
</span>
<span v-if="!detailData.itemVoList || detailData.itemVoList.length === 0">-</span>
</div>
</div>
<div class="info-item">
<span class="info-label">电站ID</span>
<span class="info-value">{{ detailData.projectId || '-' }}</span>
</div>
</div>
</div>
</div> </div>
<div class="detail-main"> <!-- 备注信息 -->
<el-descriptions :column="{ xs: 1, sm: 1, md: 2, lg: 2 }" class="detail-descriptions" border> <div v-if="detailData.remark" class="detail-card">
<el-descriptions-item label="计划类型" class="detail-item">{{ getPlanTypeText(detailData.planType) || '-' }}</el-descriptions-item> <h3 class="card-title">备注信息</h3>
<el-descriptions-item label="巡检对象" class="detail-item">{{ getObjectTypeText(detailData.objectType) || '-' }}</el-descriptions-item> <div class="card-content">
<el-descriptions-item label="巡检频率" class="detail-item">{{ detailData.inspectionFrequency || '-' }}</el-descriptions-item> <div class="description-content">
<el-descriptions-item label="负责人" class="detail-item">{{ detailData.nickName || '-' }}</el-descriptions-item> {{ detailData.remark }}
<el-descriptions-item label="开始日期" class="detail-item">{{ formatDate(detailData.beginTime) || '-' }}</el-descriptions-item> </div>
<el-descriptions-item label="结束日期" class="detail-item">{{ formatDate(detailData.endTime) || '-' }}</el-descriptions-item> </div>
<el-descriptions-item label="计划开始时间" class="detail-item">{{ detailData.planBeginTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="持续时间" class="detail-item">{{ detailData.duration || '-' }}分钟</el-descriptions-item>
<el-descriptions-item label="巡检项ID" class="detail-item">{{ detailData.inspectionItemId || '-' }}</el-descriptions-item>
<el-descriptions-item label="电站ID" class="detail-item">{{ detailData.projectId || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="detailData.remark" class="detail-remark">
<h4 class="remark-title">备注信息</h4>
<p class="remark-content">{{ detailData.remark }}</p>
</div> </div>
</div> </div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="closeDetailDialog" class="close-btn">关闭</el-button> <el-button @click="closeDetailDialog">关闭</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@ -605,13 +663,14 @@ const formatDate = (dateString) => {
const getUsersList = async () => { const getUsersList = async () => {
try { try {
const response = await xunjianUserlist(); const response = await xunjianUserlist();
const userRows = response?.data?.rows || response?.rows || []; // 适配新接口格式检查code为200且rows为数组
const userRows = response.code === 200 && response.rows && Array.isArray(response.rows) ? response.rows : [];
userList.value = userRows userList.value = userRows
.filter((item) => item && typeof item === 'object') .filter((item) => item && typeof item === 'object')
.map((item, index) => ({ .map((item) => ({
label: item.userName || `用户${index + 1}`, label: item.userName || '未知用户',
value: item.id || `id_${index}` value: String(item.userId || '') // 使用userId作为唯一标识
})); }));
if (userList.value.length === 0) { if (userList.value.length === 0) {
@ -946,39 +1005,41 @@ const handleEnable = async (row) => {
// 导航相关方法 // 导航相关方法
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/xunjianrenwu'); router.push('/znxj/xjgl/xunjianrenwu');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/xunjianjihua'); router.push('/znxj/xjgl/xunjianjihua');
}; };
</script> </script>
<style scoped> <style scoped>
@import url('./css/detail-dialog.css');
@import url('./css/step-bars.css');
.operation-inspection { .operation-inspection {
padding: 20px; padding: 20px;
background-color: #f5f7fa; background-color: #f5f7fa;
@ -1122,47 +1183,127 @@ const handleInspectionManagement3 = () => {
color: #f56c6c; color: #f56c6c;
} }
.detail-dialog .el-dialog__body { /* 弹窗样式 */
.create-plan-dialog .el-dialog__body {
padding: 24px; padding: 24px;
} }
.detail-header { /* 详情弹窗样式 - 与工单列表页面保持一致 */
display: flex; .custom-experiment-dialog .el-dialog__body {
justify-content: space-between; max-height: 60vh;
align-items: center; overflow-y: auto;
padding: 24px;
}
.task-detail-container {
padding: 10px 0;
}
/* 详情卡片样式 */
.detail-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #f0f2f5;
} }
.detail-title { .card-title {
font-size: 18px; font-size: 16px;
font-weight: bold; font-weight: 600;
color: #303133; color: #1d2129;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
} }
.detail-status-tag { .card-content {
padding: 4px 12px; padding: 0 4px;
}
/* 信息行和信息项样式 */
.info-row {
display: flex;
margin-bottom: 16px;
flex-wrap: wrap;
}
.info-item {
flex: 0 0 50%;
margin-bottom: 12px;
display: flex;
align-items: flex-start;
}
.info-item.full-width {
flex: 0 0 100%;
}
.info-label {
font-weight: 500;
color: #86909c;
margin-right: 8px;
min-width: 80px;
flex-shrink: 0;
}
.info-value {
color: #4e5969;
flex: 1;
word-break: break-all;
font-size: 14px; font-size: 14px;
} }
.detail-descriptions { /* 骨架屏样式 */
margin-bottom: 20px; .skeleton-loading {
display: flex;
flex-direction: column;
gap: 16px;
} }
.detail-item .el-descriptions__label { .skeleton-card {
background-color: #f5f5f5;
border-radius: 8px;
padding: 16px;
}
.skeleton-header {
height: 20px;
width: 30%;
background-color: #e0e0e0;
border-radius: 4px;
margin-bottom: 12px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.skeleton-row {
height: 16px;
width: 100%;
background-color: #e0e0e0;
border-radius: 4px;
}
/* 优先级标签样式 */
.task-status {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500; font-weight: 500;
color: #606266; border: 1px solid transparent;
} }
.remark-title { .description-content {
font-weight: 500;
margin-bottom: 8px;
color: #303133;
}
.remark-content {
padding: 12px; padding: 12px;
background-color: #f5f7fa; background-color: #f9f9f9;
border-radius: 4px; border-radius: 4px;
line-height: 1.6; line-height: 1.6;
color: #4e5969;
font-size: 13px;
} }
</style> </style>

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="execution-records"> <div class="execution-records">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,10 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 页面标题 -->
<TitleComponent title="运维组织模块" subtitle="实时监控人员状态、车辆状态和班组状态"></TitleComponent>
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -25,17 +22,37 @@
</div> </div>
<!-- 搜索和筛选区 --> <!-- 搜索和筛选区 -->
<div class="search-filter"> <transition :enter-active-class="'el-zoom-in-center'" :leave-active-class="'el-zoom-out-center'">
<div class="search-container"> <div v-show="showSearch" class="search-filter">
<el-input <div class="search-container">
v-model="searchKeyword" <el-form ref="queryFormRef" :model="queryParams" :inline="true">
placeholder="搜索班组名称或编号" <el-form-item label="班组名称" prop="teamName">
class="search-input" <el-input v-model="queryParams.teamName" placeholder="请输入班组名称" clearable @keyup.enter="handleQuery"></el-input>
suffix-icon="el-icon-search" </el-form-item>
@keyup.enter="handleSearch" <el-form-item label="负责区域" prop="region">
></el-input> <el-input v-model="queryParams.region" placeholder="请输入负责区域" clearable @keyup.enter="handleQuery"></el-input>
<el-button type="primary" class="new-team-btn" @click="handleCreateTeam"> <i class="el-icon-plus"></i> 新增班组 </el-button> </el-form-item>
<el-form-item label="组长" prop="leader">
<el-input v-model="queryParams.leader" placeholder="请输入组长姓名" clearable @keyup.enter="handleQuery"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="正常运行" value="正常运行"></el-option>
<el-option label="人员紧张" value="人员紧张"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</div> </div>
</transition>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px">
<el-button type="primary" class="new-team-btn" @click="handleCreateTeam"> <i class="el-icon-plus"></i> 新增班组 </el-button>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</div> </div>
<!-- 班组卡片和图表区域 --> <!-- 班组卡片和图表区域 -->
@ -180,14 +197,35 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'; import { ref, computed, onMounted, onUnmounted, nextTick, reactive, toRefs } from 'vue';
import router from '@/router'; import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import * as echarts from 'echarts'; // 导入ECharts import * as echarts from 'echarts'; // 导入ECharts
import renwuImage from '@/assets/images/renwu.png'; import renwuImage from '@/assets/images/renwu.png';
import { getCurrentInstance } from 'vue';
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
import router from '@/router';
import * as echarts from 'echarts'; // 导入ECharts
import renwuImage from '@/assets/images/renwu.png';
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 搜索条件 // 搜索条件
const searchKeyword = ref(''); const searchKeyword = ref('');
const showSearch = ref(true);
const queryFormRef = ref();
// 搜索参数
const data = reactive({
queryParams: {
teamName: '',
region: '',
leader: '',
status: ''
}
});
const { queryParams } = toRefs(data);
// 班组数据 // 班组数据
const rawTeamData = ref([ const rawTeamData = ref([
@ -255,12 +293,24 @@ const total = ref(rawTeamData.value.length);
const filteredTeams = computed(() => { const filteredTeams = computed(() => {
let teams = [...rawTeamData.value]; let teams = [...rawTeamData.value];
if (searchKeyword.value) { // 使用queryParams进行过滤
const keyword = searchKeyword.value.toLowerCase(); if (queryParams.value.teamName) {
teams = teams.filter( const keyword = queryParams.value.teamName.toLowerCase();
(team) => teams = teams.filter((team) => team.name.toLowerCase().includes(keyword));
team.name.toLowerCase().includes(keyword) || team.region.toLowerCase().includes(keyword) || team.leader.toLowerCase().includes(keyword) }
);
if (queryParams.value.region) {
const keyword = queryParams.value.region.toLowerCase();
teams = teams.filter((team) => team.region.toLowerCase().includes(keyword));
}
if (queryParams.value.leader) {
const keyword = queryParams.value.leader.toLowerCase();
teams = teams.filter((team) => team.leader.toLowerCase().includes(keyword));
}
if (queryParams.value.status) {
teams = teams.filter((team) => team.status === queryParams.value.status);
} }
return teams; return teams;
@ -299,6 +349,19 @@ const handleSearch = () => {
currentPage.value = 1; // 重置到第一页 currentPage.value = 1; // 重置到第一页
}; };
// 执行搜索
const handleQuery = () => {
currentPage.value = 1;
};
// 重置搜索
const resetQuery = () => {
if (queryFormRef.value) {
queryFormRef.value.resetFields();
}
currentPage.value = 1;
};
// 分页事件 // 分页事件
const handleSizeChange = (val) => { const handleSizeChange = (val) => {
pageSize.value = val; pageSize.value = val;
@ -337,34 +400,34 @@ const handleCreateTeam = () => {
// 导航路由跳转 // 导航路由跳转
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/cheliangzhuangtai'); router.push('/znxj/ywzz/cheliangzhuangtai');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/banzhuzhuangtai'); router.push('/znxj/ywzz/banzhuzhuangtai');
}; };
// ECharts实例 // ECharts实例

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="execution-records"> <div class="execution-records">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,10 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 页面标题 -->
<TitleComponent title="运维组织模块" subtitle="实时监控人员状态、车辆状态和班组状态"></TitleComponent>
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -74,6 +71,11 @@
<el-input v-model="plateNumber4" maxlength="1" class="plate-char"></el-input> <el-input v-model="plateNumber4" maxlength="1" class="plate-char"></el-input>
<el-input v-model="plateNumber5" maxlength="1" class="plate-char"></el-input> <el-input v-model="plateNumber5" maxlength="1" class="plate-char"></el-input>
</div> </div>
<div style="margin-top: 10px; display: flex; gap: 8px; justify-content: flex-end">
<el-input v-model="searchKeyword" placeholder="关键字(编号/类型/车牌)" clearable @keyup.enter="handleSearch" style="max-width: 220px" />
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
<el-button icon="Refresh" @click="resetFilters">重置</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -139,7 +141,6 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import router from '@/router'; import router from '@/router';
import TitleComponent from './TitleComponent.vue';
// 搜索和筛选条件 // 搜索和筛选条件
const searchKeyword = ref(''); const searchKeyword = ref('');
@ -303,34 +304,34 @@ const handleDispatch = (row) => {
// 导航路由跳转 // 导航路由跳转
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/cheliangzhuangtai'); router.push('/znxj/ywzz/cheliangzhuangtai');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/banzhuzhuangtai'); router.push('/znxj/ywzz/banzhuzhuangtai');
}; };
</script> </script>

View File

@ -0,0 +1,389 @@
/* 详情弹窗通用样式 */
/* 详情卡片样式 */
.detail-card {
margin-bottom: 20px;
padding: 20px;
background-color: #fafafa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.card-title {
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-content {
display: flex;
flex-direction: column;
gap: 12px;
}
/* 信息行和信息项样式 */
.info-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.info-item {
flex: 1;
min-width: 280px;
}
.info-item.full-width {
min-width: 100%;
}
.info-label {
display: inline-block;
width: 100px;
color: #606266;
font-weight: 500;
}
.info-value {
color: #303133;
word-break: break-word;
}
/* 步骤相关样式 - 详情弹窗专用 - 使用外部CSS样式 */
.task-detail-container .steps-container {
width: 100%;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 8px;
background-color: #fff;
}
.task-detail-container .step-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 15px;
background-color: #fafafa;
border-radius: 6px;
position: relative;
transition: all 0.3s ease;
}
.task-detail-container .step-item:hover {
background-color: #f5f7fa;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.task-detail-container .step-number {
width: 32px;
height: 32px;
background-color: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
font-size: 14px;
font-weight: bold;
flex-shrink: 0;
z-index: 1;
}
.task-detail-container .step-item:not(:last-child)::after {
content: '';
position: absolute;
top: 50px;
left: 16px;
width: 2px;
height: calc(100% + 5px);
background-color: #e4e7ed;
z-index: 0;
}
.task-detail-container .step-info {
flex: 1;
}
.task-detail-container .step-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 4px;
font-size: 14px;
}
.task-detail-container .step-purpose {
color: #606266;
margin-bottom: 4px;
font-size: 13px;
}
.task-detail-container .step-time,
.task-detail-container .step-finish-time,
.task-detail-container .step-remark {
color: #909399;
font-size: 12px;
margin-bottom: 2px;
}
.task-detail-container .step-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
flex-shrink: 0;
margin-top: 4px;
}
.step-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.step-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 4px;
font-size: 14px;
}
.step-purpose {
color: #606266;
margin-bottom: 4px;
font-size: 13px;
}
.step-time,
.step-finish-time,
.step-remark {
color: #909399;
font-size: 12px;
margin-bottom: 2px;
}
/* 步骤状态样式 - 详情弹窗专用 */
.task-detail-container .step-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
flex-shrink: 0;
margin-top: 4px;
}
/* 步骤状态样式 - 待执行 */
.task-detail-container .step-status.status-pending {
background-color: #e6f7ff;
color: #1677ff;
border: 1px solid #91d5ff;
}
/* 步骤状态样式 - 执行中 */
.task-detail-container .step-status.status-executing {
background-color: #fffbe6;
color: #fa8c16;
border: 1px solid #ffe58f;
}
/* 步骤状态样式 - 已完成 */
.task-detail-container .step-status.status-completed,
.task-detail-container .step-status.tag-completed {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
/* 步骤状态样式 - 已延期 */
.task-detail-container .step-status.status-delayed {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
/* 步骤状态样式 - 未完成 */
.task-detail-container .step-status.status-unknown {
background-color: #f5f5f5;
color: #999;
border: 1px solid #d9d9d9;
}
/* 步骤状态样式 - 失败 */
.task-detail-container .step-status.status-failed {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
/* 通用状态颜色样式 */
.status-pending {
color: #e6a23c;
}
.status-executing {
color: #409eff;
}
.status-completed {
color: #67c23a;
}
.status-delayed {
color: #f56c6c;
}
.status-unknown {
color: #909399;
}
/* 加载状态样式 */
.loading-details {
padding: 20px 0;
}
/* 骨架屏加载 */
.skeleton-loading {
display: flex;
flex-direction: column;
gap: 20px;
}
.skeleton-card {
background-color: #f2f2f2;
border-radius: 8px;
padding: 20px;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-header {
height: 24px;
width: 140px;
background-color: #e0e0e0;
border-radius: 4px;
margin-bottom: 16px;
}
.skeleton-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton-row {
height: 20px;
background-color: #e0e0e0;
border-radius: 4px;
}
.skeleton-row:nth-child(1) {
width: 70%;
}
.skeleton-row:nth-child(2) {
width: 90%;
}
.skeleton-row:nth-child(3) {
width: 60%;
}
@keyframes skeleton-loading {
0% {
opacity: 0.6;
}
50% {
opacity: 0.3;
}
100% {
opacity: 0.6;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.info-item {
min-width: 100%;
}
.info-row {
gap: 12px;
}
/* 步骤条响应式设计 */
.task-detail-container .steps-container {
padding: 10px;
}
.task-detail-container .step-item {
flex-direction: column;
align-items: flex-start;
padding: 10px;
margin-bottom: 10px;
}
.task-detail-container .step-item > * {
width: 100%;
margin-bottom: 10px;
margin-right: 0 !important;
}
.task-detail-container .step-number {
margin-bottom: 10px;
width: 24px;
height: 24px;
font-size: 12px;
}
.task-detail-container .step-item:not(:last-child)::after {
display: none;
}
}
/* 弹窗按钮样式 */
.dialog-footer .el-button {
padding: 10px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.dialog-footer .el-button:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
/* 其他相关样式 */
.fail-reason {
color: #f56c6c;
}
.no-info {
color: #909399;
font-style: italic;
padding: 10px 0;
}
.loading-state {
text-align: center;
padding: 80px 20px;
color: #6c757d;
font-size: 14px;
}
.loading-state i {
display: block;
font-size: 48px;
margin-bottom: 16px;
color: #1677ff;
}
.step-content {
padding: 30px 20px;
background-color: #fafafa;
border-radius: 8px;
margin-top: 20px;
}

View File

@ -0,0 +1,206 @@
/* 步骤容器样式 */
.steps-container {
width: 100%;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 8px;
background-color: #fff;
}
/* 单个步骤项样式 */
.step-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 15px;
background-color: #fafafa;
border-radius: 6px;
position: relative;
transition: all 0.3s ease;
}
/* 步骤项悬停效果 */
.step-item:hover {
background-color: #f5f7fa;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 步骤序号样式 */
.step-number {
width: 32px;
height: 32px;
background-color: #409eff;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
font-size: 14px;
font-weight: bold;
flex-shrink: 0;
z-index: 1;
}
/* 步骤连接线样式 */
.step-item:not(:last-child)::after {
content: '';
position: absolute;
top: 50px;
left: 16px;
width: 2px;
height: calc(100% + 5px);
background-color: #e4e7ed;
z-index: 0;
}
/* 步骤内容样式 */
.step-content {
padding: 30px 20px;
background-color: #fafafa;
border-radius: 8px;
margin-top: 20px;
}
/* 步骤信息样式 */
.step-info {
flex: 1;
}
/* 步骤名称样式 */
.step-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 4px;
font-size: 14px;
}
/* 步骤目的样式 */
.step-purpose {
color: #606266;
margin-bottom: 4px;
font-size: 13px;
}
/* 步骤时间样式 */
.step-time,
.step-finish-time,
.step-remark {
color: #909399;
font-size: 12px;
margin-bottom: 2px;
}
/* 添加步骤按钮样式 */
.add-step-btn {
color: #409eff;
display: block;
margin: 15px auto 0;
padding: 8px 16px;
border-radius: 4px;
transition: all 0.3s ease;
}
.add-step-btn:hover {
color: #66b1ff;
background-color: #ecf5ff;
}
/* 删除步骤按钮样式 */
.delete-step-btn {
color: #f56c6c;
flex-shrink: 0;
}
.delete-step-btn:hover {
color: #ff8590;
background-color: #fef0f0;
}
/* 步骤状态标签样式 */
.step-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
flex-shrink: 0;
margin-top: 4px;
}
/* 步骤状态样式 - 待执行 */
.step-status.status-pending {
background-color: #e6f7ff;
color: #1677ff;
border: 1px solid #91d5ff;
}
/* 步骤状态样式 - 执行中 */
.step-status.status-executing {
background-color: #fffbe6;
color: #fa8c16;
border: 1px solid #ffe58f;
}
/* 步骤状态样式 - 已完成 */
.step-status.status-completed {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
/* 步骤状态样式 - 已延期 */
.step-status.status-delayed {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
/* 响应式设计 - 中等屏幕 */
@media (max-width: 1024px) {
.steps-container {
padding: 15px;
}
.step-item {
padding: 12px;
margin-bottom: 12px;
}
.step-number {
width: 28px;
height: 28px;
font-size: 13px;
margin-right: 12px;
}
}
/* 响应式设计 - 小屏幕 */
@media (max-width: 768px) {
.steps-container {
padding: 10px;
}
.step-item {
flex-direction: column;
align-items: flex-start;
padding: 10px;
margin-bottom: 10px;
}
.step-item > * {
width: 100%;
margin-bottom: 10px;
margin-right: 0 !important;
}
.step-number {
margin-bottom: 10px;
width: 24px;
height: 24px;
font-size: 12px;
}
.step-item:not(:last-child)::after {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="box-container"> <div class="box-container">
<!-- 导航栏 --> <!-- 导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab active" @click="handleInspection1">待办事项</div> <div class="nav-tab active" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<div class="main-content"> <div class="main-content">
<!-- 左侧日历区域 --> <!-- 左侧日历区域 -->
<div class="calendar-container"> <div class="calendar-container">
@ -43,7 +43,7 @@
<div class="form-container"> <div class="form-container">
<div class="form-header"> <div class="form-header">
<h2>今日待办</h2> <h2>今日待办</h2>
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAddTaskDialog">添加</el-button> <el-button type="primary" icon="Plus" @click="openAddTaskDialog">添加</el-button>
</div> </div>
<!-- 待办事项列表 - 动态渲染 --> <!-- 待办事项列表 - 动态渲染 -->
@ -54,6 +54,7 @@
class="todo-item" class="todo-item"
:class="{ 'important': item.taskLevel === '重要', 'completed': item.status === 2 }" :class="{ 'important': item.taskLevel === '重要', 'completed': item.status === 2 }"
> >
<el-checkbox class="todo-checkbox" :checked="item.status === 2" @change="handleStatusChange(item, $event)"></el-checkbox>
<div <div
class="todo-color-indicator" class="todo-color-indicator"
:class="{ :class="{
@ -63,7 +64,6 @@
completed: item.status === 2 completed: item.status === 2
}" }"
></div> ></div>
<el-checkbox class="todo-checkbox" :checked="item.status === 2" @change="handleStatusChange(item, $event)"></el-checkbox>
<div class="todo-content"> <div class="todo-content">
<div class="todo-main"> <div class="todo-main">
<div class="todo-title">{{ item.title }}</div> <div class="todo-title">{{ item.title }}</div>
@ -560,25 +560,25 @@ const handleDelete = (id) => {
}; };
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
</script> </script>
@ -590,16 +590,6 @@ const handleInspection7 = () => {
min-height: 100vh; min-height: 100vh;
} }
/* 导航栏样式 */
.navigation-tabs {
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 2px;
}
/* 已完成任务的样式 */ /* 已完成任务的样式 */
.todo-color-indicator.completed { .todo-color-indicator.completed {
background-color: #dcdfe6; background-color: #dcdfe6;
@ -609,7 +599,15 @@ const handleInspection7 = () => {
color: #909399; color: #909399;
text-decoration: line-through; text-decoration: line-through;
} }
/* 导航栏样式 */
.navigation-tabs {
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 2px;
}
.nav-tab { .nav-tab {
padding: 12px 24px; padding: 12px 24px;
cursor: pointer; cursor: pointer;
@ -849,13 +847,14 @@ const handleInspection7 = () => {
/* 悬停显示操作按钮 */ /* 悬停显示操作按钮 */
.todo-item:hover .todo-actions { .todo-item:hover .todo-actions {
opacity: 1; background: linear-gradient(to right, rgba(173, 216, 230, 0), rgb(64, 158, 255));
right: 0; right: 0;
opacity: 0.8;
} }
/* 内容区域平移以给按钮留出空间 */ /* 取消内容区域平移效果 */
.todo-item:hover .todo-content { .todo-item:hover .todo-content {
transform: translateX(-120px); transform: none;
} }
.action-icon { .action-icon {
@ -942,7 +941,7 @@ const handleInspection7 = () => {
background-color: #ff4d4f; background-color: #ff4d4f;
} }
::v-deep .custom-date-cell { :deep(.custom-date-cell) {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 5px; padding: 5px;
@ -983,13 +982,13 @@ const handleInspection7 = () => {
} }
/* 穿透作用域,强制设置日历单元格为正方形 */ /* 穿透作用域,强制设置日历单元格为正方形 */
::v-deep .el-calendar-table td { :deep(.el-calendar-table td) {
padding: 2px; padding: 2px;
vertical-align: top; vertical-align: top;
width: 120px; /* 强制宽度 */ width: 120px; /* 强制宽度 */
height: 120px; /* 强制高度(与宽度一致) */ height: 120px; /* 强制高度(与宽度一致) */
} }
::v-deep .el-calendar-day { :deep(.el-calendar-day) {
padding: 0; /* 移除默认内边距 */ padding: 0; /* 移除默认内边距 */
width: 100%; width: 100%;
height: 100%; height: 100%;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-organization"> <div class="operation-organization">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -10,10 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab active" @click="handleInspection7">运维组织</div> <div class="nav-tab active" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 页面标题 -->
<TitleComponent title="运维组织模块" subtitle="实时监控人员状态、车辆状态和班组状态"></TitleComponent>
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -133,11 +130,9 @@
<script setup> <script setup>
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import router from '@/router'; import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
// 激活的选项卡 //
const activeTab = ref('personnel');
// 统计数据(保持原有数据不变) // 统计数据(保持原有数据不变)
const totalPersonnel = ref(36); const totalPersonnel = ref(36);
@ -402,34 +397,34 @@ const assignTask = (person) => {
// 导航路由跳转 // 导航路由跳转
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/cheliangzhuangtai'); router.push('/znxj/ywzz/cheliangzhuangtai');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/banzhuzhuangtai'); router.push('/znxj/ywzz/banzhuzhuangtai');
}; };
</script> </script>
@ -448,38 +443,6 @@ const handleInspectionManagement3 = () => {
margin-bottom: 16px; margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
} }
.custom-tabs {
padding-top: 1px;
}
.custom-tabs .el-tabs__header {
margin: 0 -20px;
padding: 0 20px;
border-bottom: 1px solid #e4e7ed;
}
.custom-tabs .el-tabs__nav-wrap::after {
height: 0;
}
.custom-tabs .el-tabs__item {
font-size: 14px;
color: #606266;
padding: 16px 20px;
margin-right: 20px;
}
.custom-tabs .el-tabs__item.is-active {
color: #165dff;
font-weight: 500;
border-bottom: 2px solid #165dff;
}
.custom-tabs .el-tabs__item:hover {
color: #165dff;
}
/* 内容容器样式 */ /* 内容容器样式 */
.content-container { .content-container {
display: flex; display: flex;

View File

@ -2,7 +2,7 @@
<div> <div>
<div class="operation-inspection"> <div class="operation-inspection">
<!-- 1. 顶部导航选项卡对应原试验系统的外层导航 --> <!-- 1. 顶部导航选项卡对应原试验系统的外层导航 -->
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab" @click="handleInspection2">巡检管理</div> <div class="nav-tab" @click="handleInspection2">巡检管理</div>
<div class="nav-tab active" @click="handleInspection3">试验管理</div> <div class="nav-tab active" @click="handleInspection3">试验管理</div>
@ -10,7 +10,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡和按钮组合 --> <!-- 选项卡和按钮组合 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -24,18 +24,26 @@
<!-- 4. 筛选和操作区域与试验系统filter-and-actions结构一致 --> <!-- 4. 筛选和操作区域与试验系统filter-and-actions结构一致 -->
<div class="filter-and-actions"> <div class="filter-and-actions">
<div class="filters"> <div class="filters">
<el-select v-model="filterStatus" placeholder="巡检状态" clearable> <el-input v-model="keyword" placeholder="关键字(计划名/编号)" clearable @keyup.enter="handleSearch" style="width: 220px" />
<el-select v-model="filterStatus" placeholder="试验状态" clearable>
<el-option label="全部状态" value="all"></el-option> <el-option label="全部状态" value="all"></el-option>
<el-option label="正常" value="normal"></el-option> <el-option label="已批准" value="1"></el-option>
<el-option label="需关注" value="attention"></el-option> <el-option label="进行中" value="2"></el-option>
<el-option label="有问题" value="problem"></el-option> <el-option label="已完成" value="3"></el-option>
<el-option label="未通过" value="4"></el-option>
</el-select> </el-select>
<el-select v-model="filterType" placeholder="巡检类型" clearable> <el-select v-model="filterType" placeholder="实验对象类型" clearable>
<el-option label="全部类型" value="all"></el-option> <el-option label="全部类型" value="all"></el-option>
<el-option label="数据库" value="database"></el-option> <el-option label="安全试验" value="1"></el-option>
<el-option label="服务器" value="server"></el-option> <el-option label="网络实验" value="2"></el-option>
<el-option label="网络设备" value="network"></el-option> <el-option label="性能试验" value="3"></el-option>
<el-option label="其他试验" value="4"></el-option>
</el-select>
<el-select v-model="filterManager" placeholder="负责人" clearable>
<el-option label="全部负责人" value="all"></el-option>
<el-option v-for="user in userList" :key="user.value" :label="user.label" :value="user.value" />
</el-select> </el-select>
<el-date-picker <el-date-picker
@ -49,8 +57,9 @@
></el-date-picker> ></el-date-picker>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<el-button type="primary" class="search-btn"> 搜索 </el-button> <el-button type="primary" icon="Search" class="search-btn" @click="handleSearch"> 搜索 </el-button>
<el-button type="primary" class="create-btn" @click="openRecordDialog"> <i class="fas fa-plus"></i> 新增实验记录 </el-button> <el-button icon="Refresh" class="search-btn" @click="resetFilters"> 重置 </el-button>
<el-button type="primary" icon="Plus" class="create-btn" @click="openRecordDialog"> <i class="fas fa-plus"></i> 新增实验记录 </el-button>
</div> </div>
</div> </div>
@ -67,13 +76,7 @@
<el-table-column align="center" prop="type" label="巡检类型" width="120"></el-table-column> <el-table-column align="center" prop="type" label="巡检类型" width="120"></el-table-column>
<el-table-column align="center" prop="cycle" label="巡检周期" width="120"></el-table-column> <el-table-column align="center" prop="cycle" label="巡检周期" width="120"></el-table-column>
<el-table-column align="center" prop="dateRange" label="执行时间范围"></el-table-column> <el-table-column align="center" prop="dateRange" label="执行时间范围"></el-table-column>
<el-table-column align="center" prop="progress" label="完成进度" width="120">
<template #default="scope">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: scope.row.progress + '%', backgroundColor: getProgressColor(scope.row.status) }"></div>
</div>
</template>
</el-table-column>
<el-table-column align="center" prop="status" label="状态" width="100"> <el-table-column align="center" prop="status" label="状态" width="100">
<template #default="scope"> <template #default="scope">
<span :class="['status-tag', `status-${scope.row.status}`]"> <span :class="['status-tag', `status-${scope.row.status}`]">
@ -374,10 +377,10 @@
</el-form-item> </el-form-item>
<el-form-item label="实验对象类型" class="form-item"> <el-form-item label="实验对象类型" class="form-item">
<el-select v-model="formData.testObject" placeholder="请选择实验对象类型" class="form-input"> <el-select v-model="formData.testObject" placeholder="请选择实验对象类型" class="form-input">
<el-option label="1安全试验" value="1" /> <el-option label="安全试验" value="1" />
<el-option label="2网络实验" value="2" /> <el-option label="网络实验" value="2" />
<el-option label="3性能试验" value="3" /> <el-option label="性能试验" value="3" />
<el-option label="4" value="4" /> <el-option label="其他试验" value="4" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</div> </div>
@ -418,17 +421,6 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 试验步骤 -->
<el-form-item label="试验步骤" class="form-item" style="width: 100%">
<div class="steps-container">
<div class="step-item" v-for="(step, index) in formData.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.content" placeholder="输入试验步骤" />
</div>
<el-button type="text" size="small" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item>
<!-- 所需设备与准备 --> <!-- 所需设备与准备 -->
<el-form-item label="所需资源与设备" class="form-item" style="width: 100%"> <el-form-item label="所需资源与设备" class="form-item" style="width: 100%">
<div class="equipment-list"> <div class="equipment-list">
@ -471,99 +463,113 @@
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
class="custom-experiment-dialog" class="custom-experiment-dialog"
center
> >
<div class="detail-content"> <div v-if="detailData" class="task-detail-container">
<!-- 基础信息 --> <!-- 基础信息卡片 -->
<div class="detail-section"> <div class="detail-card">
<h3 class="section-title">基础信息</h3> <h3 class="card-title">基础信息</h3>
<div class="detail-grid"> <div class="card-content">
<div class="detail-item"> <div class="info-row">
<label class="detail-label">计划名称:</label> <div class="info-item">
<span class="detail-value">{{ detailData.planName || '-' }}</span> <label class="info-label">计划名称:</label>
<span class="info-value">{{ detailData.planName || '-' }}</span>
</div>
<div class="info-item">
<label class="info-label">计划编号:</label>
<span class="info-value">{{ detailData.planCode || '-' }}</span>
</div>
</div> </div>
<div class="detail-item"> <div class="info-row">
<label class="detail-label">计划编号:</label> <div class="info-item">
<span class="detail-value">{{ detailData.planCode || '-' }}</span> <label class="info-label">实验对象:</label>
<span class="info-value">{{ getTestObjectText(detailData.testObject) || '-' }}</span>
</div>
<div class="info-item">
<label class="info-label">负责人:</label>
<span class="info-value">{{ detailData.person?.userName || '-' }}</span>
</div>
</div> </div>
<div class="detail-item"> <div class="info-row">
<label class="detail-label">实验对象:</label> <div class="info-item">
<span class="detail-value">{{ getTestObjectText(detailData.testObject) || '-' }}</span> <label class="info-label">开始时间:</label>
</div> <span class="info-value">{{ detailData.beginTime ? formatDate(detailData.beginTime) : '-' }}</span>
<div class="detail-item"> </div>
<label class="detail-label">负责人:</label> <div class="info-item">
<span class="detail-value">{{ detailData.person?.userName || '-' }}</span> <label class="info-label">结束时间:</label>
</div> <span class="info-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
<div class="detail-item"> </div>
<label class="detail-label">开始时间:</label>
<span class="detail-value">{{ detailData.beginTime ? formatDate(detailData.beginTime) : '-' }}</span>
</div>
<div class="detail-item">
<label class="detail-label">结束时间:</label>
<span class="detail-value">{{ detailData.endTime ? formatDate(detailData.endTime) : '-' }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 实验设备 --> <!-- 实验设备 -->
<div v-if="detailData.testDevice" class="detail-section"> <div v-if="detailData.testDevice" class="detail-card">
<h3 class="section-title">实验设备</h3> <h3 class="card-title">实验设备</h3>
<div class="device-list"> <div class="card-content">
<span v-for="(device, index) in detailData.testDevice.split(',')" :key="index" class="device-tag"> <div v-for="(device, index) in detailData.testDevice.split(',')" :key="index" class="info-item">
{{ device.trim() }} <label class="info-label">设备{{ index + 1 }}:</label>
</span> <span class="info-value">{{ device.trim() }}</span>
</div>
</div>
<!-- 实验步骤 -->
<div v-if="detailData.testStep" class="detail-section">
<h3 class="section-title">实验步骤</h3>
<div class="steps-container">
<div v-for="(step, index) in detailData.testStep.split(',')" :key="index" class="step-item">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">{{ step.trim() }}</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 实验信息 --> <!-- 实验信息 -->
<div class="detail-section"> <div class="detail-card">
<h3 class="section-title">实验信息</h3> <h3 class="card-title">实验信息</h3>
<div class="detail-textarea"> <div class="card-content">
<label class="detail-label">实验说明:</label> <div class="info-item full-width">
<div class="detail-text">{{ detailData.testInfo || '-' }}</div> <label class="info-label">实验说明:</label>
</div> <div class="info-value">{{ detailData.testInfo || '-' }}</div>
<div class="detail-textarea"> </div>
<label class="detail-label">实验设置:</label> <div class="info-item full-width">
<div class="detail-text">{{ detailData.testSetting || '-' }}</div> <label class="info-label">实验设置:</label>
</div> <div class="info-value">{{ detailData.testSetting || '-' }}</div>
<div class="detail-textarea"> </div>
<label class="detail-label">解决方案:</label> <div class="info-item full-width">
<div class="detail-text">{{ detailData.testSolutions || '-' }}</div> <label class="info-label">解决方案:</label>
<div class="info-value">{{ detailData.testSolutions || '-' }}</div>
</div>
</div> </div>
</div> </div>
<!-- 参与人员 --> <!-- 参与人员 -->
<div v-if="detailData.persons && detailData.persons.length > 0" class="detail-section"> <div v-if="detailData.persons && detailData.persons.length > 0" class="detail-card">
<h3 class="section-title">参与人员</h3> <h3 class="card-title">参与人员</h3>
<div class="participant-list"> <div class="card-content">
<div v-for="(person, index) in detailData.persons" :key="person.id" class="participant-item"> <div v-for="(person, index) in detailData.persons" :key="person.id" class="info-row">
<span class="participant-name">{{ person.userName }}</span> <div class="info-item">
<span class="participant-team">{{ person.teamName }}</span> <label class="info-label">姓名:</label>
<span class="info-value">{{ person.userName }}</span>
</div>
<div class="info-item">
<label class="info-label">团队:</label>
<span class="info-value">{{ person.teamName }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 巡检项目 --> <!-- 巡检项目 -->
<div v-if="detailData.inspectionItemList && detailData.inspectionItemList.length > 0" class="detail-section"> <div v-if="detailData.inspectionItemList && detailData.inspectionItemList.length > 0" class="detail-card">
<h3 class="section-title">巡检项目</h3> <h3 class="card-title">巡检项目</h3>
<div class="inspection-list"> <div class="card-content">
<div v-for="(item, index) in detailData.inspectionItemList" :key="item.id" class="inspection-item"> <div v-for="(item, index) in detailData.inspectionItemList" :key="item.id" class="info-row">
<span class="inspection-name">{{ item.name }}</span> <div class="info-item">
<span class="inspection-type">{{ item.type }}</span> <label class="info-label">项目名称:</label>
<span class="info-value">{{ item.name }}</span>
</div>
<div class="info-item">
<label class="info-label">项目类型:</label>
<span class="info-value">{{ item.type }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="loading-details">
<el-skeleton :count="6" :columns="2" />
</div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="showDetailDialog = false">关闭</el-button> <el-button @click="showDetailDialog = false">关闭</el-button>
@ -584,8 +590,10 @@ const activeTab = ref('plan'); // 默认为"巡检计划"
const timeRange = ref('month'); // 统计时间范围:月/周/日 const timeRange = ref('month'); // 统计时间范围:月/周/日
// 2. 筛选条件 // 2. 筛选条件
const keyword = ref('');
const filterStatus = ref('all'); const filterStatus = ref('all');
const filterType = ref('all'); const filterType = ref('all');
const filterManager = ref('all');
const dateRange = ref([]); const dateRange = ref([]);
// 分页参数 // 分页参数
@ -603,8 +611,14 @@ const fetchExperimentData = async () => {
const queryParams = { const queryParams = {
projectId: 1, projectId: 1,
pageSize: pageSize.value, pageSize: pageSize.value,
pageNum: currentPage.value pageNum: currentPage.value,
// 其他参数... // 根据筛选条件构建查询参数
keyword: keyword.value || undefined,
testStatus: filterStatus.value === 'all' ? undefined : filterStatus.value,
testObject: filterType.value === 'all' ? undefined : filterType.value,
personCharge: filterManager.value === 'all' ? undefined : filterManager.value,
beginTime: dateRange.value.length > 0 ? dateRange.value[0] : undefined,
endTime: dateRange.value.length > 0 ? dateRange.value[1] : undefined
}; };
const response = await shiyanlist(queryParams); const response = await shiyanlist(queryParams);
@ -631,6 +645,23 @@ const fetchExperimentData = async () => {
} }
}; };
// 搜索与重置(当前数据主要来自接口,保留前端筛选入口)
const handleSearch = () => {
// 可根据项目需要将 keyword/filter 传给接口;当前保持页内刷新
currentPage.value = 1;
fetchExperimentData();
};
const resetFilters = () => {
keyword.value = '';
filterStatus.value = 'all';
filterType.value = 'all';
filterManager.value = 'all';
dateRange.value = [];
currentPage.value = 1;
fetchExperimentData();
};
// 辅助方法 // 辅助方法
const getTestObjectText = (type) => { const getTestObjectText = (type) => {
const typeMap = { const typeMap = {
@ -691,43 +722,34 @@ const recentRecords = ref([]);
// 9. 方法:切换顶部导航 // 9. 方法:切换顶部导航
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/shiyanrenwu'); router.push('/znxj/sygl/shiyanrenwu');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/shiyanjilu'); router.push('/znxj/sygl/shiyanjilu');
};
// 10. 方法:切换功能选项卡
const switchTab = (tab) => {
activeTab.value = tab;
// 实际应用中需根据选项卡加载对应数据
if (tab === 'record') {
// 加载统计数据
updateStatData(timeRange.value);
}
}; };
// 11. 方法:更新统计数据(根据时间范围) // 11. 方法:更新统计数据(根据时间范围)
@ -789,12 +811,6 @@ const getRecordStatusText = (status) => {
return statusMap[status] || ''; return statusMap[status] || '';
}; };
// 进度条颜色
const getProgressColor = (status) => {
const colorMap = { 'drafted': '#ccc', 'in-progress': '#3b82f6', 'completed': '#10b981', 'paused': '#9e9e9e' };
return colorMap[status] || '#ccc';
};
// 18. 新增实验记录弹窗相关 // 18. 新增实验记录弹窗相关
const showRecordDialog = ref(false); const showRecordDialog = ref(false);
const saveLoading = ref(false); // 保存加载状态 const saveLoading = ref(false); // 保存加载状态
@ -811,7 +827,11 @@ const formData = ref({
envRequirements: '', envRequirements: '',
manager: '', manager: '',
participants: [], // 改为数组存储多选的用户ID participants: [], // 改为数组存储多选的用户ID
steps: [{ content: '' }, { content: '' }, { content: '' }], steps: [
{ name: '', intendedPurpose: '', intendedTime: '' },
{ name: '', intendedPurpose: '', intendedTime: '' },
{ name: '', intendedPurpose: '', intendedTime: '' }
],
equipments: [ equipments: [
{ name: '服务器(型号:XYZ-9000)', selected: false }, { name: '服务器(型号:XYZ-9000)', selected: false },
{ name: '网络测试仪(型号:NT-5000)', selected: false }, { name: '网络测试仪(型号:NT-5000)', selected: false },
@ -839,20 +859,14 @@ const userList = ref([]);
const getUsersList = async () => { const getUsersList = async () => {
try { try {
const response = await xunjianUserlist(); const response = await xunjianUserlist();
const userRows = // 适配新接口格式检查code为200且rows为数组
response?.data?.rows && Array.isArray(response.data.rows) const userRows = response.code === 200 && response.rows && Array.isArray(response.rows) ? response.rows : [];
? response.data.rows
: response?.rows && Array.isArray(response.rows)
? response.rows
: Array.isArray(response)
? response
: [];
userList.value = userRows userList.value = userRows
.filter((item) => item && typeof item === 'object') .filter((item) => item && typeof item === 'object')
.map((item, index) => ({ .map((item) => ({
label: item.userName || `用户${index + 1}`, label: item.userName || '未知用户',
value: item.id || `id_${index}` value: String(item.userId || '') // 使用userId作为唯一标识
})); }));
if (userList.value.length === 0) { if (userList.value.length === 0) {
@ -914,10 +928,6 @@ const handleSave = async () => {
personIds: formData.value.participants.join(','), personIds: formData.value.participants.join(','),
inspectionItems: '', inspectionItems: '',
testSolutions: formData.value.riskMitigation, testSolutions: formData.value.riskMitigation,
testStep: formData.value.steps
.filter((step) => step.content.trim())
.map((step) => step.content)
.join(','),
testDevice: formData.value.equipments testDevice: formData.value.equipments
.filter((equip) => equip.selected) .filter((equip) => equip.selected)
.map((equip) => equip.name) .map((equip) => equip.name)
@ -927,8 +937,9 @@ const handleSave = async () => {
id: editRecordId.value // 若后端用planId等需改为对应字段名 id: editRecordId.value // 若后端用planId等需改为对应字段名
}; };
// 4. 调用接口 // 调用接口
let response; let response;
if (editRecordId.value) { if (editRecordId.value) {
// 编辑模式:调用更新接口 // 编辑模式:调用更新接口
response = await updateshiyan(requestData); response = await updateshiyan(requestData);
@ -965,7 +976,6 @@ const resetForm = () => {
envRequirements: '', // 环境要求为空 envRequirements: '', // 环境要求为空
manager: '', // 负责人为空 manager: '', // 负责人为空
participants: [], // 参与人员为空数组 participants: [], // 参与人员为空数组
steps: [{ content: '' }, { content: '' }, { content: '' }], // 步骤内容为空
equipments: [ equipments: [
{ name: '服务器(型号:XYZ-9000)', selected: false }, { name: '服务器(型号:XYZ-9000)', selected: false },
{ name: '网络测试仪(型号:NT-5000)', selected: false }, { name: '网络测试仪(型号:NT-5000)', selected: false },
@ -1041,24 +1051,6 @@ const handleEditRecord = async (row) => {
const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data; const recordDetail = detailResponse.data.rows?.[0] || detailResponse.data;
// 兼容两种数据结构可能在rows数组中也可能直接在data中 // 兼容两种数据结构可能在rows数组中也可能直接在data中
// 3. 处理testStep将逗号分隔的字符串转换为步骤数组
const steps = [];
if (recordDetail.testStep) {
// 拆分字符串(例如 "1. 213,2. 321" → ["1. 213", "2. 321"]
const stepItems = recordDetail.testStep.split(',');
stepItems.forEach((stepText) => {
// 移除序号前缀(如"1. "),只保留内容
const content = stepText.replace(/^\d+\.\s*/, '').trim();
if (content) {
steps.push({ content });
}
});
}
// 确保至少有3个步骤如果解析后为空
while (steps.length < 3) {
steps.push({ content: '' });
}
// 4. 处理testDevice将逗号分隔的字符串转换为设备数组 // 4. 处理testDevice将逗号分隔的字符串转换为设备数组
const equipments = []; const equipments = [];
if (recordDetail.testDevice) { if (recordDetail.testDevice) {
@ -1104,7 +1096,6 @@ const handleEditRecord = async (row) => {
envRequirements: recordDetail.envRequirements || recordDetail.testSetting || '', envRequirements: recordDetail.envRequirements || recordDetail.testSetting || '',
manager: recordDetail.manager || recordDetail.personCharge || '', manager: recordDetail.manager || recordDetail.personCharge || '',
participants: participants, // 从personIds解析的数组 participants: participants, // 从personIds解析的数组
steps: steps, // 解析后的步骤数组
equipments: equipments, // 解析并合并后的设备数组 equipments: equipments, // 解析并合并后的设备数组
riskMitigation: recordDetail.riskMitigation || recordDetail.testSolutions || '' riskMitigation: recordDetail.riskMitigation || recordDetail.testSolutions || ''
}; };
@ -1132,10 +1123,6 @@ const handleEditRecord = async (row) => {
loading.value = false; loading.value = false;
} }
}; };
// 添加新步骤
const addStep = () => {
formData.value.steps.push({ content: '' });
};
// 添加新设备 // 添加新设备
const addEquipment = () => { const addEquipment = () => {
@ -1234,49 +1221,16 @@ const formatDate = (dateString) => {
</script> </script>
<style scoped> <style scoped>
/* 1. 基础容器样式(继承试验系统) */ @import url('./css/detail-dialog.css');
.operation-inspection { .operation-inspection {
padding: 20px; padding: 20px;
background-color: #f9fbfd; background-color: #f9fbfd;
min-height: 100vh; min-height: 100vh;
} }
.navigation-tabs { /* 已注释的导航栏样式移除 */
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 2px;
}
.nav-tab { /* 3. 页面标题 */
padding: 12px 24px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px;
color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1;
text-align: center;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
}
.nav-tab.active {
background-color: #409eff;
color: #fff;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
/* 3. 页面标题(与试验系统一致) */
.page-header { .page-header {
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -1908,53 +1862,6 @@ const formatDate = (dateString) => {
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1); box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
} }
/* 试验步骤样式 */
.steps-container {
border: 1px solid #e4e7ed;
border-radius: 8px;
padding: 16px;
width: 100%;
}
.step-item {
display: flex;
align-items: center;
margin-bottom: 16px;
width: 100%;
}
.step-item:last-child {
margin-bottom: 0;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #165dff;
color: white;
font-size: 16px;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
}
.step-input:focus {
border-color: #165dff;
outline: none;
}
.add-step-btn {
color: #165dff;
margin-top: 12px;
width: 100%;
text-align: center;
font-size: 14px;
}
/* 设备列表样式 */ /* 设备列表样式 */
.equipment-list { .equipment-list {
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
@ -2013,7 +1920,7 @@ const formatDate = (dateString) => {
border-color: #0d47a1; border-color: #0d47a1;
} }
/* 响应式设计 */ /* 响应式设计 - 保留必要的覆盖样式 */
@media (max-width: 768px) { @media (max-width: 768px) {
.custom-experiment-dialog { .custom-experiment-dialog {
width: 90% !important; width: 90% !important;
@ -2033,222 +1940,13 @@ const formatDate = (dateString) => {
.new-equipment-input { .new-equipment-input {
width: 100%; width: 100%;
} }
}
/* 详情弹窗样式 */ .info-row {
.custom-experiment-dialog .el-dialog__body {
padding: 20px;
overflow: hidden;
}
.detail-content {
max-height: 600px;
overflow-y: auto;
padding-right: 8px;
}
/* 详情区块 */
.detail-section {
margin-bottom: 24px;
padding: 16px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background-color: #ffffff;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #1890ff;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e8f4ff;
}
/* 基础信息网格 */
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.detail-label {
font-size: 13px;
font-weight: 500;
color: #6c757d;
}
.detail-value {
font-size: 14px;
color: #2c3e50;
padding: 4px 0;
}
/* 文本区域 */
.detail-textarea {
margin-bottom: 16px;
}
.detail-text {
font-size: 14px;
color: #495057;
line-height: 1.6;
padding: 8px 0;
min-height: 60px;
white-space: pre-wrap;
word-break: break-word;
}
/* 设备列表样式 */
.device-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.device-tag {
display: inline-block;
padding: 6px 12px;
background-color: #f0f9ff;
color: #1890ff;
border: 1px solid #bae7ff;
border-radius: 16px;
font-size: 13px;
}
/* 步骤条样式 */
.steps-container {
padding-left: 8px;
}
.step-item {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
position: relative;
}
.step-item:last-child {
margin-bottom: 0;
}
.step-item:not(:last-child)::after {
content: '';
position: absolute;
left: 17px;
top: 36px;
bottom: -16px;
width: 2px;
background-color: #e4e7ed;
z-index: 1;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #1890ff;
color: white;
font-size: 14px;
font-weight: 600;
margin-right: 16px;
flex-shrink: 0;
z-index: 2;
}
.step-content {
flex: 1;
padding: 8px 16px;
background-color: #fafafa;
border-radius: 6px;
font-size: 14px;
color: #2c3e50;
line-height: 1.5;
}
/* 列表样式 */
.participant-list,
.inspection-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.participant-item,
.inspection-item {
display: flex;
align-items: center;
gap: 24px;
padding: 12px 16px;
background-color: #f8f9fa;
border-radius: 8px;
}
.participant-name,
.inspection-name {
font-size: 14px;
font-weight: 500;
color: #2c3e50;
min-width: 120px;
}
.participant-team,
.participant-role,
.inspection-type {
font-size: 13px;
color: #6c757d;
}
.participant-item,
.inspection-item {
display: flex;
align-items: center;
gap: 24px;
padding: 12px 16px;
background-color: #f8f9fa;
border-radius: 8px;
}
.participant-name,
.inspection-name {
font-size: 14px;
font-weight: 500;
color: #2c3e50;
min-width: 120px;
}
.participant-team,
.participant-role,
.inspection-type {
font-size: 13px;
color: #6c757d;
}
/* 详情弹窗响应式设计 */
@media (max-width: 768px) {
.detail-grid {
grid-template-columns: 1fr;
}
.participant-item,
.inspection-item {
flex-direction: column; flex-direction: column;
align-items: flex-start;
gap: 8px;
} }
.participant-name, .info-item {
.inspection-name { min-width: 100%;
min-width: auto;
} }
} }
</style> </style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="operation-inspection"> <div class="operation-inspection">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -8,11 +8,11 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<div class="header-container"> <div class="header-container">
<div class="header-actions"> <div class="header-actions">
<el-button type="primary" class="export-btn">筛选</el-button> <el-button type="primary" class="export-btn">筛选</el-button>
<el-button type="primary" class="create-btn">导出数据</el-button> <el-button type="primary" icon="UploadFilled" class="create-btn">导出数据</el-button>
</div> </div>
</div> </div>
@ -54,7 +54,7 @@
></el-date-picker> ></el-date-picker>
</div> </div>
<div class="filter-actions"> <div class="filter-actions">
<el-button type="primary" class="search-btn" @click="fetchDashboardData">搜索</el-button> <el-button type="primary" icon="Search" class="search-btn" @click="fetchDashboardData">搜索</el-button>
</div> </div>
</div> </div>
@ -127,14 +127,14 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">完成率</span> <span class="text-gray-600">巡检完成率</span>
<span class="font-medium text-gray-800">{{ completionRate }}%</span> <span class="font-medium text-gray-800">{{ completionRate }}%</span>
</div> </div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: completionRate + '%' }"></div> <div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: completionRate + '%' }"></div>
</div> </div>
</div> </div>
<div> <!-- <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">解决率</span> <span class="text-gray-600">解决率</span>
<span class="font-medium text-gray-800">{{ resolutionRate }}%</span> <span class="font-medium text-gray-800">{{ resolutionRate }}%</span>
@ -142,10 +142,10 @@
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-red-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: resolutionRate + '%' }"></div> <div class="bg-red-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: resolutionRate + '%' }"></div>
</div> </div>
</div> </div> -->
<div> <div>
<div class="flex justify-between text-sm mb-1"> <div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">及时</span> <span class="text-gray-600">问题解决效</span>
<span class="font-medium text-gray-800">{{ timelinessRate }}%</span> <span class="font-medium text-gray-800">{{ timelinessRate }}%</span>
</div> </div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
@ -161,65 +161,8 @@
<!-- 发现问题种类 --> <!-- 发现问题种类 -->
<div class="py-4"> <div class="py-4">
<h3 class="section-title">发现问题种类</h3> <h3 class="section-title">发现问题种类</h3>
<div class="space-y-4"> <!-- 柱状图容器 -->
<div> <div id="problemTypesChart" class="bar-chart-container"></div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">温度异常率</span>
<span class="text-gray-500">{{ problemTypes.temperature }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.temperature + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">内存使用率</span>
<span class="text-gray-500">{{ problemTypes.memory }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.memory + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">CPU负载</span>
<span class="text-gray-500">{{ problemTypes.cpu }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out" :style="{ width: problemTypes.cpu + '%' }"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">响应时间</span>
<span class="text-gray-500">{{ problemTypes.responseTime }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.responseTime + '%' }"
></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-600">磁盘空间状态</span>
<span class="text-gray-500">{{ problemTypes.diskSpace }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-1500 ease-out"
:style="{ width: problemTypes.diskSpace + '%' }"
></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -388,16 +331,17 @@ const avgCompletionTime = ref('45分钟');
// 问题类型数据 // 问题类型数据
const problemTypes = ref({ const problemTypes = ref({
temperature: 85, // 温度异常 temperature: 0, // 温度异常数量
memory: 62, // 内存使用率 memory: 0, // 内存使用率问题数量
cpu: 45, // CPU负载 cpu: 0, // CPU负载问题数量
responseTime: 30, // 响应时间 responseTime: 0, // 响应时间问题数量
diskSpace: 15 // 磁盘空间状态 diskSpace: 0 // 磁盘空间问题数量
}); });
// ECharts 图相关 // ECharts 图相关
const pieChartRef = ref(null); const pieChartRef = ref(null);
let pieChart = null; let pieChart = null;
let barChart = null;
// 计算平均完成度 // 计算平均完成度
const averageRate = computed(() => (completionRate.value + resolutionRate.value + timelinessRate.value) / 3); const averageRate = computed(() => (completionRate.value + resolutionRate.value + timelinessRate.value) / 3);
@ -426,7 +370,7 @@ const initPieChart = () => {
}, },
series: [ series: [
{ {
name: '进度指标', name: '指标对比',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['40%', '70%'],
avoidLabelOverlap: false, avoidLabelOverlap: false,
@ -442,20 +386,15 @@ const initPieChart = () => {
label: { label: {
show: true, show: true,
fontSize: 40, fontSize: 40,
fontWeight: 'bold', fontWeight: 'bold'
formatter: function (params) {
// 鼠标悬停时显示当前指标的百分比
return params.value + '%';
}
} }
}, },
labelLine: { labelLine: {
show: false show: false
}, },
data: [ data: [
{ value: completionRate.value, name: '完成率', itemStyle: { color: '#5470c6' } }, { value: completionRate.value, name: '巡检完成率', itemStyle: { color: '#409eff' } },
{ value: resolutionRate.value, name: '解决率', itemStyle: { color: '#f56c6c' } }, { value: timelinessRate.value, name: '问题解决率', itemStyle: { color: '#67c23a' } }
{ value: timelinessRate.value, name: '及时率', itemStyle: { color: '#67c23a' } }
] ]
} }
] ]
@ -495,22 +434,18 @@ const fetchDashboardData = async () => {
// 根据时间范围确定type参数1是月2是周3是日 // 根据时间范围确定type参数1是月2是周3是日
let type; let type;
if (timeRange.value === 'month') { if (timeRange.value === 'month') {
type = 1; type = 3;
} else if (timeRange.value === 'week') { } else if (timeRange.value === 'week') {
type = 2; type = 2;
} else { } else {
// day // day
type = 3; type = 1;
} }
// 构建查询参数 // 构建查询参数
const queryParams = { const queryParams = {
projectId: 1, projectId: 1,
type: type, type: type
status: filterStatus.value !== 'all' ? filterStatus.value : undefined,
inspectionType: filterType.value !== 'all' ? filterType.value : undefined,
startTime: dateRange.value.length > 0 ? dateRange.value[0] : undefined,
endTime: dateRange.value.length > 0 ? dateRange.value[1] : undefined
}; };
// 调用接口获取数据 // 调用接口获取数据
@ -526,22 +461,26 @@ const fetchDashboardData = async () => {
solvedProblems.value = data.solvedProblemCount || 0; solvedProblems.value = data.solvedProblemCount || 0;
avgCompletionTime.value = data.averageCompletionTime ? `${data.averageCompletionTime}分钟` : '0分钟'; avgCompletionTime.value = data.averageCompletionTime ? `${data.averageCompletionTime}分钟` : '0分钟';
// 计算完成率、解决率、及时率 // 使用接口返回的xjwcl(巡检完成率)和jjxl(解决效率)
completionRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 60) : 0; completionRate.value = data.xjwcl ? parseFloat(data.xjwcl) : 0;
resolutionRate.value = data.solvedProblemCount && data.problemCount ? Math.round((data.solvedProblemCount / data.problemCount) * 100) : 0; timelinessRate.value = data.jjxl ? parseFloat(data.jjxl) : 0;
timelinessRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 50) : 0;
// 更新问题类型数据 // 由于接口不再返回解决率将其设置为0或保持原值
resolutionRate.value = 0;
// 更新问题类型数据 - 直接使用接口返回的数值,不再计算为百分比
problemTypes.value = { problemTypes.value = {
temperature: data.sbyxzt ? Math.min(100, Math.round(data.sbyxzt * 5)) : 0, // 设备运行状态映射为温度异常 temperature: data.sbyxzt || 0, // 设备运行状态类型问题数量
memory: data.ncsyl ? Math.min(100, data.ncsyl * 10) : 0, // 内存使用率 memory: data.ncsyl || 0, // 内存使用率类型问题数量
cpu: Math.round(Math.random() * 50 + 20), // CPU负载模拟数据 cpu: data.fwzt || 0, // 服务状态类型问题数量
responseTime: data.xysj ? Math.min(100, data.xysj * 5) : 0, // 响应时间 responseTime: data.xysj || 0, // 响应时间类型问题数量
diskSpace: data.cpsyl ? Math.min(100, data.cpsyl * 8) : 0 // 磁盘使用率 diskSpace: data.cpsyl || 0 // 磁盘使用率类型问题数量
}; };
// 更新饼图 // 更新饼图
initPieChart(); initPieChart();
// 更新柱状图
initBarChart();
} else { } else {
ElMessage.error(response.msg || '获取数据失败'); ElMessage.error(response.msg || '获取数据失败');
} }
@ -551,49 +490,147 @@ const fetchDashboardData = async () => {
} }
}; };
// 页面加载时获取数据 // 页面加载时直接获取数据
onMounted(() => { onMounted(() => {
fetchDashboardData(); fetchDashboardData();
}); });
// 初始化柱状图
const initBarChart = () => {
const chartDom = document.getElementById('problemTypesChart');
if (!chartDom) return;
// 销毁旧实例
if (barChart) {
barChart.dispose();
}
// 创建新实例
barChart = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
return params[0].name + ': ' + params[0].value + '个';
}
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'value',
name: '问题数量',
axisLabel: {
formatter: '{value}个'
},
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
yAxis: {
type: 'category',
data: ['温度异常', '内存使用率', 'CPU负载', '响应时间', '磁盘空间'],
axisLabel: {
interval: 0
}
},
series: [
{
name: '问题数量',
type: 'bar',
barWidth: '40%',
data: [
problemTypes.value.temperature,
problemTypes.value.memory,
problemTypes.value.cpu,
problemTypes.value.responseTime,
problemTypes.value.diskSpace
],
itemStyle: {
color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{ offset: 0, color: '#5470c6' },
{ offset: 1, color: '#91cc75' }
]),
borderRadius: [0, 4, 4, 0]
},
label: {
show: true,
position: 'right',
formatter: '{c}个'
}
}
]
};
barChart.setOption(option);
// 响应式处理
const handleResize = () => {
if (barChart) {
barChart.resize();
}
};
window.addEventListener('resize', handleResize);
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
};
// 组件卸载时销毁图表实例 // 组件卸载时销毁图表实例
onUnmounted(() => { onUnmounted(() => {
if (pieChart) { if (pieChart) {
pieChart.dispose(); pieChart.dispose();
pieChart = null; pieChart = null;
} }
if (barChart) {
barChart.dispose();
barChart = null;
}
}); });
// 导航方法 // 导航方法
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/xunjianrenwu'); router.push('/znxj/xjgl/xunjianrenwu');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/xunjianjihua'); router.push('/znxj/xjgl/xunjianjihua');
}; };
</script> </script>
@ -617,46 +654,7 @@ const handleInspectionManagement3 = () => {
} }
/* 导航栏样式 */ /* 导航栏样式 */
.navigation-tabs { /* 已移除未使用的导航样式(模板中为注释状态) */
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 2px;
}
.nav-tab {
padding: 12px 24px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px;
color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1;
text-align: center;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
}
.nav-tab.active {
background-color: #409eff;
color: #fff;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
.nav-tab {
cursor: pointer;
user-select: none;
}
/* 选项卡样式 */ /* 选项卡样式 */
.tabs-wrapper { .tabs-wrapper {
@ -802,6 +800,17 @@ const handleInspectionManagement3 = () => {
margin: 0 auto; margin: 0 auto;
} }
/* 柱状图容器 */
.bar-chart-container {
width: 100%;
height: 350px;
margin: 0 auto;
background-color: #fafafa;
border-radius: 8px;
padding: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
/* 区域标题 */ /* 区域标题 */
.section-title { .section-title {
font-size: 14px; font-size: 14px;

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="inspection-tasks"> <div class="inspection-tasks">
<div class="navigation-tabs"> <!-- <div class="navigation-tabs">
<div class="nav-tab" @click="handleInspection1">待办事项</div> <div class="nav-tab" @click="handleInspection1">待办事项</div>
<div class="nav-tab active" @click="handleInspection2">巡检管理</div> <div class="nav-tab active" @click="handleInspection2">巡检管理</div>
<div class="nav-tab" @click="handleInspection3">试验管理</div> <div class="nav-tab" @click="handleInspection3">试验管理</div>
@ -9,7 +9,7 @@
<div class="nav-tab" @click="handleInspection5">抢修管理</div> <div class="nav-tab" @click="handleInspection5">抢修管理</div>
<div class="nav-tab" @click="handleInspection6">工单管理</div> <div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div> <div class="nav-tab" @click="handleInspection7">运维组织</div>
</div> </div> -->
<!-- 选项卡 --> <!-- 选项卡 -->
<div class="tabs-wrapper"> <div class="tabs-wrapper">
@ -23,27 +23,26 @@
<!-- 筛选栏 --> <!-- 筛选栏 -->
<div class="filter-bar"> <div class="filter-bar">
<div class="filter-container"> <div class="filter-container">
<div class="filter-item">
<el-input v-model="keyword" placeholder="关键字(任务名/对象/执行人)" clearable @keyup.enter="handleSearch" />
</div>
<div class="filter-item"> <div class="filter-item">
<el-select v-model="taskStatus" placeholder="任务状态"> <el-select v-model="taskStatus" placeholder="任务状态">
<el-option label="待执行" value="pending"></el-option> <el-option label="待处理" value="1"></el-option>
<el-option label="执行中" value="executing"></el-option> <el-option label="处理中" value="3"></el-option>
<el-option label="已延期" value="delayed"></el-option> <el-option label="已完成" value="4"></el-option>
<el-option label="已完成" value="completed"></el-option> <el-option label="已延期" value="2"></el-option>
</el-select> </el-select>
</div> </div>
<div class="filter-item"> <div class="filter-item">
<el-select v-model="planType" placeholder="全部计划"> <el-select v-model="executor" placeholder="执行人" :disabled="loadingUsers">
<el-option label="每日巡检计划" value="daily"></el-option> <el-option v-for="user in usersList" :key="user.id" :label="user.name" :value="user.id" />
<el-option label="每周巡检计划" value="weekly"></el-option>
<el-option label="每月巡检计划" value="monthly"></el-option>
</el-select> </el-select>
</div> </div>
<div class="filter-item">
<el-input v-model="executor" placeholder="执行人"></el-input>
</div>
<div class="filter-actions"> <div class="filter-actions">
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button> <el-button type="primary" icon="Search" class="search-btn" @click="handleSearch"> 搜索 </el-button>
<el-button type="primary" icon="el-icon-plus" class="create-btn" @click="handleCreateTask"> 手动创建任务 </el-button> <el-button icon="Refresh" class="create-btn" @click="resetFilters"> 重置 </el-button>
<el-button type="primary" icon="Plus" class="create-btn" @click="handleCreateTask"> 手动创建任务 </el-button>
</div> </div>
</div> </div>
</div> </div>
@ -133,7 +132,7 @@
</div> </div>
<!-- 添加新任务弹窗 --> <!-- 添加新任务弹窗 -->
<el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="500px" :before-close="handleCancelCreateTask"> <el-dialog v-model="createTaskDialogVisible" title="添加新任务" width="750px" :before-close="handleCancelCreateTask">
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px"> <el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="80px">
<el-form-item label="任务名称" prop="taskName"> <el-form-item label="任务名称" prop="taskName">
<el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" /> <el-input v-model="createTaskForm.taskName" placeholder="输入任务名称" />
@ -202,7 +201,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="问题类型" prop="problemType"> <!-- <el-form-item label="问题类型" prop="problemType">
<el-select v-model="createTaskForm.problemType" placeholder="选择问题类型"> <el-select v-model="createTaskForm.problemType" placeholder="选择问题类型">
<el-option label="磁盘使用率" value="1" /> <el-option label="磁盘使用率" value="1" />
<el-option label="内存使用率" value="2" /> <el-option label="内存使用率" value="2" />
@ -210,6 +209,27 @@
<el-option label="响应时间" value="4" /> <el-option label="响应时间" value="4" />
<el-option label="设备运行状态" value="5" /> <el-option label="设备运行状态" value="5" />
</el-select> </el-select>
</el-form-item> -->
<!-- 步骤条 -->
<el-form-item label="执行步骤" class="form-item" style="width: 100%">
<div class="steps-container">
<div class="step-item" v-for="(step, index) in createTaskForm.steps" :key="index">
<div class="step-number">{{ index + 1 }}</div>
<el-input v-model="step.name" placeholder="输入步骤名称" style="flex: 1; margin-right: 10px" />
<el-input v-model="step.intendedPurpose" placeholder="输入预期目的" style="flex: 1; margin-right: 10px" />
<el-date-picker
v-model="step.intendedTime"
type="datetime"
placeholder="选择计划时间"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
style="width: 180px; margin-right: 10px"
/>
<el-button v-if="createTaskForm.steps.length > 1" type="text" @click="removeStep(index)" style="color: #f56c6c"> 删除 </el-button>
</div>
<el-button type="text" class="add-step-btn" @click="addStep">添加步骤</el-button>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -304,7 +324,7 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="info-label">联系电话</span> <span class="info-label">联系电话</span>
<span class="info-value">{{ detailData.person?.phone || '-' }}</span> <span class="info-value">{{ detailData.person?.phonenumber || '-' }}</span>
</div> </div>
</div> </div>
<div class="info-row"> <div class="info-row">
@ -312,10 +332,6 @@
<span class="info-label">性别</span> <span class="info-label">性别</span>
<span class="info-value">{{ detailData.person?.sex === '1' ? '男' : detailData.person?.sex === '2' ? '女' : '-' }}</span> <span class="info-value">{{ detailData.person?.sex === '1' ? '男' : detailData.person?.sex === '2' ? '女' : '-' }}</span>
</div> </div>
<div class="info-item">
<span class="info-label">民族</span>
<span class="info-value">{{ detailData.person?.nation || '-' }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -356,6 +372,26 @@
</div> </div>
</div> </div>
<!-- 执行步骤信息卡片 -->
<div v-if="detailData.nodes && detailData.nodes.length > 0" class="detail-card">
<h3 class="card-title">执行步骤</h3>
<div class="steps-container">
<div v-for="(node, index) in detailData.nodes" :key="node.id || index" class="step-item">
<div class="step-number">{{ node.code || index + 1 }}</div>
<div class="step-info">
<div class="step-name">{{ node.name || '未命名步骤' }}</div>
<div class="step-purpose">{{ node.intendedPurpose || '无说明' }}</div>
<div class="step-time">计划时间{{ formatDateTime(node.intendedTime) }}</div>
<div v-if="node.finishTime" class="step-finish-time">完成时间{{ formatDateTime(node.finishTime) }}</div>
<div v-if="node.remark" class="step-remark">备注{{ node.remark }}</div>
</div>
<div class="step-status" :class="getStatusClass(node.status)">
{{ node.status === '2' ? '未执行' : node.status === '3' ? '失败' : '已完成' }}
</div>
</div>
</div>
</div>
<!-- 执行结果信息卡片 --> <!-- 执行结果信息卡片 -->
<div v-if="detailData.taskType === '2' || detailData.taskType === 2" class="detail-card"> <div v-if="detailData.taskType === '2' || detailData.taskType === 2" class="detail-card">
<h3 class="card-title">延期信息</h3> <h3 class="card-title">延期信息</h3>
@ -388,47 +424,26 @@
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import router from '@/router'; import router from '@/router';
import { xjrenwuDetail, xjrenwuExport, xjrenwulist, addxjrenwu, updatexjrenwu, delxjrenwu } from '@/api/zhinengxunjian/xunjian/renwu'; import { xjrenwuDetail, xjrenwulist, addxjrenwu, updatexjrenwu } from '@/api/zhinengxunjian/xunjian/renwu';
import { xunjianUserlist, xunjianlist } from '@/api/zhinengxunjian/xunjian/index'; import { xunjianUserlist, xunjianlist } from '@/api/zhinengxunjian/xunjian/index';
import { ElMessage, ElLoading } from 'element-plus'; import { addjiedian } from '@/api/zhinengxunjian/jiedian/index';
import { ElMessage, ElLoading, ElForm } from 'element-plus';
// 根据任务类型获取对应的文本1待执行2已延期3执行中4已完成 import { formatDate } from '@/utils/index';
const getTaskTypeText = (type) => {
const typeMap = {
'1': '待执行',
'2': '已延期',
'3': '执行中',
'4': '已完成'
};
// 处理可能的数字输入
return typeMap[type.toString()] || '未知类型';
};
// 根据问题类型获取对应的文本1磁盘使用率2内存使用率3服务状态4响应时间5设备运行状态
const getProblemTypeText = (type) => {
const problemTypeMap = {
'1': '磁盘使用率',
'2': '内存使用率',
'3': '服务状态',
'4': '响应时间',
'5': '设备运行状态'
};
// 处理可能的数字输入
return problemTypeMap[type.toString()] || '未知问题';
};
// 激活的选项卡
const activeTab = ref('task');
// 筛选条件 // 筛选条件
const taskStatus = ref(''); const taskStatus = ref('');
const planType = ref(''); const planType = ref('');
const executor = ref(''); const executor = ref('');
const keyword = ref('');
// 执行人列表相关
const usersList = ref([]);
const loadingUsers = ref(false);
// 任务数据 - 初始为空数组通过API获取 // 任务数据 - 初始为空数组通过API获取
const tasks = ref([]); const tasks = ref([]);
// 详情弹窗相关变量 // 任务详情弹窗相关变量
const detailDialogVisible = ref(false); const detailDialogVisible = ref(false);
const detailData = ref(null); const detailData = ref(null);
const isDetailLoading = ref(false); const isDetailLoading = ref(false);
@ -452,13 +467,41 @@ const getStatusClass = (status) => {
const statusStr = status?.toString() || ''; const statusStr = status?.toString() || '';
const statusClassMap = { const statusClassMap = {
'1': 'status-pending', '1': 'status-pending',
'2': 'status-delayed', '2': 'status-unknown', // 未完成状态显示为灰色
'3': 'status-executing', '3': 'status-failed', // 失败状态显示为红色
'4': 'status-completed' '4': 'status-completed' // 已完成状态显示为绿色
}; };
return statusClassMap[statusStr] || 'status-unknown'; return statusClassMap[statusStr] || 'status-unknown';
}; };
// 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
try {
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
} catch (error) {
return dateTime;
}
};
// 获取步骤状态文本
const getStepStatusText = (status) => {
const statusStr = status?.toString() || '';
const statusMap = {
'1': '待执行',
'2': '执行中',
'3': '已完成',
'4': '已延期'
};
return statusMap[statusStr] || '未知状态';
};
// 状态映射配置 // 状态映射配置
const statusConfig = { const statusConfig = {
pending: { pending: {
@ -505,17 +548,17 @@ const getTaskList = async () => {
const params = { const params = {
pageSize: pageSize.value, pageSize: pageSize.value,
pageNum: currentPage.value, pageNum: currentPage.value,
personId: executor.value !== '' ? executor.value : undefined, projectId: 1,
// 根据任务状态映射到后端需要的taskType status: taskStatus.value || undefined,
taskType: taskStatus.value ? mapTaskStatusToType(taskStatus.value) : undefined, planType: planType.value || undefined,
// 添加计划类型筛选 personId: executor.value || undefined,
planType: planType.value || undefined keyword: keyword.value.trim() || undefined
}; };
const response = await xjrenwulist(params); const response = await xjrenwulist(params);
if (response.code === 200 && response.rows) { if (response.code === 200 && response.rows) {
tasks.value = response.rows.map((item) => { const mapped = response.rows.map((item) => {
// 获取原始数据中的id // 获取原始数据中的id
const taskId = item.id || ''; const taskId = item.id || '';
if (!taskId) { if (!taskId) {
@ -566,13 +609,16 @@ const getTaskList = async () => {
return task; return task;
}); });
const kw = keyword.value.trim();
total.value = response.total || tasks.value.length; const filtered = kw
? mapped.filter((t) =>
// 搜索后如果没有结果,显示提示信息 [t.title, t.target, t.executor, t.relatedPlan, t.statusText]
if (tasks.value.length === 0) { .filter(Boolean)
ElMessage.info('未找到符合条件的任务'); .some((v) => String(v).toLowerCase().includes(kw.toLowerCase()))
} )
: mapped;
tasks.value = filtered;
total.value = kw ? filtered.length : response.total || filtered.length;
} }
} catch (error) { } catch (error) {
console.error('获取巡检任务数据失败:', error); console.error('获取巡检任务数据失败:', error);
@ -583,47 +629,10 @@ const getTaskList = async () => {
} }
}; };
// 辅助函数将前端状态映射为后端需要的taskType
const mapTaskStatusToType = (status) => {
const statusMap = {
'pending': '1',
'delayed': '2',
'executing': '3',
'completed': '4'
};
return statusMap[status] || '';
};
// 根据person对象获取执行人姓名
const getExecutorName = (person) => {
if (person && typeof person === 'object' && person.userName) {
return person.userName;
}
const executorMap = {
'zhangming': '张明',
'lihua': '李华',
'wangqiang': '王强',
'zhaowei': '赵伟'
};
return executorMap[person] || '未知用户';
};
// 根据plan对象获取计划名称
const getPlanName = (plan) => {
if (plan && typeof plan === 'object' && plan.planName) {
return plan.planName;
}
const planMap = {
'daily': '每日巡检计划',
'weekly': '每周巡检计划',
'monthly': '每月巡检计划'
};
return planMap[plan] || '未知计划';
};
// 页面加载时获取数据 // 页面加载时获取数据
onMounted(() => { onMounted(() => {
getTaskList(); getTaskList();
getUsersList();
}); });
// 分页相关 // 分页相关
@ -675,10 +684,11 @@ const createTaskForm = ref({
timeRange: [], timeRange: [],
workTimeRange1: null, workTimeRange1: null,
workTimeRange2: null, workTimeRange2: null,
relatedPlan: 'all', relatedPlan: '',
executor: '', executor: '',
taskType: '1', // 默认待执行 taskType: '1', // 默认待执行
problemType: '' // problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }] // 任务步骤数组
}); });
const createTaskRules = { const createTaskRules = {
@ -694,6 +704,17 @@ const handleCreateTask = () => {
openCreateTaskDialog(); openCreateTaskDialog();
}; };
// 重置步骤表单
const resetStepForm = () => {
Object.keys(stepForm).forEach((key) => {
stepForm[key] = '';
});
currentStep.value = 0;
if (stepFormRef.value) stepFormRef.value.resetFields();
if (deviceFormRef.value) deviceFormRef.value.resetFields();
if (faultFormRef.value) faultFormRef.value.resetFields();
};
// 构建timeInfo字符串 // 构建timeInfo字符串
const getTaskTimeInfoString = () => { const getTaskTimeInfoString = () => {
const timeInfoArray = []; const timeInfoArray = [];
@ -719,6 +740,21 @@ const getTaskTimeInfoString = () => {
return timeInfoArray.join(','); return timeInfoArray.join(',');
}; };
// 添加步骤
const addStep = () => {
createTaskForm.value.steps.push({ name: '', intendedPurpose: '', intendedTime: '' });
};
// 删除步骤
const removeStep = (index) => {
// 确保至少保留一个步骤
if (createTaskForm.value.steps.length <= 1) {
ElMessage.warning('至少需要保留一个步骤');
return;
}
createTaskForm.value.steps.splice(index, 1);
};
// 保存任务 // 保存任务
const handleSaveTask = async () => { const handleSaveTask = async () => {
// 表单验证 // 表单验证
@ -727,6 +763,13 @@ const handleSaveTask = async () => {
return; return;
} }
// 验证所有步骤
const hasEmptyStep = createTaskForm.value.steps.some((step) => !step.name.trim() || !step.intendedPurpose.trim());
if (hasEmptyStep) {
ElMessage.warning('请填写完整所有步骤信息');
return;
}
try { try {
// 获取timeInfo字符串 // 获取timeInfo字符串
const taskTimeInfo = getTaskTimeInfoString(); const taskTimeInfo = getTaskTimeInfoString();
@ -736,13 +779,47 @@ const handleSaveTask = async () => {
return; return;
} }
// 准备步骤数据,与工单列表页面保持一致的格式
const stepsData = createTaskForm.value.steps
.filter((step) => step.name.trim() && step.intendedPurpose.trim())
.map((step, index) => ({
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
params: {},
module: 2,
code: index + 1,
name: step.name,
intendedPurpose: step.intendedPurpose,
intendedTime: step.intendedTime ? new Date(step.intendedTime).toISOString() : new Date().toISOString(),
finishTime: '',
remark: '',
status: 2
}));
// 调用添加节点接口,直接传递步骤数组
const jiedianResponse = await addjiedian(stepsData);
if (jiedianResponse.code !== 200) {
ElMessage.error('创建步骤失败');
return;
}
// 获取返回的ids实际返回格式中msg字段包含ids字符串data为null
let nodeIds = '';
if (jiedianResponse.code === 200 && jiedianResponse.msg) {
nodeIds = jiedianResponse.msg;
} else {
ElMessage.warning('未获取到有效的步骤ID');
return;
}
// 构建接口所需的数据结构 // 构建接口所需的数据结构
const apiData = { const apiData = {
createDept: 0, projectId: 1,
createBy: 0,
createTime: new Date().toISOString(), createTime: new Date().toISOString(),
updateBy: 0,
updateTime: new Date().toISOString(), updateTime: new Date().toISOString(),
startTime: formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss'),
params: { params: {
property1: 'string', property1: 'string',
property2: 'string' property2: 'string'
@ -757,7 +834,8 @@ const handleSaveTask = async () => {
personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0, personId: createTaskForm.value.executor !== 'all' ? createTaskForm.value.executor : 0,
taskProgress: 0, taskProgress: 0,
taskType: createTaskForm.value.taskType, taskType: createTaskForm.value.taskType,
problemType: createTaskForm.value.problemType // problemType: createTaskForm.value.problemType,
nodeIds: nodeIds // 添加步骤ID字符串与工单列表页面保持一致
}; };
// 调用新增任务接口 // 调用新增任务接口
@ -774,10 +852,11 @@ const handleSaveTask = async () => {
timeRange: [], timeRange: [],
workTimeRange1: null, workTimeRange1: null,
workTimeRange2: null, workTimeRange2: null,
relatedPlan: 'all', relatedPlan: '',
executor: '', executor: '',
taskType: '1', taskType: '1',
problemType: '' // problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
}; };
// 重新获取任务列表 // 重新获取任务列表
getTaskList(); getTaskList();
@ -805,31 +884,41 @@ const planList = ref([]);
// 获取负责人列表 // 获取负责人列表
const getUsersList = async () => { const getUsersList = async () => {
loadingUsers.value = true;
try { try {
const response = await xunjianUserlist(); const response = await xunjianUserlist();
// 适配新接口格式检查code为200且rows为数组
const userRows = response.code === 200 && response.rows && Array.isArray(response.rows) ? response.rows : [];
const userRows = // 更新userList变量用于创建任务弹窗
response?.data?.rows && Array.isArray(response.data.rows)
? response.data.rows
: response?.rows && Array.isArray(response.rows)
? response.rows
: Array.isArray(response)
? response
: [];
userList.value = userRows userList.value = userRows
.filter((item) => item && typeof item === 'object') .filter((item) => item && typeof item === 'object')
.map((item, index) => ({ .map((item) => ({
label: item.userName || `用户${index + 1}`, label: item.userName || '未知用户',
value: item.id || `id_${index}` value: String(item.userId || '') // 使用userId作为唯一标识
})); }));
// 同时更新usersList变量用于筛选栏
usersList.value = userRows
.filter((item) => item && typeof item === 'object')
.map((item) => ({
id: String(item.userId || ''),
name: item.userName || '未知用户'
}));
// 空数据处理
if (userList.value.length === 0) { if (userList.value.length === 0) {
userList.value = [{ label: '默认用户', value: 'default' }]; userList.value = [{ label: '默认用户', value: 'default' }];
} }
if (usersList.value.length === 0) {
usersList.value = [{ id: 'default', name: '默认用户' }];
}
} catch (error) { } catch (error) {
console.error('获取负责人列表失败:', error); console.error('获取负责人列表失败:', error);
userList.value = [{ label: '默认用户', value: 'default' }]; userList.value = [{ label: '默认用户', value: 'default' }];
usersList.value = [{ id: 'default', name: '默认用户' }];
} finally {
loadingUsers.value = false;
} }
}; };
@ -853,11 +942,8 @@ const getPlansList = async () => {
label: item.planName || `计划${item.id}`, label: item.planName || `计划${item.id}`,
value: item.id.toString() value: item.id.toString()
})); }));
planList.value.unshift({ label: '全部计划', value: 'all' });
} catch (error) { } catch (error) {
console.error('获取计划列表失败:', error); console.error('获取计划列表失败:', error);
planList.value = [{ label: '全部计划', value: 'all' }];
} }
}; };
@ -875,8 +961,13 @@ const handleCancelCreateTask = () => {
taskName: '', taskName: '',
inspectionTarget: '', inspectionTarget: '',
timeRange: [], timeRange: [],
relatedPlan: 'all', workTimeRange1: null,
executor: 'all' workTimeRange2: null,
relatedPlan: '',
executor: '',
taskType: '1',
// problemType: '',
steps: [{ name: '', intendedPurpose: '', intendedTime: '' }]
}; };
}; };
@ -928,35 +1019,35 @@ const handleCloseDetailDialog = () => {
}; };
const handleInspectionManagement1 = () => { const handleInspectionManagement1 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspectionManagement2 = () => { const handleInspectionManagement2 = () => {
router.push('/rili/xunjianrenwu'); router.push('/znxj/xjgl/xunjianrenwu');
}; };
const handleInspectionManagement3 = () => { const handleInspectionManagement3 = () => {
router.push('/rili/xunjianjihua'); router.push('/znxj/xjgl/xunjianjihua');
}; };
const handleInspection1 = () => { const handleInspection1 = () => {
router.push('/rili/rili'); router.push('/znxj/rili');
}; };
const handleInspection2 = () => { const handleInspection2 = () => {
router.push('/rili/InspectionManagement'); router.push('/znxj/xjgl/InspectionManagement');
}; };
const handleInspection3 = () => { const handleInspection3 = () => {
router.push('/rili/shiyanguanli'); router.push('/znxj/sygl/shiyanguanli');
}; };
const handleInspection4 = () => { const handleInspection4 = () => {
router.push('/rili/baoxiuguanli'); router.push('/znxj/bxgl/baoxiuguanli');
}; };
const handleInspection5 = () => { const handleInspection5 = () => {
router.push('/rili/qiangxiuguanli'); router.push('/znxj/qxgl/qiangxiuguanli');
}; };
const handleInspection6 = () => { const handleInspection6 = () => {
router.push('/rili/gongdanliebiao'); router.push('/znxj/gdgl/gongdanliebiao');
}; };
const handleInspection7 = () => { const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai'); router.push('/znxj/ywzz/renyuanzhuangtai');
}; };
// 处理任务操作按钮点击事件 // 处理任务操作按钮点击事件
@ -981,6 +1072,7 @@ const handleAction = async (task) => {
const updateData = { const updateData = {
...originalTask.rawData, ...originalTask.rawData,
id: task.id, id: task.id,
startTime: formatDate(new Date().toString()),
taskType: '3', // 3表示执行中 taskType: '3', // 3表示执行中
status: 'executing', status: 'executing',
taskProgress: 0 taskProgress: 0
@ -1007,14 +1099,7 @@ const handleAction = async (task) => {
const originalTask = tasks.value[taskIndex]; const originalTask = tasks.value[taskIndex];
const now = new Date(); const finishTime = formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const finishTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
const updateData = { const updateData = {
...originalTask.rawData, ...originalTask.rawData,
@ -1046,6 +1131,9 @@ const handleAction = async (task) => {
</script> </script>
<style scoped> <style scoped>
@import url('./css/step-bars.css');
@import url('./css/detail-dialog.css');
.inspection-tasks { .inspection-tasks {
padding: 20px; padding: 20px;
background-color: #f5f7fa; background-color: #f5f7fa;
@ -1395,76 +1483,6 @@ const handleAction = async (task) => {
} }
} }
/* 任务详情弹窗样式 */
.task-detail-container {
max-height: 600px;
overflow-y: auto;
}
.detail-card {
margin-bottom: 20px;
padding: 20px;
background-color: #fafafa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.card-title {
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 2px solid #409eff;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.info-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.info-item {
flex: 1;
min-width: 280px;
}
.info-item.full-width {
min-width: 100%;
}
.info-label {
display: inline-block;
width: 100px;
color: #606266;
font-weight: 500;
}
.info-value {
color: #303133;
word-break: break-word;
}
.fail-reason {
color: #f56c6c;
}
.no-info {
color: #909399;
font-style: italic;
padding: 10px 0;
}
.loading-details {
padding: 20px 0;
}
/* 状态颜色样式 */ /* 状态颜色样式 */
.status-pending { .status-pending {
color: #e6a23c; color: #e6a23c;
@ -1580,4 +1598,11 @@ const handleAction = async (task) => {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
} }
.step-content {
padding: 30px 20px;
background-color: #fafafa;
border-radius: 8px;
margin-top: 20px;
}
</style> </style>

File diff suppressed because it is too large Load Diff