feat(scada): 新增物联组态

添加组态图片相关页面、路由及API,包含图片展示、设备监控和布局图功能
This commit is contained in:
NewName
2026-03-24 18:39:10 +08:00
parent 20ca199aa2
commit e9898b7e6a
24 changed files with 7033 additions and 0 deletions

44
src/api/scada/picture.js Normal file
View File

@@ -0,0 +1,44 @@
import request from '@/utils/request';
// 查询组态列表
export function listCenter(query) {
return request({
url: '/scada/picture/list',
method: 'get',
params: query
});
}
// 查询组态详情
export function getCenter(id) {
return request({
url: '/scada/picture/' + id,
method: 'get'
});
}
// 新增组态
export function addCenter(data) {
return request({
url: '/scada/picture',
method: 'post',
data: data
});
}
// 修改组态
export function updateCenter(data) {
return request({
url: '/scada/picture',
method: 'put',
data: data
});
}
// 删除组态
export function delCenter(id) {
return request({
url: '/scada/picture/' + id,
method: 'delete'
});
}

3931
src/data/screen.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -124,6 +124,21 @@ export const constantRoutes = [
// component: () => import('@/views/scada/topo/share'),
// },
//以上组态特有
{
path: '/scada/picture',
component: () => import('@/views/scada/picture/index'),
hidden: true,
},
{
path: '/scada/picture/monitor',
component: () => import('@/views/scada/picture/monitor'),
hidden: true,
},
{
path: '/scada/picture/screen',
component: () => import('@/views/scada/picture/screen'),
hidden: true,
},
];
// 动态路由,基于用户权限动态去加载

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,158 @@
<template>
<div class="center-wrap">
<div v-if="showType == 'card'">
<el-row :gutter="15">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" style="margin-top: 7.5px; margin-bottom: 7.5px"
v-for="item in centerList" :key="item.id">
<el-card class="card-wrap" :body-style="{ padding: '10px' }">
<div class="img-wrap" @click="goToDetail(item)">
<el-image style="width: 100%; height: 100%; border-radius: 5px" lazy
:src="baseApi + item.pageImage" fit="cover"></el-image>
</div>
<div class="tag-wrap">
<span>{{ item.pageResolution ? item.pageResolution : '未知' }}</span>
</div>
<div class="name-wrap">
<span>{{ item.pageName }}</span>
</div>
</el-card>
</el-col>
</el-row>
<el-empty description="暂无数据" v-if="total == 0"></el-empty>
</div>
</div>
</template>
<script>
export default {
name: 'Center',
data() {
return {
loading: false, // 遮罩层
baseApi: process.env.VUE_APP_BASE_API,
// 固定数据
centerList: [
{
"createBy": "1",
"createTime": "2026-03-23 17:31:51",
"updateBy": null,
"updateTime": "2026-03-23 17:31:51",
"remark": null,
"pageNum": null,
"pageSize": null,
"id": 5,
"guid": "2b5759ba-8ee7-43e8-bb72-234986e52715",
"scadaData": null,
"serialNumbers": null,
"deviceName": null,
"isMainPage": null,
"pageName": "国瑞药业冻干三物联组态",
"pageResolution": null,
"isShare": null,
"shareUrl": null,
"sharePass": null,
"pageImage": "/profile/upload/2026/03/23/11冻干3_1 (1)_20260323173148A001.png",
"tenantId": null,
"tenantName": null,
"delFlag": 0,
"base64": null,
"bindDeviceList": null
}
],
total: 1, // 总条数
showType: 'card', // 展示方式
};
},
mounted() {
// 不再调用接口
},
methods: {
// 跳转到screen.vue组件
goToDetail(row) {
// 方法1: 使用路由跳转
// this.$router.push({
// path: '/scada/picture/screen',
// query: {
// id: row.id,
// guid: row.guid,
// },
// });
// 方法2: 在新标签页打开
const routeData = this.$router.resolve({
path: '/scada/picture/screen',
query: {
id: row.id,
guid: row.guid,
},
});
window.open(routeData.href, '_blank');
},
},
};
</script>
<style lang="scss" scoped>
.center-wrap {
padding: 20px;
.card-wrap {
position: relative;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.img-wrap {
height: 200px;
width: 100%;
cursor: pointer;
}
.tag-wrap {
position: absolute;
top: 0;
left: 0;
background-color: #1890ff;
border-top-left-radius: 5px;
border-bottom-right-radius: 5px;
padding: 5px 15px;
font-size: 12px;
color: #fff;
}
.name-wrap {
height: 20px;
line-height: 20px;
margin-top: 10px;
font-size: 14px;
}
.tools-wrap {
margin-top: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.right-wrap {
display: flex;
flex-direction: row;
align-items: center;
font-size: 13px;
cursor: pointer;
}
}
}
}
.disable {
::v-deep .el-upload--picture-card {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,416 @@
<template>
<div class="center-wrap">
<el-form @submit.native.prevent :model="queryParams" ref="queryForm" size="small" :inline="true"
v-show="showSearch" label-width="48px">
<el-form-item label="名称" prop="pageName">
<el-input v-model="queryParams.pageName" placeholder="请输入页面名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="handleResetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb10">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
v-hasPermi="['scada:center:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
v-hasPermi="['scada:center:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple"
@click="handleDelete" v-hasPermi="['scada:center:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
v-hasPermi="['scada:center:export']">导出</el-button>
</el-col>
<template>
<el-tooltip effect="dark" content="切换卡片/列表" placement="top" style="float: right;">
<el-button size="mini" circle icon="el-icon-s-grid" @click="handleChangeShowType" />
</el-tooltip>
</template>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList">
</right-toolbar>
</el-row>
<div v-if="showType == 'card'">
<el-row :gutter="15" v-loading="loading">
<el-checkbox-group v-model="ids" @change="checkboxChange">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" style="margin-top: 7.5px; margin-bottom: 7.5px"
v-for="item in centerList" :key="item.id">
<el-card class="card-wrap" :body-style="{ padding: '10px' }">
<div class="img-wrap">
<el-image style="width: 100%; height: 100%; border-radius: 5px" lazy
:src="baseApi + item.pageImage" fit="cover" v-hasPermi="['scada:center:query']"
@click="goToDetail(item)"></el-image>
</div>
<div class="tag-wrap">
<span>{{ item.pageResolution ? item.pageResolution : '未知' }}</span>
</div>
<div class="name-wrap">
<span>{{ item.pageName }}</span>
</div>
<div class="tools-wrap">
<el-checkbox class="checkbox" :label="item.id" :key="item.id"><span
v-show="false">占位符</span></el-checkbox>
<div class="right-wrap">
<!-- 二期开发 -->
<!-- <i class="el-icon-share" style="color: #e6a23c" @click="handleShare(item)"></i> -->
<el-dropdown style="margin-left: 8px">
<span class="el-dropdown-link"
v-hasPermi="['scada:center:preview', 'scada:center:remove', 'scada:center:edit']">
<svg-icon style="fill: #1890ff" icon-class="more-vertical"></svg-icon>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit" v-hasPermi="['scada:center:query']">
<el-button size="mini" type="text" icon="el-icon-view"
@click="goToDetail(item)">详情</el-button>
</el-dropdown-item>
<el-dropdown-item command="preview" v-hasPermi="['scada:center:preview']">
<el-button style="color: #e6a23c" type="text" size="mini"
icon="el-icon-view" @click="handlePreview(item)">预览</el-button>
</el-dropdown-item>
<el-dropdown-item command="remove" v-hasPermi="['scada:center:remove']">
<el-button style="color: #f56c6c" size="mini" type="text"
icon="el-icon-delete" @click="handleDelete(item)">删除</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</el-card>
</el-col>
</el-checkbox-group>
</el-row>
<el-empty description="暂无数据" v-if="total == 0"></el-empty>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
<div v-if="showType == 'list'">
<el-table v-loading="loading" :data="centerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="id" align="center" prop="id" width="100" />
<el-table-column label="名称" align="center" prop="pageName" />
<el-table-column label="分辨率" align="center" prop="pageResolution" width="120">
<template slot-scope="scope">
<span>{{ scope.row.pageResolution ? scope.row.pageResolution : '未知' }}</span>
</template>
</el-table-column>
<el-table-column label="封面" align="center" prop="pageImage" width="120">
<template slot-scope="scope">
<image-preview :src="scope.row.pageImage" :width="50" :height="50" />
</template>
</el-table-column>
<!-- 二期开发 -->
<!-- <el-table-column label="分享" align="center" prop="share" width="120">
<template slot-scope="scope">
<el-image style="width: 50px; height: 50px" :src="scope.row.share" fit="fit" @click="handleShare(scope.row)"></el-image>
</template>
</el-table-column> -->
<el-table-column label="更新时间" align="center" prop="updateTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="goToDetail(scope.row)"
v-hasPermi="['scada:center:query']">详情</el-button>
<el-button style="color: #e6a23c" size="mini" type="text" icon="el-icon-view"
@click="handlePreview(item)" v-hasPermi="['scada:center:preview']">预览</el-button>
<el-button style="color: #f56c6c" size="mini" type="text" icon="el-icon-delete"
@click="handleDelete(scope.row)" v-hasPermi="['scada:center:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
<!-- 添加或修改组态信息对话框 -->
<el-dialog :title="dialog.title" :visible.sync="dialog.open" width="400px" append-to-body>
<div class="el-divider el-divider--horizontal" style="margin-top: -25px"></div>
<el-form ref="dialogForm" :model="dialog.form" :rules="dialog.rules" label-width="65px">
<el-form-item label="封面" prop="pageImage">
<image-upload v-model="dialog.form.pageImage" :multiple="false"
:class="{ disable: uploadDisabled }" />
</el-form-item>
<el-form-item label="名称" prop="pageName">
<el-input v-model="dialog.form.pageName" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item label="描述" prop="remark">
<el-input v-model="dialog.form.remark" placeholder="请输入描述" clearable />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleDialogSubmit"> </el-button>
<el-button @click="handleDialogCancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listCenter, getCenter, delCenter, addCenter, updateCenter } from '@/api/scada/center';
export default {
name: 'Center',
dicts: ['sys_page_size'],
computed: {
uploadDisabled: function () {
return this.dialog.form.pageImage !== '';
},
},
data() {
return {
loading: true, // 遮罩层
baseApi: process.env.VUE_APP_BASE_API,
single: true, // 非单个禁用
multiple: true, // 非多个禁用
showSearch: true, // 显示搜索条件
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
pageName: null,
},
ids: [], // 选中数组
centerList: [], // 组态中心表格数据
total: 0, // 总条数
showType: 'card', // 展示方式
dialog: {
open: false, // 弹出层标题
title: '', // 对话框标题
// 表单参数
form: {
pageImage: '',
pageName: '',
remark: '',
},
// 表单校验
rules: {
pageName: [{ required: true, message: '请输入名称', trigger: 'change' }],
},
},
};
},
mounted() {
this.$busEvent.$on('updateCenter', () => {
this.getList();
});
this.getList();
},
methods: {
// 查询组态中心列表
getList() {
this.loading = true;
listCenter(this.queryParams).then((response) => {
if (response.code === 200) {
this.centerList = response.rows;
this.total = response.total;
}
this.loading = false;
});
},
// 搜索按钮操作
handleQuery() {
this.ids = [];
this.queryParams.pageNum = 1;
this.getList();
},
// 重置按钮操作
handleResetQuery() {
this.ids = [];
this.resetForm('queryForm');
this.handleQuery();
},
// 新增按钮操作
handleAdd() {
this.reset();
this.dialog.open = true;
this.dialog.title = '添加组态信息';
},
// 表单重置
reset() {
this.dialog.form = {
pageImage: '',
pageName: '',
remark: '',
};
this.resetForm('dialogForm');
},
// 修改按钮操作
handleUpdate(row) {
this.dialog.title = '修改组态信息';
const id = row.id || this.ids;
getCenter(id).then((res) => {
if (res.code === 200) {
this.dialog.form = res.data;
this.dialog.open = true;
}
});
},
// 提交按钮
handleDialogSubmit() {
this.$refs['dialogForm'].validate((valid) => {
if (valid) {
if (this.dialog.form.id != null) {
updateCenter(this.dialog.form).then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('修改成功');
this.dialog.open = false;
this.getList();
}
});
} else {
addCenter(this.dialog.form).then((res) => {
if (res.code === 200) {
this.$modal.msgSuccess('新增成功');
this.dialog.open = false;
this.getList();
}
});
}
}
});
},
// 取消按钮
handleDialogCancel() {
this.dialog.open = false;
},
// 删除按钮操作
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal
.confirm('是否确认删除组态编号为"' + ids + '"的数据项?')
.then(() => {
this.loading = true;
return delCenter(ids);
})
.then(() => {
this.loading = true;
this.getList();
this.$modal.msgSuccess('删除成功');
})
.catch(() => { });
},
// 跳转组态详情
goToDetail(row) {
this.$router.push({
path: '/scada/topo/editor',
query: {
id: row.id,
guid: row.guid,
},
});
},
// 分享
handleShare(row) {
this.$alert('二期开发', '提示', {
confirmButtonText: '确定',
});
},
// 跳转预览详情
handlePreview(row) {
const routeUrl = this.$router.resolve({
path: '/topo/fullscreen',
query: {
id: row.id,
guid: row.guid,
},
});
window.open(routeUrl.href, '_blank');
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map((item) => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
// 切换显示方式
handleChangeShowType() {
this.ids = [];
this.showType = this.showType == 'card' ? 'list' : 'card';
},
// 导出按钮操作
handleExport() {
this.download(
'scada/center/export',
{
...this.queryParams,
},
`组态${new Date().getTime()}.xlsx`
);
},
// 卡片选择
checkboxChange(selection) {
this.single = selection.length != 1;
this.multiple = !selection.length;
},
},
};
</script>
<style lang="scss" scoped>
.center-wrap {
padding: 20px;
.card-wrap {
position: relative;
border-radius: 5px;
.img-wrap {
height: 200px;
width: 100%;
// transition: transform 0.3s ease;
// &:hover {
// cursor: pointer;
// transform: scale(1.1);
// }
}
.tag-wrap {
position: absolute;
top: 0;
left: 0;
background-color: #1890ff;
border-top-left-radius: 5px;
border-bottom-right-radius: 5px;
padding: 5px 15px;
font-size: 12px;
color: #fff;
}
.name-wrap {
height: 20px;
line-height: 20px;
margin-top: 10px;
font-size: 14px;
}
.tools-wrap {
margin-top: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.right-wrap {
display: flex;
flex-direction: row;
align-items: center;
font-size: 13px;
cursor: pointer;
}
}
}
}
.disable {
::v-deep .el-upload--picture-card {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,670 @@
<template>
<div class="app">
<div class="header">国瑞药业冻干三物联组态</div>
<!-- 上部分 -->
<div class="top-section">
<!-- 左侧设备列表 -->
<div class="device-sidebar">
<div class="sidebar-title">设备列表</div>
<div class="table-container">
<table class="device-table">
<thead>
<tr>
<th>设备名称</th>
<th>设备状态</th>
</tr>
</thead>
<tbody>
<tr v-for="device in filteredDevices" :key="device.id">
<td>{{ device.name }}</td>
<td>
<span
class="status-badge"
:class="getStatusClass(device.status)"
>
{{ getStatusText(device.status) }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<button
class="page-btn arrow-btn"
@click="goToPreviousDevicePage"
:disabled="deviceCurrentPage === 1"
>
&lt;
</button>
<button
v-for="page in deviceTotalPages"
:key="page"
class="page-btn"
:class="{ active: deviceCurrentPage === page }"
@click="deviceCurrentPage = page"
>
{{ page }}
</button>
<button
class="page-btn arrow-btn"
@click="goToNextDevicePage"
:disabled="deviceCurrentPage === deviceTotalPages"
>
&gt;
</button>
</div>
</div>
<!-- 右侧维修保养模块 -->
<div class="maintenance-panel">
<!-- 左侧维修统计故障类型 -->
<div class="chart-container">
<div class="chart-title">维修统计故障类型</div>
<div id="maintenanceChart" class="chart"></div>
</div>
<!-- 右侧表格区域 -->
<div class="table-section">
<!-- 上部分切换按钮 -->
<div class="table-controls">
<button
class="control-btn"
:class="{ active: activeTable === '维修完成情况' }"
@click="activeTable = '维修完成情况'"
>
维修完成情况
</button>
<button
class="control-btn"
:class="{ active: activeTable === '保养统计' }"
@click="activeTable = '保养统计'"
>
保养统计
</button>
</div>
<!-- 下部分表格 -->
<div class="table-content">
<!-- 维修完成情况表 -->
<table class="device-table" v-if="activeTable === '维修完成情况'">
<thead>
<tr>
<th>工单编号</th>
<th>工单名称</th>
<th>设备名称</th>
<th>优先级</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in filteredMaintenanceData" :key="index">
<td>{{ item.workOrderId }}</td>
<td>{{ item.workOrderName }}</td>
<td>{{ item.deviceName }}</td>
<td>{{ item.priority }}</td>
<td>
<span
class="status-badge"
:class="getStatusClass(item.status)"
>
{{ getStatusText(item.status) }}
</span>
</td>
</tr>
</tbody>
</table>
<!-- 保养统计表 -->
<table class="device-table" v-if="activeTable === '保养统计'">
<thead>
<tr>
<th>保养编号</th>
<th>保养名称</th>
<th>设备名称</th>
<th>保养周期</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in filteredMaintenanceData" :key="index">
<td>{{ item.maintenanceId }}</td>
<td>{{ item.maintenanceName }}</td>
<td>{{ item.deviceName }}</td>
<td>{{ item.maintenanceCycle }}</td>
<td>
<span
class="status-badge"
:class="{
'status-running': item.status === '已完成',
'status-warning': item.status === '待开始',
'status-error': item.status === '故障'
}"
>
{{ item.status }}
</span>
</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<div class="pagination">
<button
class="page-btn arrow-btn"
@click="goToPreviousPage"
:disabled="maintenanceCurrentPage === 1"
>
&lt;
</button>
<button
v-for="page in maintenanceTotalPages"
:key="page"
class="page-btn"
:class="{ active: maintenanceCurrentPage === page }"
@click="maintenanceCurrentPage = page"
>
{{ page }}
</button>
<button
class="page-btn arrow-btn"
@click="goToNextPage"
:disabled="maintenanceCurrentPage === maintenanceTotalPages"
>
&gt;
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 下半部分设备图 -->
<div class="bottom-section">
<div class="layout-container">
<img
src="./icon/c354ee887a2f66c387ef3cd49be905b3.png"
alt="设备布局"
class="layout-image"
/>
<!-- 流动线 -->
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// 设备列表数据模拟2页每页8个
devices: [
{ id: 1, name: '全自动贴标机', status: 'running' },
{ id: 2, name: '蒸汽喷射式灭菌器', status: 'running' },
{ id: 3, name: '压片机', status: 'warning' },
{ id: 4, name: '冻干机自动进出料系统', status: 'running' },
{ id: 5, name: '热风循环灭菌烘箱', status: 'error' },
{ id: 6, name: '包装机', status: 'running' },
{ id: 7, name: '配料系统', status: 'running' },
{ id: 8, name: '全自动胶囊充填机', status: 'warning' },
{ id: 9, name: '颗粒包装机', status: 'running' },
{ id: 10, name: '粉末包装机', status: 'running' },
{ id: 11, name: '液体灌装机', status: 'running' },
{ id: 12, name: '西林瓶灌装机', status: 'warning' },
{ id: 13, name: '安瓿瓶灌装机', status: 'running' },
{ id: 14, name: '口服液灌装机', status: 'running' },
{ id: 15, name: '软膏灌装机', status: 'error' },
{ id: 16, name: '栓剂成型机', status: 'running' },
{ id: 17, name: '软胶囊机', status: 'running' },
{ id: 18, name: '硬胶囊机', status: 'warning' },
{ id: 19, name: '丸剂成型机', status: 'running' },
{ id: 20, name: '片剂包衣机', status: 'running' }
],
selectedDevice: 1,
deviceCurrentPage: 1,
deviceItemsPerPage: 8,
// 表格数据
activeTable: '维修完成情况',
maintenanceCurrentPage: 1,
maintenanceItemsPerPage: 8,
maintenanceData: [
// 维修完成情况数据20条
{ workOrderId: 'GZ20241106001', workOrderName: '片剂生产', deviceName: '压片机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106002', workOrderName: '胶囊填充', deviceName: '胶囊充填机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106003', workOrderName: '药品包装', deviceName: '包装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106004', workOrderName: '原料灭菌', deviceName: '灭菌器', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106005', workOrderName: '冻干处理', deviceName: '冻干机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106006', workOrderName: '配料准备', deviceName: '配料系统', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106007', workOrderName: '贴标处理', deviceName: '贴标机', priority: '低', status: 'running' },
{ workOrderId: 'GZ20241106008', workOrderName: '烘干处理', deviceName: '灭菌烘箱', priority: '高', status: 'error' },
{ workOrderId: 'GZ20241106009', workOrderName: '颗粒包装', deviceName: '颗粒包装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106010', workOrderName: '粉末包装', deviceName: '粉末包装机', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106011', workOrderName: '液体灌装', deviceName: '液体灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106012', workOrderName: '西林瓶灌装', deviceName: '西林瓶灌装机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106013', workOrderName: '安瓿瓶灌装', deviceName: '安瓿瓶灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106014', workOrderName: '口服液灌装', deviceName: '口服液灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106015', workOrderName: '软膏灌装', deviceName: '软膏灌装机', priority: '高', status: 'error' },
{ workOrderId: 'GZ20241106016', workOrderName: '栓剂成型', deviceName: '栓剂成型机', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106017', workOrderName: '软胶囊生产', deviceName: '软胶囊机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106018', workOrderName: '硬胶囊生产', deviceName: '硬胶囊机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106019', workOrderName: '丸剂成型', deviceName: '丸剂成型机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106020', workOrderName: '片剂包衣', deviceName: '片剂包衣机', priority: '高', status: 'running' }
],
maintenanceStats: [
// 保养统计数据20条
{ maintenanceId: 'BY20241106001', maintenanceName: '季度保养', deviceName: '压片机', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106002', maintenanceName: '月度保养', deviceName: '胶囊充填机', maintenanceCycle: '1个月', status: '待开始' },
{ maintenanceId: 'BY20241106003', maintenanceName: '年度保养', deviceName: '包装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106004', maintenanceName: '季度保养', deviceName: '灭菌器', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106005', maintenanceName: '月度保养', deviceName: '冻干机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106006', maintenanceName: '季度保养', deviceName: '配料系统', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106007', maintenanceName: '月度保养', deviceName: '贴标机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106008', maintenanceName: '年度保养', deviceName: '灭菌烘箱', maintenanceCycle: '12个月', status: '故障' },
{ maintenanceId: 'BY20241106009', maintenanceName: '季度保养', deviceName: '颗粒包装机', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106010', maintenanceName: '月度保养', deviceName: '粉末包装机', maintenanceCycle: '1个月', status: '待开始' },
{ maintenanceId: 'BY20241106011', maintenanceName: '年度保养', deviceName: '液体灌装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106012', maintenanceName: '季度保养', deviceName: '西林瓶灌装机', maintenanceCycle: '3个月', status: '待开始' },
{ maintenanceId: 'BY20241106013', maintenanceName: '月度保养', deviceName: '安瓿瓶灌装机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106014', maintenanceName: '年度保养', deviceName: '口服液灌装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106015', maintenanceName: '季度保养', deviceName: '软膏灌装机', maintenanceCycle: '3个月', status: '故障' },
{ maintenanceId: 'BY20241106016', maintenanceName: '月度保养', deviceName: '栓剂成型机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106017', maintenanceName: '年度保养', deviceName: '软胶囊机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106018', maintenanceName: '季度保养', deviceName: '硬胶囊机', maintenanceCycle: '3个月', status: '待开始' },
{ maintenanceId: 'BY20241106019', maintenanceName: '月度保养', deviceName: '丸剂成型机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106020', maintenanceName: '年度保养', deviceName: '片剂包衣机', maintenanceCycle: '12个月', status: '已完成' }
]
};
},
computed: {
deviceTotalPages() {
return Math.ceil(this.devices.length / this.deviceItemsPerPage);
},
filteredDevices() {
const start = (this.deviceCurrentPage - 1) * this.deviceItemsPerPage;
return this.devices.slice(start, start + this.deviceItemsPerPage);
},
maintenanceTotalPages() {
const data = this.activeTable === '维修完成情况' ? this.maintenanceData : this.maintenanceStats;
return Math.ceil(data.length / this.maintenanceItemsPerPage);
},
filteredMaintenanceData() {
const data = this.activeTable === '维修完成情况' ? this.maintenanceData : this.maintenanceStats;
const start = (this.maintenanceCurrentPage - 1) * this.maintenanceItemsPerPage;
return data.slice(start, start + this.maintenanceItemsPerPage);
}
},
methods: {
selectDevice(deviceId) {
this.selectedDevice = deviceId;
},
getStatusClass(status) {
switch(status) {
case 'running': return 'status-running';
case 'warning': return 'status-warning';
case 'error': return 'status-error';
default: return '';
}
},
getStatusText(status) {
switch(status) {
case 'running': return '运行中';
case 'warning': return '预警';
case 'error': return '故障';
default: return '未知';
}
},
goToPreviousPage() {
if (this.maintenanceCurrentPage > 1) {
this.maintenanceCurrentPage--;
}
},
goToNextPage() {
if (this.maintenanceCurrentPage < this.maintenanceTotalPages) {
this.maintenanceCurrentPage++;
}
},
goToPreviousDevicePage() {
if (this.deviceCurrentPage > 1) {
this.deviceCurrentPage--;
}
},
goToNextDevicePage() {
if (this.deviceCurrentPage < this.deviceTotalPages) {
this.deviceCurrentPage++;
}
},
initChart() {
const chartDom = document.getElementById('maintenanceChart');
const myChart = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: {
color: '#ffffff'
}
},
series: [
{
name: '故障类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#0a0e27',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold',
color: '#ffffff'
}
},
labelLine: {
show: false
},
data: [
{ value: 4, name: '机械故障', itemStyle: { color: '#ff6b6b' } },
{ value: 3, name: '电气故障', itemStyle: { color: '#4ecdc4' } },
{ value: 2, name: '软件故障', itemStyle: { color: '#ffe66d' } },
{ value: 1, name: '其他故障', itemStyle: { color: '#1a535c' } }
]
}
]
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
}
},
mounted() {
this.initChart();
}
};
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
font-family: Arial, sans-serif;
background: url('./icon/b4875ec42c53f353804aad374e932828.png') no-repeat center !important;
background-size: cover !important;
color: #ffffff;
overflow: hidden;
position: relative;
z-index: 1;
}
#app {
background: transparent !important;
}
.header {
background: transparent;
color: #00ffff;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
text-align: center;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
flex-shrink: 0;
}
.top-section {
display: grid;
grid-template-columns: 40% 60%;
gap: 50px;
flex: 1;
padding: 20px;
overflow: hidden;
}
.device-sidebar {
background: url('./icon/c73de8bc60f90ef1a6e63178f6d8f862.png') no-repeat center;
background-size: cover;
padding: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-title {
font-size: 14px;
font-weight: bold;
color: #00ffff;
margin-bottom: 15px;
}
.table-container {
padding: 15px;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.table-section {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.table-content {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.table-title {
font-size: 14px;
font-weight: bold;
color: #00ffff;
}
.table-controls {
display: flex;
gap: 0;
}
.control-btn {
padding: 6px 12px;
background: #0066cc;
border: 1px solid #0066cc;
color: #ffffff;
cursor: pointer;
font-size: 11px;
transition: all 0.3s ease;
}
.control-btn:first-child {
border-radius: 4px 0 0 4px;
}
.control-btn:last-child {
border-radius: 0 4px 4px 0;
}
.control-btn:hover {
background: #0088ff;
}
.control-btn.active {
background: #0088ff;
}
.device-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
flex: 1;
overflow: auto;
}
.device-table th,
.device-table td {
padding: 8px 10px;
text-align: left;
border-bottom: 1px solid rgba(0, 255, 255, 0.2);
}
.device-table th {
background: rgba(0, 255, 255, 0.1);
color: #00ffff;
font-weight: bold;
}
.device-table tbody tr:hover {
background: rgba(0, 255, 255, 0.1);
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 15px;
gap: 5px;
align-items: center;
}
.page-btn {
width: 30px;
height: 30px;
background: transparent;
border: none;
color: #ffffff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
transition: all 0.3s ease;
}
.page-btn.arrow-btn {
background: rgba(0, 255, 255, 0.1);
border-radius: 4px;
}
.page-btn:hover {
background: rgba(0, 255, 255, 0.2);
}
.page-btn.active {
background: #00ffff;
color: #000000;
border-radius: 4px;
font-weight: bold;
}
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: bold;
}
.status-running {
background: rgba(0, 255, 0, 0.2);
color: #00ff00;
}
.status-warning {
background: rgba(255, 255, 0, 0.2);
color: #ffff00;
}
.status-error {
background: rgba(255, 0, 0, 0.2);
color: #ff0000;
}
.bottom-section {
flex: 1;
padding: 20px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.layout-image {
width: 95%;
height: 95%;
object-fit: contain;
}
.maintenance-panel {
background: url('./icon/f0f06ffbc9509f233fe5b4e670416c31.png') no-repeat center;
background-size: cover;
padding: 15px;
display: grid;
grid-template-columns: 2fr 3fr;
gap: 10px;
overflow: hidden;
}
.chart-container {
padding: 15px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.chart-title {
font-size: 14px;
font-weight: bold;
color: #00ffff;
margin-bottom: 15px;
}
.chart {
width: 100%;
height: 200px;
}
.layout-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,787 @@
<template>
<div class="container">
<div class="header">国瑞药业冻干三物联组态</div>
<!-- 上部分 -->
<div class="top-section">
<!-- 左侧设备列表 -->
<div class="device-sidebar">
<div class="sidebar-title">设备列表</div>
<div class="table-container">
<table class="device-table">
<thead>
<tr>
<th>设备名称</th>
<th>设备状态</th>
</tr>
</thead>
<tbody>
<tr v-for="device in filteredDevices" :key="device.id">
<td>{{ device.name }}</td>
<td>
<span
class="status-badge"
:class="getStatusClass(device.status)"
>
{{ getStatusText(device.status) }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<button
class="page-btn arrow-btn"
@click="goToPreviousDevicePage"
:disabled="deviceCurrentPage === 1"
>
&lt;
</button>
<button
v-for="page in deviceTotalPages"
:key="page"
class="page-btn"
:class="{ active: deviceCurrentPage === page }"
@click="deviceCurrentPage = page"
>
{{ page }}
</button>
<button
class="page-btn arrow-btn"
@click="goToNextDevicePage"
:disabled="deviceCurrentPage === deviceTotalPages"
>
&gt;
</button>
</div>
</div>
<!-- 右侧维修保养模块 -->
<div class="maintenance-panel">
<!-- 左侧维修统计故障类型 -->
<div class="chart-container">
<div class="chart-title">维修统计故障类型</div>
<div id="maintenanceChart" class="chart"></div>
</div>
<!-- 右侧表格区域 -->
<div class="table-section">
<!-- 上部分切换按钮 -->
<div class="table-controls">
<button
class="control-btn"
:class="{ active: activeTable === '维修完成情况' }"
@click="activeTable = '维修完成情况'"
>
维修完成情况
</button>
<button
class="control-btn"
:class="{ active: activeTable === '保养统计' }"
@click="activeTable = '保养统计'"
>
保养统计
</button>
</div>
<!-- 下部分表格 -->
<div class="table-content">
<!-- 维修完成情况表 -->
<table class="device-table" v-if="activeTable === '维修完成情况'">
<thead>
<tr>
<th>工单编号</th>
<th>工单名称</th>
<th>设备名称</th>
<th>优先级</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in filteredMaintenanceData" :key="index">
<td>{{ item.workOrderId }}</td>
<td>{{ item.workOrderName }}</td>
<td>{{ item.deviceName }}</td>
<td>{{ item.priority }}</td>
<td>
<span
class="status-badge"
:class="getStatusClass(item.status)"
>
{{ getStatusText(item.status) }}
</span>
</td>
</tr>
</tbody>
</table>
<!-- 保养统计表 -->
<table class="device-table" v-if="activeTable === '保养统计'">
<thead>
<tr>
<th>保养编号</th>
<th>保养名称</th>
<th>设备名称</th>
<th>保养周期</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in filteredMaintenanceData" :key="index">
<td>{{ item.maintenanceId }}</td>
<td>{{ item.maintenanceName }}</td>
<td>{{ item.deviceName }}</td>
<td>{{ item.maintenanceCycle }}</td>
<td>
<span
class="status-badge"
:class="{
'status-running': item.status === '已完成',
'status-warning': item.status === '待开始',
'status-error': item.status === '故障'
}"
>
{{ item.status }}
</span>
</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<div class="pagination">
<button
class="page-btn arrow-btn"
@click="goToPreviousPage"
:disabled="maintenanceCurrentPage === 1"
>
&lt;
</button>
<button
v-for="page in maintenanceTotalPages"
:key="page"
class="page-btn"
:class="{ active: maintenanceCurrentPage === page }"
@click="maintenanceCurrentPage = page"
>
{{ page }}
</button>
<button
class="page-btn arrow-btn"
@click="goToNextPage"
:disabled="maintenanceCurrentPage === maintenanceTotalPages"
>
&gt;
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 下半部分设备图 -->
<div class="bottom-section">
<div class="layout-container">
<img
src="./icon/c354ee887a2f66c387ef3cd49be905b3.png"
alt="设备布局"
class="layout-image"
>
<!-- 标记点 -->
<div class="marker marker-1" @click="handleMarkerClick(1)"></div>
<div class="marker marker-2" @click="handleMarkerClick(2)"></div>
<div class="marker marker-3" @click="handleMarkerClick(3)"></div>
<div class="marker marker-4" @click="handleMarkerClick(4)"></div>
<div class="marker marker-5" @click="handleMarkerClick(5)"></div>
<div class="marker marker-6" @click="handleMarkerClick(6)"></div>
<div class="marker marker-7" @click="handleMarkerClick(7)"></div>
<div class="marker marker-8" @click="handleMarkerClick(8)"></div>
<div class="marker marker-9" @click="handleMarkerClick(98)"></div>
<div class="marker marker-16" @click="handleMarkerClick(16)"></div>
<div class="marker marker-15" @click="handleMarkerClick(15)"></div>
<div class="marker marker-17" @click="handleMarkerClick(17)"></div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// URL参数
id: '',
guid: '',
// 设备列表数据模拟2页每页10个
devices: [
{ id: 1, name: '全自动贴标机', status: 'running' },
{ id: 2, name: '蒸汽喷射式灭菌器', status: 'running' },
{ id: 3, name: '压片机', status: 'warning' },
{ id: 4, name: '冻干机自动进出料系统', status: 'running' },
{ id: 5, name: '热风循环灭菌烘箱', status: 'error' },
{ id: 6, name: '包装机', status: 'running' },
{ id: 7, name: '配料系统', status: 'running' },
{ id: 8, name: '全自动胶囊充填机', status: 'warning' },
{ id: 9, name: '颗粒包装机', status: 'running' },
{ id: 10, name: '粉末包装机', status: 'running' },
{ id: 11, name: '液体灌装机', status: 'running' },
{ id: 12, name: '西林瓶灌装机', status: 'warning' },
{ id: 13, name: '安瓿瓶灌装机', status: 'running' },
{ id: 14, name: '口服液灌装机', status: 'running' },
{ id: 15, name: '软膏灌装机', status: 'error' },
{ id: 16, name: '栓剂成型机', status: 'running' },
{ id: 17, name: '软胶囊机', status: 'running' },
{ id: 18, name: '硬胶囊机', status: 'warning' },
{ id: 19, name: '丸剂成型机', status: 'running' },
{ id: 20, name: '片剂包衣机', status: 'running' }
],
selectedDevice: 1,
deviceCurrentPage: 1,
deviceItemsPerPage: 5,
// 表格数据
activeTable: '维修完成情况',
maintenanceCurrentPage: 1,
maintenanceItemsPerPage: 5,
maintenanceData: [
// 维修完成情况数据20条
{ workOrderId: 'GZ20241106001', workOrderName: '片剂生产', deviceName: '压片机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106002', workOrderName: '胶囊填充', deviceName: '胶囊充填机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106003', workOrderName: '药品包装', deviceName: '包装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106004', workOrderName: '原料灭菌', deviceName: '灭菌器', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106005', workOrderName: '冻干处理', deviceName: '冻干机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106006', workOrderName: '配料准备', deviceName: '配料系统', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106007', workOrderName: '贴标处理', deviceName: '贴标机', priority: '低', status: 'running' },
{ workOrderId: 'GZ20241106008', workOrderName: '烘干处理', deviceName: '灭菌烘箱', priority: '高', status: 'error' },
{ workOrderId: 'GZ20241106009', workOrderName: '颗粒包装', deviceName: '颗粒包装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106010', workOrderName: '粉末包装', deviceName: '粉末包装机', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106011', workOrderName: '液体灌装', deviceName: '液体灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106012', workOrderName: '西林瓶灌装', deviceName: '西林瓶灌装机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106013', workOrderName: '安瓿瓶灌装', deviceName: '安瓿瓶灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106014', workOrderName: '口服液灌装', deviceName: '口服液灌装机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106015', workOrderName: '软膏灌装', deviceName: '软膏灌装机', priority: '高', status: 'error' },
{ workOrderId: 'GZ20241106016', workOrderName: '栓剂成型', deviceName: '栓剂成型机', priority: '中', status: 'running' },
{ workOrderId: 'GZ20241106017', workOrderName: '软胶囊生产', deviceName: '软胶囊机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106018', workOrderName: '硬胶囊生产', deviceName: '硬胶囊机', priority: '中', status: 'warning' },
{ workOrderId: 'GZ20241106019', workOrderName: '丸剂成型', deviceName: '丸剂成型机', priority: '高', status: 'running' },
{ workOrderId: 'GZ20241106020', workOrderName: '片剂包衣', deviceName: '片剂包衣机', priority: '高', status: 'running' }
],
maintenanceStats: [
// 保养统计数据20条
{ maintenanceId: 'BY20241106001', maintenanceName: '季度保养', deviceName: '压片机', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106002', maintenanceName: '月度保养', deviceName: '胶囊充填机', maintenanceCycle: '1个月', status: '待开始' },
{ maintenanceId: 'BY20241106003', maintenanceName: '年度保养', deviceName: '包装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106004', maintenanceName: '季度保养', deviceName: '灭菌器', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106005', maintenanceName: '月度保养', deviceName: '冻干机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106006', maintenanceName: '季度保养', deviceName: '配料系统', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106007', maintenanceName: '月度保养', deviceName: '贴标机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106008', maintenanceName: '年度保养', deviceName: '灭菌烘箱', maintenanceCycle: '12个月', status: '故障' },
{ maintenanceId: 'BY20241106009', maintenanceName: '季度保养', deviceName: '颗粒包装机', maintenanceCycle: '3个月', status: '已完成' },
{ maintenanceId: 'BY20241106010', maintenanceName: '月度保养', deviceName: '粉末包装机', maintenanceCycle: '1个月', status: '待开始' },
{ maintenanceId: 'BY20241106011', maintenanceName: '年度保养', deviceName: '液体灌装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106012', maintenanceName: '季度保养', deviceName: '西林瓶灌装机', maintenanceCycle: '3个月', status: '待开始' },
{ maintenanceId: 'BY20241106013', maintenanceName: '月度保养', deviceName: '安瓿瓶灌装机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106014', maintenanceName: '年度保养', deviceName: '口服液灌装机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106015', maintenanceName: '季度保养', deviceName: '软膏灌装机', maintenanceCycle: '3个月', status: '故障' },
{ maintenanceId: 'BY20241106016', maintenanceName: '月度保养', deviceName: '栓剂成型机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106017', maintenanceName: '年度保养', deviceName: '软胶囊机', maintenanceCycle: '12个月', status: '已完成' },
{ maintenanceId: 'BY20241106018', maintenanceName: '季度保养', deviceName: '硬胶囊机', maintenanceCycle: '3个月', status: '待开始' },
{ maintenanceId: 'BY20241106019', maintenanceName: '月度保养', deviceName: '丸剂成型机', maintenanceCycle: '1个月', status: '已完成' },
{ maintenanceId: 'BY20241106020', maintenanceName: '年度保养', deviceName: '片剂包衣机', maintenanceCycle: '12个月', status: '已完成' }
]
};
},
computed: {
deviceTotalPages() {
return Math.ceil(this.devices.length / this.deviceItemsPerPage);
},
filteredDevices() {
const start = (this.deviceCurrentPage - 1) * this.deviceItemsPerPage;
return this.devices.slice(start, start + this.deviceItemsPerPage);
},
maintenanceTotalPages() {
const data = this.activeTable === '维修完成情况' ? this.maintenanceData : this.maintenanceStats;
return Math.ceil(data.length / this.maintenanceItemsPerPage);
},
filteredMaintenanceData() {
const data = this.activeTable === '维修完成情况' ? this.maintenanceData : this.maintenanceStats;
const start = (this.maintenanceCurrentPage - 1) * this.maintenanceItemsPerPage;
return data.slice(start, start + this.maintenanceItemsPerPage);
}
},
methods: {
// 获取URL参数
getUrlParams() {
this.id = this.$route.query.id || '';
this.guid = this.$route.query.guid || '';
console.log('URL参数:', { id: this.id, guid: this.guid });
},
selectDevice(deviceId) {
this.selectedDevice = deviceId;
},
getStatusClass(status) {
switch(status) {
case 'running': return 'status-running';
case 'warning': return 'status-warning';
case 'error': return 'status-error';
default: return '';
}
},
getStatusText(status) {
switch(status) {
case 'running': return '运行中';
case 'warning': return '预警';
case 'error': return '故障';
default: return '未知';
}
},
goToPreviousPage() {
if (this.maintenanceCurrentPage > 1) {
this.maintenanceCurrentPage--;
}
},
goToNextPage() {
if (this.maintenanceCurrentPage < this.maintenanceTotalPages) {
this.maintenanceCurrentPage++;
}
},
goToPreviousDevicePage() {
if (this.deviceCurrentPage > 1) {
this.deviceCurrentPage--;
}
},
goToNextDevicePage() {
if (this.deviceCurrentPage < this.deviceTotalPages) {
this.deviceCurrentPage++;
}
},
// 标记点点击事件处理函数(预留)
handleMarkerClick(markerId) {
console.log('Marker clicked:', markerId);
// 后续可以根据markerId执行不同的操作
// 例如:显示设备详情、跳转到设备监控页面等
},
initChart() {
const chartDom = document.getElementById('maintenanceChart');
const myChart = this.$echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)',
textStyle: {
color: '#ffffff'
}
},
legend: {
orient: 'vertical',
left: 'left',
textStyle: {
color: '#ffffff',
fontSize: 12
}
},
series: [
{
name: '故障类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 0,
borderColor: '#001a33',
borderWidth: 2
},
label: {
show: true,
position: 'outside',
formatter: '{b}\n{d}%',
color: '#ffffff',
fontSize: 12
},
labelLine: {
show: true,
lineStyle: {
color: '#ffffff'
}
},
data: [
{ value: 14, name: '机械故障', itemStyle: { color: '#ff8c00' } },
{ value: 5, name: '电气故障', itemStyle: { color: '#9370db' } },
{ value: 4, name: '系统故障', itemStyle: { color: '#4682b4' } },
{ value: 4, name: '其他', itemStyle: { color: '#ffd700' } }
]
}
]
};
myChart.setOption(option);
window.addEventListener('resize', () => {
myChart.resize();
});
}
},
mounted() {
this.getUrlParams();
this.initChart();
}
};
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
height: 100vh;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
background: url('./icon/b4875ec42c53f353804aad374e932828.png') no-repeat center center;
background-size: 100% 100%;
color: #ffffff;
overflow: hidden;
}
.header {
text-align: center;
padding: 10px 0;
font-size: 28px;
font-weight: bold;
color: #ffffff;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
flex-shrink: 0;
}
.top-section {
display: grid;
grid-template-columns: 30% 68%;
gap: 30px;
flex: 1;
padding: 40px;
padding-bottom: 20px;
overflow: hidden;
}
.device-sidebar {
background: url('./icon/c73de8bc60f90ef1a6e63178f6d8f862.png') no-repeat center;
background-size: 100% 100%;
padding: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-title {
font-size: 16px;
font-weight: bold;
color: #ffffff;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #00ffff;
flex-shrink: 0;
padding-left:20px;
}
.table-container {
padding: 15px;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.table-section {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.table-content {
flex: 1;
overflow: auto;
display: flex;
flex-direction: column;
}
.table-controls {
display: flex;
gap: 0;
margin-bottom: 10px;
}
.control-btn {
padding: 6px 12px;
background: #0066cc;
border: 1px solid #0066cc;
color: #ffffff;
cursor: pointer;
font-size: 11px;
transition: all 0.3s ease;
}
.control-btn:first-child {
border-radius: 4px 0 0 4px;
}
.control-btn:last-child {
border-radius: 0 4px 4px 0;
}
.control-btn:hover {
background: #0088ff;
}
.control-btn.active {
background: #0088ff;
}
.device-table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
flex: 1;
overflow: auto;
}
.device-table th,
.device-table td {
padding: 8px 10px;
text-align: left;
border-bottom: 1px solid rgba(0, 255, 255, 0.2);
}
.device-table th {
background: rgba(0, 255, 255, 0.1);
color: #ffffff;
font-weight: bold;
}
.device-table tbody tr:hover {
background: rgba(0, 255, 255, 0.1);
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 15px;
gap: 5px;
align-items: center;
}
.page-btn {
width: 30px;
height: 30px;
background: transparent;
border: none;
color: #ffffff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
transition: all 0.3s ease;
}
.page-btn.arrow-btn {
background: rgba(0, 255, 255, 0.1);
border-radius: 4px;
}
.page-btn:hover {
background: rgba(0, 255, 255, 0.2);
}
.page-btn.active {
background: #00ffff;
color: #000000;
border-radius: 4px;
font-weight: bold;
}
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: bold;
}
.status-running {
background: rgba(0, 255, 0, 0.2);
color: #00ff00;
}
.status-warning {
background: rgba(255, 255, 0, 0.2);
color: #ffff00;
}
.status-error {
background: rgba(255, 0, 0, 0.2);
color: #ff0000;
}
.bottom-section {
flex: 1;
padding: 20px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.layout-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.layout-container {
position: relative;
width: 90%;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
.marker {
position: absolute;
width: 30px;
height: 30px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
transform: translate(-50%, -50%);
cursor: pointer;
transition: transform 0.3s ease;
}
.marker:hover {
transform: translate(-50%, -50%) scale(1.2);
}
.marker-1 {
transform: scale(1.5);
top: 45%;
left: 245px;
background-image: url('./icon/371b06b22391dea6aa6adf7fb36c7d2b.png');
}
.marker-2 {
transform: scale(1.5);
top: 45%;
left: 375px;
background-image: url('./icon/371b06b22391dea6aa6adf7fb36c7d2b.png');
}
.marker-3 {
transform: scale(1.5);
top: 45%;
left: 510px;
background-image: url('./icon/371b06b22391dea6aa6adf7fb36c7d2b.png');
}
.marker-4 {
transform: scale(1.5);
top: 42%;
left: 653px;
background-image: url('./icon/371b06b22391dea6aa6adf7fb36c7d2b.png');
}
.marker-5 {
transform: scale(1.5);
top: 55%;
left: 409px;
background-image: url('./icon/633bec62b4c6d35098f06c189591135d.png');
}
.marker-6 {
transform: scale(1.5);
top: 55%;
left: 546px;
background-image: url('./icon/633bec62b4c6d35098f06c189591135d.png');
}
.marker-15 {
transform: scale(1.5);
top: 55%;
left: 686px;
background-image: url('./icon/633bec62b4c6d35098f06c189591135d.png');
}
.marker-16 {
transform: scale(1.5);
top: 55%;
left: 272px;
background-image: url('./icon/633bec62b4c6d35098f06c189591135d.png');
}
.marker-7 {
transform: scale(1.5);
top: 10%;
left: 985px;
background-image: url('./icon/b257f08f180fba833e8302c54f815ba8.png');
}
.marker-8 {
transform: scale(1.5);
top: 24%;
left: 992px;
background-image: url('./icon/b257f08f180fba833e8302c54f815ba8.png');
}
.marker-9 {
transform: scale(1.5);
top: 39%;
left: 1003px;
background-image: url('./icon/b257f08f180fba833e8302c54f815ba8.png');
}
.marker-17 {
transform: scale(1.5);
top: 69%;
left: 230px;
background-image: url('./icon/2729bbc4b984dec97dbb2d97ceb39ae6.png');
}
.chart-container {
padding: 15px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.chart-title {
font-size: 14px;
font-weight: bold;
color: #ffffff;
margin-bottom: 10px;
flex-shrink: 0;
margin-top: -10px;
padding-left: 10px;
}
.chart {
flex: 1;
min-height: 0;
}
.maintenance-panel {
background: url('./icon/f0f06ffbc9509f233fe5b4e670416c31.png') no-repeat center;
background-size: 100% 100%;
padding: 15px;
display: grid;
grid-template-columns: 2fr 3fr;
gap: 10px;
overflow: hidden;
}
</style>