Files
klp-oa/klp-ui/src/views/wms/post/eqp/approval.vue
2026-07-08 11:25:13 +08:00

286 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container count-container">
<DragResizePanel :initialSize="280" :minSize="280" :maxSize="600">
<template #panelA>
<div class="left-panel">
<div class="panel-header">
<div class="header-title">
<i class="el-icon-s-check"></i>
<span>维修审批</span>
<el-button size="mini" type="text" icon="el-icon-refresh" @click="getList" style="margin-left:4px;" title="刷新列表"></el-button>
</div>
</div>
<div class="search-row">
<el-input v-model="queryParams.planNo" placeholder="搜索计划编号..." clearable prefix-icon="el-icon-search"
size="small" @keyup.enter.native="handleQuery" @clear="handleQuery" />
</div>
<div v-loading="loading" class="list-body">
<div v-for="item in dataList" :key="item.planId" class="list-item"
:class="{ active: currentRow && currentRow.planId === item.planId }" @click="handleRowClick(item)">
<div class="item-main">
<span class="item-title">{{ item.planNo }}</span>
<span class="item-sub">{{ item.planName }}</span>
</div>
<div class="item-meta">
<el-tag size="mini">待审批</el-tag>
</div>
<div class="item-actions">
<el-button size="mini" type="text" icon="el-icon-view" @click.stop="handleRowClick(item)"></el-button>
</div>
</div>
<div v-if="dataList.length === 0 && !loading" class="list-empty">
<i class="el-icon-folder-opened"></i>
<span>暂无待审批维修计划</span>
</div>
</div>
<div class="list-footer">
<pagination :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" />
</div>
</div>
</template>
<template #panelB>
<div class="right-panel">
<div v-if="!currentRow" class="empty-tip">
<i class="el-icon-info"></i>
<span>请在左侧列表中选择一条维修计划进行审批</span>
</div>
<div v-else v-loading="detailLoading" class="detail-content">
<div class="doc-header">
<div class="doc-header-top">
<div class="doc-title-group">
<div class="doc-title">{{ currentRow.planNo }}</div>
<div class="doc-subtitle">Maintenance Plan · Approval</div>
</div>
<div class="doc-header-right">
<el-button size="mini" type="text" icon="el-icon-refresh" @click="handleRefreshDetail" title="刷新详情">刷新</el-button>
</div>
</div>
<div class="doc-status-row">
<span class="doc-status-label">Approval / 状态</span>
<el-tag size="small">待审批</el-tag>
</div>
</div>
<div class="detail-meta">
<span><i class="el-icon-document"></i>{{ currentRow.planName }}</span>
<span v-if="currentRow.repairType === 1">定期保养</span>
<span v-else-if="currentRow.repairType === 2">安全整改</span>
<span v-else-if="currentRow.repairType === 3">专项检修</span>
<span v-else-if="currentRow.repairType === 4">故障维修</span>
<span v-if="currentRow.priorityLevel === 2"><el-tag size="mini" type="danger">重要</el-tag></span>
<span v-if="currentRow.plannedStartTime"><i class="el-icon-time"></i>开始: {{ parseTime(currentRow.plannedStartTime, '{y}-{m}-{d}') }}</span>
<span v-if="currentRow.plannedEndTime"><i class="el-icon-time"></i>结束: {{ parseTime(currentRow.plannedEndTime, '{y}-{m}-{d}') }}</span>
<span v-if="currentRow.dutyDept"><i class="el-icon-s-home"></i>{{ currentRow.dutyDept }}</span>
<span v-if="currentRow.planOwner"><i class="el-icon-user-solid"></i>{{ currentRow.planOwner }}</span>
<span v-if="currentRow.budgetAmount"><i class="el-icon-money"></i>¥{{ currentRow.budgetAmount }}</span>
</div>
<el-divider />
<div class="section-title">
<span>计划说明 <span class="en-sub">· Description</span></span>
</div>
<div class="remark-content">{{ currentRow.planDescription || '无' }}</div>
<el-divider />
<div class="section-title">
<span>审批操作 <span class="en-sub">· Approval Actions</span></span>
</div>
<div class="flow-actions">
<el-button type="danger" size="small" icon="el-icon-close" :loading="rejectLoading" @click="handleReject">驳回</el-button>
<el-button type="primary" size="small" icon="el-icon-check" :loading="approveLoading" @click="handleApprove">审批通过</el-button>
</div>
<el-divider />
<div class="section-title">
<span>维修明细 <span class="en-sub">· Maintenance Details</span></span>
</div>
<el-table :data="detailList" border size="small" style="width:100%">
<el-table-column label="编号" align="center" width="60">
<template slot-scope="scope">{{ scope.$index + 1 }}</template>
</el-table-column>
<el-table-column label="时间" align="center" width="130">
<template slot-scope="scope">{{ parseTime(scope.row.itemPlanDate, '{y}/{m}/{d}') || '-' }}</template>
</el-table-column>
<el-table-column label="具体工作内容" align="center" prop="repairContent" min-width="220" show-overflow-tooltip />
<el-table-column label="产线执行人员" align="center" prop="lineExecutor" min-width="150" show-overflow-tooltip />
<el-table-column label="设备执行人员" align="center" prop="equipmentExecutor" min-width="150" show-overflow-tooltip />
<el-table-column label="预计完成时间" align="center" prop="plannedCompleteTime" min-width="140" show-overflow-tooltip />
<el-table-column label="实际完成时间" align="center" prop="actualCompleteTime" min-width="140" show-overflow-tooltip />
<el-table-column label="总耗时h" align="center" prop="totalHours" min-width="110" show-overflow-tooltip />
<el-table-column label="验收人" align="center" prop="acceptanceUser" min-width="110" show-overflow-tooltip />
<el-table-column label="是否完成" align="center" width="100">
<template slot-scope="scope">{{ scope.row.detailStatus === 2 ? '是' : '否' }}</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
<div v-if="detailList.length === 0" class="empty-data" style="margin-top:8px;">暂无维修明细</div>
<div class="section-gap" />
<div class="section-title">备注 <span class="en-sub">· Remarks</span></div>
<div class="remark-content">{{ currentRow.remark || '无' }}</div>
</div>
</div>
</template>
</DragResizePanel>
</div>
</template>
<script>
import { listMaintenancePlan, getMaintenancePlan, updateMaintenancePlan } from "@/api/flow/maintenancePlan";
import { listMaintenancePlanDetail } from "@/api/flow/maintenancePlanDetail";
import DragResizePanel from "@/components/DragResizePanel/index.vue";
import { parseTime } from '@/utils/klp'
export default {
name: "EqpApproval",
components: { DragResizePanel },
data() {
return {
loading: false,
detailLoading: false,
approveLoading: false,
rejectLoading: false,
total: 0,
queryParams: { pageNum: 1, pageSize: 10, planNo: undefined, approvalStatus: 1 },
dataList: [],
currentRow: null,
detailList: []
};
},
created() { this.getList(); },
methods: {
parseTime,
getList() {
this.loading = true;
var self = this;
listMaintenancePlan(this.queryParams).then(function(response) {
self.dataList = response.rows;
self.total = response.total;
self.loading = false;
});
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
handleRowClick(row) {
this.currentRow = row;
this.loadDetail(row.planId);
},
loadDetail(planId) {
this.detailLoading = true;
var self = this;
getMaintenancePlan(planId).then(function(response) {
self.currentRow = response.data;
self.loadDetailList(planId);
}).finally(function() { self.detailLoading = false; });
},
loadDetailList(planId) {
var self = this;
listMaintenancePlanDetail({ planId: planId, pageNum: 1, pageSize: 999 }).then(function(r) {
self.detailList = r.rows || [];
});
},
handleRefreshDetail() {
if (this.currentRow && this.currentRow.planId) this.loadDetail(this.currentRow.planId);
},
handleApprove() {
var self = this;
this.$modal.confirm('确认通过"' + this.currentRow.planNo + '"的审批?').then(function() {
self.approveLoading = true;
return updateMaintenancePlan({ planId: self.currentRow.planId, approvalStatus: 2, planStatus: 1 });
}).then(function() {
self.$modal.msgSuccess("审批通过,维修计划已进入待维修状态");
self.approveLoading = false;
self.currentRow = null;
self.getList();
}).catch(function() { self.approveLoading = false; });
},
handleReject() {
var self = this;
this.$prompt('请输入驳回原因', '驳回审批', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputValidator: function(val) { return val ? true : '驳回原因不能为空'; }
}).then(function({ value }) {
self.rejectLoading = true;
return updateMaintenancePlan({ planId: self.currentRow.planId, approvalStatus: 3, remark: value });
}).then(function() {
self.$modal.msgSuccess("已驳回");
self.rejectLoading = false;
self.currentRow = null;
self.getList();
}).catch(function() { self.rejectLoading = false; });
}
}
};
</script>
<style scoped>
.count-container { height: calc(100vh - 84px); }
.left-panel { display: flex; flex-direction: column; height: 100%; background: #f5f7fa; border-right: 1px solid #e4e7ed; }
.panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px 8px; background: #f5f7fa; }
.header-title { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; color: #303133; }
.header-title i { color: #409eff; font-size: 16px; }
.search-row { display: flex; align-items: center; gap: 6px; padding: 0 14px 10px; background: #f5f7fa; }
.list-body { flex: 1; overflow-y: auto; padding: 0 6px; }
.list-item { display: flex; align-items: center; padding: 10px 12px; margin-bottom: 2px; cursor: pointer; border-radius: 6px; transition: all 0.15s; }
.list-item:hover { background: #ebeef5; }
.list-item.active { background: #d9ecff; }
.list-item.active .item-title { color: #409eff; font-weight: 600; }
.item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.item-title { font-size: 13px; font-weight: 500; color: #303133; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.item-sub { font-size: 12px; color: #909399; }
.item-meta { flex-shrink: 0; margin: 0 8px; }
.item-actions { flex-shrink: 0; opacity: 0; transition: opacity 0.15s; }
.list-item:hover .item-actions { opacity: 1; }
.list-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 0; color: #c0c4cc; font-size: 13px; gap: 8px; }
.list-empty i { font-size: 32px; }
.list-footer { border-top: 1px solid #e4e7ed; padding: 2px 8px 0; background: #f5f7fa; }
.right-panel { height: 100%; overflow-y: auto; padding: 12px 16px; background: #faf8f5; }
.right-panel .detail-content { margin: 0 auto; background: #ffffff; padding: 28px 32px 36px; box-shadow: 0 1px 4px rgba(0,0,0,0.06), 0 2px 12px rgba(0,0,0,0.04); min-height: 100%; }
.empty-tip { display: flex; align-items: center; justify-content: center; height: 100%; color: #909399; font-size: 14px; gap: 8px; }
.doc-header { margin-bottom: 18px; padding-bottom: 14px; border-bottom: 2px solid #1a3c6e; }
.doc-header-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.doc-title-group { flex: 1; min-width: 0; }
.doc-title { font-family: 'Georgia', 'Times New Roman', 'Noto Serif SC', 'SimSun', serif; font-size: 24px; font-weight: 700; color: #1a1a1a; line-height: 1.3; letter-spacing: 0.5px; }
.doc-subtitle { font-family: 'Georgia', 'Times New Roman', serif; font-size: 12px; font-weight: 400; color: #8c8c8c; font-style: italic; letter-spacing: 0.8px; margin-top: 2px; }
.doc-header-right { flex-shrink: 0; }
.doc-status-row { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.doc-status-label { font-family: 'Georgia', 'Times New Roman', serif; font-size: 11px; color: #8c8c8c; letter-spacing: 0.3px; }
.detail-meta { display: flex; flex-wrap: wrap; gap: 16px; font-size: 12px; color: #909399; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #e0dcd6; }
.detail-meta span { display: inline-flex; align-items: center; gap: 4px; }
.detail-meta i { font-size: 13px; }
.section-title { font-family: 'Georgia', 'Times New Roman', 'Noto Serif SC', 'SimSun', serif; width: 100%; font-size: 15px; font-weight: 700; color: #1a1a1a; margin: 22px 0 12px 0; padding: 0 0 10px 0; border-bottom: 1px solid #d4d0c8; display: flex; align-items: center; gap: 10px; letter-spacing: 0.3px; }
.section-title:first-child { margin-top: 0; }
.section-title .en-sub { font-size: 11px; font-weight: 400; color: #8c8c8c; letter-spacing: 0.5px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; }
.flow-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.remark-content { padding: 12px 16px; background: #faf8f5; border: 1px solid #e8e4de; border-radius: 2px; font-size: 13px; line-height: 1.8; color: #1a1a1a; }
.section-gap { height: 16px; }
.empty-data { color: #8c8c8c; font-size: 13px; font-style: italic; }
.right-panel .el-table { border: 1px solid #e8e4de !important; border-radius: 2px !important; font-size: 12px !important; }
.right-panel .el-table thead th { background-color: #2c3e50 !important; color: #ffffff !important; font-weight: 600 !important; font-size: 11px !important; letter-spacing: 0.5px !important; border-bottom: none !important; font-family: 'Georgia', 'Times New Roman', serif; }
.right-panel .el-table thead th .cell { color: #ffffff !important; }
.right-panel .el-table__body tr:hover > td { background-color: #f7f5f0 !important; }
.right-panel .el-table--border td { border-right: 1px solid #f0ece6 !important; }
.right-panel .el-table--border th { border-right: 1px solid #3a5166 !important; }
.right-panel .el-table td { padding: 6px 4px !important; color: #3a3a3a !important; }
.right-panel .el-divider--horizontal { margin: 8px 0 4px; background-color: #e0dcd6; }
.right-panel .el-tag { border-radius: 2px; font-family: 'Georgia', 'Times New Roman', serif; letter-spacing: 0.3px; }
</style>