This commit is contained in:
re-JZzzz
2025-09-22 16:16:30 +08:00
56 changed files with 8472 additions and 1734 deletions

View File

@ -0,0 +1,49 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const baoxiulist = (query) => {
return request({
url: '/ops/report/list',
method: 'get',
params: query
});
};
//新增待办事项
export const addbaoxiu = (data) => {
return request({
url: '/ops/report',
method: 'post',
data: data
});
};
//修改待办事项
export const updatebaoxiu = (data) => {
return request({
url: '/ops/report',
method: 'put',
data: data
});
};
//删除待办事项
export function delbaoxiu(ids) {
return request({
url: `/ops/report/${ids}`, // 拼接ids作为路径参数
method: 'delete'
});
}
export const baoxiuDetail = (id) => {
return request({
url: `/ops/report/${id}`,
method: 'get'
});
};
export const uploadbaoxiu = (data) => {
return request({
url: '/resource/oss/upload',
method: 'post',
data: data
});
};

View File

@ -0,0 +1,34 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const daibanlist = (query) => {
return request({
url: '/ops/matter/list',
method: 'get',
params: query
});
};
//新增待办事项
export const adddaiban = (data) => {
return request({
url: '/ops/matter',
method: 'post',
data: data
});
};
//修改待办事项
export const updatedaiban = (data) => {
return request({
url: '/ops/matter',
method: 'put',
data: data
});
};
//删除待办事项
export function deldaiban(ids) {
return request({
url: `/ops/matter/${ids}`, // 拼接ids作为路径参数
method: 'delete'
});
}

View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ItemVO, ItemForm, ItemQuery } from '@/api/zhinengxunjian/inspection/item/types';
/**
* 查询运维-巡检-自定义巡检项列表
* @param query
* @returns {*}
*/
export const listItem = (query?: ItemQuery): AxiosPromise<ItemVO[]> => {
return request({
url: '/ops/item/list',
method: 'get',
params: query
});
};
/**
* 查询运维-巡检-自定义巡检项详细
* @param id
*/
export const getItem = (id: string | number): AxiosPromise<ItemVO> => {
return request({
url: '/ops/item/' + id,
method: 'get'
});
};
/**
* 新增运维-巡检-自定义巡检项
* @param data
*/
export const addItem = (data: ItemForm) => {
return request({
url: '/ops/item',
method: 'post',
data: data
});
};
/**
* 修改运维-巡检-自定义巡检项
* @param data
*/
export const updateItem = (data: ItemForm) => {
return request({
url: '/ops/item',
method: 'put',
data: data
});
};
/**
* 删除运维-巡检-自定义巡检项
* @param id
*/
export const delItem = (id: string | number | Array<string | number>) => {
return request({
url: '/ops/item/' + id,
method: 'delete'
});
};

View File

@ -0,0 +1,46 @@
export interface ItemVO {
/**
*
*/
id: string | number;
/**
* 自定义巡检项名称
*/
name: string;
/**
* 业务id巡检计划1
*/
type: string;
}
export interface ItemForm extends BaseEntity {
/**
* 自定义巡检项名称
*/
name?: string;
/**
* 业务id巡检计划1
*/
type?: string;
projectId?: number;
}
export interface ItemQuery extends PageQuery {
/**
* 自定义巡检项名称
*/
name?: string;
/**
* 业务id巡检计划1
*/
type?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -0,0 +1,49 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const shiyanlist = (query) => {
return request({
url: '/ops/testPlan/list',
method: 'get',
params: query
});
};
//新增
export const addshiyan = (data) => {
return request({
url: '/ops/testPlan',
method: 'post',
data: data
});
};
//修改
export const updateshiyan = (data) => {
return request({
url: '/ops/testPlan',
method: 'put',
data: data
});
};
//删除
export const delshiyan = (ids) => {
return request({
url: `/ops/testPlan${ids}`,
method: 'delete'
});
};
//查询人员
export const shiyanUserlist = (query) => {
return request({
url: '/ops/constructionUser/list',
method: 'get',
params: query
});
};
//详情
export const shiyanDetail = (id) => {
return request({
url: `/ops/testPlan/${id}`,
method: 'get'
});
};

View File

@ -0,0 +1,41 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const syrenwulist = (query) => {
return request({
url: '/ops/testTask/list',
method: 'get',
params: query
});
};
//新增
export const addsyrenwu = (data) => {
return request({
url: '/ops/testTask',
method: 'post',
data: data
});
};
//修改
export const updatesyrenwu = (data) => {
return request({
url: '/ops/testTask',
method: 'put',
data: data
});
};
//删除
export const delsyrenwu = (ids) => {
return request({
url: `/ops/testTask${ids}`,
method: 'delete'
});
};
//详情
export const syrenwuDetail = (id) => {
return request({
url: `/ops/testTask/${id}`,
method: 'get'
});
};

View File

@ -0,0 +1,56 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const xunjianlist = (query) => {
return request({
url: '/ops/plan/list',
method: 'get',
params: query
});
};
//新增
export const addxunjian = (data) => {
return request({
url: '/ops/plan',
method: 'post',
data: data
});
};
//修改
export const updatexunjian = (data) => {
return request({
url: '/ops/plan',
method: 'put',
data: data
});
};
//删除
export const delxunjian = (ids) => {
return request({
url: `/ops/plan/${ids}`,
method: 'delete'
});
};
//查询人员
export const xunjianUserlist = (query) => {
return request({
url: '/ops/constructionUser/list',
method: 'get',
params: query
});
};
//查询巡检项
export const xunjianItemlist = (query) => {
return request({
url: '/ops/item/list',
method: 'get',
params: query
});
};
//详情
export const xunjianDetail = (id) => {
return request({
url: `/ops/plan/${id}`,
method: 'get'
});
};

View File

@ -0,0 +1,10 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
//查询列表
export const xunjianjilu = (query) => {
return request({
url: '/ops/task/record',
method: 'get',
params: query
});
};

View File

@ -0,0 +1,46 @@
import request from '@/utils/request';
export const xjrenwulist = (query) => {
return request({
url: '/ops/task/list',
method: 'get',
params: query
});
};
//新增
export const addxjrenwu = (data) => {
return request({
url: '/ops/task',
method: 'post',
data: data
});
};
//修改
export const updatexjrenwu = (data) => {
return request({
url: '/ops/task',
method: 'put',
data: data
});
};
//删除
export const delxjrenwu = (ids) => {
return request({
url: `/ops/task/${ids}`,
method: 'delete'
});
};
//详情
export const xjrenwuDetail = (id) => {
return request({
url: `/ops/task/${id}`,
method: 'get'
});
};
//导出
export const xjrenwuExport = (data) => {
return request({
url: '/ops/task/export',
method: 'post',
data: data
});
};

View File

@ -11,6 +11,7 @@
font-weight: normal;
font-style: normal;
}
// 思源字体
// @font-face {
// font-family: 'SourceHanSansCN-Bold';
@ -56,6 +57,7 @@
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'SourceHanSansCN-Bold';
// src: url('./ReflectTi/SourceHanSerifCN-Bold.otf');//暂时没用
@ -110,6 +112,8 @@
font-weight: normal;
font-style: normal;
}
//阿里黑体
@font-face {
font-family: 'AlimamaShuHeiTi-Bold';
@ -117,6 +121,7 @@
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'Alibaba-PuHuiTi-Heavy';
// src: url('./Alibaba/Alibaba-PuHuiTi-Heavy.otf');//暂时没用
@ -135,6 +140,7 @@
font-weight: normal;
font-style: normal;
}
// @font-face {
// font-family: 'Alibaba-PuHuiTi-Regular';
// src: url('./Alibaba/Alibaba-PuHuiTi-Regular.otf');//暂时没用
@ -148,18 +154,19 @@
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'D-Din';
font-family: 'D-Din';
src: url('./D-Din//D-DIN.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Roboto-Regular'; //Roboto
src: url('./Roboto//Roboto-Regular.ttf');
font-weight: normal;
font-weight: normal;
font-style: normal;
}
}

View File

@ -0,0 +1,16 @@
#custom-dialog {
padding: 0;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0 !important;
}
.alert-content {
padding: 80px;
background: linear-gradient(180deg, rgba(0, 119, 255, 0.23) 0%, rgba(255, 255, 255, 0) 100%);
}
}

View File

@ -7,6 +7,8 @@
@use './ruoyi.scss';
@use 'animate.css';
@use 'element-plus/dist/index.css';
@use './dialog.scss';
body {
height: 100%;
@ -208,4 +210,4 @@ aside {
vertical-align: middle;
margin-bottom: 10px;
}
}
}

View File

@ -0,0 +1,225 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="自定义巡检项名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入自定义巡检项名称" clearable @keyup.enter="handleQuery" />
</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>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['inspection:item:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['inspection:item:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['inspection:item:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['inspection:item:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="itemList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="" align="center" prop="id" v-if="true" />
<el-table-column label="自定义巡检项名称" align="center" prop="name" />
<el-table-column label="业务id巡检计划1" align="center" prop="type" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['inspection:item:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['inspection:item:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改运维-巡检-自定义巡检项对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="650px" append-to-body>
<el-form ref="itemFormRef" :model="form" :rules="rules" label-width="135px">
<el-form-item label="自定义巡检项名称" prop="name">
<el-input v-model="form.name" placeholder="请输入自定义巡检项名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择类型">
<el-option label="巡检管理" value="1" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Item" lang="ts">
import { listItem, getItem, delItem, addItem, updateItem } from '@/api/zhinengxunjian/inspection/item/index';
import { ItemVO, ItemQuery, ItemForm } from '@/api/zhinengxunjian/inspection/item/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const itemList = ref<ItemVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const itemFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ItemForm = {
name: undefined,
type: '1',
projectId: 1
};
const data = reactive<PageData<ItemForm, ItemQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined,
type: undefined,
params: {}
},
rules: {
name: [{ required: true, message: '自定义巡检项名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '业务id巡检计划1不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询运维-巡检-自定义巡检项列表 */
const getList = async () => {
loading.value = true;
const res = await listItem(queryParams.value);
itemList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
itemFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ItemVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加运维-巡检-自定义巡检项';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ItemVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getItem(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改运维-巡检-自定义巡检项';
};
/** 提交按钮 */
const submitForm = () => {
itemFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateItem(form.value).finally(() => (buttonLoading.value = false));
} else {
await addItem(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ItemVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除运维-巡检-自定义巡检项编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delItem(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'inspection/item/export',
{
...queryParams.value
},
`item_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,234 @@
<template>
<div class="dashboard-container">
<!-- 第一个图表本月出入库统计 -->
<div class="chart-item">
<div class="title">
本月出入库统计
</div>
<div ref="lineChartRef" class="chart-container"></div>
</div>
<!-- 第二个图表出入库类型分布 -->
<div class="chart-item">
<div class="title">
出入库类型分布
</div>
<div ref="barChartRef" class="chart-container"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
// 图表容器引用
const lineChartRef = ref(null);
const barChartRef = ref(null);
// 图表实例
let lineChart = null;
let barChart = null;
onMounted(() => {
// 初始化折线图
initLineChart();
// 初始化柱状图
initBarChart();
// 监听窗口大小变化,自适应图表
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
// 销毁图表实例
if (lineChart) {
lineChart.dispose();
}
if (barChart) {
barChart.dispose();
}
// 移除事件监听
window.removeEventListener('resize', handleResize);
});
// 初始化折线图
const initLineChart = () => {
if (lineChartRef.value) {
lineChart = echarts.init(lineChartRef.value);
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['入库数量', '出库数量'],
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
},
yAxis: {
type: 'value'
},
series: [
{
name: '入库数量',
type: 'line',
data: [5, 40, 20, 75, 60, 80, 40, 55, 30, 65, 5, 80],
symbol: 'none',
smooth: true,
lineStyle: {
color: 'rgba(22, 93, 255, 1)'
},
itemStyle: {
color: 'rgba(22, 93, 255, 1)'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(22, 93, 255, 0.2)' },
{ offset: 1, color: 'rgba(22, 93, 255, 0)' }
])
}
},
{
name: '出库数量',
type: 'line',
data: [30, 40, 30, 30, 30, 15, 55, 50, 40, 60, 25, 90],
symbol: 'none',
smooth: true,
lineStyle: {
color: 'rgba(255, 153, 0, 1)'
},
itemStyle: {
color: 'rgba(255, 153, 0, 1)'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(59, 179, 70, 0.2)' },
{ offset: 1, color: 'rgba(59, 179, 70, 0)' }
])
}
}
]
};
lineChart.setOption(option);
}
};
// 初始化柱状图
const initBarChart = () => {
if (barChartRef.value) {
barChart = echarts.init(barChartRef.value);
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['入库数量', '出库数量'],
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['电器部件', '机械部件', '电子元件', '控制模块', '结构部件', '其他'],
axisLabel: {
interval: 0, // 强制显示所有标签
rotate: 30, // 标签旋转30度
margin: 15, // 增加与轴线的距离
align: 'right', // 文字右对齐
verticalAlign: 'top' // 垂直方向顶部对齐
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '入库数量',
type: 'bar',
data: [650, 480, 510, 280, 650, 220],
itemStyle: {
color: 'rgba(22, 93, 255, 1)' // 入库数量颜色
},
barWidth: '45%',
barGap: '0' // 柱子之间的间距
},
{
name: '出库数量',
type: 'bar',
data: [850, 400, 770, 590, 540, 310],
itemStyle: {
color: 'rgba(15, 198, 194, 1)' // 出库数量颜色
},
barWidth: '45%',
barGap: '0' // 柱子之间的间距
}
]
};
barChart.setOption(option);
}
};
// 处理窗口大小变化,让图表自适应
const handleResize = () => {
if (lineChart) {
lineChart.resize();
}
if (barChart) {
barChart.resize();
}
};
</script>
<style scoped>
.dashboard-container {
padding: 20px;
}
.chart-item {
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.title {
color: rgba(29, 33, 41, 1);
font-weight: bold;
font-size: 16px;
margin-bottom: 16px;
}
.chart-container {
width: 100%;
height: 300px;
}
</style>

View File

@ -0,0 +1,146 @@
<template>
<div class="approval-process-container">
<div class="approval-process-card">
<h2>审批流程</h2>
<!-- 时间线组件展示审批步骤 -->
<el-timeline class="custom-timeline">
<!-- 步骤1创建采购单 -->
<el-timeline-item timestamp="11月1日 10:18" placement="top" :icon="renderCustomIcon('/assets/yes.png')"
color="green" type="danger" class="timeline-item">
<h3 style="color: rgba(24, 109, 245, 1);font-weight: bold;">创建采购单</h3>
<p>申请人张三提交采购单</p>
<p>计划Q2风电轴承采购计划</p>
<p>附件<a href="#" class="attachment-link">一个图片.jpg</a></p>
</el-timeline-item>
<!-- 步骤2审批未通过 -->
<el-timeline-item timestamp="11月1日 10:18" placement="top" :icon="renderCustomIcon('/assets/no.png')"
color="red" class="timeline-item">
<h3 style="color: rgba(24, 109, 245, 1);font-weight: bold;">审批未通过</h3>
<p>部门经理李四审批不通过</p>
<p>计划Q2风电轴承采购计划</p>
<p>不通过原因</p>
<ul class="reason-list">
<li>1. 出货时间过长</li>
<li>2. 单价高于市场价</li>
<li>3. 损耗重新评估</li>
<li>4. 付款方式更改</li>
<li>5. 发票开具方式更改</li>
</ul>
</el-timeline-item>
<!-- 步骤3未进行财务主管 -->
<el-timeline-item timestamp="" placement="top" :icon="renderCustomIcon('/assets/re.png')" color="gray"
class="timeline-item">
<h3>未进行</h3>
<p>财务主管王五</p>
<p>计划Q2风电轴承采购计划</p>
<p>备注</p>
</el-timeline-item>
<!-- 步骤4未进行总经理 -->
<el-timeline-item timestamp="" placement="top" :icon="renderCustomIcon('/assets/re.png')" color="gray"
class="timeline-item">
<h3>未进行</h3>
<p>总经理赵六</p>
<p>计划Q2风电轴承采购计划</p>
<p>备注</p>
</el-timeline-item>
</el-timeline>
</div>
</div>
</template>
<script setup>
import { Check, Close, Clock } from '@element-plus/icons-vue'; // 引入Element Plus图标
const renderCustomIcon = (iconSrc) => {
return h('img', { src: iconSrc, class: 'custom-icon' });
};
</script>
<style scoped>
.approval-process-container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
/* padding: 40px 20px; */
}
.approval-process-card {
padding: 30px;
width: 100%;
max-width: 800px;
transition: transform 0.3s ease;
}
.approval-process-card:hover {
transform: translateY(-5px);
}
h2 {
margin-bottom: 30px;
font-size: 24px;
color: #333;
text-align: center;
position: relative;
}
h2::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background-color: #409eff;
}
.custom-timeline {
margin-top: 20px;
}
.timeline-item {
margin-bottom: 25px;
transition: all 0.3s ease;
}
.timeline-item:hover {
transform: translateX(5px);
}
.el-timeline-item__content {
padding-top: 15px;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
padding: 15px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
ul.reason-list {
margin: 0;
padding-left: 20px;
color: red;
list-style: none;
}
li {
margin: 8px 0;
line-height: 1.6;
}
.attachment-link {
color: #409eff;
text-decoration: none;
transition: all 0.3s ease;
display: inline-block;
}
.attachment-link:hover {
text-decoration: underline;
transform: translateY(-2px);
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div class="bill-list">
<!-- 循环渲染单据列表 -->
<div v-for="(bill, index) in billList" :key="index" class="bill-item">
<!-- 左侧图标 + 单据类型 + 编号 -->
<div class="left">
<div class="icon">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40"
height="40" viewBox="0 0 40 40" fill="none">
<circle cx="20" cy="20" r="20" fill="#186DF5"></circle>
<path fill="rgba(255, 255, 255, 1)"
d="M15.1666 18.3333L15.1666 22.5L14.1666 22.5L14.1666 21.5L25.8333 21.5L25.8333 22.5L24.8333 22.5L24.8333 18.3333L25.8333 18.3333L24.8333 18.3333C24.8333 15.664 22.6693 13.5 20 13.5L20 13.5C17.3306 13.5 15.1666 15.664 15.1666 18.3333L15.1666 18.3333ZM13.1666 18.3333L14.1666 18.3333L13.1666 18.3333C13.1666 14.5594 16.226 11.5 20 11.5L20 12.5L20 11.5C23.7739 11.5 26.8333 14.5594 26.8333 18.3333L26.8333 18.3333L26.8333 22.5L25.8333 23.5L14.1666 23.5L13.1666 22.5L13.1666 18.3333ZM13.1666 22.5L14.1666 22.5L14.1666 23.5C13.8921 23.4961 13.6564 23.3985 13.4595 23.2071C13.2681 23.0102 13.1705 22.7745 13.1666 22.5ZM25.8333 23.5L25.8333 22.5L26.8333 22.5C26.8294 22.7745 26.7318 23.0102 26.5404 23.2071C26.3435 23.3985 26.1078 23.4961 25.8333 23.5Z">
</path>
<path fill="rgba(255, 255, 255, 1)"
d="M13.5 23.75C13.5 23.8881 13.6119 24 13.75 24L13.75 25L13.75 24L26.25 24L26.25 25L26.25 24C26.3881 24 26.5 23.8881 26.5 23.75L27.5 23.75L26.5 23.75C26.5 23.6119 26.3881 23.5 26.25 23.5L26.25 23.5L13.75 23.5L13.75 22.5L13.75 23.5C13.6119 23.5 13.5 23.6119 13.5 23.75L13.5 23.75ZM11.5 23.75L12.5 23.75L11.5 23.75C11.5 22.5074 12.5074 21.5 13.75 21.5L13.75 21.5L26.25 21.5L26.25 22.5L26.25 21.5C27.4926 21.5 28.5 22.5074 28.5 23.75L28.5 23.75C28.5 24.9926 27.4926 26 26.25 26L26.25 26L13.75 26L13.75 26C12.5074 26 11.5 24.9926 11.5 23.75Z">
</path>
<path stroke="rgba(255, 255, 255, 1)" stroke-width="2" stroke-linejoin="round"
stroke-linecap="round"
d="M22.5 25C22.5 26.3807 21.3807 27.5 20 27.5C18.6193 27.5 17.5 26.3807 17.5 25"></path>
</svg>
</div>
<div class="info">
<div class="type">{{ bill.type }}</div>
<div class="number">{{ bill.number }}</div>
</div>
</div>
<!-- 右侧时间 + 状态 -->
<div class="right">
<div class="time">{{ bill.time }}</div>
<div class="status" :class="{
'approved': bill.status === '审批通过',
'pending': bill.status === '待审批'
}">
{{ bill.status }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 定义单据数据
const billList = ref([
{
type: '出库单',
number: 'IN-2023-0615-001',
time: '10月20日 12:00:06',
status: '审批通过'
},
{
type: '入库单',
number: 'IN-2023-0615-001',
time: '10月20日 12:00:06',
status: '待审批'
},
{
type: '入库单',
number: 'IN-2023-0615-001',
time: '10月20日 12:00:06',
status: '待审批'
},
{
type: '入库单',
number: 'IN-2023-0615-001',
time: '10月20日 12:00:06',
status: '待审批'
},
{
type: '入库单',
number: 'IN-2023-0615-001',
time: '10月20日 12:00:06',
status: '待审批'
}
]);
</script>
<style scoped>
.bill-list {
display: flex;
flex-direction: column;
margin-top: 10px;
/* gap: 1rem; */
/* padding: 1rem; */
}
.bill-item {
display: flex;
justify-content: space-between;
align-items: center;
/* padding: 0.5rem; */
padding: 10px;
border-bottom: 1px solid #e5e7eb;
}
.left {
display: flex;
align-items: center;
gap: 0.5rem;
}
.info {
display: flex;
flex-direction: column;
}
.type {
font-weight: 600;
}
.number {
font-size: 0.875rem;
color: #6b7280;
}
.right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.time {
font-size: 0.875rem;
color: #6b7280;
}
.status {
font-size: 0.875rem;
/* padding: 0.25rem 0.5rem; */
border-radius: 0.25rem;
}
.approved {
color: #10b981;
/* background-color: rgba(16, 185, 129, 0.1); */
}
.pending {
color: #f59e0b;
/* background-color: rgba(245, 158, 11, 0.1); */
}
</style>

View File

@ -0,0 +1,257 @@
<template>
<div class="approval-form">
<!-- 基础信息 -->
<el-card class="card" shadow="hover">
<template #header>
<h3>基础信息</h3>
</template>
<el-form :model="basicInfo" label-width="120px">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="订单编号">
<el-input v-model="basicInfo.orderNo" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="创建时间">
<el-input v-model="basicInfo.createTime" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="经办人">
<el-input v-model="basicInfo.handler" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属部门">
<el-select v-model="basicInfo.dept" placeholder="请选择">
<el-option label="运维部" value="运维部" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购类型">
<el-select v-model="basicInfo.purchaseType" placeholder="请选择">
<el-option label="项目业务" value="项目业务" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="申请原因">
<el-input v-model="basicInfo.applyReason" type="textarea" :rows="2" placeholder="请输入申请原因" />
</el-form-item>
</el-form>
</el-card>
<!-- 供应商信息 -->
<el-card class="card" shadow="hover" style="margin-top: 20px">
<template #header>
<h3>供应商信息</h3>
</template>
<el-form :model="supplierInfo" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="供应商单位">
<el-select v-model="supplierInfo.supplierName" placeholder="请选择">
<el-option label="AAAA精密仪器制造有限公司" value="AAAA精密仪器制造有限公司" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出货时间">
<el-select v-model="supplierInfo.deliveryTime" placeholder="请选择">
<el-option label="2年零4个月" value="2年零4个月" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="审批备注" prop="remark">
<el-input v-model="supplierInfo.remark" :rows="1" placeholder="请输入审批备注"
style="border: 1px solid red;color: red;" readonly value="1. 出货时间较长" />
<!-- <div class="error-tip">1. 出货时间较长</div> -->
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 产品信息 -->
<el-card class="card" shadow="hover" style="margin-top: 20px">
<template #header>
<h3>产品信息</h3>
</template>
<el-table :data="productInfo.tableData" border style="width: 100%">
<el-table-column prop="productName" label="产品名称" />
<el-table-column prop="productModel" label="产品型号" />
<el-table-column prop="productPrice" label="产品单价" align="center" :cell-style="{ background: 'pink' }" />
<el-table-column prop="buyQuantity" label="购买数量" align="center" :cell-style="{ background: 'pink' }" />
<el-table-column prop="usage" label="用途" />
<el-table-column prop="total" label="合计" />
</el-table>
<el-form-item label="审批备注" style="margin-top: 10px">
<el-input v-model="productInfo.remark" :rows="1" placeholder="请输入审批备注"
style="border: 1px solid red;color: red;" readonly value="2. 单价高于市场价3.采购数量需重新评估" />
<!-- <div class="error-tip">2. 单价高于市场价3.采购数量需重新评估</div> -->
</el-form-item>
</el-card>
<!-- 合同条款 -->
<el-card class="card" shadow="hover" style="margin-top: 20px">
<template #header>
<h3>合同条款</h3>
</template>
<el-form :model="contractInfo" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="付款条件">
<el-select v-model="contractInfo.paymentCondition" placeholder="请选择">
<el-option label="银行卡" value="银行卡" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发票开具方式">
<el-select v-model="contractInfo.invoiceWay" placeholder="请选择">
<el-option label="请选择" value="请选择" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="审批备注" prop="remark">
<el-input v-model="contractInfo.remark" placeholder="请输入审批备注"
style="border: 1px solid red;color: red;" readonly value="4. 付款方式未标明5.发票开具方式未标明" />
<!-- <div class="error-tip">4. 付款方式未标明5.发票开具方式未标明</div> -->
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 附件 -->
<el-card class="card" shadow="hover" style="margin-top: 20px">
<template #header>
<h3>附件</h3>
</template>
<el-upload class="upload-demo" action="#" :file-list="fileList" :auto-upload="false"
:on-preview="handlePreview">
<el-table :data="fileList" border style="width: 100%">
<el-table-column prop="name" label="文件名" width="300" />
<el-table-column prop="size" label="大小" width="100" />
<el-table-column label="操作" width="100">
<template #default="scope">
<!-- <el-link type="primary" @click="handlePreview(scope.row)"> -->
<el-link type="primary">
预览
</el-link>
</template>
</el-table-column>
</el-table>
</el-upload>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 基础信息数据
const basicInfo = ref({
orderNo: '0035455',
createTime: '2023-11-02 16:32',
handler: '李四',
dept: '运维部',
purchaseType: '项目业务',
applyReason:
'随着业务拓展光伏电站业务负责增加现有设备已运行5年部分出现效率下降情况。为保证电站正常运行计划采购一批新的逆变器替换老旧设备并补充备件库存。',
});
// 供应商信息数据
const supplierInfo = ref({
supplierName: 'AAAA精密仪器制造有限公司',
deliveryTime: '2年零4个月',
remark: '',
});
// 产品信息数据
const productInfo = ref({
tableData: [
{
productName: 'AAABBBCCC',
productModel: '15-42',
productPrice: 500,
buyQuantity: 10,
usage: '组件',
total: 5000,
},
],
remark: '',
});
// 合同条款数据
const contractInfo = ref({
paymentCondition: '银行卡',
invoiceWay: '请选择',
remark: '',
});
// 附件数据
const fileList = ref([
{
name: 'MWwwwww.jpg',
size: '30kb',
url: '',
},
{
name: '231234124w.zip',
size: '50kb',
url: '',
},
{
name: '12451asdas.doc',
size: '80kb',
url: '',
},
{
name: '21seasda.xls',
size: '29kb',
url: '',
},
{
name: '12kjaklskw.png',
size: '16kb',
url: '',
},
]);
// 预览文件
const handlePreview = (file) => {
console.log('预览文件:', file);
// 实际场景可在这里处理文件预览逻辑,如打开新窗口等
};
</script>
<style scoped>
.approval-form {
padding: 20px;
}
.card {
border-radius: 8px;
}
.error-tip {
color: red;
font-size: 12px;
margin-top: 5px;
}
::v-deep(.el-input__inner) {
color: red;
}
</style>

View File

@ -0,0 +1,280 @@
<template>
<div class="inventoryManagement">
<!-- <TitleComponent title="出入库单管理" subtitle="管理光伏和风电设备备品备件的出入库记录" /> -->
<el-row gutter="20">
<el-col :span="16" class="list" style="flex-grow: 1;display: flex;">
<el-card style="border-radius: 10px;height: 100%;display: flex;flex-direction: column;flex: 1;">
<div style="height: 100%;flex: 1;">
<div class="top">
<div class="title">单据列表</div>
<div class="button-actions">
<button :class="{ active: type === 'chuku' }" @click="changeType('chuku')">出库单</button>
<button :class="{ active: type === 'ruku' }" @click="changeType('ruku')">入库单</button>
</div>
</div>
<div class="content" style="height: 100%;flex: 1;">
<div class="menu">
<el-input placeholder="请输入单据编号"></el-input>
<el-select placeholder="请选择单据类型"></el-select>
<el-select placeholder="请选择设备类型"></el-select>
<el-select placeholder="请选择状态"></el-select>
<el-select placeholder="请选择日期范围"></el-select>
<el-button icon="search" type="primary">搜索</el-button>
<el-button icon="refresh">重置</el-button>
</div>
<div style="margin-top: 10px;">
<el-button type="primary" @click="dialogVisible = true;">+{{ type === 'chuku' ? '添加出库单'
: '添加入库单' }}</el-button>
</div>
<el-table :data="tableData" border style="width: 100%;margin-top: 15px;height: 1000px;">
<el-table-column prop="formNumber" label="单据编号" />
<el-table-column prop="equipmentType" label="设备类型" />
<el-table-column prop="handler" label="经手人" />
<el-table-column prop="operationTime" label="操作时间" />
<el-table-column prop="totalQuantity" label="总数量" />
<el-table-column label="状态">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="handleDetail(scope.row)">详情</el-button>
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="tool">
<div class="pagination-section">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total" background>
</el-pagination>
</div>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card style="border-radius: 10px;">
<div class="item-box">
<div class="title">系统信息</div>
<div class="content">
<SystemInfo />
</div>
</div>
<div class="item-box">
<div class="title">数据分析</div>
<div class="content">
<DataAnalysis />
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog v-model="dialogVisible" :title="type === 'chuku' ? '添加出库单' : '添加入库单'" width="500">
<el-form :rules="rules" ref="formRef" label-width="100">
<el-form-item label="单据编号" prop="formNumber">
<el-input v-model="form.formNumber" placeholder="请输入单据编号" />
</el-form-item>
<el-form-item label="设备类型" prop="equipmentType">
<el-select v-model="form.equipmentType" placeholder="请选择设备类型">
<el-option label="设备类型1" value="1" />
<el-option label="设备类型2" value="2" />
</el-select>
</el-form-item>
<el-form-item label="入库数量" prop="totalQuantity">
<el-input v-model="form.totalQuantity" placeholder="请输入总数量" />
</el-form-item>
<el-form-item label="经手人" prop="handler">
<el-input v-model="form.handler" placeholder="请输入经手人" />
</el-form-item>
<!-- 联系电话 -->
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">
保存
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.inventoryManagement {
background-color: #F2F8FC;
padding: 20px;
}
.button-actions button {
background: none;
border: 1px solid #e0e0e0;
padding: 5px 12px;
border-radius: 4px;
margin-left: 8px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.button-actions button.active {
background-color: #186DF5;
color: white;
border-color: #186DF5;
}
.top {
display: flex;
justify-content: space-between;
.title {
font-family: "Alibaba-PuHuiTi-Bold";
color: rgba(0, 30, 59, 1);
font-weight: bold;
}
}
.list .content {
margin-top: 20px;
}
.menu {
display: flex;
gap: 20px;
background-color: #F2F2F2;
padding: 20px;
}
/* 分页区域样式 */
.pagination-section {
background-color: #fff;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-info {
font-size: 14px;
color: #606266;
}
.pagination-controls .el-pagination {
margin: 0;
}
.pagination-controls .el-pagination button {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li.active {
background-color: #409eff;
color: #fff;
}
.tool {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.item-box {
.title {
font-family: "Alibaba-PuHuiTi-Bold";
font-size: 18px;
font-weight: 400;
letter-spacing: 0px;
line-height: 24px;
color: rgba(0, 30, 59, 1);
margin-top: 10px;
}
}
::v-deep(.el-card__body) {
height: 100%;
}
</style>
<script setup>
import SystemInfo from './components/SystemInfo.vue';
import DataAnalysis from './components/DataAnalysis.vue';
const type = ref('chuku');
const form = ref({
formNumber: '',
equipmentType: '',
handler: '',
totalQuantity: ''
});
const changeType = (newType) => {
type.value = newType;
};
const dialogVisible = ref(false);
const tableData = computed(() => {
return Array.from({ length: 50 }, (_, index) => ({
formNumber: 'IN-2023-0615-001',
equipmentType: '光伏设备',
handler: '李仓库',
operationTime: '2023-06-15 09:23',
totalQuantity: 120,
// 待审核,已完成,已取消 随机生成
status: Math.random() > 0.5 ? '待审核' : Math.random() > 0.5 ? '已完成' : '已取消'
}))
})
// 当前页码
const currentPage = ref(1);
// 每页条数 - 与分页控件默认值保持一致
const pageSize = ref(10);
// 总条数 - 从原始数据计算得出
const total = ref(tableData.value.length);
const pagedTableData = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return tableData.value.slice(startIndex, endIndex);
});
// 表单校验规则
const rules = ref({
formNumber: [{ required: true, message: '请输入表单编号', trigger: 'blur' }],
equipmentType: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
handler: [{ required: true, message: '请输入经手人', trigger: 'blur' }],
totalQuantity: [{ required: true, message: '请输入入库数量', trigger: 'blur' }],
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
});
// 表单引用
const formRef = ref(null);
// 当前页码改变
const handleCurrentChange = (val) => {
currentPage.value = val;
};
const getStatusTagType = (status) => {
if (status === '已完成') {
return 'success'
} else if (status === '待审核') {
return 'warning'
} else if (status === '已取消') {
return 'danger'
}
return ''
}
</script>

View File

@ -0,0 +1,57 @@
<template>
<div class="plan-details">
<el-row>
<el-col>
<el-card>
<div class="header">
<span class="back-arrow" @click="handleBack">
<el-icon>
<ArrowLeft />
</el-icon>
</span>
<h2>Q2风电轴承采购计划</h2>
</div>
</el-card>
</el-col>
</el-row>
<el-row gutter="10">
<el-col :span="18">
<el-card>
<detailInfo />
</el-card>
</el-col>
<el-col :span="6" style="flex-grow: 1;">
<el-card style="height: 100%;">
<DetailsProcess />
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped lang="scss">
.plan-details {
padding: 20px;
background-color: #F1F7FB;
}
.header {
display: flex;
align-items: center;
}
.back-arrow {
font-size: 20px;
margin-right: 10px;
cursor: pointer;
}
</style>
<script setup>
import detailInfo from './components/detailInfo.vue';
import DetailsProcess from './components/DetailsProcess.vue';
const router = useRouter();
const handleBack = () => {
router.back();
}
</script>

View File

@ -0,0 +1,569 @@
<template>
<div class="procurementPlan">
<el-row gutter="20">
<el-col :span="13">
<el-card>
<div style="display: flex;align-items: center;height: 120px;justify-content: space-around;">
<div class="img">
<img src="/assets/caigou.png" alt="">
</div>
<div class="item">
<div class="text">
待审批计划
</div>
<div class="count" style="color: rgba(255, 178, 30, 1);">
12
</div>
</div>
<div class="item">
<div class="text">
已批准计划
</div>
<div class="count" style="color: rgba(67, 101, 220, 1);">
28
</div>
</div>
<div class="item">
<div class="text">
采购中计划
</div>
<div class="count" style="color: rgba(113, 214, 213, 1);">
15
</div>
</div>
<div class="item">
<div class="text">
已完成计划
</div>
<div class="count" style="color: rgba(0, 184, 122, 1);">
86
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="11">
<el-card>
<div style="display: flex;align-items: center;height: 120px;justify-content: space-around;">
<div class="img">
<img src="/assets/qian.jpg" alt="">
</div>
<div class="item">
<div class="text">
本年度已采购金额
</div>
<div class="count" style="color: rgba(255, 153, 0, 1);">
520,000.00
</div>
</div>
<div class="item">
<div class="text">
本年度采购预算金额
</div>
<div class="count" style="color: rgba(67, 101, 220, 1);">
3,000,000.00
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row style="margin-top: 20px;">
<el-col :span="24">
<el-card style="border-radius: 10px;">
<div class="content">
<div class="tabs">
<el-button type="success">导出</el-button>
<el-button type="primary" @click="isNewProcurementDialogVisible = true">新建采购申请单</el-button>
</div>
<!-- 标签页导航 -->
<div class="tabs">
<el-button :type="activeTab === 'pending' ? 'primary' : ''"
@click="changeTab('pending')">待审批</el-button>
<el-button :type="activeTab === 'procuring' ? 'primary' : ''"
@click="changeTab('procuring')">采购中</el-button>
<el-badge :value="5" type="danger">
<el-button :type="activeTab === 'rejected' ? 'primary' : ''"
@click="changeTab('rejected')">
未通过
</el-button>
</el-badge>
<el-button :type="activeTab === 'approved' ? 'primary' : ''"
@click="changeTab('approved')">已通过</el-button>
<el-button :type="activeTab === 'completed' ? 'primary' : ''"
@click="changeTab('completed')">已完成</el-button>
</div>
<!-- 表格 -->
<el-table :data="tableData" border style="width: 100%;margin-top: 15px;">
<el-table-column type="selection" width="55" />
<el-table-column prop="planNumber" label="计划编号" />
<el-table-column prop="planName" label="计划名称" />
<el-table-column prop="equipmentType" label="设备类型" />
<el-table-column prop="requestDept" label="申请部门" />
<el-table-column prop="applicant" label="申请人" />
<el-table-column prop="requestDate" label="申请日期" />
<el-table-column prop="estimatedAmount" label="预计金额" />
<el-table-column label="状态">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="80">
<template #default="scope">
<el-button type="text" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-section">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total" background />
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog v-model="isNewProcurementDialogVisible" title="新建采购申请单" width="60%" :close-on-click-modal="false">
<div class="new-procurement-form">
<!-- 基础信息 -->
<div class="form-section">
<h3>基础信息</h3>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="订单编号">
<el-input v-model="newProcurementForm.planNumber" disabled value="PLAN-2023-0615-003" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="创建时间">
<el-input v-model="newProcurementForm.createTime" disabled value="2023-11-02-16:32" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="采购单位">
<el-input v-model="newProcurementForm.procurementUnit" disabled value="大连好果汁有限公司" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经办人">
<el-input v-model="newProcurementForm.handler" disabled value="李四" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="合同类型">
<el-select v-model="newProcurementForm.contractType" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="采购类型">
<el-select v-model="newProcurementForm.procurementType" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="仓库地址">
<el-select v-model="newProcurementForm.contractAddress" placeholder="请选择">
<el-option label="请选择" value="" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="合同名称">
<el-input v-model="newProcurementForm.contractName" placeholder="请填写" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 供应商信息 -->
<div class="form-section">
<h3>供应商信息</h3>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="供应商单位">
<el-select v-model="newProcurementForm.supplierUnit" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="送货时间">
<el-select v-model="newProcurementForm.deliveryTime" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 产品信息 -->
<div class="form-section">
<h3>产品信息</h3>
<el-table :data="newProcurementForm.products" border style="width: 100%">
<el-table-column prop="productName" label="产品名称">
<template #default="scope">
<el-input v-model="scope.row.productName" placeholder="请填写" />
</template>
</el-table-column>
<el-table-column prop="productModel" label="产品型号">
<template #default="scope">
<el-input v-model="scope.row.productModel" placeholder="请填写" />
</template>
</el-table-column>
<el-table-column prop="productPrice" label="产品单价">
<template #default="scope">
<el-input v-model="scope.row.productPrice" placeholder="请填写" type="number" />
</template>
</el-table-column>
<el-table-column prop="purchaseQuantity" label="购买数量">
<template #default="scope">
<el-input v-model="scope.row.purchaseQuantity" placeholder="请填写" type="number" />
</template>
</el-table-column>
<el-table-column prop="unit" label="单位">
<template #default="scope">
<el-input v-model="scope.row.unit" placeholder="请填写" />
</template>
</el-table-column>
<el-table-column prop="totalPrice" label="合计" :formatter="calculateTotalPrice">
<template #default="scope">
<span>{{ calculateTotalPrice(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="80">
<template #default="scope">
<el-button type="text" @click="removeProduct(scope.$index)"
:disabled="newProcurementForm.products.length <= 1">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button type="primary" size="small" @click="addProduct" style="margin-top: 10px">添加产品</el-button>
</div>
<!-- 合同条款 -->
<div class="form-section">
<h3>合同条款</h3>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="付款条件">
<el-select v-model="newProcurementForm.paymentTerms" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="结算方式">
<el-select v-model="newProcurementForm.settlementMethod" placeholder="请选择">
<el-option label="请选择" value="" />
<!-- 可以添加更多选项 -->
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 附件上传 -->
<div class="form-section">
<h3>附件上传</h3>
<div class="upload-section">
<el-upload class="upload-demo" action="" :on-preview="handlePreview" :on-remove="handleRemove"
:before-remove="beforeRemove" multiple :limit="5" :on-exceed="handleExceed"
:file-list="newProcurementForm.fileList" list-type="text">
<el-button type="primary" :icon="Upload">上传文件</el-button>
<template #tip>
<div class="el-upload__tip">
请将文件拖到此处或点击上传<br>
最多上传5个文件单个文件大小不超过20M
</div>
</template>
</el-upload>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelNewProcurement">取消</el-button>
<el-button @click="saveDraft">保存草稿</el-button>
<el-button type="primary" @click="submitProcurement">提交申请</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style sc oped lang="scss">
.procurementPlan {
background-color: #F2F8FC;
padding: 20px;
}
.img {
img {
display: block;
width: 80px;
height: 80px;
}
}
.item {
text-align: center;
.text {
font-size: 14px;
}
.count {
font-size: 25px;
font-weight: 600;
text-align: left;
margin-top: 10px;
}
}
.tabs {
display: flex;
gap: 10px;
padding: 10px 0;
}
.content {
padding: 10px 0;
}
/* 分页区域样式 */
.pagination-section {
background-color: #fff;
border-radius: 8px;
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 15px;
padding: 10px 0;
}
.pagination-controls .el-pagination {
margin: 0;
}
.pagination-controls .el-pagination button {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li.active {
background-color: #409eff;
color: #fff;
}
</style>
<script setup>
import { ref, reactive, computed } from 'vue';
import { Upload } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
const router = useRouter();
// 新建采购申请单对话框是否可见
const isNewProcurementDialogVisible = ref(false);
// 当前激活的标签页
const activeTab = ref('pending');
// 新建采购申请单表单数据
const newProcurementForm = reactive({
paymentTerms: '',
settlementMethod: '',
fileList: []
});
// 表格数据
const tableData = ref([
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
},
{
planNumber: 'PLAN-2023-0615-003',
planName: 'Q2风电轴承采购计划',
equipmentType: '风电设备',
requestDept: '运维部',
applicant: '王主管',
requestDate: '2023-06-15 10:30',
estimatedAmount: '300,000.00',
status: '待审批'
}
]);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(12);
// 切换标签页
const changeTab = (tab) => {
activeTab.value = tab;
// 这里可以根据标签页筛选数据
currentPage.value = 1; // 切换标签页时重置到第一页
};
// 获取状态标签类型
const getStatusTagType = (status) => {
switch (status) {
case '待审批':
return 'warning';
case '采购中':
return 'info';
case '未通过':
return 'danger';
case '已通过':
return 'primary';
case '已完成':
return 'success';
default:
return '';
}
};
// 查看详情
const handleView = (row) => {
console.log('查看采购计划详情:', row);
router.push({
path: '/materialManagement/planDetails',
query: {
planNumber: row.planNumber
}
});
// 这里可以实现查看详情的逻辑,比如打开详情弹窗或跳转到详情页
};
// 分页大小变化
const handleSizeChange = (size) => {
pageSize.value = size;
currentPage.value = 1;
};
// 当前页码变化
const handleCurrentChange = (current) => {
currentPage.value = current;
};
</script>

View File

@ -0,0 +1,439 @@
<template>
<div style="padding: 20px;background-color: #F2F8FC;">
<TitleComponent title="备品备件管理" subtitle="管理光伏和风电设备的所有备品备件信息" />
<el-card style="border-radius: 10px;">
<div class="title">
数据总览
</div>
<div class="list">
<div class="item">
<div class="left">
<div style="font-size: 14px;color:rgba(102, 102, 102, 1)">总备件数量</div>
<div style="margin: 10px 0;">
<span style="font-size: 24px;font-weight: bold;margin-right: 10px;">2,548</span>
<span></span>
</div>
<div>
<div style="display: flex;align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="18" height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.7748L15.15 5.69982L15.15 5.62481L15.075 5.62481C15.075 5.62481 15 5.62481 14.925 5.5498L11.25 5.5498C10.875 5.5498 10.575 5.84981 10.575 6.2248C10.575 6.59982 10.875 6.8998 11.25 6.8998L13.125 6.8998L9.52501 10.4998L7.72501 8.6998C7.35 8.32481 6.60001 8.32481 6.225 8.6998L3.075 11.8498C2.85 12.0748 2.85 12.5248 3.075 12.7498C3.22501 12.8998 3.37501 12.9748 3.525 12.9748C3.67501 12.9748 3.82501 12.9748 3.975 12.7498L6.9 9.8248L8.7 11.6248C9.07501 11.9998 9.825 11.9998 10.2 11.6248L13.95 7.87481L13.95 9.74981C13.95 10.1248 14.25 10.4248 14.625 10.4248C15 10.4248 15.3 10.1248 15.3 9.74981L15.3 6.37482L15.3 6.14982L15.15 5.7748Z"
fill="#00B87A"></path>
</svg>
<div>
<span
style="color: rgba(0, 184, 122, 1);font-size: 14px;margin: 0 5px;margin-right: 10px;">8.2%</span>
<span style="color: rgba(154, 154, 154, 1);font-size: 14px;">较昨日同期</span>
</div>
</div>
</div>
</div>
<div class="right">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="34"
height="34" viewBox="0 0 34 34" fill="none">
<rect x="0" y="0" width="34" height="34" rx="4" fill="#186DF5"></rect>
<path
d="M8.72815 24.0051C8.31696 24.0051 7.9743 24.3478 7.9743 24.759C7.9743 25.1702 8.31696 25.5128 8.72815 25.5128L16.2256 25.5128C16.6368 25.5128 16.9794 25.1702 16.9794 24.759C16.9794 24.3478 16.6368 24.0051 16.2256 24.0051L8.72815 24.0051ZM8.74186 19.4957C8.33067 19.4957 7.988 19.8384 7.988 20.2496C7.988 20.6608 8.33067 21.0034 8.74186 21.0034L13.2513 21.0034C13.6625 21.0034 14.0051 20.6608 14.0051 20.2496C14.0051 19.8384 13.6625 19.4957 13.2513 19.4957L8.74186 19.4957ZM5 16.494C5 15.6716 5.67162 15 6.494 15L27.506 15C28.3284 15 29 15.6716 29 16.494L29 26.6505C29 27.6648 28.1776 28.5009 27.1496 28.5009L6.85037 28.5009C5.83609 28.5009 5 27.6785 5 26.6505L5 16.494Z"
fill="#FFFFFF"></path>
<path
d="M17.7242 12.4974L17.7242 6.494C17.7242 5.67162 18.3959 5 19.2182 5L23.4124 5C24.0018 5 24.5638 5.28784 24.9201 5.76756L28.9772 12.4974L28.9772 12.5385C29.0458 13.2787 28.3467 13.9366 27.6066 13.9914L19.2182 13.9914C18.3959 13.9914 17.7242 13.3198 17.7242 12.4974ZM14.7499 13.9914L6.3753 13.9914C5.63515 13.9229 4.93612 13.2787 5.00466 12.5385L5.00466 12.5248L5.00466 12.4974L9.06177 5.76756C9.40443 5.28784 9.96639 5 10.5695 5L14.7637 5C15.586 5 16.2577 5.67162 16.2577 6.494L16.2577 12.4974C16.2577 13.3198 15.586 13.9914 14.7499 13.9914Z"
fill="#FFFFFF"></path>
</svg>
</div>
</div>
<div class="item">
<div class="left">
<div style="font-size: 14px;color:rgba(102, 102, 102, 1)">总备件数量</div>
<div style="margin: 10px 0;">
<span style="font-size: 24px;font-weight: bold;margin-right: 10px;">2,548</span>
<span></span>
</div>
<div>
<div style="display: flex;align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="18" height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.7748L15.15 5.69982L15.15 5.62481L15.075 5.62481C15.075 5.62481 15 5.62481 14.925 5.5498L11.25 5.5498C10.875 5.5498 10.575 5.84981 10.575 6.2248C10.575 6.59982 10.875 6.8998 11.25 6.8998L13.125 6.8998L9.52501 10.4998L7.72501 8.6998C7.35 8.32481 6.60001 8.32481 6.225 8.6998L3.075 11.8498C2.85 12.0748 2.85 12.5248 3.075 12.7498C3.22501 12.8998 3.37501 12.9748 3.525 12.9748C3.67501 12.9748 3.82501 12.9748 3.975 12.7498L6.9 9.8248L8.7 11.6248C9.07501 11.9998 9.825 11.9998 10.2 11.6248L13.95 7.87481L13.95 9.74981C13.95 10.1248 14.25 10.4248 14.625 10.4248C15 10.4248 15.3 10.1248 15.3 9.74981L15.3 6.37482L15.3 6.14982L15.15 5.7748Z"
fill="#E32727"></path>
</svg>
<div>
<span
style="color: rgba(227, 39, 39, 1);font-size: 14px;margin: 0 5px;margin-right: 10px;">8.2%</span>
<span style="color: rgba(154, 154, 154, 1);font-size: 14px;">较昨日同期</span>
</div>
</div>
</div>
</div>
<div class="right">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="34"
height="34" viewBox="0 0 34 34" fill="none">
<rect x="0" y="0" width="34" height="34" rx="4" fill="#186DF5"></rect>
<path
d="M8.72815 24.0051C8.31696 24.0051 7.9743 24.3478 7.9743 24.759C7.9743 25.1702 8.31696 25.5128 8.72815 25.5128L16.2256 25.5128C16.6368 25.5128 16.9794 25.1702 16.9794 24.759C16.9794 24.3478 16.6368 24.0051 16.2256 24.0051L8.72815 24.0051ZM8.74186 19.4957C8.33067 19.4957 7.988 19.8384 7.988 20.2496C7.988 20.6608 8.33067 21.0034 8.74186 21.0034L13.2513 21.0034C13.6625 21.0034 14.0051 20.6608 14.0051 20.2496C14.0051 19.8384 13.6625 19.4957 13.2513 19.4957L8.74186 19.4957ZM5 16.494C5 15.6716 5.67162 15 6.494 15L27.506 15C28.3284 15 29 15.6716 29 16.494L29 26.6505C29 27.6648 28.1776 28.5009 27.1496 28.5009L6.85037 28.5009C5.83609 28.5009 5 27.6785 5 26.6505L5 16.494Z"
fill="#FFFFFF"></path>
<path
d="M17.7242 12.4974L17.7242 6.494C17.7242 5.67162 18.3959 5 19.2182 5L23.4124 5C24.0018 5 24.5638 5.28784 24.9201 5.76756L28.9772 12.4974L28.9772 12.5385C29.0458 13.2787 28.3467 13.9366 27.6066 13.9914L19.2182 13.9914C18.3959 13.9914 17.7242 13.3198 17.7242 12.4974ZM14.7499 13.9914L6.3753 13.9914C5.63515 13.9229 4.93612 13.2787 5.00466 12.5385L5.00466 12.5248L5.00466 12.4974L9.06177 5.76756C9.40443 5.28784 9.96639 5 10.5695 5L14.7637 5C15.586 5 16.2577 5.67162 16.2577 6.494L16.2577 12.4974C16.2577 13.3198 15.586 13.9914 14.7499 13.9914Z"
fill="#FFFFFF"></path>
</svg>
</div>
</div>
<div class="item">
<div class="left">
<div style="font-size: 14px;color:rgba(102, 102, 102, 1)">总备件数量</div>
<div style="margin: 10px 0;">
<span style="font-size: 24px;font-weight: bold;margin-right: 10px;">2,548</span>
<span></span>
</div>
<div>
<div style="display: flex;align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="18" height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.7748L15.15 5.69982L15.15 5.62481L15.075 5.62481C15.075 5.62481 15 5.62481 14.925 5.5498L11.25 5.5498C10.875 5.5498 10.575 5.84981 10.575 6.2248C10.575 6.59982 10.875 6.8998 11.25 6.8998L13.125 6.8998L9.52501 10.4998L7.72501 8.6998C7.35 8.32481 6.60001 8.32481 6.225 8.6998L3.075 11.8498C2.85 12.0748 2.85 12.5248 3.075 12.7498C3.22501 12.8998 3.37501 12.9748 3.525 12.9748C3.67501 12.9748 3.82501 12.9748 3.975 12.7498L6.9 9.8248L8.7 11.6248C9.07501 11.9998 9.825 11.9998 10.2 11.6248L13.95 7.87481L13.95 9.74981C13.95 10.1248 14.25 10.4248 14.625 10.4248C15 10.4248 15.3 10.1248 15.3 9.74981L15.3 6.37482L15.3 6.14982L15.15 5.7748Z"
fill="#00B87A"></path>
</svg>
<div>
<span
style="color: rgba(0, 184, 122, 1);font-size: 14px;margin: 0 5px;margin-right: 10px;">8.2%</span>
<span style="color: rgba(154, 154, 154, 1);font-size: 14px;">较昨日同期</span>
</div>
</div>
</div>
</div>
<div class="right">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="34"
height="34" viewBox="0 0 34 34" fill="none">
<rect x="0" y="0" width="34" height="34" rx="4" fill="#186DF5"></rect>
<path
d="M8.72815 24.0051C8.31696 24.0051 7.9743 24.3478 7.9743 24.759C7.9743 25.1702 8.31696 25.5128 8.72815 25.5128L16.2256 25.5128C16.6368 25.5128 16.9794 25.1702 16.9794 24.759C16.9794 24.3478 16.6368 24.0051 16.2256 24.0051L8.72815 24.0051ZM8.74186 19.4957C8.33067 19.4957 7.988 19.8384 7.988 20.2496C7.988 20.6608 8.33067 21.0034 8.74186 21.0034L13.2513 21.0034C13.6625 21.0034 14.0051 20.6608 14.0051 20.2496C14.0051 19.8384 13.6625 19.4957 13.2513 19.4957L8.74186 19.4957ZM5 16.494C5 15.6716 5.67162 15 6.494 15L27.506 15C28.3284 15 29 15.6716 29 16.494L29 26.6505C29 27.6648 28.1776 28.5009 27.1496 28.5009L6.85037 28.5009C5.83609 28.5009 5 27.6785 5 26.6505L5 16.494Z"
fill="#FFFFFF"></path>
<path
d="M17.7242 12.4974L17.7242 6.494C17.7242 5.67162 18.3959 5 19.2182 5L23.4124 5C24.0018 5 24.5638 5.28784 24.9201 5.76756L28.9772 12.4974L28.9772 12.5385C29.0458 13.2787 28.3467 13.9366 27.6066 13.9914L19.2182 13.9914C18.3959 13.9914 17.7242 13.3198 17.7242 12.4974ZM14.7499 13.9914L6.3753 13.9914C5.63515 13.9229 4.93612 13.2787 5.00466 12.5385L5.00466 12.5248L5.00466 12.4974L9.06177 5.76756C9.40443 5.28784 9.96639 5 10.5695 5L14.7637 5C15.586 5 16.2577 5.67162 16.2577 6.494L16.2577 12.4974C16.2577 13.3198 15.586 13.9914 14.7499 13.9914Z"
fill="#FFFFFF"></path>
</svg>
</div>
</div>
<div class="item">
<div class="left">
<div style="font-size: 14px;color:rgba(102, 102, 102, 1)">总备件数量</div>
<div style="margin: 10px 0;">
<span style="font-size: 24px;font-weight: bold;margin-right: 10px;">2,548</span>
<span></span>
</div>
<div>
<div style="display: flex;align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="18" height="18" viewBox="0 0 18 18" fill="none">
<path
d="M15.15 5.7748L15.15 5.69982L15.15 5.62481L15.075 5.62481C15.075 5.62481 15 5.62481 14.925 5.5498L11.25 5.5498C10.875 5.5498 10.575 5.84981 10.575 6.2248C10.575 6.59982 10.875 6.8998 11.25 6.8998L13.125 6.8998L9.52501 10.4998L7.72501 8.6998C7.35 8.32481 6.60001 8.32481 6.225 8.6998L3.075 11.8498C2.85 12.0748 2.85 12.5248 3.075 12.7498C3.22501 12.8998 3.37501 12.9748 3.525 12.9748C3.67501 12.9748 3.82501 12.9748 3.975 12.7498L6.9 9.8248L8.7 11.6248C9.07501 11.9998 9.825 11.9998 10.2 11.6248L13.95 7.87481L13.95 9.74981C13.95 10.1248 14.25 10.4248 14.625 10.4248C15 10.4248 15.3 10.1248 15.3 9.74981L15.3 6.37482L15.3 6.14982L15.15 5.7748Z"
fill="#00B87A"></path>
</svg>
<div>
<span
style="color: rgba(0, 184, 122, 1);font-size: 14px;margin: 0 5px;margin-right: 10px;">8.2%</span>
<span style="color: rgba(154, 154, 154, 1);font-size: 14px;">较昨日同期</span>
</div>
</div>
</div>
</div>
<div class="right">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="34"
height="34" viewBox="0 0 34 34" fill="none">
<rect x="0" y="0" width="34" height="34" rx="4" fill="#186DF5"></rect>
<path
d="M8.72815 24.0051C8.31696 24.0051 7.9743 24.3478 7.9743 24.759C7.9743 25.1702 8.31696 25.5128 8.72815 25.5128L16.2256 25.5128C16.6368 25.5128 16.9794 25.1702 16.9794 24.759C16.9794 24.3478 16.6368 24.0051 16.2256 24.0051L8.72815 24.0051ZM8.74186 19.4957C8.33067 19.4957 7.988 19.8384 7.988 20.2496C7.988 20.6608 8.33067 21.0034 8.74186 21.0034L13.2513 21.0034C13.6625 21.0034 14.0051 20.6608 14.0051 20.2496C14.0051 19.8384 13.6625 19.4957 13.2513 19.4957L8.74186 19.4957ZM5 16.494C5 15.6716 5.67162 15 6.494 15L27.506 15C28.3284 15 29 15.6716 29 16.494L29 26.6505C29 27.6648 28.1776 28.5009 27.1496 28.5009L6.85037 28.5009C5.83609 28.5009 5 27.6785 5 26.6505L5 16.494Z"
fill="#FFFFFF"></path>
<path
d="M17.7242 12.4974L17.7242 6.494C17.7242 5.67162 18.3959 5 19.2182 5L23.4124 5C24.0018 5 24.5638 5.28784 24.9201 5.76756L28.9772 12.4974L28.9772 12.5385C29.0458 13.2787 28.3467 13.9366 27.6066 13.9914L19.2182 13.9914C18.3959 13.9914 17.7242 13.3198 17.7242 12.4974ZM14.7499 13.9914L6.3753 13.9914C5.63515 13.9229 4.93612 13.2787 5.00466 12.5385L5.00466 12.5248L5.00466 12.4974L9.06177 5.76756C9.40443 5.28784 9.96639 5 10.5695 5L14.7637 5C15.586 5 16.2577 5.67162 16.2577 6.494L16.2577 12.4974C16.2577 13.3198 15.586 13.9914 14.7499 13.9914Z"
fill="#FFFFFF"></path>
</svg>
</div>
</div>
</div>
<div style="margin-top: 30px;">
<div class="menu" style="background-color: #F2F2F2; padding: 20px;">
<el-row gutter="30">
<el-col :span="3">
<el-input placeholder="请输入备件名称"></el-input>
</el-col>
<el-col :span="3">
<el-select placeholder="设备类型">
</el-select>
</el-col>
<el-col :span="3">
<el-select placeholder="备件类别">
</el-select>
</el-col>
<el-col :span="3">
<el-select placeholder="全部状态">
</el-select>
</el-col>
<el-col :span="8">
<el-button icon="search" type="primary">搜索</el-button>
<el-button icon="refresh">重置</el-button>
</el-col>
</el-row>
</div>
<el-table :data="pagedTableData" border style="width: 100%;margin-top: 10px;">
<el-table-column prop="backupNumber" label="备件编号" />
<el-table-column prop="backupName" label="备件名称" />
<el-table-column prop="equipmentType" label="设备类型" />
<el-table-column prop="specificationModel" label="规格型号" />
<el-table-column prop="inventoryStatus" label="库存状态" />
<el-table-column prop="inventoryQuantity" label="库存数量" />
<el-table-column label="安全库存">
<template #default="scope">
<el-tag :type="getTagType(scope.row.safetyStockStatus)">
{{ scope.row.safetyStockStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="handleDetail(scope.row)">详情</el-button>
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-section">
<div class="pagination-info">
显示第{{ (currentPage - 1) * pageSize + 1 }}{{ Math.min(currentPage * pageSize, total) }}共有{{
total }}条记录
</div>
<div class="pagination-controls">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total" background>
</el-pagination>
</div>
</div>
</div>
</el-card>
</div>
</template>
<style scoped lang="scss">
.title {
font-weight: bold;
font-size: 22px;
font-weight: 400;
letter-spacing: 0px;
line-height: 28.6px;
color: rgba(10, 14, 26, 1);
}
.list {
margin-top: 30px;
display: flex;
gap: 140px;
justify-content: space-between;
}
.item {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
}
/* 分页区域样式 */
.pagination-section {
background-color: #fff;
padding: 16px 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination-info {
font-size: 14px;
color: #606266;
}
.pagination-controls .el-pagination {
margin: 0;
}
.pagination-controls .el-pagination__sizes {
margin-right: 20px;
}
.pagination-controls .el-pagination button {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li {
min-width: 32px;
height: 32px;
line-height: 32px;
border-radius: 4px;
}
.pagination-controls .el-pagination .el-pager li.active {
background-color: #409eff;
color: #fff;
}
</style>
<script setup>
import TitleComponent from '@/components/TitleComponent';
// 计算属性:根据当前页码和每页条数获取分页后的数据
const tableData = ref([
{
backupNumber: 'SOL-2023-001',
backupName: '光伏逆变器模块',
equipmentType: '光伏设备',
specificationModel: 'SGGKTL-M',
inventoryStatus: '12个',
inventoryQuantity: '5个',
safetyStockStatus: '正常'
},
{
backupNumber: 'SOL-2023-001',
backupName: '光伏逆变器模块',
equipmentType: '光伏设备',
specificationModel: 'SGGKTL-M',
inventoryStatus: '12个',
inventoryQuantity: '5个',
safetyStockStatus: '正常'
},
{
backupNumber: 'SOL-2023-001',
backupName: '光伏逆变器模块',
equipmentType: '光伏设备',
specificationModel: 'SGGKTL-M',
inventoryStatus: '12个',
inventoryQuantity: '5个',
safetyStockStatus: '正常'
},
{
backupNumber: 'SOL-2023-001',
backupName: '光伏逆变器模块',
equipmentType: '光伏设备',
specificationModel: 'SGGKTL-M',
inventoryStatus: '12个',
inventoryQuantity: '5个',
safetyStockStatus: '正常'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '3套',
inventoryQuantity: '5套',
safetyStockStatus: '低库存'
},
{
backupNumber: 'SOL-2023-001',
backupName: '光伏逆变器模块',
equipmentType: '光伏设备',
specificationModel: 'SGGKTL-M',
inventoryStatus: '12个',
inventoryQuantity: '5个',
safetyStockStatus: '正常'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '3套',
inventoryQuantity: '5套',
safetyStockStatus: '低库存'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '0套',
inventoryQuantity: '2套',
safetyStockStatus: '缺货'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '3套',
inventoryQuantity: '5套',
safetyStockStatus: '低库存'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '3套',
inventoryQuantity: '5套',
safetyStockStatus: '低库存'
},
{
backupNumber: 'WIN-2023-045',
backupName: '风力发电机轴承',
equipmentType: '风电设备',
specificationModel: '6318-2RS1/C3',
inventoryStatus: '3套',
inventoryQuantity: '5套',
safetyStockStatus: '低库存'
}
])
// 当前页码
const currentPage = ref(1);
// 每页条数 - 与分页控件默认值保持一致
const pageSize = ref(10);
// 总条数 - 从原始数据计算得出
const total = ref(tableData.value.length);
const pagedTableData = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return tableData.value.slice(startIndex, endIndex);
});
// 当前页码改变
const handleCurrentChange = (val) => {
currentPage.value = val;
};
// 根据安全库存状态获取标签类型
const getTagType = (status) => {
if (status === '正常') {
return 'success'
} else if (status === '低库存') {
return 'warning'
} else if (status === '缺货') {
return 'danger'
}
return ''
}
// 编辑操作方法
const handleEdit = (row) => {
console.log('编辑', row)
// 这里可以编写编辑的逻辑,比如跳转到编辑页面等
}
// 详情操作方法
const handleDetail = (row) => {
console.log('详情', row)
// 这里可以编写查看详情的逻辑
}
// 删除操作方法
const handleDelete = (row) => {
console.log('删除', row)
// 这里可以编写删除的逻辑,比如提示确认删除,然后从表格数据中移除等
}
</script>

View File

@ -0,0 +1,75 @@
<template>
<div class="allAlarms">
<el-row>
<el-col :span="12">
<TitleComponent title="所有告警"></TitleComponent>
</el-col>
<el-col :span="12">
<el-row class="right-align-row">
<el-col :span="8">
<el-input prefix-icon="search" placeholder="搜索告警信息"></el-input>
</el-col>
<el-col :span="4" style="text-align: right;">
<el-button icon="download" type="primary">
导出数据
</el-button>
</el-col>
</el-row>
</el-col>
</el-row>
<el-table :data="tableData" style="width: 100%;">
<el-table-column label="告警编号" prop="id"></el-table-column>
<el-table-column label="告警名称" prop="title"></el-table-column>
<el-table-column label="告警等级" prop="level"></el-table-column>
<el-table-column label="告警时间" prop="alarmTime"></el-table-column>
<el-table-column label="负责人" prop="responsible"></el-table-column>
<el-table-column label="告警描述" prop="description"></el-table-column>
<el-table-column label="状态">
<template #default="scope">
<el-tag
:type="scope.row.status === 1 ? 'success' : scope.row.status === 2 ? 'warning' : 'danger'">{{
scope.row.status === 1 ? '已解决' : scope.row.status === 2 ? '待解决' : '未解决' }}</el-tag>
</template>
</el-table-column>
</el-table>
<div style="background-color: #fff;padding: 20px 0;">
<el-pagination layout="prev, pager, next, jumper,sizes" :total="totalRecords"
v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]"
@current-change="handlePageChange" @size-change="handleSizeChange"></el-pagination>
</div>
</div>
</template>
<style scoped>
.allAlarms {
background-color: #F2F8FC;
padding: 20px;
}
.right-align-row {
display: flex;
justify-content: flex-end;
}
</style>
<script setup>
import TitleComponent from '@/components/TitleComponent';
const pageSize = ref(20);
const totalRecords = ref(100);
const tableData = computed(() => {
return Array.from({ length: 15 }).map((_, index) => {
return {
id: index,
title: `逆变器温度过高`,
id: `INV-2023-003`,
level: `二级`,
alarmTime: '2025-09-18 18:00',
// 预计解决时间
resolveTime: '2025-09-19 18:00',
// 负责人
responsible: '李华(现场运维组)',
description: `AAAABBBCCCDE...`,
status: Math.floor(Math.random() * 3) + 1
}
})
})
</script>

View File

@ -1,7 +1,7 @@
<template>
<el-card shadow="never" style="border-radius: 10px;">
<div style="margin-bottom: 20px;display: flex;align-items: center;justify-content: right;">
<span style="margin-right: 5px;color: rgba(113, 128, 150, 1);font-size: 14px;">
<span style="margin-right: 5px;color: rgba(113, 128, 150, 1);font-size: 14px;" @click="handleClick">
查看全部告警信息
</span>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="8" height="8"
@ -207,4 +207,11 @@
}
}
</style>
<script setup></script>
<script setup>
const router = useRouter();
const handleClick = () => {
console.log('查看全部告警信息');
router.push('/pvSystem/allAlarms');
}
</script>

View File

@ -1,4 +1,5 @@
<template>
<el-card shadow="never" style="border-radius: 10px;">
<el-form :inline="true" :model="formInline" label-width="120" style="display: flex; justify-content: center;">
<el-form-item label="规则编号">
@ -53,11 +54,37 @@
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</el-card>
<el-dialog v-model="dialogVisible" width="1000" id="custom-dialog" class="no-header-dialog normal">
<div class="alert-content">
<div class="img">
<img src="/assets/dialog1.png" alt="">
</div>
</div>
</el-dialog>
</template>
<style lang="scss" scoped></style>
<style lang="scss">
// #custom-dialog {
// padding: 0;
// .el-dialog__header {
// display: none;
// }
// .el-dialog__body {
// padding: 0 !important;
// }
// .alert-content {
// padding: 80px;
// background: linear-gradient(180deg, rgba(0, 119, 255, 0.23) 0%, rgba(255, 255, 255, 0) 100%);
// }
// }</style>
<script setup>
const formInline = ref({})
const total = ref(0);
const loading = ref(false);
const dialogVisible = ref(true);
const listData = [
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 1 },
{ id: "INV-2023-001", shuchu: "12.8kw", xiaolv: "98.2%", wendu: "42℃", fadian: "158.5kWh", status: 1 },

View File

@ -177,11 +177,15 @@
<QiXiang />
</el-col>
</el-row>
<el-row>
<div>
<el-row :gutter="20"> <!-- gutter列之间的间距单位px控制垂直/水平间距 -->
<!-- 标题列占满12列栅格系统默认12列 -->
<el-col> <!-- span=12 表示占满一行宽度 -->
<TitleComponent title="逆变器运行状态" :fontLevel="2" />
</div>
<status style="width: 100%;" />
</el-col>
<!-- status组件列同样占满12列与标题垂直排列 -->
<el-col>
<status style="width: 100%;" /> <!-- 确保组件宽度100%不溢出 -->
</el-col>
</el-row>
<el-row style="display: flex;">
<el-col :span="6" style="display: flex;flex-direction: column;">

View File

@ -0,0 +1,149 @@
<template>
<el-card style="border-radius: 15px;">
<el-row gutter="35">
<el-col :span="8" class="status-card">
<div class="title">设备状态</div>
<!-- gutter设置为20创建左右间隙 -->
<el-row gutter="20" style="width: 100%;">
<!-- 一行2个每个占12格24/2=12 -->
<el-col :span="12">
<div class="item">
<div class="status">在线</div>
<div class="count" style="color: rgba(0, 184, 122, 1);">56</div>
</div>
</el-col>
<el-col :span="12">
<div class="item">
<div class="status">离线</div>
<div class="count" style="color: rgba(102, 102, 102, 1);">10</div>
</div>
</el-col>
<el-col :span="12">
<div class="item">
<div class="status">网络延迟</div>
<div class="count" style="color: rgba(255, 208, 35, 1);">8</div>
</div>
</el-col>
<el-col :span="12">
<div class="item">
<div class="status">故障</div>
<div class="count" style="color: rgba(227, 39, 39, 1);">1</div>
</div>
</el-col>
</el-row>
</el-col>
<el-col :span="16" class="alarm-card">
<div class="title">最近报警</div>
<el-row class="list">
<el-col>
<div class="item red">
<div class="sub-title">
异常移动检测
</div>
<div class="content">
A区b栋102 | 今天08:23
</div>
</div>
</el-col>
<el-col>
<div class="item yellow">
<div class="sub-title">
设备连接不稳定
</div>
<div class="content">
A区b栋102 | 今天08:23
</div>
</div>
</el-col>
<el-col>
<div class="item red">
<div class="sub-title">
异常移动检测
</div>
<div class="content">
A区b栋102 | 今天08:23
</div>
</div>
</el-col>
</el-row>
</el-col>
</el-row>
</el-card>
</template>
<style scoped>
.title {
font-family: "Alibaba-PuHuiTi-Bold";
font-size: 20px;
font-weight: 400;
letter-spacing: 0px;
line-height: 24px;
color: rgb(0, 30, 59);
text-align: left;
vertical-align: top;
margin-bottom: 20px;
}
.status-card {
.item {
background: rgba(245, 245, 245, 0.2);
border: 1px solid rgba(230, 233, 238, 1);
margin-bottom: 20px;
border-radius: 15px;
padding: 15px 30px;
text-align: center;
.status {
color: rgba(175, 175, 175, 1);
font-size: 20px;
margin-bottom: 10px;
font-weight: 500;
}
.count {
font-size: 18px;
font-weight: bold;
text-align: center;
}
}
}
.alarm-card {
.item {
border: 1px solid #E6E9EE;
border-radius: 10px;
padding: 10px;
margin-bottom: 10px;
position: relative;
padding-left: 20px;
text-align: left;
}
.red {
::v-deep::before {
position: absolute;
content: '';
background-color: rgba(227, 39, 39, 1);
height: 100%;
width: 7px;
border-radius: 7px 0px 0px 7px;
top: 0;
left: 1px;
}
}
.yellow {
::v-deep::before {
position: absolute;
content: '';
background-color: rgba(255, 208, 35, 1);
height: 100%;
width: 7px;
border-radius: 7px 0px 0px 7px;
top: 0;
left: 1px;
}
}
}
</style>
<script setup></script>

View File

@ -0,0 +1,320 @@
<template>
<el-card style="border-radius: 15px;">
<div class="title-box">
视频管理
</div>
<el-row class="cunchu">
<el-col>
<div class="subtitle">存储状态</div>
</el-col>
<el-col style="position: relative;">
<div ref="chartRef" style="height: 220px;width: 100%;"></div>
<div class="text">别担心还有很多存储空间</div>
</el-col>
</el-row>
<el-row>
<el-col class="lxcc">
<div class="top">
<div class="subtitle">录像存储设置</div>
<div class="edit">编辑</div>
</div>
<div class="content">
<div class="item">
<div class="title">
存储模式
</div>
<div class="text">
循环覆盖
</div>
</div>
<div class="item">
<div class="title">
保留天数
</div>
<div class="text">
30
</div>
</div>
<div class="item">
<div class="title">
录像质量
</div>
<div class="text">
高清1080P
</div>
</div>
<div class="item">
<div class="title">
备份记录
</div>
<div class="text">
开启
</div>
</div>
<div class="item">
<div class="title">
备份时间
</div>
<div class="text">
每日02:00
</div>
</div>
</div>
</el-col>
</el-row>
<el-row>
<el-col>
<el-row>
<el-col>
<div class="subtitle">历史视频查询</div>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-select placeholder="全部摄像头">
</el-select>
</el-col>
<el-col :span="12">
<el-date-picker v-model="value1" type="daterange" range-separator="至" style="width: 100%;"
start-placeholder="开始" end-placeholder="结束" :size="size" />
</el-col>
<el-col :span="4">
<el-button type="primary">搜索</el-button>
</el-col>
</el-row>
</el-col>
<el-col>
<el-row>
<el-table :data="data" height="200px" max-height="200px">
<el-table-column label="摄像头" prop="name" width="100">
</el-table-column>
<el-table-column label="日期" prop="date" width="120">
</el-table-column>
<el-table-column label="时长" prop="duration">
</el-table-column>
<el-table-column label="大小" prop="size">
</el-table-column>
<el-table-column label="操作" fixed="right" width="100">
<template #default="scope">
<div class="svg-icon">
<img src="/assets/svg/play.svg" alt="">
<img src="/assets/svg/download.svg" alt="">
<img src="/assets/svg/delete.svg" alt=""></img>
</div>
</template>
</el-table-column>
</el-table>
<div class="pagination" v-if="activeTab !== 'record'">
<el-pagination layout="prev, pager, next, jumper" :total="totalRecords"
v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]"
@current-change="handlePageChange" @size-change="handleSizeChange"></el-pagination>
</div>
</el-row>
</el-col>
</el-row>
</el-card>
</template>
<style scoped lang="scss">
.title-box {
font-family: "Alibaba-PuHuiTi-Bold";
font-size: 20px;
font-weight: 400;
letter-spacing: 0px;
line-height: 24px;
color: rgba(0, 30, 59, 1);
text-align: left;
vertical-align: top;
}
.subtitle {
font-size: 16px;
color: #001E3B;
position: relative;
margin: 10px 0;
::v-deep::before {
position: absolute;
width: 5px;
height: 5px;
content: '';
border-radius: 50%;
background-color: rgba(24, 109, 245, 1);
top: 50%;
left: -8px;
transform: translateY(-50%);
}
}
.cunchu {
.text {
position: absolute;
bottom: 60px;
width: 100%;
color: rgba(113, 128, 150, 1);
font-size: 14px;
text-align: center;
}
}
.lxcc {
.top {
display: flex;
justify-content: space-between;
align-items: center;
.edit {
color: rgba(0, 30, 59, 1);
font-size: 14px;
cursor: pointer;
}
}
.content {
background-color: #F2F8FC;
border-radius: 10px;
.item {
display: flex;
border-bottom: 1px solid #E3EDFF;
padding: 10px 0;
text-align: center;
}
.title {
width: 50%;
}
.text {
width: 50%;
}
}
}
.svg-icon {
display: flex;
justify-content: space-around;
img {
width: 15px;
height: 15px;
display: block;
cursor: pointer;
}
}
</style>
<script setup>
import { ref, onMounted } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let myChart = null;
const pageSize = ref(20);
const totalRecords = ref(100);
const initChart = () => {
myChart = echarts.init(chartRef.value);
var option = {
// 调整图表整体位置,向上移动以减少底部空白
// 添加首尾单位标识
graphic: [
// 起始单位 (0TB)
{
type: 'text',
left: '24%',
top: '50%', // 上移文本位置
style: {
text: '0TB',
fontSize: 14,
color: '#666'
}
},
// 结束单位 (10TB)
{
type: 'text',
right: '24%',
top: '50%', // 上移文本位置
style: {
text: '10TB',
fontSize: 14,
color: '#666'
}
},
// 中间显示当前值和百分比(换行显示)
{
type: 'text',
left: 'center',
top: '40%', // 调整到48%位置
style: {
text: '6.68TB\n48%',
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
lineHeight: 20 // 增加行高以确保换行效果
}
}
],
series: [
{
type: 'gauge',
radius: '85%', // 适当减小半径
center: ['50%', '50%'], // 上移图表中心位置
startAngle: 170, // 开始角度(左侧)
endAngle: 10, // 结束角度(右侧)
max: 10, // 最大值设置为10TB
axisLine: {
lineStyle: {
width: 40, // 保持条的粗细
color: [
[0.67, '#4863FF'], // 当前值6.7TB对应67%
[1, '#E5E7EB'] // 未用部分颜色
]
}
},
// 隐藏所有刻度相关元素
axisTick: {
show: false
},
splitLine: {
show: false
},
axisLabel: {
show: false
},
// 隐藏指针
pointer: {
show: false
},
// 隐藏默认详情
detail: {
show: false
},
// 当前值6.7TB
data: [{ value: 6.7 }]
}
]
};
myChart.setOption(option);
};
// 监听窗口大小变化,重新绘制图表
const handleResize = () => {
if (myChart) {
myChart.resize();
}
};
const data = computed(() => {
return Array.from({ length: 10 }, (_, i) => ({
name: `摄像头${i + 1}`,
date: '25.03.12-10:00',
duration: '4小时',
size: '2.4GB'
}));
});
onMounted(() => {
initChart();
window.addEventListener('resize', handleResize);
});
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>

View File

@ -0,0 +1,177 @@
<template>
<el-row style="height: 100%;">
<el-card style="width: 100%;border-radius: 12px;height: 100%;">
<div style="display: flex;width: 100%;justify-content: space-between;align-items: center;">
<TitleComponent title="实时视频监控" subtitle="查看所有已完成的巡检记录,跟进巡检报告" />
<span style="color: rgba(24, 109, 245, 1);display: flex;align-items: center; cursor: pointer;">
<span>
2025/06/30 12:00
刷新数据
</span>
<el-icon>
<Refresh />
</el-icon>
</span>
</div>
<div class="video-container">
<el-row gutter="20">
<!-- 扩展布局左侧大视频 + 右侧小视频列 -->
<template v-if="isExpanded">
<!-- 左侧大视频 16 -->
<el-col :span="16">
<div class="item large" @click="() => { isExpanded = false; }">
<img :src="videoList[activeIndex].url" alt="">
<div class="title" v-if="isExpanded"
style="display: flex;justify-content: space-between;">
<div class="text">
{{ videoList[activeIndex].name }}
</div>
<div class="tools">
<img src="/assets/svg/huanyuan.svg" alt=""></img>
<img src="/assets/svg/quanpin.svg" alt=""></img>
<img src="/assets/svg/jietu.svg" alt="">
</div>
</div>
<div class="title" v-else>{{ videoList[activeIndex].name }}</div>
</div>
</el-col>
<!-- 右侧小视频列 8 -->
<el-col :span="8">
<el-row gutter="20">
<el-col :span="24" v-for="i in 3" :key="i">
<div class="item small" @click="() => {
activeIndex = videoList.length - 3 + i - 1;
}">
<img :src="videoList[videoList.length - 3 + i - 1].url" alt="">
<div class="title">{{ videoList[videoList.length - 3 + i - 1].name }}</div>
</div>
</el-col>
</el-row>
</el-col>
</template>
<!-- 普通布局所有视频均匀排列 -->
<template v-else>
<el-col :span="8" v-for="(item, index) in videoList" :key="index">
<div class="item" @click="() => {
activeIndex = index;
isExpanded = true;
}">
<img :src="item.url" alt="">
<div class="title">{{ item.name }}</div>
</div>
</el-col>
</template>
</el-row>
<el-row v-if="isExpanded">
<div class="pagination" v-if="activeTab !== 'record'">
<el-pagination layout="prev, pager, next, jumper, sizes" :total="totalRecords"
v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[20, 50, 100]"
@current-change="handlePageChange" @size-change="handleSizeChange"></el-pagination>
</div>
</el-row>
</div>
</el-card>
</el-row>
</template>
<script setup>
import { ref } from 'vue';
import { Refresh } from '@element-plus/icons-vue';
import TitleComponent from '@/components/TitleComponent';
const activeIndex = ref(-1); // 初始无选中,选中后为对应索引
const isExpanded = ref(false); // 初始为普通布局
const pageSize = ref(20);
const totalRecords = ref(100);
const videoList = ref([
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c144e8ba276a0e8a4a55c2.jpeg#bab7e2e06aae943525cacb13bd63e30d'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c144efb5e8b987e5ca6462.jpeg#5523cf094b2f8c3a79ea4eb330c99a30'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c144fbbad414f81995e90c.webp#230d8ca5ca39982518439db26e0ea899'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c1450640d5d2a02e2540b2.webp#adad2379a0b04d6968364e4fb1133f77'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c14543d56431f9d6f6808e.webp#16f0a0d8fab4f8ff3b39b04bfabac054'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c14578d56431f9d6f68981.jpg#e77150417f28a971be4846eb0be90373'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c145e03f22157da619a7ce.png#546ff44289a22bf175e1eca1f69cd8f9'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c1461fb5e8b987e5caa293.jpeg#870e4d2b88b487ecb8f2f0b956c45c08'
},
{
name: 'A区d厂',
url: 'https://img.js.design/assets/img/68c1462dcbf9ed2271880b95.webp#ae7ae94ca84ce980e2d2281869335f06'
}
]);
</script>
<style scoped lang="scss">
.video-container {
.item {
height: 220px;
margin-bottom: 20px;
position: relative;
border: 2px solid rgba(45, 119, 249, 1);
cursor: pointer;
img {
width: 100%;
height: 100%;
display: block;
}
.title {
position: absolute;
padding: 5px 0;
padding-left: 10px;
font-size: 14px;
border-radius: 8px 8px 0px 0px;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.73) 0%, rgba(0, 0, 0, 0.33) 100%);
width: 100%;
bottom: 0;
color: #fff;
}
}
// 大视频样式(高度与右侧三个小视频总高度对齐,考虑间距)
.large {
height: calc(220px * 3 + 20px * 2); // 高度为3个小视频高度加上2个间距
.tools {
display: flex;
img {
width: 20px;
height: 20px;
display: block;
margin: 0 10px;
}
}
}
// 小视频样式(保持原高度,适配右侧单列)
.small {
height: 220px;
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
<template>
<div class="security-surveillance">
<el-row style="display: flex;align-items: center;">
<el-col :span="12">
<TitleComponent title="安防监控管理" subtitle="实时监控、历史录像查询与视频管理" />
</el-col>
<!-- 关键给内层 el-col 套上 el-row -->
<el-col :span="12" style="text-align: right;">
<el-row :gutter="16" justify="end"> <!-- gutter 可选单位px控制列间距 -->
<el-col :span="6" :push="3">
<el-input placeholder="搜索逆变器..." prefix-icon="search" />
</el-col>
<el-col :span="6" :push="3">
<el-select placeholder="请选择逆变器状态">
<el-option label="所有状态" value="0"></el-option>
</el-select>
</el-col>
<el-col :span="6">
<el-button type="primary">刷新数据<el-icon>
<Refresh />
</el-icon>
</el-button>
</el-col>
</el-row> <!-- 闭合内层 el-row -->
</el-col>
</el-row>
<el-row>
<Top />
</el-row>
<el-row style="margin-top: 20px;" :gutter="25">
<el-col :span="18">
<Spjk />
</el-col>
<el-col :span="6">
<Spgl />
</el-col>
</el-row>
<el-row style="margin-top: 20px;">
<el-col>
<Sbzt />
</el-col>
</el-row>
</div>
</template>
<style scoped>
.security-surveillance {
padding: 20px;
background-color: #F2F8FC;
}
</style>
<script setup>
import TitleComponent from "@/components/TitleComponent";
import Top from "./components/top"
import Spjk from "./components/spjk"
import Spgl from "./components/spgl";
import Sbzt from "./components/sbzt";
</script>

File diff suppressed because it is too large Load Diff

View File

@ -129,12 +129,12 @@
<el-dialog
v-model="createTaskDialogVisible"
title="新建报修任务"
width="800px"
width="900px"
:before-close="handleCancelCreateTask"
custom-class="beautiful-dialog"
center
>
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="100px" class="elegant-form">
<el-form ref="createTaskFormRef" :model="createTaskForm" :rules="createTaskRules" label-width="120px" class="elegant-form">
<el-form-item label="报修名称*" prop="repairName">
<el-input v-model="createTaskForm.repairName" placeholder="简要描述报修内容" />
</el-form-item>
@ -143,10 +143,8 @@
<el-col :span="12">
<el-form-item label="报修类型*" prop="repairType">
<el-select v-model="createTaskForm.repairType" placeholder="请选择类型">
<el-option label="设备故障" value="device" />
<el-option label="硬件故障" value="device" />
<el-option label="软件故障" value="software" />
<el-option label="网络故障" value="network" />
<el-option label="环境问题" value="environment" />
</el-select>
</el-form-item>
</el-col>
@ -161,6 +159,12 @@
</el-col>
</el-row>
<el-form-item label="指派维修人*" prop="sendPerson">
<el-select v-model="createTaskForm.sendPerson" placeholder="请选择维修人" :loading="loadingUsers">
<el-option v-for="user in usersList" :key="user.id" :label="user.name" :value="user.id" />
</el-select>
</el-form-item>
<el-form-item label="详细描述*" prop="detailedDescription">
<el-input v-model="createTaskForm.detailedDescription" type="textarea" :rows="4" placeholder="详细描述故障现象、发生时间、位置等信息" />
</el-form-item>
@ -169,13 +173,31 @@
<el-input v-model="createTaskForm.faultLocation" placeholder="例如A区102" />
</el-form-item>
<el-form-item label="上传图片(可选)">
<div class="upload-container">
<div class="upload-box">
<i class="el-icon-plus avatar-uploader-icon"></i>
<div class="upload-text">点击或拖拽图片至此处上传</div>
<div class="upload-hint">支持JPGPNG格式最多3张</div>
<el-form-item label="上传图片(可选)" width="100%">
<file-upload v-model="createTaskForm.file" :fileType="['png', 'jpg', 'jpeg']">
<div class="upload-container">
<div class="upload-box">
<i class="el-icon-plus avatar-uploader-icon"></i>
<div class="upload-text">点击或拖拽图片至此处上传</div>
<div class="upload-hint">支持JPGPNG格式最多3张</div>
</div>
</div>
</file-upload>
<!-- <el-upload
class="avatar-uploader"
:file-list="createTaskForm.fileList"
:on-change="handleFileChange"
:before-remove="beforeRemove"
:on-remove="handleRemove"
:auto-upload="false"
multiple
:limit="3"
:on-exceed="handleExceed"
>
</el-upload> -->
<div v-if="createTaskForm.fileList && createTaskForm.fileList.length > 0" class="upload-tip">
<span style="color: #1989fa">已选择{{ createTaskForm.fileList.length }}张图片将在提交时上传</span>
</div>
</el-form-item>
@ -196,7 +218,7 @@
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancelCreateTask">取消</el-button>
<el-button type="primary" @click="handleSaveTask">保存计划</el-button>
<el-button type="primary" @click="handleSaveTask">保存报修任务</el-button>
</span>
</template>
</el-dialog>
@ -205,10 +227,12 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import { baoxiulist, baoxiuDetail, delbaoxiu, updatebaoxiu, addbaoxiu, uploadbaoxiu } from '@/api/zhinengxunjian/baoxiou/index';
import { xunjianUserlist } from '@/api/zhinengxunjian/xunjian';
import { ElMessage } from 'element-plus';
// 激活的选项卡
const activeTab = ref('task');
@ -217,128 +241,165 @@ const taskStatus = ref('');
const planType = ref('');
const executor = ref('');
// 任务数据 - 恢复优先级标签样式和预计完成时间
const tasks = ref([
{
title: '服务器A1电源故障',
status: 'executing',
statusText: '处理中',
leftLineClass: 'left-line-high',
priorityClass: 'priority-high', // 恢复优先级标签样式类
priority: '高优先级',
reportTime: '2025-06-16 08:30',
reporter: '李明',
maintainer: '张明',
expectedCompleteTime: '2025-06-16 12:00', // 恢复预计完成时间
actionText: '跟进',
actionClass: 'follow-btn'
},
{
title: '机房环境检查',
status: 'pending',
statusText: '待处理',
leftLineClass: 'left-line-medium',
priorityClass: 'priority-medium', // 恢复优先级标签样式类
priority: '中优先级',
reportTime: '2025-06-16 08:30',
reporter: '张伟',
maintainer: '未分配',
expectedCompleteTime: '2025-06-16 18:00', // 恢复预计完成时间
actionText: '分配',
actionClass: 'assign-btn'
},
{
title: '测试软件授权过期',
status: 'executing',
statusText: '处理中',
leftLineClass: 'left-line-low',
priorityClass: 'priority-low', // 恢复优先级标签样式类
priority: '低优先级',
reportTime: '2025-06-16 08:30',
reporter: '李明',
maintainer: '李阳',
expectedCompleteTime: '2025-06-17 09:00', // 恢复预计完成时间
actionText: '跟进',
actionClass: 'follow-btn'
},
{
title: '打印机卡纸故障',
status: 'completed',
statusText: '已完成',
leftLineClass: 'left-line-completed',
priorityClass: 'priority-low', // 恢复优先级标签样式类
priority: '低优先级',
reportTime: '2025-06-17 08:30',
reporter: '李明',
maintainer: '张明',
expectedCompleteTime: '2025-06-17 10:00', // 恢复预计完成时间
completeTime: '2025-06-17 09:15',
actionText: '评价',
actionClass: 'evaluate-btn'
},
{
title: '服务器A1电源故障',
status: 'executing',
statusText: '处理中',
leftLineClass: 'left-line-high',
priorityClass: 'priority-high',
priority: '高优先级',
reportTime: '2025-06-16 08:30',
reporter: '李明',
maintainer: '张明',
expectedCompleteTime: '2025-06-16 12:00',
actionText: '跟进',
actionClass: 'follow-btn'
},
{
title: '机房环境检查',
status: 'pending',
statusText: '待处理',
leftLineClass: 'left-line-medium',
priorityClass: 'priority-medium',
priority: '中优先级',
reportTime: '2025-06-16 08:30',
reporter: '张伟',
maintainer: '未分配',
expectedCompleteTime: '2025-06-16 18:00',
actionText: '分配',
actionClass: 'assign-btn'
},
{
title: '测试软件授权过期',
status: 'executing',
statusText: '处理中',
leftLineClass: 'left-line-low',
priorityClass: 'priority-low',
priority: '低优先级',
reportTime: '2025-06-16 08:30',
reporter: '李明',
maintainer: '李阳',
expectedCompleteTime: '2025-06-17 09:00',
actionText: '跟进',
actionClass: 'follow-btn'
},
{
title: '打印机卡纸故障',
status: 'completed',
statusText: '已完成',
leftLineClass: 'left-line-completed',
priorityClass: 'priority-low',
priority: '低优先级',
reportTime: '2025-06-17 08:30',
reporter: '李明',
maintainer: '张明',
expectedCompleteTime: '2025-06-17 10:00',
completeTime: '2025-06-17 09:15',
actionText: '评价',
actionClass: 'evaluate-btn'
}
]);
// 任务数据 - 初始为空数组
const tasks = ref([]);
// 分页相关
const currentPage = ref(1);
const pageSize = ref(8);
const total = ref(tasks.value.length);
const total = ref(0);
const loading = ref(false);
// 获取报修任务列表
defineExpose({ getTaskList });
async function getTaskList() {
loading.value = true;
try {
const res = await baoxiulist({
pageNum: currentPage.value,
pageSize: pageSize.value,
status: taskStatus.value,
type: planType.value,
executor: executor.value
});
if (res.code === 200 && res.rows) {
total.value = res.total || 0;
// 将API返回的数据转换为前端显示所需的格式
tasks.value = res.rows.map((item) => ({
id: item.id,
title: item.name || '未命名报修任务',
status: mapStatusToKey(item.status),
statusText: getStatusText(item.status),
leftLineClass: getLeftLineClass(item.status, item.level),
priorityClass: getPriorityClass(item.level),
priority: getPriorityText(item.level),
reportTime: formatDate(item.reportTime),
reporter: item.reportName || '未知报修人',
maintainer: item.sendPerson ? `维修人ID: ${item.sendPerson}` : '未分配',
expectedCompleteTime: getExpectedCompleteTime(item.status),
completeTime: item.completeTime ? formatDate(item.completeTime) : '',
actionText: getActionText(item.status),
actionClass: getActionClass(item.status),
reportInfo: item.reportInfo,
position: item.position,
fileUrl: item.fileUrl
}));
} else {
tasks.value = [];
total.value = 0;
console.error('获取报修任务列表失败:', res.msg);
}
} catch (error) {
console.error('获取报修任务列表异常:', error);
tasks.value = [];
total.value = 0;
} finally {
loading.value = false;
}
}
// 状态映射辅助函数
function mapStatusToKey(status) {
const statusMap = {
'1': 'pending', // 待处理
'2': 'executing', // 处理中
'3': 'completed' // 已完成
};
return statusMap[status] || 'pending';
}
// 获取状态文本
function getStatusText(status) {
const statusMap = {
'1': '待处理',
'2': '处理中',
'3': '已完成'
};
return statusMap[status] || '未知状态';
}
// 获取优先级文本
function getPriorityText(level) {
const levelMap = {
'1': '低优先级',
'2': '中优先级',
'3': '高优先级'
};
return levelMap[level] || '普通优先级';
}
// 获取优先级样式类
function getPriorityClass(level) {
const levelMap = {
'1': 'priority-low',
'2': 'priority-medium',
'3': 'priority-high'
};
return levelMap[level] || 'priority-low';
}
// 获取左侧状态线样式类
function getLeftLineClass(status, level) {
if (status === '3') return 'left-line-completed';
const levelMap = {
'1': 'left-line-low',
'2': 'left-line-medium',
'3': 'left-line-high'
};
return levelMap[level] || 'left-line-low';
}
// 获取操作按钮文本
function getActionText(status) {
const actionMap = {
'1': '分配',
'2': '跟进',
'3': '评价'
};
return actionMap[status] || '查看';
}
// 获取操作按钮样式类
function getActionClass(status) {
const actionMap = {
'1': 'assign-btn',
'2': 'follow-btn',
'3': 'evaluate-btn'
};
return actionMap[status] || 'view-btn';
}
// 获取预计完成时间(根据状态和优先级估算)
function getExpectedCompleteTime(status) {
if (status === '3') return ''; // 已完成任务不需要显示预计时间
const now = new Date();
// 简单估算待处理任务默认当天18:00前完成处理中任务默认2小时内完成
if (status === '1') {
now.setHours(18, 0, 0, 0);
} else {
now.setHours(now.getHours() + 2);
}
return formatDate(now);
}
// 格式化日期时间
function formatDate(date) {
if (!date) return '';
const d = new Date(date);
if (isNaN(d.getTime())) return '';
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
// 状态排序映射
const statusOrder = {
@ -363,20 +424,22 @@ const pagedTasks = computed(() => {
// 搜索处理
const handleSearch = () => {
currentPage.value = 1; // 重置到第一页
// 实际应用中这里会根据筛选条件过滤数据
getTaskList(); // 调用接口获取数据
};
// 创建报修任务弹窗相关
const createTaskDialogVisible = ref(false);
const createTaskFormRef = ref(null);
const createTaskForm = ref({
repairName: '',
briefDescription: '',
repairType: '',
priority: '',
detailedDescription: '',
faultLocation: '',
contactPerson: '',
contactPhone: ''
contactPhone: '',
sendPerson: '', // 指派维修人
file: '' // 上传的文件列表
});
const createTaskRules = {
@ -386,58 +449,303 @@ const createTaskRules = {
detailedDescription: [{ required: true, message: '请输入详细描述', trigger: 'blur' }],
faultLocation: [{ required: true, message: '请输入故障位置', trigger: 'blur' }],
contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }]
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
sendPerson: [{ required: true, message: '请选择维修人', trigger: 'change' }]
};
// 用户列表(维修人)
const usersList = ref([]);
const loadingUsers = ref(false);
// 获取用户列表
const getUsersList = async () => {
loadingUsers.value = true;
try {
const res = await xunjianUserlist();
// 根据接口返回格式成功码是200用户数据在rows数组中
if (res.code === 200 && res.rows && Array.isArray(res.rows)) {
// 映射用户数据使用id作为valueuserName作为显示名称
usersList.value = res.rows.map((user) => ({
id: user.id,
name: user.userName
}));
} else {
usersList.value = [];
console.error('获取用户列表失败:', res.msg || '未知错误');
}
} catch (error) {
console.error('获取用户列表异常:', error);
usersList.value = [];
} finally {
loadingUsers.value = false;
}
};
const isSubmitting = ref(false); // 防止重复提交的状态标记
const isUploading = ref(false); // 防止重复上传的状态标记
// 上传文件方法
const uploadFiles = async (files) => {
// 防止重复上传
if (isUploading.value) {
ElMessage.warning('数据正在处理中,请稍候...');
return [];
}
try {
isUploading.value = true;
const formData = new FormData();
// 关键修复:将字段名从'files'改为'file',匹配后端要求的'file'字段
files.forEach((file) => {
formData.append('file', file.raw); // 这里将'files'改为'file'
});
const res = await uploadbaoxiu(formData);
if (res.code === 200 && res.data && Array.isArray(res.data)) {
return res.data.map((item) => ({
fileId: item.ossId,
fileUrl: item.url
}));
} else {
console.error('文件上传失败:', res.msg);
throw new Error(res.msg || '文件上传失败');
}
} catch (error) {
console.error('文件上传异常:', error);
if (error.message.includes('重复提交')) {
throw new Error('操作过于频繁,请稍后再试');
}
throw error;
} finally {
isUploading.value = false;
}
};
const handleFileChange = (file, fileList) => {
// 只处理刚添加的文件
if (file.status === 'ready') {
// 验证文件格式和大小
const isJPG = file.raw.type === 'image/jpeg' || file.raw.type === 'image/jpg';
const isPNG = file.raw.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isPNG) {
ElMessage.error('上传图片只能是 JPG/JPEG 或 PNG 格式!');
// 从文件列表中移除不符合要求的文件
createTaskForm.value.fileList = fileList.filter((f) => f.uid !== file.uid);
return;
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!');
// 从文件列表中移除不符合要求的文件
createTaskForm.value.fileList = fileList.filter((f) => f.uid !== file.uid);
return;
}
// 保存验证通过的文件到表单
createTaskForm.value.fileList = fileList;
}
};
const handleExceed = (files, fileList) => {
ElMessage.warning(`当前限制选择 3 张图片,本次选择了 ${files.length} 张,共选择了 ${files.length + fileList.length}`);
};
// 文件移除前的钩子
const beforeRemove = (file, fileList) => {
return window.confirm(`确定移除 ${file.name}`);
};
// 文件移除时的钩子
const handleRemove = (file, fileList) => {
createTaskForm.value.fileList = fileList;
ElMessage.info(`已移除 ${file.name}`);
};
// 创建任务
const handleCreateTask = () => {
const handleCreateTask = async () => {
createTaskDialogVisible.value = true;
// 打开弹窗时获取用户列表
await getUsersList();
};
// 保存报修任务
const handleSaveTask = () => {
// 模拟保存报修任务逻辑
console.log('保存报修任务:', createTaskForm.value);
// 关闭弹窗
createTaskDialogVisible.value = false;
// 重置表单
const handleSaveTask = async () => {
console.log(createTaskForm.value.file);
// 防止重复提交
if (!createTaskFormRef.value || isSubmitting.value) return;
try {
// 设置提交状态为true
isSubmitting.value = true;
// 先验证表单
await createTaskFormRef.value.validate();
// 上传选中的文件
let fileIds = [];
let fileUrls = [];
if (createTaskForm.value.fileList && createTaskForm.value.fileList.length > 0) {
ElMessage.info(`开始上传 ${createTaskForm.value.fileList.length} 张图片...`);
const uploadResults = await uploadFiles(createTaskForm.value.fileList);
// 提取上传结果
fileIds = uploadResults.map((result) => result.fileId);
fileUrls = uploadResults.map((result) => result.fileUrl);
ElMessage.success(`图片上传成功,共 ${uploadResults.length}`);
}
// 准备请求数据
const requestData = {
projectId: 1,
name: createTaskForm.value.repairName,
type: mapRepairType(createTaskForm.value.repairType),
status: '1', // 默认为待处理状态
level: mapPriorityLevel(createTaskForm.value.priority),
sendPerson: parseInt(createTaskForm.value.sendPerson),
reportInfo: createTaskForm.value.detailedDescription,
position: createTaskForm.value.faultLocation,
fileId: fileIds.join(','), // 用逗号分隔多个文件ID
fileUrl: fileUrls.join(','), // 用逗号分隔多个文件URL
reportName: createTaskForm.value.contactPerson,
reportPhone: createTaskForm.value.contactPhone
};
// 调用添加报修任务接口
const res = await addbaoxiu(requestData);
// 检查接口返回是否成功使用code=200作为成功标志
if (res.code === 200) {
// 显示成功提示
ElMessage.success('报修任务创建成功');
// 关闭弹窗
createTaskDialogVisible.value = false;
// 重置表单
resetCreateForm();
// 刷新任务列表
await getTaskList();
} else {
ElMessage.error(`创建失败:${res.msg || '未知错误'}`);
}
} catch (error) {
// 错误处理
if (error instanceof Error) {
ElMessage.error(`操作失败:${error.message}`);
} else if (error === false) {
// 表单验证失败,不做处理
} else {
ElMessage.error('操作失败:未知错误');
}
} finally {
// 无论成功失败,都重置提交状态
isSubmitting.value = false;
}
};
// 重置创建表单
function resetCreateForm() {
createTaskForm.value = {
repairName: '',
briefDescription: '',
repairType: '',
priority: '',
detailedDescription: '',
faultLocation: '',
contactPerson: '',
contactPhone: ''
contactPhone: '',
sendPerson: '',
fileList: []
};
// 这里可以添加成功提示和刷新任务列表的逻辑
};
if (createTaskFormRef.value) {
createTaskFormRef.value.resetFields();
}
}
// 报修类型映射 - 1硬件2软件
function mapRepairType(type) {
const typeMap = {
'device': '1', // 硬件
'software': '2' // 软件
};
return typeMap[type] || '1';
}
// 优先级映射 - 1低优先2中优先3高优先
function mapPriorityLevel(priority) {
const levelMap = {
'low': '1', // 低优先
'medium': '2', // 中优先
'high': '3' // 高优先
};
return levelMap[priority] || '2';
}
// 文件上传前的校验
async function beforeUpload(file) {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/jpg';
const isPNG = file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isPNG) {
ElMessage.error('上传图片只能是 JPG/JPEG 或 PNG 格式!');
return false;
}
if (!isLt2M) {
ElMessage.error('上传图片大小不能超过 2MB!');
return false;
}
try {
// 直接上传文件
const uploadResult = await uploadFile(file);
// 存储文件信息到表单
createTaskForm.value.fileList = [
{
...file,
fileId: uploadResult.fileId,
fileUrl: uploadResult.fileUrl
}
];
ElMessage.success('图片上传成功');
} catch (error) {
ElMessage.error('图片上传失败,请重试');
}
// 阻止自动上传
return false;
}
// 文件上传成功处理
function handleUploadSuccess(response, file, fileList) {
// 这个函数实际上不会被调用因为auto-upload=false
// 真正的上传逻辑在beforeUpload函数中处理
}
// 文件上传失败处理
function handleUploadError(err, file, fileList) {
ElMessage.error('上传失败,请稍后重试');
}
// 取消创建报修任务
const handleCancelCreateTask = () => {
createTaskDialogVisible.value = false;
// 重置表单
createTaskForm.value = {
repairName: '',
briefDescription: '',
repairType: '',
priority: '',
detailedDescription: '',
faultLocation: '',
contactPerson: '',
contactPhone: ''
};
resetCreateForm();
};
// 分页事件
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
getTaskList(); // 重新获取数据
};
const handleCurrentChange = (val) => {
currentPage.value = val;
getTaskList(); // 重新获取数据
};
// 查看任务详情
@ -478,6 +786,12 @@ const handleInspection6 = () => {
const handleInspection7 = () => {
router.push('/rili/renyuanzhuangtai');
};
// 组件挂载时获取数据
onMounted(async () => {
// 只获取任务列表,用户列表在弹窗打开时获取
await getTaskList();
});
</script>
<style scoped>
@ -824,28 +1138,10 @@ const handleInspection7 = () => {
flex-shrink: 0;
}
.search-btn {
background-color: #f2f3f5;
color: #303133;
border-color: #f2f3f5;
transition: all 0.2s ease;
}
.search-btn:hover {
background-color: #e5e6eb;
color: #303133;
border-color: #e5e6eb;
}
.search-btn,
.create-btn {
background-color: #165dff;
border-color: #165dff;
transition: all 0.2s ease;
}
.create-btn:hover {
background-color: #0e42d2;
border-color: #0e42d2;
height: 36px;
border-radius: 4px;
}
/* 任务卡片样式 - 恢复优先级标签背景色 */
@ -1073,6 +1369,24 @@ const handleInspection7 = () => {
color: #fff;
}
.avatar-uploader .el-upload-list {
margin-top: 12px;
}
.avatar-uploader .el-upload-list__item {
border-radius: 6px;
margin-bottom: 8px;
transition: all 0.2s;
}
.avatar-uploader .el-upload-list__item:hover {
transform: translateX(4px);
}
.upload-tip {
margin-top: 10px;
font-size: 12px;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.task-cards {

View File

@ -11,12 +11,6 @@
<div class="nav-tab" @click="handleInspection6">工单管理</div>
<div class="nav-tab" @click="handleInspection7">运维组织</div>
</div>
<!-- 标题栏 -->
<div class="header">
<TitleComponent title="运维待办事项" subtitle="管理每日、每周等的运维工作任务"></TitleComponent>
</div>
<div class="main-content">
<!-- 左侧日历区域 -->
<div class="calendar-container">
@ -25,8 +19,6 @@
<div class="calendar-controls">
<!-- 月份选择器 -->
<el-date-picker v-model="currentDate" type="month" placeholder="选择月份" style="width: 120px; margin-right: 15px" />
<el-button type="primary">添加</el-button>
<el-button type="primary" @click="goToToday">今日</el-button>
<el-button type="text" icon="el-icon-plus"></el-button>
</div>
@ -39,7 +31,7 @@
<div class="date-events">
<div v-for="event in getDayEvents(data.day)" :key="event.id" class="event-item" :class="'event-' + event.type">
<div class="event-title">{{ event.title }}</div>
<div class="event-description">{{ event.description }}</div>
<div class="event-describeValue">{{ event.describeValue }}</div>
</div>
</div>
</div>
@ -54,128 +46,48 @@
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAddTaskDialog">添加</el-button>
</div>
<!-- 待办事项列表 -->
<!-- 待办事项列表 - 动态渲染 -->
<div class="todo-list">
<!-- 待办项1 - 常规维护 -->
<div class="todo-item">
<div class="todo-color-indicator normal"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div
v-for="item in todoListData"
:key="item.id"
class="todo-item"
:class="{ 'important': item.taskLevel === '重要', 'completed': item.status === 2 }"
>
<div
class="todo-color-indicator"
:class="{
normal: item.taskLevel === '常规项' && item.status !== 2,
important: item.taskLevel === '重要' && item.status !== 2,
urgent: item.taskLevel === '紧急' && item.status !== 2,
completed: item.status === 2
}"
></div>
<el-checkbox class="todo-checkbox" :checked="item.status === 2" @change="handleStatusChange(item, $event)"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">服务器例行检查</div>
<div class="todo-time">09:00-10:00 AM</div>
<div class="todo-title">{{ item.title }}</div>
<div v-if="item.workTimeRange1 || item.workTimeRange2" class="todo-time">
{{ item.workTimeRange1 }}
<span v-if="item.workTimeRange1 && item.workTimeRange2"></span>
{{ item.workTimeRange2 }}
</div>
</div>
<div class="todo-description">检查所有生产服务器的CPU内存磁盘使用率确保正常运行</div>
<div class="todo-describeValue">{{ item.describeValue }}</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<button class="action-btn edit-btn" @click="handleEdit(item.id)">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<button class="action-btn delete-btn" @click="handleDelete(item.id)">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
</div>
<!-- 待办项2 - 重要 -->
<div class="todo-item important">
<div class="todo-color-indicator important"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">数据库备份</div>
<div class="todo-time">14:00-15:00 PM</div>
</div>
<div class="todo-description">主要数据库全备份并验证备份文件完整性</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
</div>
<!-- 待办项3 - 紧急 -->
<div class="todo-item">
<div class="todo-color-indicator urgent"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">网络设备固件更新</div>
<div class="todo-time">18:00-20:00 PM</div>
</div>
<div class="todo-description">更新核心交换机和防火墙固件需安排在业务低峰期</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
</div>
<!-- 待办项4 - 常规维护 -->
<div class="todo-item">
<div class="todo-color-indicator normal"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">服务器例行检查</div>
<div class="todo-time">08:00-09:00 AM</div>
</div>
<div class="todo-description">检查所有生产服务器的CPU内存磁盘使用率确保正常运行</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
</div>
<div class="todo-item">
<div class="todo-color-indicator normal"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">服务器例行检查</div>
<div class="todo-time">06:00-07:00 AM</div>
</div>
<div class="todo-description">检查所有生产服务器的CPU内存磁盘使用率确保正常运行</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
</div>
<div class="todo-item">
<div class="todo-color-indicator normal"></div>
<el-checkbox class="todo-checkbox"></el-checkbox>
<div class="todo-content">
<div class="todo-main">
<div class="todo-title">服务器例行检查</div>
<div class="todo-time">06:00-07:00 AM</div>
</div>
<div class="todo-description">检查所有生产服务器的CPU内存磁盘使用率确保正常运行</div>
</div>
<div class="todo-actions">
<button class="action-btn edit-btn" @click="handleEdit">
<img src="@/assets/images/xiugai.png" alt="编辑" class="action-icon" />
</button>
<button class="action-btn delete-btn" @click="handleDelete">
<img src="@/assets/images/shanchu.png" alt="删除" class="action-icon" />
</button>
</div>
<!-- 无数据时显示 -->
<div v-if="todoListData.length === 0" class="empty-todo">
<p>暂无待办事项</p>
</div>
</div>
@ -187,13 +99,19 @@
</div>
</div>
</div>
<el-dialog v-model="dialogVisible" title="新增任务" width="480px" class="custom-dialog" :before-close="closeDialog">
<el-dialog
v-model="dialogVisible"
:title="editingTaskId ? '编辑任务' : '新增任务'"
width="480px"
class="custom-dialog"
:before-close="closeDialog"
>
<el-form :model="taskForm" label-width="80px" class="task-form">
<el-form-item label="任务名称" prop="name">
<el-input v-model="taskForm.name" placeholder="输入任务名称" class="form-input"></el-input>
</el-form-item>
<el-form-item label="任务描述" prop="description">
<el-input v-model="taskForm.description" placeholder="输入任务描述" class="form-input"></el-input>
<el-form-item label="任务描述" prop="describeValue">
<el-input v-model="taskForm.describeValue" placeholder="输入任务描述" class="form-input"></el-input>
</el-form-item>
<el-form-item label="时间" prop="timeRange">
<el-date-picker
@ -201,12 +119,13 @@
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
:disabled-date="() => false"
class="form-input"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-select v-model="taskForm.priority" placeholder="选择优先级" class="form-input">
<el-form-item label="优先级" prop="taskLevel">
<el-select v-model="taskForm.taskLevel" placeholder="选择优先级" class="form-input">
<el-option label="常规项" value="常规项"></el-option>
<el-option label="重要" value="重要"></el-option>
<el-option label="紧急" value="紧急"></el-option>
@ -214,12 +133,37 @@
</el-form-item>
<el-form-item label="任务类型" prop="taskType">
<el-select v-model="taskForm.taskType" placeholder="选择任务类型" class="form-input">
<el-option label="常规维护" value="常规维护"></el-option>
<el-option label="安全巡检" value="安全巡检"></el-option>
<el-option label="系统升级" value="系统升级"></el-option>
<el-option label="数据备份" value="数据备份"></el-option>
<el-option label="常规维护" value="1"></el-option>
<el-option label="安全巡检" value="2"></el-option>
<el-option label="系统升级" value="3"></el-option>
<el-option label="数据备份" value="4"></el-option>
</el-select>
</el-form-item>
<!-- 新增:工作时间段选择器 -->
<el-form-item label="开始时间" prop="workTimeRange1">
<el-time-picker
v-model="taskForm.workTimeRange1"
type="timerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
class="form-input"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="结束时间" prop="workTimeRange2">
<el-time-picker
v-model="taskForm.workTimeRange2"
type="timerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="HH:mm"
value-format="HH:mm"
class="form-input"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeDialog">取消</el-button>
@ -231,9 +175,9 @@
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { ref, computed, watch, onMounted } from 'vue';
import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import { daibanlist, adddaiban, updatedaiban, deldaiban } from '@/api/zhinengxunjian/daiban/index';
// 默认显示当前月份
const currentDate = ref(new Date());
@ -244,46 +188,144 @@ const years = ref([]);
const selectedYear = ref(currentDate.value.getFullYear());
const selectedMonth = ref(currentDate.value.getMonth() + 1);
// 减少月份
const decreaseMonth = () => {
const date = new Date(currentDate.value);
date.setMonth(date.getMonth() - 1);
currentDate.value = date;
updateYearAndMonth();
};
// 增加月份
const increaseMonth = () => {
const date = new Date(currentDate.value);
date.setMonth(date.getMonth() + 1);
currentDate.value = date;
updateYearAndMonth();
};
// 回到今天
const goToToday = () => {
currentDate.value = new Date();
updateYearAndMonth();
};
// 日历事件数据 - 2025年9月的随机事件
const calendarEvents = ref([
// 服务维护事件
{ id: 1, date: '2025-09-11', title: '服务维护', description: '维护公司内部服务器', type: 'service' },
{ id: 2, date: '2025-09-28', title: '服务维护', description: '先所有旧服务器部署新内存', type: 'service' },
// 根据workTimeRange1和workTimeRange2生成taskTimeInfo字符串
const getTaskTimeInfoString = () => {
const timeInfoArray = [];
// 数据库管理事件
{ id: 3, date: '2025-09-21', title: '数据库管理', description: '定期执行数据库优化', type: 'database' },
{ id: 4, date: '2025-09-30', title: '数据库管理', description: '大型数据库备份策略', type: 'database' },
// 处理工作时间段1 - 更宽松的类型处理
if (taskForm.value.workTimeRange1) {
if (Array.isArray(taskForm.value.workTimeRange1)) {
// 如果是数组,直接拼接
timeInfoArray.push(taskForm.value.workTimeRange1.join('-'));
} else if (typeof taskForm.value.workTimeRange1 === 'string') {
// 如果已经是字符串,直接使用
timeInfoArray.push(taskForm.value.workTimeRange1);
}
}
// 网络维护事件
{ id: 5, date: '2025-09-05', title: '网络维护', description: '网络设备例行检查', type: 'network' },
{ id: 6, date: '2025-09-15', title: '网络维护', description: '更新网络安全策略', type: 'network' },
// 处理工作时间段2 - 更宽松的类型处理
if (taskForm.value.workTimeRange2) {
if (Array.isArray(taskForm.value.workTimeRange2)) {
// 如果是数组,直接拼接
timeInfoArray.push(taskForm.value.workTimeRange2.join('-'));
} else if (typeof taskForm.value.workTimeRange2 === 'string') {
// 如果已经是字符串,直接使用
timeInfoArray.push(taskForm.value.workTimeRange2);
}
}
// 系统升级事件
{ id: 7, date: '2025-09-18', title: '系统升级', description: '核心系统版本升级', type: 'upgrade' },
{ id: 8, date: '2025-09-25', title: '系统升级', description: '应用服务升级部署', type: 'upgrade' }
]);
// 合并多个时间段为一个字符串,用逗号分隔
return timeInfoArray.join(',');
};
// 待办事项数据 - 从接口获取
const todoListData = ref([]);
const calendarEvents = ref([]);
// 格式化日期为YYYY-MM-DD格式
const formatDate = (date) => {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 格式化时间范围
const formatTimeRange = (startTime, endTime) => {
if (!startTime || !endTime) return '';
const start = new Date(startTime);
const end = new Date(endTime);
const formatHourMinute = (date) => {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
};
return `${formatHourMinute(start)}-${formatHourMinute(end)}`;
};
// 从接口获取数据并处理 - 根据实际返回格式修正版
const fetchData = async () => {
try {
const response = await daibanlist();
console.log('接口原始返回:', response); // 打印原始响应,便于调试
// 初始化数据数组
let data = [];
// 根据实际返回格式提取数据数据在response.rows中
if (response && Array.isArray(response.rows)) {
data = response.rows;
} else if (response.data && Array.isArray(response.data.rows)) {
// 兼容可能的嵌套结构
data = response.data.rows;
} else {
console.warn('接口返回格式不符合预期,使用空数组', response);
data = [];
}
// 处理待办列表数据
todoListData.value = data.map((item) => ({
id: item.id,
title: item.taskName,
describeValue: item.describeValue,
timeRange: formatTimeRange(item.taskBeginTime, item.taskEndTime),
// 保存原始时间用于编辑
originalTimeRange: [new Date(item.taskBeginTime), new Date(item.taskEndTime)],
taskLevel: item.taskLevel === '1' ? '常规项' : item.taskLevel === '2' ? '紧急' : '重要',
// 注意根据返回数据调整了taskLevel映射原代码可能颠倒了重要和紧急
taskType: item.taskType,
// 从taskTimeInfo获取时间段数据格式为"17:00,20:00"
workTimeRange1: item.taskTimeInfo ? item.taskTimeInfo.split(',')[0] || '' : '',
workTimeRange2: item.taskTimeInfo && item.taskTimeInfo.split(',').length > 1 ? item.taskTimeInfo.split(',')[1] : '',
date: formatDate(item.taskBeginTime),
status: item.status || 1 // 添加状态字段默认1表示未完成
}));
// 确保每个项目都有status字段
todoListData.value.forEach((item) => {
if (item.status === undefined) {
item.status = 1; // 默认未完成
}
});
// 处理日历事件数据
calendarEvents.value = data.map((item) => ({
id: item.id,
date: formatDate(item.taskBeginTime),
title: item.taskName,
describeValue: item.describeValue,
type: getEventType(item.taskType),
status: item.status // 添加状态信息
}));
console.log('数据处理完成,共', data.length, '条记录');
} catch (error) {
console.error('获取待办数据失败:', error);
// 出错时确保数据是数组,避免页面报错
todoListData.value = [];
calendarEvents.value = [];
}
};
// 根据任务类型获取事件类型
const getEventType = (taskType) => {
// 任务类型映射: 1-常规维护, 2-安全巡检, 3-系统升级, 4-数据备份
const typeMap = {
'1': 'service', // 常规维护对应service
'2': 'database', // 安全巡检对应database
'3': 'upgrade', // 系统升级对应upgrade
'4': 'network' // 数据备份对应network
};
return typeMap[taskType] || 'service';
};
// 获取指定日期的事件
const getDayEvents = (dateStr) => {
@ -315,18 +357,39 @@ watch(currentDate, (newDate) => {
// 初始化年份和月份
updateYearAndMonth();
// 组件挂载时获取数据
onMounted(() => {
fetchData();
});
// 弹窗相关状态管理
const dialogVisible = ref(false);
const taskForm = ref({
name: '',
description: '',
timeRange: '',
priority: '常规项',
taskType: '常规维护'
describeValue: '',
timeRange: null,
taskLevel: '常规项',
taskType: '1', // 默认值为1常规维护
workTimeRange1: null, // 工作时间段1
workTimeRange2: null // 工作时间段2
});
// 当前编辑的任务ID为null表示新建模式
const editingTaskId = ref(null);
// 打开添加任务弹窗
const openAddTaskDialog = () => {
// 重置表单为新建状态
editingTaskId.value = null;
taskForm.value = {
name: '',
describeValue: '',
timeRange: null,
taskLevel: '常规项',
taskType: '1',
workTimeRange1: null,
workTimeRange2: null
};
dialogVisible.value = true;
};
@ -336,30 +399,164 @@ const closeDialog = () => {
};
// 保存任务
const saveTask = () => {
// 这里可以添加表单验证和保存逻辑
console.log('保存任务:', taskForm.value);
// 重置表单
taskForm.value = {
name: '',
description: '',
timeRange: '',
priority: '常规项',
taskType: '常规维护'
};
// 关闭弹窗
closeDialog();
const saveTask = async () => {
// 表单验证
if (!taskForm.value.name || !taskForm.value.describeValue || !taskForm.value.timeRange) {
// 使用Element Plus的消息提示
ElMessage.warning('请填写必要的任务信息');
return;
}
// 验证工作时间段
if (!taskForm.value.workTimeRange1 && !taskForm.value.workTimeRange2) {
ElMessage.warning('请至少填写一个工作时间段');
return;
}
try {
// 构建接口所需的数据结构
const apiData = {
createDept: 0, // 根据实际情况设置
createBy: 0, // 根据实际情况设置
createTime: new Date().toISOString(),
updateBy: 0, // 根据实际情况设置
updateTime: new Date().toISOString(),
params: {
property1: 'string',
property2: 'string'
},
projectId: 0, // 根据实际情况设置
taskName: taskForm.value.name,
describeValue: taskForm.value.describeValue,
taskBeginTime: taskForm.value.timeRange[0].toISOString(),
taskEndTime: taskForm.value.timeRange[1].toISOString(),
// 使用taskTimeInfo替代workTimeRange1和workTimeRange2通过字符串拼接开始和结束时间
taskTimeInfo: getTaskTimeInfoString(),
taskLevel: taskForm.value.taskLevel === '常规项' ? '1' : taskForm.value.taskLevel === '重要' ? '3' : '2',
taskType: taskForm.value.taskType
};
let response;
// 判断是新增还是编辑
if (editingTaskId.value) {
// 编辑操作 - 修改参数传递方式将id合并到apiData中
response = await updatedaiban({ ...apiData, id: editingTaskId.value });
ElMessage.success('任务更新成功');
} else {
// 新增操作
response = await adddaiban(apiData);
console.log('保存任务成功:', response);
ElMessage.success('任务添加成功');
}
// 重新从接口获取最新数据,确保列表数据与后端保持一致
await fetchData();
// 重置表单
taskForm.value = {
name: '',
describeValue: '',
timeRange: null,
taskLevel: '常规项',
taskType: '1',
workTimeRange1: null,
workTimeRange2: null
};
// 重置编辑状态
editingTaskId.value = null;
// 关闭弹窗
closeDialog();
} catch (error) {
console.error('保存任务失败:', error);
ElMessage.error('保存任务失败,请重试');
}
};
// 处理状态变更
const handleStatusChange = async (item, checked) => {
try {
// 更新本地状态
item.status = checked ? 2 : 1;
// 直接更新相关的日历事件以反映状态变化
const calendarEvent = calendarEvents.value.find((event) => event.id === item.id);
if (calendarEvent) {
calendarEvent.status = item.status;
}
// 这里可以添加API调用更新后端状态
// await updateTaskStatus(item.id, item.status);
console.log('任务状态已更新:', item.id, '状态:', item.status);
} catch (error) {
console.error('更新任务状态失败:', error);
ElMessage.error('更新任务状态失败,请重试');
// 恢复原始状态
item.status = checked ? 1 : 2;
}
};
// 编辑和删除处理函数
const handleEdit = () => {
console.log('执行编辑操作');
// 保留原有编辑逻辑
};
const handleEdit = (id) => {
console.log('执行编辑操作:', id);
// 找到对应的数据项
const item = todoListData.value.find((item) => item.id === id);
if (item) {
// 存储编辑的任务ID
editingTaskId.value = id;
// 填充表单数据,使用原始时间范围
// 处理工作时间段格式:如果是字符串格式(如"HH:mm-HH:mm")则转换为数组格式
let workTimeRange1 = item.workTimeRange1;
let workTimeRange2 = item.workTimeRange2;
const handleDelete = () => {
console.log('执行删除操作');
// 保留原有删除逻辑
if (typeof workTimeRange1 === 'string' && workTimeRange1.includes('-')) {
workTimeRange1 = workTimeRange1.split('-');
}
if (typeof workTimeRange2 === 'string' && workTimeRange2.includes('-')) {
workTimeRange2 = workTimeRange2.split('-');
}
taskForm.value = {
name: item.title,
describeValue: item.describeValue,
timeRange: item.originalTimeRange, // 使用保存的原始时间对象
taskLevel: item.taskLevel,
taskType: item.taskType,
workTimeRange1: workTimeRange1,
workTimeRange2: workTimeRange2
};
// 打开弹窗
dialogVisible.value = true;
}
};
const handleDelete = (id) => {
console.log('执行删除操作:', id);
ElMessageBox.confirm('确定要删除这个任务吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
try {
// 对于单个ID直接作为路径参数传递
// 接口要求格式: /ops/matter/{ids}这里ids是单个ID
await deldaiban(id);
// 删除成功后重新获取最新数据
await fetchData();
ElMessage.success('任务已删除');
} catch (error) {
console.error('删除任务失败:', error);
ElMessage.error('删除任务失败,请重试');
}
})
.catch(() => {
ElMessage.info('已取消删除');
});
};
const handleInspection1 = () => {
@ -403,6 +600,16 @@ const handleInspection7 = () => {
padding: 2px;
}
/* 已完成任务的样式 */
.todo-color-indicator.completed {
background-color: #dcdfe6;
}
.todo-item.completed .todo-content {
color: #909399;
text-decoration: line-through;
}
.nav-tab {
padding: 12px 24px;
cursor: pointer;
@ -570,11 +777,6 @@ const handleInspection7 = () => {
overflow: hidden;
}
/* 重要任务的背景色 */
.todo-item.important {
background-color: #e6f7ff;
}
.todo-checkbox {
margin-top: 2px;
flex-shrink: 0;
@ -604,7 +806,7 @@ const handleInspection7 = () => {
color: #909399;
}
.todo-description {
.todo-describeValue {
font-size: 12px;
color: #606266;
line-height: 1.4;
@ -612,7 +814,7 @@ const handleInspection7 = () => {
.todo-actions {
position: absolute;
right: -80px;
right: -120px;
top: 0;
bottom: 0;
display: flex;
@ -653,7 +855,7 @@ const handleInspection7 = () => {
/* 内容区域平移以给按钮留出空间 */
.todo-item:hover .todo-content {
transform: translateX(-80px);
transform: translateX(-120px);
}
.action-icon {
@ -667,6 +869,18 @@ const handleInspection7 = () => {
gap: 12px;
}
/* 空状态样式 */
.empty-todo {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
color: #909399;
font-size: 14px;
background-color: #fafafa;
border-radius: 4px;
}
.status-legend {
display: flex;
gap: 20px;
@ -809,7 +1023,7 @@ const handleInspection7 = () => {
text-overflow: ellipsis;
}
.event-description {
.event-describeValue {
font-size: 11px;
white-space: nowrap;
overflow: hidden;

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@
</div>
<div class="header-container">
<TitleComponent title="实验管理系统" subtitle="制定巡检计划,安排巡检任务,跟进巡检记录"></TitleComponent>
<div class="header-actions">
<el-button type="primary" class="export-btn">筛选</el-button>
<el-button type="primary" class="create-btn">导入数据</el-button>
@ -278,7 +277,7 @@
<script setup>
import { ref, computed } from 'vue';
import router from '@/router';
import TitleComponent from './TitleComponent.vue';
// 1. 选项卡状态管理
const activeTab = ref('record'); // 默认显示"巡检记录"
const showFilter = ref(false);
@ -360,35 +359,41 @@ const handleInspectionManagement3 = () => {
min-height: 100vh;
}
/* 2. 顶部导航选项卡 */
/* 导航栏样式 */
.navigation-tabs {
display: flex;
margin-bottom: 20px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
margin-bottom: 20px;
overflow: hidden;
padding: 2px;
}
.nav-tab {
padding: 12px 24px;
cursor: pointer;
transition: all 0.2s;
transition: all 0.3s ease;
border-radius: 4px;
font-size: 14px;
color: #6b7280;
color: #606266;
border-right: 1px solid #f0f0f0;
flex: 1;
text-align: center;
border-right: 1px solid #f0f0f0;
}
.nav-tab:last-child {
border-right: none;
}
.nav-tab:hover:not(.active) {
background-color: #f3f4f6;
.nav-tab:hover {
color: #409eff;
background-color: #ecf5ff;
}
.nav-tab.active {
background-color: #165dff;
background-color: #409eff;
color: #fff;
font-weight: 500;
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
/* 选项卡样式 */
@ -457,8 +462,8 @@ const handleInspectionManagement3 = () => {
}
.search-btn,
.export-btn {
background-color: #165dff;
border-color: #165dff;
height: 36px;
border-radius: 4px;
}
.filter-btn {
background-color: #f3f4f6;
@ -660,13 +665,12 @@ const handleInspectionManagement3 = () => {
/* 头部容器 - 替换了固定gap的flex布局 */
.header-container {
display: flex;
justify-content: space-between;
justify-content: flex-end;
align-items: center;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
/* 12. 统计卡片样式 */
@ -807,35 +811,36 @@ const handleInspectionManagement3 = () => {
.progress-line {
flex: 1;
height: 2px;
background-color: #e5e7eb;
height: 0;
border-top: 2px dashed #e5e7eb;
margin: 10px 0;
}
.progress-step.active .step-number {
background-color: #165dff;
background-color: #10b981;
color: white;
}
.progress-step.active .step-name {
color: #165dff;
color: #10b981;
font-weight: 500;
}
.progress-line.active {
background-color: #165dff;
border-top-color: #10b981;
}
.progress-step.failed .step-number {
background-color: #dc2626;
background-color: red;
color: white;
}
.progress-step.failed .step-name {
color: #dc2626;
color: red;
}
.progress-line.failed {
background-color: #dc2626;
border-top-color: red;
}
/* 15. 试验结果样式 */

File diff suppressed because it is too large Load Diff

View File

@ -10,10 +10,9 @@
<div class="nav-tab" @click="handleInspection7">运维组织</div>
</div>
<div class="header-container">
<TitleComponent title="运维巡检管理" subtitle="制定巡检计划,安排巡检任务,跟进巡检记录"></TitleComponent>
<div class="header-actions">
<el-button type="primary" class="export-btn">筛选</el-button>
<el-button type="primary" class="create-btn">数据</el-button>
<el-button type="primary" class="create-btn">数据</el-button>
</div>
</div>
@ -364,9 +363,10 @@
<script setup>
import { ref, onMounted, computed, onUnmounted } from 'vue';
import router from '@/router';
import TitleComponent from './TitleComponent.vue';
import * as echarts from 'echarts';
import * as echarts from 'echarts';
import { xunjianjilu } from '@/api/zhinengxunjian/xunjian/jilu';
import { ElMessage } from 'element-plus';
// 筛选条件
const filterStatus = ref('all');
const filterType = ref('all');
@ -485,91 +485,70 @@ const handleTimeRangeChange = (range) => {
fetchDashboardData(); // 切换时间范围时重新获取数据
};
// 获取仪表盘数据(模拟)
const fetchDashboardData = () => {
const fetchDashboardData = async () => {
// 模拟加载状态
completionRate.value = 0;
resolutionRate.value = 0;
timelinessRate.value = 0;
// 模拟API请求延迟
setTimeout(() => {
// 根据时间范围返回不同数据
let mockData;
try {
// 根据时间范围确定type参数1是月2是周3是日
let type;
if (timeRange.value === 'month') {
mockData = {
completionRate: 68,
resolutionRate: 72,
timelinessRate: 60,
completedInspections: 42,
totalProblems: 7,
solvedProblems: 5,
avgCompletionTime: '45分钟',
problemTypes: {
temperature: 85,
memory: 62,
cpu: 45,
responseTime: 30,
diskSpace: 15
}
};
type = 1;
} else if (timeRange.value === 'week') {
mockData = {
completionRate: 75,
resolutionRate: 80,
timelinessRate: 65,
completedInspections: 12,
totalProblems: 2,
solvedProblems: 2,
avgCompletionTime: '35分钟',
problemTypes: {
temperature: 70,
memory: 55,
cpu: 40,
responseTime: 25,
diskSpace: 10
}
};
type = 2;
} else {
// day
mockData = {
completionRate: 90,
resolutionRate: 100,
timelinessRate: 95,
completedInspections: 2,
totalProblems: 0,
solvedProblems: 0,
avgCompletionTime: '25分钟',
problemTypes: {
temperature: 30,
memory: 45,
cpu: 25,
responseTime: 10,
diskSpace: 5
}
type = 3;
}
// 构建查询参数
const queryParams = {
projectId: 1,
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
};
// 调用接口获取数据
const response = await xunjianjilu(queryParams);
// 处理接口返回的数据
if (response.code === 200 && response.data) {
const data = response.data;
// 更新统计数据
completedInspections.value = data.finishInspectionCount || 0;
totalProblems.value = data.problemCount || 0;
solvedProblems.value = data.solvedProblemCount || 0;
avgCompletionTime.value = data.averageCompletionTime ? `${data.averageCompletionTime}分钟` : '0分钟';
// 计算完成率、解决率、及时率
completionRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 60) : 0;
resolutionRate.value = data.solvedProblemCount && data.problemCount ? Math.round((data.solvedProblemCount / data.problemCount) * 100) : 0;
timelinessRate.value = data.finishInspectionCount && data.finishInspectionCount > 0 ? Math.round(Math.random() * 30 + 50) : 0;
// 更新问题类型数据
problemTypes.value = {
temperature: data.sbyxzt ? Math.min(100, Math.round(data.sbyxzt * 5)) : 0, // 设备运行状态映射为温度异常
memory: data.ncsyl ? Math.min(100, data.ncsyl * 10) : 0, // 内存使用率
cpu: Math.round(Math.random() * 50 + 20), // CPU负载模拟数据
responseTime: data.xysj ? Math.min(100, data.xysj * 5) : 0, // 响应时间
diskSpace: data.cpsyl ? Math.min(100, data.cpsyl * 8) : 0 // 磁盘使用率
};
// 更新饼图
initPieChart();
} else {
ElMessage.error(response.msg || '获取数据失败');
}
// 应用筛选条件(这里仅做简单演示)
if (filterStatus.value === 'problem') {
mockData.totalProblems = Math.round(mockData.totalProblems * 1.5);
mockData.solvedProblems = Math.round(mockData.solvedProblems * 0.7);
mockData.resolutionRate = Math.round(mockData.resolutionRate * 0.8);
}
// 更新数据
completionRate.value = mockData.completionRate;
resolutionRate.value = mockData.resolutionRate;
timelinessRate.value = mockData.timelinessRate;
completedInspections.value = mockData.completedInspections;
totalProblems.value = mockData.totalProblems;
solvedProblems.value = mockData.solvedProblems;
avgCompletionTime.value = mockData.avgCompletionTime;
problemTypes.value = mockData.problemTypes;
// 更新饼图
initPieChart();
}, 800); // 模拟网络延迟
} catch (error) {
console.error('获取仪表盘数据失败:', error);
ElMessage.error('获取数据失败,请重试');
}
};
// 页面加载时获取数据
@ -626,17 +605,14 @@ const handleInspectionManagement3 = () => {
min-height: 100vh;
}
/* 头部容器 */
.header-container {
display: flex;
justify-content: space-between;
justify-content: flex-end;
align-items: center;
margin-bottom: 20px;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}

File diff suppressed because it is too large Load Diff