feat: 新增多个业务组件与功能,优化页面样式与依赖

1. 新增QRCode二维码组件、ChecklistSelect选择器、DragResizePanel拖拽面板
2. 更新项目依赖,新增qrcode、html2canvas等工具库
3. 重构侧边栏Logo与导航栏样式,调整布局与视觉效果
4. 优化设备巡检日报页面,支持多日期范围查询与多维度统计
5. 修复迷你按钮样式,更新设备巡检记录页面功能与交互
6. 更新项目logo资源与部分样式变量
This commit is contained in:
2026-06-08 14:21:39 +08:00
parent 022312d02c
commit dd70c94dd7
12 changed files with 1920 additions and 1036 deletions

View File

@@ -39,22 +39,26 @@
"@riophae/vue-treeselect": "0.4.0",
"axios": "0.28.1",
"bpmn-js": "^11.1.0",
"bpmnlint": "^6.4.0",
"bpmn-js-bpmnlint": "^0.15.0",
"bpmnlint": "^6.4.0",
"bpmnlint-loader": "^0.1.4",
"file-drops": "^0.4.0",
"clipboard": "2.0.8",
"core-js": "3.37.1",
"diagram-js": "^11.4.1",
"dom-to-image": "^2.6.0",
"echarts": "5.4.0",
"element-ui": "2.15.14",
"file-drops": "^0.4.0",
"file-saver": "2.0.5",
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"html2canvas": "^1.4.1",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"pdf-lib": "^1.17.1",
"qrcode": "^1.5.4",
"quill": "2.0.2",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -53,7 +53,7 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
.el-button { padding: 5px 12px !important; font-size: 12px !important; }
.el-button--medium { padding: 6px 14px !important; font-size: 12px !important; }
.el-button--small { padding: 4px 10px !important; font-size: 12px !important; }
.el-button--mini { padding: 3px 8px !important; font-size: 12px !important; }
.el-button--mini { padding: 4px 4px !important; font-size: 12px !important; }
// 表格
.el-table {

View File

@@ -1,227 +1,227 @@
#app {
.main-container {
height: 100%;
transition: margin-left .28s;
margin-left: $base-sidebar-width;
position: relative;
}
.sidebarHide {
margin-left: 0!important;
}
.sidebar-container {
-webkit-transition: width .28s;
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 2px 0 6px rgba(0,21,41,.35);
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
.el-menu-item, .el-submenu__title {
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
&:hover {
background-color: rgba(255,255,255,.06) !important;
}
}
& .theme-dark .is-active > .el-submenu__title {
color: $base-menu-color-active !important;
}
.el-menu-item.is-active {
background-color: #1d4e89 !important;
color: #ffffff !important;
border-left: 3px solid #5dade2;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $base-sidebar-width !important;
&:hover { background-color: rgba(255,255,255,.06) !important; }
}
& .theme-dark .nest-menu .el-submenu>.el-submenu__title,
& .theme-dark .el-submenu .el-menu-item {
background-color: $base-sub-menu-background !important;
&:hover { background-color: $base-sub-menu-hover !important; }
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $base-sidebar-width !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: $base-sidebar-width !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$base-sidebar-width, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: rgba(0, 0, 0, 0.06) !important;
}
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}
#app {
.main-container {
height: 100%;
transition: margin-left .28s;
margin-left: $base-sidebar-width;
position: relative;
}
.sidebarHide {
margin-left: 0!important;
}
.sidebar-container {
-webkit-transition: width .28s;
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 2px 0 6px rgba(0,21,41,.35);
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 56px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
.el-menu-item, .el-submenu__title {
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
&:hover {
background-color: rgba(255,255,255,.06) !important;
}
}
& .theme-dark .is-active > .el-submenu__title {
color: $base-menu-color-active !important;
}
.el-menu-item.is-active {
background-color: #1d4e89 !important;
color: #ffffff !important;
border-left: 3px solid #5dade2;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: $base-sidebar-width !important;
&:hover { background-color: rgba(255,255,255,.06) !important; }
}
& .theme-dark .nest-menu .el-submenu>.el-submenu__title,
& .theme-dark .el-submenu .el-menu-item {
background-color: $base-sub-menu-background !important;
&:hover { background-color: $base-sub-menu-hover !important; }
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $base-sidebar-width !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: $base-sidebar-width !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$base-sidebar-width, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: rgba(0, 0, 0, 0.06) !important;
}
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@@ -0,0 +1,72 @@
<template>
<el-select
v-model="innerValue"
filterable
:loading="loading"
placeholder="请选择检验清单"
clearable
@change="handleChange"
style="width: 100%"
>
<el-option
v-for="item in options"
:key="item.checkId"
:label="`${item.checkNo || '无编号'} - ${item.partName || ''}`"
:value="item.checkId"
/>
</el-select>
</template>
<script>
import { listEquipmentChecklist } from "@/api/mill/equipmentChecklist";
export default {
name: "ChecklistSelect",
props: {
value: {
type: [String, Number],
default: ""
}
},
data() {
return {
loading: false,
options: [],
innerValue: this.value
};
},
watch: {
value(val) {
this.innerValue = val;
}
},
mounted() {
this.loadAll();
},
methods: {
async loadAll() {
this.loading = true;
try {
const res = await listEquipmentChecklist({ pageNum: 1, pageSize: 9999 });
if (res.code === 200) {
this.options = res.rows || [];
}
} catch (e) {
console.error("获取检验清单列表失败", e);
} finally {
this.loading = false;
}
},
handleChange(val) {
if (!val) {
this.$emit('input', "");
this.$emit('change', "", null);
} else {
this.$emit('input', val);
const row = this.options.find(o => o.checkId === val);
this.$emit('change', val, row || null);
}
}
}
};
</script>

View File

@@ -0,0 +1,159 @@
<template>
<div class="drag-resize-panel" :class="{ 'vertical': direction === 'vertical' }">
<div class="panel-a" :style="panelAstyle">
<slot name="panelA"></slot>
</div>
<div class="resizer" :class="{ 'vertical-resizer': direction === 'vertical' }" @mousedown="startResize"></div>
<div class="panel-b" :style="panelBstyle">
<slot name="panelB"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'DragResizePanel',
props: {
direction: {
type: String,
default: 'horizontal',
validator: function(value) {
return ['horizontal', 'vertical'].includes(value);
}
},
// 设置panelA的初始大小最大值和最小值默认值为300px10000px100px
// panelB占据剩余空间
initialSize: {
type: Number,
default: 300
},
minSize: {
type: Number,
default: 100
},
maxSize: {
type: Number,
default: 10000
}
},
data() {
return {
currentSize: this.initialSize,
isResizing: false,
startPosition: 0
};
},
computed: {
panelAstyle() {
if (this.direction === 'horizontal') {
return { width: this.currentSize + 'px' };
} else {
return { height: this.currentSize + 'px' };
}
},
panelBstyle() {
if (this.direction === 'horizontal') {
return { flex: 1 };
} else {
return { flex: 1 };
}
}
},
methods: {
startResize(e) {
e.preventDefault();
this.isResizing = true;
this.startPosition = this.direction === 'horizontal' ? e.clientX : e.clientY;
this.startSize = this.currentSize;
document.addEventListener('mousemove', this.handleResize);
document.addEventListener('mouseup', this.stopResize);
},
handleResize(e) {
if (!this.isResizing) return;
const delta = this.direction === 'horizontal' ? e.clientX - this.startPosition : e.clientY - this.startPosition;
const newSize = this.startSize + delta;
if (newSize >= this.minSize && newSize <= this.maxSize) {
this.currentSize = newSize;
}
},
stopResize() {
this.isResizing = false;
document.removeEventListener('mousemove', this.handleResize);
document.removeEventListener('mouseup', this.stopResize);
}
}
};
</script>
<style scoped>
.drag-resize-panel {
display: flex;
height: 100%;
overflow: hidden;
}
.drag-resize-panel.vertical {
flex-direction: column;
}
.panel-a {
position: relative;
overflow: hidden;
}
.panel-b {
position: relative;
overflow: auto;
}
.resizer {
width: 4px;
cursor: col-resize;
position: relative;
transition: background-color 0.3s;
height: 100%;
}
.resizer.vertical-resizer {
width: 100%;
height: 4px;
cursor: row-resize;
}
.resizer:hover {
background-color: #409eff;
}
.resizer::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 40px;
width: 2px;
background-color: #c0c4cc;
border-radius: 1px;
transition: background-color 0.3s;
}
.resizer.vertical-resizer::before {
width: 40px;
height: 2px;
}
.resizer:hover::before {
background-color: #409eff;
}
.resizer:active {
background-color: #409eff;
}
.resizer:active::before {
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="qrcode-wrapper">
<canvas ref="qrcode"></canvas>
<p v-if="text" class="qrcode-text">{{ text }}</p>
</div>
</template>
<script>
import QRCode from 'qrcode';
export default {
name: 'QRCode',
props: {
content: {
type: String,
required: true
},
size: {
type: Number,
default: 90
},
text: {
type: String,
default: ''
}
},
data() {
return {
qrcode: null
}
},
watch: {
content: {
handler(newVal, oldVal) {
if (newVal && newVal !== oldVal) {
this.generateQRCode();
}
},
immediate: true
}
},
mounted() {
this.generateQRCode();
},
methods: {
generateQRCode() {
const el = this.$refs.qrcode;
const content = this.content;
if (!content || !el) return;
QRCode.toCanvas(el, content, {
width: this.size,
height: this.size,
margin: 0
});
}
}
}
</script>
<style scoped>
.qrcode-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.qrcode-text {
margin-top: 8px;
font-size: 14px;
color: #303133;
text-align: center;
}
</style>

View File

@@ -1,199 +1,221 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<i class="el-icon-user-solid user-icon" />
<span class="user-name">{{ name }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
export default {
components: {
Breadcrumb,
TopNav,
Hamburger,
Screenfull,
SizeSelect,
Search,
RuoYiGit,
RuoYiDoc
},
computed: {
...mapGetters([
'sidebar',
'name',
'device'
]),
setting: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
},
topNav: {
get() {
return this.$store.state.settings.topNav
}
}
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
logout() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('LogOut').then(() => {
location.href = '/index'
})
}).catch(() => {})
}
}
}
</script>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color:transparent;
color: #ccc;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
.breadcrumb-container {
float: left;
}
.topmenu-container {
position: absolute;
left: 50px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #ccc;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 16px;
.avatar-wrapper {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: #5a6f85;
font-size: 12px;
.user-icon {
font-size: 16px;
color: #1d4e89;
}
.user-name {
font-size: 12px;
color: #2c3e50;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-icon-caret-bottom {
font-size: 11px;
color: #909399;
}
}
}
}
}
</style>
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!topNav" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="topNav" id="topmenu-container" class="topmenu-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<i class="el-icon-user-solid user-icon" />
<span class="user-name">{{ name }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown">
<router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item divided @click.native="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from '@/components/TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
export default {
name: 'Navbar',
components: {
Breadcrumb,
TopNav,
Hamburger,
Screenfull,
SizeSelect,
Search,
RuoYiGit,
RuoYiDoc
},
computed: {
...mapGetters([
'sidebar',
'name',
'device'
]),
setting: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
},
topNav: {
get() {
return this.$store.state.settings.topNav
}
}
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
logout() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('LogOut').then(() => {
location.href = '/index'
})
}).catch(() => {})
}
}
}
</script>
<style lang="scss" scoped>
.navbar {
height: 50px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
.hamburger-container {
line-height: 50px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.25s;
-webkit-tap-highlight-color: transparent;
padding: 0 12px;
color: #5a6f85;
&:hover {
background: rgba(26, 77, 137, 0.04);
color: #1d4e89;
}
}
.breadcrumb-container {
float: left;
margin-left: 4px;
}
.topmenu-container {
position: absolute;
left: 50px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
float: right;
height: 100%;
display: flex;
align-items: center;
margin-left: auto;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 10px;
height: 100%;
font-size: 16px;
color: #6b7b8d;
&.hover-effect {
cursor: pointer;
transition: all 0.25s;
&:hover {
background: rgba(26, 77, 137, 0.04);
color: #1d4e89;
}
}
}
.avatar-container {
margin-right: 8px;
padding: 0 12px 0 8px;
.avatar-wrapper {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: #5a6f85;
font-size: 13px;
.user-icon {
font-size: 18px;
color: #1d4e89;
background: rgba(29, 78, 137, 0.1);
border-radius: 50%;
padding: 4px;
}
.user-name {
font-size: 13px;
font-weight: 500;
color: #2c3e50;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-icon-caret-bottom {
font-size: 11px;
color: #a0abb8;
transition: transform 0.2s;
}
}
&:hover {
.el-icon-caret-bottom {
transform: rotate(180deg);
}
}
}
}
}
</style>

View File

@@ -1,94 +1,119 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
</router-link>
</transition>
</div>
</template>
<script>
import logoImg from '@/assets/logo/logo.png'
import variables from '@/assets/styles/variables.scss'
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
computed: {
variables() {
return variables;
},
sideTheme() {
return this.$store.state.settings.sideTheme
}
},
data() {
return {
title: process.env.VUE_APP_TITLE,
logo: logoImg
}
}
}
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
background-color: #fff;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<div class="sidebar-logo-inner">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
</div>
</router-link>
</transition>
</div>
</template>
<script>
import logoImg from '@/assets/logo/logo.png'
import variables from '@/assets/styles/variables.scss'
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
computed: {
variables() {
return variables;
},
sideTheme() {
return this.$store.state.settings.sideTheme
}
},
data() {
return {
title: process.env.VUE_APP_TITLE,
logo: logoImg
}
}
}
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 56px;
line-height: 56px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
& .sidebar-logo-link {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
& .sidebar-logo-inner {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
& .sidebar-logo {
width: 28px;
height: 28px;
flex-shrink: 0;
border-radius: 4px;
vertical-align: middle;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 700;
line-height: 56px;
font-size: 15px;
font-family: Avenir, Helvetica Neue, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
vertical-align: middle;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
}
}
&.collapse {
height: 50px;
line-height: 50px;
.sidebar-logo-link {
.sidebar-logo {
width: 24px;
height: 24px;
margin: 0;
}
}
}
}
</style>

View File

@@ -1,160 +1,308 @@
<template>
<div class="app-container" v-loading="loading">
<el-form size="small" :inline="true" style="margin-bottom: 10px;">
<el-form-item label="日期">
<el-date-picker v-model="queryDate" type="date" value-format="yyyy-MM-dd"
placeholder="选择日期" @change="handleQuery" style="width: 160px;" />
</el-form-item>
<el-form-item label="产线段">
<el-input v-model="lineSection" placeholder="请输入产线段" clearable style="width: 130px;" @change="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<el-row>
<el-form label-width="80px" inline>
<el-form-item label="时间段">
<el-date-picker v-model="dateRange" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
@change="handleQuery" style="width: 260px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
</el-row>
<el-descriptions title="巡检日报" :column="6" border style="margin-bottom: 16px;">
<el-descriptions title="巡检日报" :column="4" border style="margin-bottom: 16px;">
<el-descriptions-item label="巡检部位数">{{ summary.partCount }}</el-descriptions-item>
<el-descriptions-item label="巡检项数">{{ summary.checklistCount }}</el-descriptions-item>
<el-descriptions-item label="总巡检次数">{{ summary.totalCount }}</el-descriptions-item>
<el-descriptions-item label="理论应检次数">{{ summary.expectedTotal }}</el-descriptions-item>
<el-descriptions-item label="通过">
<el-tag type="success" size="small">{{ summary.passCount }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="故障">
<el-descriptions-item label="不通过">
<el-tag type="danger" size="small">{{ summary.failCount }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="通过率">{{ summary.passRate }}</el-descriptions-item>
<el-descriptions-item label="检验完成率">{{ summary.completionRate }}</el-descriptions-item>
</el-descriptions>
<el-table :data="tableData" border stripe style="width: 100%;" :cell-style="cellStyle">
<el-table-column label="巡检部位" prop="inspectPart" align="center" width="130" />
<el-table-column label="巡检内容" prop="checkContent" align="center" min-width="200" show-overflow-tooltip />
<el-table-column label="检验标准" prop="checkStandard" align="center" min-width="160" show-overflow-tooltip />
<el-table-column label="白班" align="center" width="160">
<!-- 负责人汇总 -->
<el-card class="section-card" v-if="inspectorSummary.length" style="margin-bottom: 16px;">
<div slot="header"><i class="el-icon-user"></i> 按实际巡检人汇总</div>
<el-table :data="inspectorSummary" size="small" stripe border>
<el-table-column prop="inspector" label="巡检人" />
<el-table-column prop="total" label="巡检次数" align="center" />
<el-table-column prop="pass" label="通过" align="center" />
<el-table-column prop="fail" label="不通过" align="center" />
<el-table-column prop="passRate" label="通过率" align="center" />
</el-table>
</el-card>
<el-card class="section-card" v-if="personSummary.length" style="margin-bottom: 16px;">
<div slot="header"><i class="el-icon-user"></i> 按负责人汇总</div>
<el-table :data="personSummary" size="small" stripe border>
<el-table-column prop="responsiblePerson" label="负责人" />
<el-table-column prop="partCount" label="负责部位数" align="center" />
<el-table-column prop="checkCount" label="巡检项数" align="center" />
<el-table-column prop="totalInspections" label="实检次数" align="center" />
<el-table-column prop="expectedTotal" label="应检次数" align="center" />
<el-table-column prop="passCount" label="通过" align="center" />
<el-table-column prop="failCount" label="不通过" align="center" />
<el-table-column label="完成率" align="center">
<template slot-scope="scope">
<el-progress :percentage="scope.row.completionRate" :stroke-width="6" :show-text="true" />
</template>
</el-table-column>
</el-table>
</el-card>
<el-table :data="tableData" border stripe style="width: 100%" :cell-style="cellStyle"
@cell-mouse-enter="handleCellEnter" @cell-mouse-leave="handleCellLeave">
<el-table-column label="巡检部位" align="center" prop="partName" width="140" />
<el-table-column label="巡检内容" align="center" prop="checkContent" min-width="200" show-overflow-tooltip />
<el-table-column label="白班" align="center" width="150">
<template slot-scope="scope">
<div v-if="scope.row.dayRecords && scope.row.dayRecords.length" style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;">
<el-tooltip v-for="(r, idx) in scope.row.dayRecords" :key="idx" placement="top">
<div slot="content">
{{ r.inspectTime }} | 白班 | {{ r.inspector }}<br/>
{{ r.runStatus === 1 ? '✓ 正常' : '✗ 故障' }}
<span v-if="r.abnormalDesc"> {{ r.abnormalDesc }}</span>
</div>
<span :class="r.runStatus === 1 ? 'result-pass' : 'result-fail'" class="result-icon">
{{ r.runStatus === 1 ? '✓' : '✗' }}
</span>
</el-tooltip>
<div v-if="scope.row.dayRecords && scope.row.dayRecords.length" style="display: flex; gap: 8px;">
<div v-for="(r, idx) in scope.row.dayRecords" :key="idx" style="margin:2px 0;">
<el-tooltip placement="top" popper-class="inspect-tooltip">
<div slot="content">
<div>{{ r.inspectTime }} | 白班 | {{ r.inspector }} | {{ r.runStatus == 1 ? '通过' : '不通过' }}{{ r.abnormalDesc ? '异常: ' + r.abnormalDesc : '' }}</div>
</div>
<span v-if="r.runStatus == 1" class="result-icon result-pass"></span>
<span v-else class="result-icon result-fail"></span>
</el-tooltip>
</div>
</div>
<span v-else class="result-none"></span>
<span v-else class="result-none">-</span>
</template>
</el-table-column>
<el-table-column label="夜班" align="center" width="160">
<el-table-column label="夜班" align="center" width="150">
<template slot-scope="scope">
<div v-if="scope.row.nightRecords && scope.row.nightRecords.length" style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;">
<el-tooltip v-for="(r, idx) in scope.row.nightRecords" :key="idx" placement="top">
<div slot="content">
{{ r.inspectTime }} | 夜班 | {{ r.inspector }}<br/>
{{ r.runStatus === 1 ? '✓ 正常' : '✗ 故障' }}
<span v-if="r.abnormalDesc"> {{ r.abnormalDesc }}</span>
</div>
<span :class="r.runStatus === 1 ? 'result-pass' : 'result-fail'" class="result-icon">
{{ r.runStatus === 1 ? '✓' : '✗' }}
</span>
</el-tooltip>
<div v-if="scope.row.nightRecords && scope.row.nightRecords.length" style="display: flex; gap: 8px;">
<div v-for="(r, idx) in scope.row.nightRecords" :key="idx" style="margin:2px 0;">
<el-tooltip placement="top" popper-class="inspect-tooltip">
<div slot="content">
<div>{{ r.inspectTime }} | 夜班 | {{ r.inspector }} | {{ r.runStatus == 1 ? '通过' : '不通过' }} {{ r.abnormalDesc ? '异常: ' + r.abnormalDesc : '' }}</div>
</div>
<span v-if="r.runStatus == 1" class="result-icon result-pass"></span>
<span v-else class="result-icon result-fail"></span>
</el-tooltip>
</div>
</div>
<span v-else class="result-none"></span>
<span v-else class="result-none">-</span>
</template>
</el-table-column>
<el-table-column prop="expectedCount" label="应检" width="55" align="center" />
<el-table-column prop="actualCount" label="实检" width="55" align="center" />
<el-table-column label="差异" width="55" align="center">
<template slot-scope="scope">
<span v-if="scope.row.diff > 0" class="diff-over">+{{ scope.row.diff }}</span>
<span v-else-if="scope.row.diff < 0" class="diff-under">{{ scope.row.diff }}</span>
<span v-else class="diff-ok">0</span>
</template>
</el-table-column>
<el-table-column prop="passCount" label="通过" width="55" align="center" />
<el-table-column prop="failCount" label="不通过" width="60" align="center" />
</el-table>
</div>
</template>
<script>
import { listEquipmentPart } from '@/api/mill/equipmentPart'
import { listEquipmentChecklist } from '@/api/mill/equipmentChecklist'
import { listEquipmentInspectionRecord } from '@/api/mill/equipmentInspectionRecord'
import { listEquipmentPart } from "@/api/mill/equipmentPart";
import { listEquipmentChecklist } from "@/api/mill/equipmentChecklist";
import { listEquipmentInspectionRecord } from "@/api/mill/equipmentInspectionRecord";
export default {
name: 'EqpDay',
name: "DailyInspectionReport",
data() {
const today = new Date()
const pad = n => String(n).padStart(2, '0')
const d = new Date();
const today = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
return {
loading: false,
queryDate: `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`,
lineSection: undefined,
dateRange: [today, today],
partList: [],
checklistList: [],
records: []
}
checklistData: [],
records: [],
};
},
computed: {
checklistList() {
return this.checklistData.map(cl => {
const part = this.partList.find(p => p.partId === cl.partId);
return {
...cl,
partName: cl.partName || (part ? part.inspectPart : ''),
};
});
},
tableData() {
const recordMap = {}
const recordMap = {};
this.records.forEach(r => {
const key = `${r.checkId}_${r.shift}`
if (!recordMap[key]) recordMap[key] = []
recordMap[key].push(r)
})
const key = `${r.checkId}_${r.shift}`;
if (!recordMap[key]) recordMap[key] = [];
recordMap[key].push(r);
});
return this.checklistList.map(cl => {
const part = this.partList.find(p => p.partId === cl.partId) || {}
const dayRecords = recordMap[`${cl.checkId}_1`] || [];
const nightRecords = recordMap[`${cl.checkId}_2`] || [];
const expectedCount = this.daysInRange * 4;
const actualCount = dayRecords.length + nightRecords.length;
const passCount = [...dayRecords, ...nightRecords].filter(r => r.runStatus === 1).length;
const failCount = [...dayRecords, ...nightRecords].filter(r => r.runStatus === 2).length;
return {
checkId: cl.checkId,
inspectPart: part.inspectPart || cl.partName,
partName: cl.partName,
checkContent: cl.checkContent,
checkStandard: cl.checkStandard,
dayRecords: recordMap[`${cl.checkId}_1`] || [],
nightRecords: recordMap[`${cl.checkId}_2`] || []
}
})
dayRecords,
nightRecords,
expectedCount,
actualCount,
diff: actualCount - expectedCount,
passCount,
failCount,
};
});
},
summary() {
const totalCount = this.records.length
const passCount = this.records.filter(r => r.runStatus === 1).length
const failCount = this.records.filter(r => r.runStatus === 2).length
const days = this.daysInRange || 1;
const expectedTotal = this.checklistList.length * days * 4;
const totalCount = this.records.length;
const passCount = this.records.filter(r => r.runStatus === 1).length;
const failCount = this.records.filter(r => r.runStatus === 2).length;
return {
partCount: this.partList.length,
checklistCount: this.checklistList.length,
totalCount,
expectedTotal,
passCount,
failCount,
passRate: totalCount > 0 ? (passCount / totalCount * 100).toFixed(1) + '%' : '0%'
}
}
},
mounted() {
this.handleQuery()
passRate: totalCount > 0 ? (passCount / totalCount * 100).toFixed(1) + "%" : "0%",
completionRate: expectedTotal > 0 ? (totalCount / expectedTotal * 100).toFixed(1) + "%" : "0%",
};
},
daysInRange() {
if (!this.dateRange || this.dateRange.length !== 2) return 1;
const start = new Date(this.dateRange[0]);
const end = new Date(this.dateRange[1]);
return Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
},
inspectorSummary() {
const map = {};
this.records.forEach(r => {
const name = r.inspector || '未指定';
if (!map[name]) {
map[name] = { inspector: name, total: 0, pass: 0, fail: 0 };
}
map[name].total++;
if (r.runStatus === 1) map[name].pass++;
else if (r.runStatus === 2) map[name].fail++;
});
return Object.values(map).map(p => ({
...p,
passRate: p.total > 0 ? (p.pass / p.total * 100).toFixed(1) + '%' : '0%',
}));
},
personSummary() {
const personMap = {};
this.partList.forEach(p => {
const person = p.responsiblePerson || '未指定';
if (!personMap[person]) {
personMap[person] = { responsiblePerson: person, partIds: new Set(), checkIds: new Set(), recordIds: new Set(), passIds: new Set(), failIds: new Set() };
}
personMap[person].partIds.add(p.partId);
this.checklistData.filter(cl => cl.partId === p.partId).forEach(cl => personMap[person].checkIds.add(cl.checkId));
});
this.records.forEach(r => {
for (const person of Object.values(personMap)) {
if (person.checkIds.has(r.checkId)) {
person.recordIds.add(r.recordId);
if (r.runStatus === 1) person.passIds.add(r.recordId);
if (r.runStatus === 2) person.failIds.add(r.recordId);
break;
}
}
});
return Object.values(personMap).map(p => ({
responsiblePerson: p.responsiblePerson,
partCount: p.partIds.size,
checkCount: p.checkIds.size,
totalInspections: p.recordIds.size,
expectedTotal: p.checkIds.size * this.daysInRange * 4,
passCount: p.passIds.size,
failCount: p.failIds.size,
completionRate: p.checkIds.size * this.daysInRange * 4 > 0
? Math.round(p.recordIds.size / (p.checkIds.size * this.daysInRange * 4) * 100)
: 0,
}));
},
},
methods: {
cellStyle({ columnIndex }) {
if (columnIndex === 3 || columnIndex === 4) return { fontSize: '20px', padding: '4px 8px' }
getToday() {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
},
cellStyle({ columnIndex }) {
if (columnIndex === 2 || columnIndex === 3) return { fontSize: "20px", padding: "4px" };
},
handleCellEnter(row, column) {},
handleCellLeave(row, column) {},
async handleQuery() {
if (!this.queryDate) return
this.loading = true
if (!this.dateRange || this.dateRange.length !== 2) return;
this.loading = true;
try {
const partParams = { pageNum: 1, pageSize: 9999 }
if (this.lineSection) partParams.lineSection = this.lineSection
const partParams = {};
const recordParams = {
startInspectTime: this.dateRange[0] + ' 00:00:00',
endInspectTime: this.dateRange[1] + ' 23:59:59',
pageSize: 9999,
};
const [partRes, checklistRes, recordRes] = await Promise.all([
listEquipmentPart(partParams),
listEquipmentChecklist(partParams),
listEquipmentInspectionRecord({ startInspectTime: this.queryDate, endInspectTime: this.queryDate, pageNum: 1, pageSize: 9999 })
])
this.partList = partRes.rows || []
this.checklistList = checklistRes.rows || []
this.records = recordRes.rows || []
listEquipmentChecklist({ pageSize: 9999 }),
listEquipmentInspectionRecord(recordParams),
]);
if (partRes.code === 200) this.partList = partRes.rows || [];
if (checklistRes.code === 200) this.checklistData = checklistRes.rows || [];
if (recordRes.code === 200) this.records = recordRes.rows || [];
} catch (e) {
console.error('查询失败', e)
console.error("查询失败", e);
} finally {
this.loading = false
this.loading = false;
}
}
}
}
},
},
};
</script>
<style scoped>
.result-icon { display: inline-block; font-size: 20px; font-weight: bold; line-height: 1; cursor: default; }
.result-icon {
display: inline-block;
font-size: 22px;
font-weight: bold;
line-height: 1;
}
.result-pass { color: #67c23a; }
.result-fail { color: #f56c6c; }
.result-none { color: #c0c4cc; font-size: 16px; }
.diff-over { color: #e6a23c; font-weight: bold; }
.diff-under { color: #f56c6c; font-weight: bold; }
.diff-ok { color: #67c23a; }
.section-card {
margin-bottom: 16px;
}
</style>
<style>
.inspect-tooltip {
max-width: 400px;
line-height: 1.6;
font-size: 13px;
}
</style>

View File

@@ -1,341 +1,649 @@
<template>
<div class="app-container">
<el-row :gutter="16" style="height: calc(100vh - 135px);">
<!-- 左侧设备巡检部位列表 -->
<el-col :span="7" style="height: 100%;">
<el-card style="height: 100%; display: flex; flex-direction: column;" :body-style="{flex:1, overflow:'hidden', display:'flex', flexDirection:'column', padding:'10px'}">
<div slot="header" style="display:flex; align-items:center; justify-content:space-between;">
<span style="font-weight:bold;">设备列表</span>
</div>
<el-form size="small" :inline="true" style="flex-shrink:0; margin-bottom:6px;">
<el-form-item style="margin-bottom:0;">
<el-input v-model="partKeyword" placeholder="搜索设备名称" prefix-icon="el-icon-search"
clearable style="width:180px;" @input="filterParts" />
<DragResizePanel direction="horizontal" :initialSize="500" :minSize="350"
style="height: calc(100vh - 164px); margin-top: 10px;">
<template #panelA>
<div style="height: 100%; padding: 0 10px; display: flex; flex-direction: column;">
<el-form :model="partQueryParams" size="small" :inline="true" label-width="80px">
<el-form-item label="巡检部位" prop="inspectPart">
<el-input v-model="partQueryParams.inspectPart" placeholder="请输入巡检部位" clearable
@keyup.enter.native="handlePartQuery" />
</el-form-item>
<el-form-item label="产线段" prop="lineSection">
<el-input v-model="partQueryParams.lineSection" placeholder="请输入产线段" clearable
@keyup.enter.native="handlePartQuery" />
</el-form-item>
<el-form-item label="负责人" prop="responsiblePerson">
<el-input
v-model="partQueryParams.responsiblePerson"
placeholder="请输入负责人"
clearable
@keyup.enter.native="handlePartQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handlePartQuery">搜索</el-button>
</el-form-item>
</el-form>
<div style="flex-shrink:0; margin-bottom:6px;">
<el-button type="primary" plain size="mini" icon="el-icon-plus" @click="handlePartAdd">新增</el-button>
<el-button type="success" plain size="mini" icon="el-icon-edit" :disabled="!currentPart" @click="handlePartUpdate(currentPart)">修改</el-button>
<el-button type="danger" plain size="mini" icon="el-icon-delete" :disabled="!currentPart" @click="handlePartDelete(currentPart)">删除</el-button>
</div>
<el-row :gutter="10" class="mb8">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handlePartAdd">新增</el-button>
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="partSingle"
@click="handlePartUpdate">修改</el-button>
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="partMultiple"
@click="handlePartDelete">删除</el-button>
</el-row>
<div v-loading="partLoading" style="flex:1; overflow-y:auto;">
<div v-if="filteredPartList.length === 0" style="color:#909399; text-align:center; padding:20px; font-size:13px;">暂无数据</div>
<div v-for="item in filteredPartList" :key="item.partId"
class="part-card" :class="{ 'part-card-active': currentPart && currentPart.partId === item.partId }"
@click="handleSelectPart(item)">
<div style="flex:1; min-width:0;">
<div class="part-name">{{ item.inspectPart }}</div>
<div class="part-meta">
<span v-if="item.lineSection">{{ item.lineSection }}</span>
<span v-if="item.lineSection && item.remark"> · </span>
<span v-if="item.remark">{{ item.remark }}</span>
<div v-loading="partLoading" style="flex: 1; overflow-y: auto; overflow-x: hidden;">
<div v-for="item in equipmentPartList" :key="item.partId" class="part-card"
:class="{ 'part-card-selected': isCurrentPart(item) }" @click="handlePartRowClick(item)">
<el-checkbox :value="!!partCheckedMap[item.partId]" @click.stop @change="togglePartSelect(item)"
class="part-checkbox" />
<div class="part-card-body">
<div class="part-card-name">
<pre>{{ item.inspectPart }}</pre>
</div>
<div class="part-card-meta">
<template v-if="item.lineSection">{{ item.lineSection }} | </template>
<template v-if="item.responsiblePerson"> <span class="part-card-person">负责人{{ item.responsiblePerson }}</span></template>
<template v-if="item.remark"> | {{ item.remark }}</template>
</div>
</div>
<el-tag v-if="item.checklistCount > 0" size="mini" type="info" style="flex-shrink:0; margin-left:4px;">{{ item.checklistCount }}</el-tag>
</div>
</div>
</el-card>
</el-col>
<!-- 右侧该设备的巡检清单 -->
<el-col :span="17" style="height: 100%;">
<el-card style="height: 100%; display: flex; flex-direction: column;" :body-style="{flex:1, overflow:'hidden', display:'flex', flexDirection:'column', padding:'10px'}">
<div slot="header" style="display:flex; align-items:center; justify-content:space-between;">
<span style="font-weight:bold;">
巡检要求清单
<span v-if="currentPart" style="font-weight:normal; color:#606266; font-size:13px; margin-left:8px;">
{{ currentPart.inspectPart }}
</span>
<span v-else style="font-weight:normal; color:#c0c4cc; font-size:13px; margin-left:8px;">请从左侧选择设备</span>
</span>
<el-button v-if="currentPart" size="mini" type="primary" plain icon="el-icon-tickets"
@click="goToRecord(currentPart)">查看巡检记录</el-button>
</div>
<div style="flex-shrink:0; margin-bottom:6px;">
<el-button type="primary" plain size="mini" icon="el-icon-plus" :disabled="!currentPart" @click="handleChecklistAdd">新增</el-button>
<el-button type="success" plain size="mini" icon="el-icon-edit" :disabled="checklistIds.length !== 1" @click="handleChecklistUpdate()">修改</el-button>
<el-button type="danger" plain size="mini" icon="el-icon-delete" :disabled="checklistIds.length === 0" @click="handleChecklistDelete()">删除</el-button>
</div>
<div v-loading="checklistLoading" style="flex:1; overflow:hidden;">
<div v-if="!currentPart" style="display:flex; align-items:center; justify-content:center; height:100%; color:#c0c4cc;">
<div style="text-align:center;">
<i class="el-icon-d-arrow-left" style="font-size:40px;"></i>
<div style="margin-top:10px; font-size:14px;">点击左侧设备查看巡检要求</div>
<div class="part-card-actions" @click.stop>
<el-button size="mini" type="text" icon="el-icon-document-copy" @click="handlePartCopy(item)" />
<el-button size="mini" type="text" icon="el-icon-plus" @click="handleAddChecklistForPart(item)" />
<el-button size="mini" type="text" icon="el-icon-full-screen" @click="handleShowQRCode(item)" />
<el-button size="mini" type="text" icon="el-icon-edit" @click="handlePartUpdate(item)" />
<el-button size="mini" type="text" icon="el-icon-delete" @click="handlePartDelete(item)" />
</div>
</div>
<el-table v-else :data="checklistData" height="100%" @selection-change="handleChecklistSelectionChange">
<el-table-column type="selection" width="45" align="center" />
<el-table-column label="检验编号" prop="checkNo" width="100" />
<el-table-column label="部件名称" prop="partName" width="120" />
<el-table-column label="检验内容" prop="checkContent" show-overflow-tooltip min-width="180" />
<el-table-column label="设备状态" prop="equipmentState" width="80" align="center">
</div>
</div>
</template>
<template #panelB>
<div style="height: 100%; padding: 0 10px; display: flex; flex-direction: column; overflow: hidden;">
<el-form :model="checklistQueryParams" ref="checklistQueryForm" size="small" :inline="true"
label-width="80px">
<el-form-item label="检验编号" prop="checkNo">
<el-input v-model="checklistQueryParams.checkNo" placeholder="请输入检验编号" clearable
@keyup.enter.native="handleChecklistQuery" />
</el-form-item>
<el-form-item label="检验部位" prop="partId">
<el-select v-model="checklistQueryParams.partId" placeholder="请选择检验部位" clearable size="mini"
@change="handleChecklistQuery" style="width: 150px;">
<el-option v-for="item in equipmentPartList" :key="item.partId" :label="item.inspectPart"
:value="item.partId" />
</el-select>
</el-form-item>
<el-form-item label="设备状态" prop="equipmentState">
<el-select v-model="checklistQueryParams.equipmentState" placeholder="请选择设备状态" clearable
@change="handleChecklistQuery" style="width: 150px;">
<el-option label="运行" value="运行" />
<el-option label="停止" value="停止" />
</el-select>
</el-form-item>
<el-form-item label="检验标准" prop="checkStandard">
<el-input v-model="checklistQueryParams.checkStandard" placeholder="请输入检验标准" clearable
@keyup.enter.native="handleChecklistQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleChecklistQuery">搜索</el-button>
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleChecklistAdd">新增</el-button>
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="checklistSingle"
@click="handleChecklistUpdate">修改</el-button>
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="checklistMultiple"
@click="handleChecklistDelete">删除</el-button>
</el-form-item>
</el-form>
<div v-loading="checklistLoading" style="flex: 1; overflow: hidden;">
<el-table :data="equipmentChecklistList" height="calc(100% - 20px)"
@selection-change="handleChecklistSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="检验编号" align="center" prop="checkNo" />
<el-table-column label="设备部件名称" align="center" prop="partName" width="120" />
<el-table-column label="检验内容" align="center" prop="checkContent" show-overflow-tooltip />
<el-table-column label="设备状态" align="center" prop="equipmentState" />
<el-table-column label="检验标准" align="center" prop="checkStandard" show-overflow-tooltip />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-tag :type="scope.row.equipmentState === '运行' ? 'success' : 'warning'" size="mini">
{{ scope.row.equipmentState || '—' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="检验标准" prop="checkStandard" show-overflow-tooltip min-width="150" />
<el-table-column label="责任人" prop="responsiblePerson" width="90" align="center" />
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleChecklistUpdate(scope.row)" />
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleChecklistDelete(scope.row)" />
<el-button size="mini" type="text" icon="el-icon-document-copy"
@click.stop="handleChecklistCopy(scope.row)">
</el-button>
<el-button size="mini" type="text" icon="el-icon-edit"
@click.stop="handleChecklistUpdate(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-delete"
@click.stop="handleChecklistDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</el-col>
</el-row>
<!-- 巡检部位对话框 -->
<el-dialog :title="partTitle" :visible.sync="partOpen" width="450px" append-to-body>
<!-- <pagination v-show="checklistTotal > 0" :total="checklistTotal" :page.sync="checklistQueryParams.pageNum"
:limit.sync="checklistQueryParams.pageSize" @pagination="getChecklistList" /> -->
</div>
</template>
</DragResizePanel>
<el-dialog :title="partTitle" :visible.sync="partOpen" width="500px" append-to-body>
<el-form ref="partForm" :model="partForm" :rules="partRules" label-width="80px">
<el-form-item label="产线段" prop="lineSection">
<el-input v-model="partForm.lineSection" placeholder="请输入产线段" />
</el-form-item>
<el-form-item label="设备名称" prop="inspectPart">
<el-input v-model="partForm.inspectPart" placeholder="请输入设备/巡检部位名称" />
<el-form-item label="巡检部位" prop="inspectPart">
<el-input v-model="partForm.inspectPart" placeholder="请输入巡检部位" />
</el-form-item>
<el-form-item label="备注">
<el-form-item label="负责人" prop="responsiblePerson">
<el-input v-model="partForm.responsiblePerson" placeholder="请输入负责人" clearable />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="partForm.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer">
<div slot="footer" class="dialog-footer">
<el-button :loading="partButtonLoading" type="primary" @click="submitPartForm"> </el-button>
<el-button @click="partOpen = false"> </el-button>
<el-button @click="cancelPart"> </el-button>
</div>
</el-dialog>
<!-- 检验清单对话框 -->
<el-dialog :title="checklistTitle" :visible.sync="checklistOpen" width="520px" append-to-body>
<el-form ref="checklistForm" :model="checklistForm" :rules="checklistRules" label-width="90px">
<el-form-item label="检验编号">
<el-dialog :title="qrCodeTitle" :visible.sync="qrCodeOpen" width="360px" append-to-body>
<div ref="qrCodeWrapper" style="display: flex; flex-direction: column; align-items: center; padding: 10px 0;">
<QRCode ref="qrCodeRef" :content="qrCodeContent" :size="200" :text="qrCodeText" />
</div>
<div slot="footer" class="dialog-footer">
<el-button :loading="qrCodeLoading" type="primary" @click="saveQRCodeImage">保存图片</el-button>
<el-button :loading="qrCodeLoading" type="primary" @click="printQRCode">打印</el-button>
</div>
</el-dialog>
<el-dialog :title="checklistTitle" :visible.sync="checklistOpen" width="500px" append-to-body>
<el-form ref="checklistForm" :model="checklistForm" :rules="checklistRules" label-width="80px">
<el-form-item label="检验编号" prop="checkNo">
<el-input v-model="checklistForm.checkNo" placeholder="请输入检验编号" />
</el-form-item>
<el-form-item label="部件名称">
<el-input v-model="checklistForm.partName" placeholder="请输入设备部件名称" />
<el-form-item label="检验部位" prop="partId">
<el-select v-model="checklistForm.partId" placeholder="请选择检验部位" clearable style="width: 100%;">
<el-option v-for="item in equipmentPartList" :key="item.partId" :label="item.inspectPart"
:value="item.partId" />
</el-select>
</el-form-item>
<el-form-item label="检验内容">
<el-input v-model="checklistForm.checkContent" type="textarea" :rows="3" placeholder="请输入检验内容" />
<el-input v-model="checklistForm.checkContent" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="设备状态">
<el-select v-model="checklistForm.equipmentState" placeholder="请选择" clearable style="width:100%;">
<el-form-item label="设备状态" prop="equipmentState">
<el-select v-model="checklistForm.equipmentState" placeholder="请选择设备状态" clearable style="width: 100%;">
<el-option label="运行" value="运行" />
<el-option label="停止" value="停止" />
</el-select>
</el-form-item>
<el-form-item label="检验标准">
<el-input v-model="checklistForm.checkStandard" type="textarea" :rows="3" placeholder="请输入检验标准" />
<el-form-item label="检验标准" prop="checkStandard">
<el-input v-model="checklistForm.checkStandard" type="textarea" placeholder="请输入检验标准" />
</el-form-item>
<el-form-item label="责任人">
<el-input v-model="checklistForm.responsiblePerson" placeholder="请输入责任人" />
</el-form-item>
<el-form-item label="备注">
<el-form-item label="备注" prop="remark">
<el-input v-model="checklistForm.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer">
<div slot="footer" class="dialog-footer">
<el-button :loading="checklistButtonLoading" type="primary" @click="submitChecklistForm"> </el-button>
<el-button @click="checklistOpen = false"> </el-button>
<el-button @click="cancelChecklist"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEquipmentPart, getEquipmentPart, addEquipmentPart, updateEquipmentPart, delEquipmentPart } from '@/api/mill/equipmentPart'
import { listEquipmentChecklist, getEquipmentChecklist, addEquipmentChecklist, updateEquipmentChecklist, delEquipmentChecklist } from '@/api/mill/equipmentChecklist'
import DragResizePanel from "@/components/DragResizePanel";
import QRCode from "@/components/QRCode";
import domToImage from 'dom-to-image';
import html2canvas from 'html2canvas';
import { PDFDocument } from 'pdf-lib';
import { listEquipmentPart, getEquipmentPart, delEquipmentPart, addEquipmentPart, updateEquipmentPart } from "@/api/mill/equipmentPart";
import { listEquipmentChecklist, getEquipmentChecklist, delEquipmentChecklist, addEquipmentChecklist, updateEquipmentChecklist } from "@/api/mill/equipmentChecklist";
export default {
name: 'EqpCheck',
name: "EquipmentPartChecklist",
components: { DragResizePanel, QRCode },
data() {
return {
// 设备列表
showSearch: true,
// Part (left side)
partLoading: false,
partButtonLoading: false,
allPartList: [],
filteredPartList: [],
partKeyword: '',
currentPart: null,
partIds: [],
partSingle: true,
partMultiple: true,
partTotal: 0,
equipmentPartList: [],
partTitle: "",
partOpen: false,
partTitle: '',
partQueryParams: {
pageNum: 1,
pageSize: 1000,
inspectPart: undefined,
lineSection: undefined,
responsiblePerson: undefined
},
partCheckedMap: {},
partForm: {},
partRules: {
inspectPart: [{ required: true, message: '设备名称不能为空', trigger: 'blur' }]
inspectPart: [
{ required: true, message: "巡检部位不能为空", trigger: "blur" }
],
lineSection: [
{ required: true, message: "产线段不能为空", trigger: "blur" }
]
},
// 检验清单
// Checklist (right side)
checklistLoading: false,
checklistButtonLoading: false,
checklistData: [],
checklistIds: [],
checklistSingle: true,
checklistMultiple: true,
checklistTotal: 0,
equipmentChecklistList: [],
checklistTitle: "",
checklistOpen: false,
checklistTitle: '',
checklistQueryParams: {
pageNum: 1,
pageSize: 1000,
checkNo: undefined,
partId: undefined,
partName: undefined,
equipmentState: undefined,
checkStandard: undefined,
responsiblePerson: undefined
},
checklistForm: {},
checklistRules: {}
checklistRules: {},
currentPart: {},
// QR Code
qrCodeOpen: false,
qrCodeTitle: '',
qrCodeContent: '',
qrCodeText: '',
qrCodeLoading: false,
};
},
computed: {
currentPartId() {
return this.currentPart?.partId || "";
}
},
created() {
this.getPartList()
// 如果路由带了 partId 参数,自动选中
const partId = this.$route.query.partId
if (partId) {
this._autoSelectPartId = Number(partId)
}
this.getPartList();
this.getChecklistList();
},
methods: {
// ===== 设备列表 =====
getPartList() {
this.partLoading = true
listEquipmentPart({ pageNum: 1, pageSize: 1000 }).then(res => {
this.allPartList = res.rows || []
// 为每个设备附上检验项数(后续可优化为批量接口)
this.filteredPartList = [...this.allPartList]
this.partLoading = false
// 自动选中路由传来的 partId
if (this._autoSelectPartId) {
const found = this.allPartList.find(p => p.partId === this._autoSelectPartId)
if (found) this.handleSelectPart(found)
this._autoSelectPartId = null
// ========== Part (Left) ==========
getPartList(splitChild = false) {
this.partLoading = true;
listEquipmentPart(this.partQueryParams).then(response => {
this.equipmentPartList = response.rows;
if (splitChild) {
this.checklistLoading = true;
// 收集所有的checklistList
this.equipmentChecklistList = this.equipmentPartList.flatMap(item => item.checklistList || []);
this.checklistLoading = false;
}
})
this.partTotal = response.total;
this.partLoading = false;
});
},
filterParts() {
const kw = (this.partKeyword || '').trim().toLowerCase()
this.filteredPartList = kw
? this.allPartList.filter(p => (p.inspectPart || '').toLowerCase().includes(kw) || (p.lineSection || '').toLowerCase().includes(kw))
: [...this.allPartList]
handlePartQuery() {
this.partQueryParams.pageNum = 1;
this.getPartList();
},
handleSelectPart(part) {
this.currentPart = part
this.checklistData = []
this.checklistIds = []
this.loadChecklistForPart(part.partId)
isCurrentPart(item) {
return this.currentPart && this.currentPart.partId === item.partId;
},
loadChecklistForPart(partId) {
this.checklistLoading = true
listEquipmentChecklist({ partId, pageNum: 1, pageSize: 1000 }).then(res => {
this.checklistData = res.rows || []
this.checklistLoading = false
})
togglePartSelect(item) {
const checked = !this.partCheckedMap[item.partId];
this.$set(this.partCheckedMap, item.partId, checked);
this.updatePartSelection();
},
updatePartSelection() {
const selected = this.equipmentPartList.filter(item => this.partCheckedMap[item.partId]);
this.partIds = selected.map(item => item.partId);
this.partSingle = selected.length !== 1;
this.partMultiple = !selected.length;
},
handlePartRowClick(row) {
this.checklistQueryParams.partId = row.partId;
this.checklistQueryParams.pageNum = 1;
this.currentPart = row;
this.getChecklistList();
},
resetPartForm() {
this.partForm = {
partId: undefined,
lineSection: undefined,
inspectPart: undefined,
responsiblePerson: undefined,
remark: undefined
};
this.resetForm("partForm");
},
cancelPart() {
this.partOpen = false;
this.resetPartForm();
},
handlePartAdd() {
this.partForm = {}
this.$nextTick(() => { this.$refs.partForm && this.$refs.partForm.clearValidate() })
this.partTitle = '新增设备'
this.partOpen = true
this.resetPartForm();
this.partOpen = true;
this.partTitle = "添加检验部位";
},
handlePartUpdate(row) {
if (!row) return
this.partLoading = true
getEquipmentPart(row.partId).then(res => {
this.partLoading = false
this.partForm = res.data
this.partTitle = '修改设备'
this.partOpen = true
})
this.partLoading = true;
this.resetPartForm();
const partId = row.partId || this.partIds;
getEquipmentPart(partId).then(response => {
this.partLoading = false;
this.partForm = response.data;
this.partOpen = true;
this.partTitle = "修改检验部位";
});
},
submitPartForm() {
this.$refs.partForm.validate(valid => {
if (!valid) return
this.partButtonLoading = true
const fn = this.partForm.partId ? updateEquipmentPart : addEquipmentPart
fn(this.partForm).then(() => {
this.$modal.msgSuccess(this.partForm.partId ? '修改成功' : '新增成功')
this.partOpen = false
this.getPartList()
}).finally(() => { this.partButtonLoading = false })
})
this.$refs["partForm"].validate(valid => {
if (valid) {
this.partButtonLoading = true;
if (this.partForm.partId != null) {
updateEquipmentPart(this.partForm).then(response => {
this.$modal.msgSuccess("修改成功");
this.partOpen = false;
this.getPartList();
}).finally(() => {
this.partButtonLoading = false;
});
} else {
addEquipmentPart(this.partForm).then(response => {
this.$modal.msgSuccess("新增成功");
this.partOpen = false;
this.getPartList();
}).finally(() => {
this.partButtonLoading = false;
});
}
}
});
},
handlePartDelete(row) {
if (!row) return
this.$modal.confirm(`确认删除设备"${row.inspectPart}"`).then(() => {
this.partLoading = true
return delEquipmentPart(row.partId)
const partIds = row.partId || this.partIds;
this.$modal.confirm('是否确认删除检验部位编号为"' + partIds + '"的数据项?').then(() => {
this.partLoading = true;
return delEquipmentPart(partIds);
}).then(() => {
if (this.currentPart && this.currentPart.partId === row.partId) {
this.currentPart = null
this.checklistData = []
}
this.getPartList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {}).finally(() => { this.partLoading = false })
this.partLoading = false;
this.getPartList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { }).finally(() => {
this.partLoading = false;
});
},
handlePartCopy(row) {
this.partLoading = true;
this.resetPartForm();
getEquipmentPart(row.partId).then(response => {
this.partLoading = false;
this.partForm = response.data;
this.partForm.partId = undefined;
this.partOpen = true;
this.partTitle = "复制新增检验部位";
});
},
handleAddChecklistForPart(row) {
this.resetChecklistForm();
this.checklistForm.partId = row.partId;
this.checklistOpen = true;
this.checklistTitle = "添加设备检验清单";
},
handleShowQRCode(row) {
this.qrCodeContent = `eqpPart::${row.partId}`;
this.qrCodeText = row.inspectPart;
this.qrCodeTitle = `二维码 - ${row.inspectPart}`;
this.qrCodeOpen = true;
},
async saveQRCodeImage() {
const el = this.$refs.qrCodeWrapper;
if (!el) return;
this.qrCodeLoading = true;
try {
const dataUrl = await domToImage.toPng(el);
const link = document.createElement('a');
link.href = dataUrl;
link.download = `二维码_${this.qrCodeText}_${new Date().getTime()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.$modal.msgSuccess('图片保存成功');
} catch (error) {
console.error('保存二维码图片失败:', error);
this.$modal.msgError('保存图片失败');
} finally {
this.qrCodeLoading = false;
}
},
async printQRCode() {
const el = this.$refs.qrCodeWrapper;
if (!el) return;
this.qrCodeLoading = true;
try {
const canvas = await html2canvas(el, {
backgroundColor: '#ffffff',
scale: 3,
useCORS: true,
});
const mmToPt = 72 / 25.4;
const sizeMm = 100;
const sizePt = sizeMm * mmToPt;
const pdfDoc = await PDFDocument.create();
const imgPng = await pdfDoc.embedPng(canvas.toDataURL('image/png'));
const page = pdfDoc.addPage([sizePt, sizePt]);
const imgDims = imgPng.scale(1);
const scale = Math.min(sizePt / imgDims.width, sizePt / imgDims.height);
const dw = imgDims.width * scale;
const dh = imgDims.height * scale;
page.drawImage(imgPng, {
x: (sizePt - dw) / 2,
y: (sizePt - dh) / 2,
width: dw,
height: dh,
});
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
} catch (error) {
console.error('打印二维码失败:', error);
this.$modal.msgError('打印失败');
} finally {
this.qrCodeLoading = false;
}
},
handlePartExport() {
this.download('eqp/equipmentPart/export', {
...this.partQueryParams
}, `equipmentPart_${new Date().getTime()}.xlsx`);
},
// ===== 检验清单 =====
// ========== Checklist (Right) ==========
getChecklistList() {
this.checklistLoading = true;
listEquipmentChecklist(this.checklistQueryParams).then(response => {
this.equipmentChecklistList = response.rows;
this.checklistTotal = response.total;
this.checklistLoading = false;
});
},
handleChecklistQuery() {
this.checklistQueryParams.pageNum = 1;
this.getChecklistList();
},
handleChecklistSelectionChange(selection) {
this.checklistIds = selection.map(i => i.checkId)
this.checklistIds = selection.map(item => item.checkId);
this.checklistSingle = selection.length !== 1;
this.checklistMultiple = !selection.length;
},
resetChecklistForm() {
this.checklistForm = {
checkId: undefined,
checkNo: undefined,
partId: undefined,
partName: undefined,
checkContent: undefined,
equipmentState: undefined,
checkStandard: undefined,
responsiblePerson: undefined,
remark: undefined
};
this.resetForm("checklistForm");
},
cancelChecklist() {
this.checklistOpen = false;
this.resetChecklistForm();
},
handleChecklistAdd() {
if (!this.currentPart) return
this.checklistForm = { partId: this.currentPart.partId }
this.$nextTick(() => { this.$refs.checklistForm && this.$refs.checklistForm.clearValidate() })
this.checklistTitle = '新增巡检要求'
this.checklistOpen = true
this.resetChecklistForm();
if (this.currentPart?.partId) {
this.checklistForm.partId = this.currentPart.partId;
}
this.checklistOpen = true;
this.checklistTitle = "添加设备检验清单";
},
handleChecklistCopy(row) {
this.checklistLoading = true;
this.resetChecklistForm();
getEquipmentChecklist(row.checkId).then(response => {
this.checklistLoading = false;
this.checklistForm = response.data;
this.checklistForm.checkId = undefined;
this.checklistOpen = true;
this.checklistTitle = "复制新增设备检验清单";
});
},
handleChecklistUpdate(row) {
const checkId = row ? row.checkId : this.checklistIds[0]
this.checklistLoading = true
getEquipmentChecklist(checkId).then(res => {
this.checklistLoading = false
this.checklistForm = res.data
this.checklistTitle = '修改巡检要求'
this.checklistOpen = true
})
this.checklistLoading = true;
this.resetChecklistForm();
const checkId = row.checkId || this.checklistIds;
getEquipmentChecklist(checkId).then(response => {
this.checklistLoading = false;
this.checklistForm = response.data;
this.checklistOpen = true;
this.checklistTitle = "修改设备检验清单";
});
},
submitChecklistForm() {
this.$refs.checklistForm.validate(valid => {
if (!valid) return
this.checklistButtonLoading = true
const fn = this.checklistForm.checkId ? updateEquipmentChecklist : addEquipmentChecklist
fn(this.checklistForm).then(() => {
this.$modal.msgSuccess(this.checklistForm.checkId ? '修改成功' : '新增成功')
this.checklistOpen = false
if (this.currentPart) this.loadChecklistForPart(this.currentPart.partId)
}).finally(() => { this.checklistButtonLoading = false })
})
this.$refs["checklistForm"].validate(valid => {
if (valid) {
this.checklistButtonLoading = true;
if (this.checklistForm.checkId != null) {
updateEquipmentChecklist(this.checklistForm).then(response => {
this.$modal.msgSuccess("修改成功");
this.checklistOpen = false;
this.getChecklistList();
}).finally(() => {
this.checklistButtonLoading = false;
});
} else {
addEquipmentChecklist(this.checklistForm).then(response => {
this.$modal.msgSuccess("新增成功");
this.checklistOpen = false;
this.getChecklistList();
}).finally(() => {
this.checklistButtonLoading = false;
});
}
}
});
},
handleChecklistDelete(row) {
const ids = row ? [row.checkId] : this.checklistIds
this.$modal.confirm('确认删除选中的巡检要求').then(() => {
this.checklistLoading = true
return delEquipmentChecklist(ids.join(','))
const checkIds = row.checkId || this.checklistIds;
this.$modal.confirm('是否确认删除设备检验清单编号为"' + checkIds + '"的数据项').then(() => {
this.checklistLoading = true;
return delEquipmentChecklist(checkIds);
}).then(() => {
if (this.currentPart) this.loadChecklistForPart(this.currentPart.partId)
this.$modal.msgSuccess('删除成功')
}).catch(() => {}).finally(() => { this.checklistLoading = false })
this.checklistLoading = false;
this.getChecklistList();
this.$modal.msgSuccess("删除成功");
}).catch(() => { }).finally(() => {
this.checklistLoading = false;
});
},
// ===== 路由跳转 =====
goToRecord(part) {
this.$router.push({ path: '/mill/eqp/record', query: { partId: part.partId, partName: part.inspectPart } })
handleChecklistExport() {
this.download('eqp/equipmentChecklist/export', {
...this.checklistQueryParams
}, `equipmentChecklist_${new Date().getTime()}.xlsx`);
}
}
}
};
</script>
<style scoped>
.part-card {
display: flex;
align-items: center;
padding: 8px 10px;
padding: 6px 8px;
margin-bottom: 2px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
border-left: 3px solid transparent;
transition: all 0.15s;
}
.part-card:hover { background: #f5f7fa; }
.part-card-active { background: #ecf5ff; border-left-color: #409eff; }
.part-name { font-size: 13px; font-weight: 500; color: #303133; line-height: 1.4; }
.part-meta { font-size: 11px; color: #909399; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.part-card:hover {
background-color: #f5f7fa;
}
.part-card-selected {
background-color: #ecf5ff;
border-left-color: #409eff;
}
.part-checkbox {
margin-right: 8px;
flex-shrink: 0;
}
.part-card-body {
flex: 1;
min-width: 0;
overflow: hidden;
}
.part-card-name {
font-size: 13px;
font-weight: 500;
color: #303133;
line-height: 1.4;
}
.part-card-meta {
font-size: 11px;
color: #909399;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.part-card-person {
color: #409eff;
font-weight: 600;
}
.part-card-actions {
flex-shrink: 0;
margin-left: 4px;
white-space: nowrap;
}
.part-card-actions .el-button {
padding: 2px 3px;
font-size: 13px;
}
</style>

View File

@@ -1,31 +1,39 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="设备" prop="partId">
<el-select v-model="queryParams.partId" placeholder="请选择设备" clearable filterable
style="width: 180px;" @change="handleQuery">
<el-option v-for="p in partOptions" :key="p.partId" :label="p.inspectPart" :value="p.partId" />
</el-select>
</el-form-item>
<el-form-item label="班次" prop="shift">
<el-select v-model="queryParams.shift" placeholder="班次" clearable style="width: 90px;" @change="handleQuery">
<el-select v-model="queryParams.shift" placeholder="请选择班次" clearable
@change="handleQuery" style="width: 150px;">
<el-option label="白班" :value="1" />
<el-option label="夜班" :value="2" />
<el-option label="夜班" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="巡检时间">
<el-date-picker v-model="dateRange" type="daterange" value-format="yyyy-MM-dd"
range-separator="-" start-placeholder="开始" end-placeholder="结束" clearable
@change="handleDateRangeChange" style="width: 220px;" />
</el-form-item>
<el-form-item label="状态" prop="runStatus">
<el-select v-model="queryParams.runStatus" placeholder="运行状态" clearable style="width: 90px;" @change="handleQuery">
<el-option label="正常" :value="1" />
<el-option label="故障" :value="2" />
</el-select>
<el-date-picker clearable
v-model="dateRange"
type="daterange"
value-format="yyyy-MM-dd"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
@change="handleDateRangeChange">
</el-date-picker>
</el-form-item>
<el-form-item label="巡检人" prop="inspector">
<el-input v-model="queryParams.inspector" placeholder="巡检人" clearable style="width: 110px;" @keyup.enter.native="handleQuery" />
<el-input
v-model="queryParams.inspector"
placeholder="请输入巡检人"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="异常描述" prop="abnormalDesc">
<el-input
v-model="queryParams.abnormalDesc"
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>
@@ -34,267 +42,333 @@
</el-form>
<el-row :gutter="10" class="mb8">
<!-- 返回按钮如果是从设备页跳过来的 -->
<el-col :span="1.5" v-if="fromPartId">
<el-button size="mini" icon="el-icon-back" @click="goBack">返回设备</el-button>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 来自设备的筛选提示 -->
<el-alert v-if="fromPartName" :title="`当前显示设备「${fromPartName}」的巡检记录`"
type="info" show-icon :closable="false" style="margin-bottom:10px;" />
<el-table v-loading="loading" :data="recordList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" :data="equipmentInspectionRecordList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="设备/部位" prop="partName" align="center" width="130" />
<el-table-column label="检验内容" prop="checkContent" align="center" show-overflow-tooltip min-width="160" />
<el-table-column label="班次" align="center" width="70">
<!-- <el-table-column label="巡检记录ID" align="center" prop="recordId" v-if="true"/> -->
<el-table-column label="设备部件名称" align="center" prop="partName" />
<!-- <el-table-column label="检验清单ID" align="center" prop="checkId" /> -->
<el-table-column label="班次" align="center" prop="shift">
<template slot-scope="scope">
<el-tag :type="scope.row.shift === 1 ? 'primary' : 'info'" size="mini">
{{ scope.row.shift === 1 ? '白班' : '夜班' }}
</el-tag>
<span>{{ scope.row.shift == 1 ? '白班' : '夜班' }}</span>
</template>
</el-table-column>
<el-table-column label="巡检时间" align="center" prop="inspectTime" width="155">
<template slot-scope="scope">{{ parseTime(scope.row.inspectTime, '{y}-{m}-{d} {h}:{i}') }}</template>
</el-table-column>
<el-table-column label="运行状态" align="center" width="85">
<el-table-column label="巡检时间" align="center" prop="inspectTime" width="180">
<template slot-scope="scope">
<el-tag :type="scope.row.runStatus === 1 ? 'success' : 'danger'" size="mini">
{{ scope.row.runStatus === 1 ? '正常' : '故障' }}
</el-tag>
<span>{{ parseTime(scope.row.inspectTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="巡检人" prop="inspector" align="center" width="90" />
<el-table-column label="异常描述" prop="abnormalDesc" align="center" show-overflow-tooltip min-width="130" />
<el-table-column label="现场图像" align="center" width="90">
<el-table-column label="运行状态" align="center" prop="runStatus">
<template slot-scope="scope">
<el-image v-if="scope.row.photo" :src="scope.row.photo" fit="cover"
:preview-src-list="[scope.row.photo]" style="width:50px; height:50px;" />
<span v-else style="color:#c0c4cc;"></span>
<span>{{ scope.row.runStatus == 1 ? '正常' : '故障' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="110" fixed="right">
<el-table-column label="巡检人" align="center" prop="inspector" />
<el-table-column label="现场图像" align="center" prop="photo">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="goToDevice(scope.row)">设备</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"></el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"></el-button>
<el-image
v-if="scope.row.photo"
:src="scope.row.photo"
fit="fill"
:preview-src-list="[scope.row.photo]"
style="width: 100px; height: 100px;"
/>
</template>
</el-table-column>
<el-table-column label="异常描述" align="center" prop="abnormalDesc" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</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" />
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 新增/修改对话框 -->
<el-dialog :title="title" :visible.sync="open" width="520px" append-to-body>
<!-- 添加或修改设备巡检记录对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="设备" prop="partId">
<el-select v-model="form.partId" placeholder="请选择设备" filterable clearable style="width:100%;"
@change="onFormPartChange">
<el-option v-for="p in partOptions" :key="p.partId" :label="p.inspectPart" :value="p.partId" />
</el-select>
<el-form-item label="检验清单" prop="checkId">
<checklist-select v-model="form.checkId" />
</el-form-item>
<el-form-item label="检验项" prop="checkId">
<el-select v-model="form.checkId" placeholder="请选择检验项" filterable clearable style="width:100%;"
:disabled="!form.partId">
<el-option v-for="c in formChecklistOptions" :key="c.checkId"
:label="(c.partName || '') + (c.checkContent ? ' — ' + c.checkContent : '')"
:value="c.checkId" />
</el-select>
</el-form-item>
<el-form-item label="班次" prop="shift">
<el-select v-model="form.shift" style="width:100%;">
<el-form-item label="班次" prop="shift">
<el-select v-model="form.shift" placeholder="请选择班次">
<el-option label="白班" :value="1" />
<el-option label="夜班" :value="2" />
<el-option label="夜班" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="巡检时间" prop="inspectTime">
<el-date-picker v-model="form.inspectTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择巡检时间" style="width:100%;" />
<el-date-picker clearable
v-model="form.inspectTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择巡检时间">
</el-date-picker>
</el-form-item>
<el-form-item label="运行状态" prop="runStatus">
<el-select v-model="form.runStatus" style="width:100%;">
<el-select v-model="form.runStatus" placeholder="请选择运行状态">
<el-option label="正常" :value="1" />
<el-option label="故障" :value="2" />
<el-option label="故障" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="巡检人" prop="inspector">
<el-input v-model="form.inspector" placeholder="请输入巡检人" />
</el-form-item>
<el-form-item label="异常描述">
<el-input v-model="form.abnormalDesc" type="textarea" :rows="2" placeholder="请输入异常描述" />
<el-form-item label="异常描述" prop="abnormalDesc">
<el-input v-model="form.abnormalDesc" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="备注">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer">
<div slot="footer" class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="open = false"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEquipmentInspectionRecord, getEquipmentInspectionRecord, addEquipmentInspectionRecord, updateEquipmentInspectionRecord, delEquipmentInspectionRecord } from '@/api/mill/equipmentInspectionRecord'
import { listEquipmentPart } from '@/api/mill/equipmentPart'
import { listEquipmentChecklist } from '@/api/mill/equipmentChecklist'
import { listEquipmentInspectionRecord, getEquipmentInspectionRecord, delEquipmentInspectionRecord, addEquipmentInspectionRecord, updateEquipmentInspectionRecord } from "@/api/mill/equipmentInspectionRecord";
import ChecklistSelect from "@/components/ChecklistSelect";
export default {
name: 'EqpRecord',
name: "EquipmentInspectionRecord",
components: { ChecklistSelect },
data() {
return {
loading: false,
// 按钮loading
buttonLoading: false,
showSearch: true,
total: 0,
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
recordList: [],
partOptions: [],
formChecklistOptions: [],
title: '',
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 设备巡检记录表格数据
equipmentInspectionRecordList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
open: false,
// 时间段筛选
dateRange: undefined,
fromPartId: null,
fromPartName: '',
// 查询参数
queryParams: {
pageNum: 1, pageSize: 15,
partId: undefined, shift: undefined, runStatus: undefined,
inspector: undefined, startInspectTime: undefined, endInspectTime: undefined
pageNum: 1,
pageSize: 10,
checkId: undefined,
shift: undefined,
startTime: undefined,
endTime: undefined,
runStatus: undefined,
inspector: undefined,
abnormalDesc: undefined,
},
// 表单参数
form: {},
// 表单校验
rules: {
checkId: [{ required: true, message: '请选择检验项', trigger: 'change' }],
shift: [{ required: true, message: '请选择班次', trigger: 'change' }],
inspectTime: [{ required: true, message: '请选择巡检时间', trigger: 'change' }],
runStatus: [{ required: true, message: '请选择运行状态', trigger: 'change' }]
}
}
};
},
created() {
// 从路由参数读取设备过滤条件
const { partId, partName } = this.$route.query
if (partId) {
this.queryParams.partId = Number(partId)
this.fromPartId = Number(partId)
this.fromPartName = partName || ''
}
listEquipmentPart({ pageNum: 1, pageSize: 1000 }).then(res => {
this.partOptions = res.rows || []
})
this.getList()
this.getList();
},
methods: {
/** 查询设备巡检记录列表 */
getList() {
this.loading = true
listEquipmentInspectionRecord(this.queryParams).then(res => {
this.recordList = res.rows || []
this.total = res.total
this.loading = false
})
this.loading = true;
listEquipmentInspectionRecord(this.queryParams).then(response => {
this.equipmentInspectionRecordList = response.rows;
this.total = response.total;
this.loading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
recordId: undefined,
checkId: undefined,
shift: undefined,
inspectTime: undefined,
runStatus: undefined,
inspector: undefined,
abnormalDesc: undefined,
remark: undefined,
createBy: undefined,
createTime: undefined,
updateBy: undefined,
updateTime: undefined,
delFlag: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
if (this.dateRange && Array.isArray(this.dateRange)) {
this.queryParams.startTime = this.dateRange[0];
this.queryParams.endTime = this.dateRange[1];
} else {
this.queryParams.startTime = undefined;
this.queryParams.endTime = undefined;
}
this.queryParams.pageNum = 1;
this.getList();
},
handleDateRangeChange(val) {
if (val && val.length === 2) {
this.queryParams.startInspectTime = val[0]
this.queryParams.endInspectTime = val[1]
} else {
this.queryParams.startInspectTime = undefined
this.queryParams.endInspectTime = undefined
if (!val) {
this.queryParams.startTime = undefined;
this.queryParams.endTime = undefined;
}
this.handleQuery()
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = undefined
this.queryParams = { pageNum: 1, pageSize: 15, partId: this.fromPartId || undefined, shift: undefined, runStatus: undefined, inspector: undefined, startInspectTime: undefined, endInspectTime: undefined }
this.getList()
this.resetForm("queryForm");
this.dateRange = undefined;
this.handleQuery();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(i => i.recordId)
this.single = selection.length !== 1
this.ids = selection.map(item => item.recordId)
this.single = selection.length!==1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.form = { partId: this.queryParams.partId }
if (this.form.partId) this.loadFormChecklist(this.form.partId)
this.$nextTick(() => { this.$refs.form && this.$refs.form.clearValidate() })
this.title = '新增巡检记录'
this.open = true
this.reset();
this.open = true;
this.title = "添加设备巡检记录";
},
/** 修改按钮操作 */
handleUpdate(row) {
const recordId = row ? row.recordId : this.ids[0]
getEquipmentInspectionRecord(recordId).then(res => {
this.form = res.data
if (this.form.partId) this.loadFormChecklist(this.form.partId)
this.title = '修改巡检记录'
this.open = true
})
},
onFormPartChange(partId) {
this.form.checkId = undefined
this.formChecklistOptions = []
if (partId) this.loadFormChecklist(partId)
},
loadFormChecklist(partId) {
listEquipmentChecklist({ partId, pageNum: 1, pageSize: 1000 }).then(res => {
this.formChecklistOptions = res.rows || []
})
this.loading = true;
this.reset();
const recordId = row.recordId || this.ids
getEquipmentInspectionRecord(recordId).then(response => {
this.loading = false;
this.form = response.data;
this.open = true;
this.title = "修改设备巡检记录";
});
},
/** 提交按钮 */
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
this.buttonLoading = true
const fn = this.form.recordId ? updateEquipmentInspectionRecord : addEquipmentInspectionRecord
fn(this.form).then(() => {
this.$modal.msgSuccess(this.form.recordId ? '修改成功' : '新增成功')
this.open = false
this.getList()
}).finally(() => { this.buttonLoading = false })
})
this.$refs["form"].validate(valid => {
if (valid) {
this.buttonLoading = true;
if (this.form.recordId != null) {
updateEquipmentInspectionRecord(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
} else {
addEquipmentInspectionRecord(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
}).finally(() => {
this.buttonLoading = false;
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row ? [row.recordId] : this.ids
this.$modal.confirm('确认删除选中的巡检记录?').then(() => {
this.loading = true
return delEquipmentInspectionRecord(ids.join(','))
const recordIds = row.recordId || this.ids;
this.$modal.confirm('是否确认删除设备巡检记录编号为"' + recordIds + '"的数据项').then(() => {
this.loading = true;
return delEquipmentInspectionRecord(recordIds);
}).then(() => {
this.getList()
this.$modal.msgSuccess('删除成功')
}).catch(() => {}).finally(() => { this.loading = false })
this.loading = false;
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
}).finally(() => {
this.loading = false;
});
},
/** 导出按钮操作 */
handleExport() {
this.download('/mill/eqp/record/export', { ...this.queryParams }, `巡检记录_${new Date().getTime()}.xlsx`)
},
// 返回设备页并自动选中该设备
goBack() {
this.$router.push({ path: '/mill/eqp/check', query: { partId: this.fromPartId } })
},
// 从记录跳到设备查看
goToDevice(row) {
if (row.partId) {
this.$router.push({ path: '/mill/eqp/check', query: { partId: row.partId } })
}
this.download('eqp/equipmentInspectionRecord/export', {
...this.queryParams
}, `equipmentInspectionRecord_${new Date().getTime()}.xlsx`)
}
}
}
};
</script>