Files
klp-oa/klp-ui/src/views/wms/post/eqp/execute.vue

348 lines
19 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-promotion"></i>
<span>维修执行</span>
<el-button size="mini" type="text" icon="el-icon-refresh" @click="getList" style="margin-left:4px;" title="刷新列表"></el-button>
</div>
<el-select v-model="queryParams.planStatus" placeholder="执行状态" clearable size="mini" @change="handleQuery" class="header-filter">
<el-option label="待维修" :value="1" />
<el-option label="维修中" :value="2" />
<el-option label="已完成" :value="3" />
</el-select>
</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 v-if="item.planStatus === 1" type="info" size="mini">待维修</el-tag>
<el-tag v-else-if="item.planStatus === 2" type="warning" size="mini">维修中</el-tag>
<el-tag v-else-if="item.planStatus === 3" type="success" 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 · Execution</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">Status / 状态</span>
<el-tag v-if="currentRow.planStatus === 1" type="info" size="small">待维修</el-tag>
<el-tag v-else-if="currentRow.planStatus === 2" type="warning" size="small">维修中</el-tag>
<el-tag v-else-if="currentRow.planStatus === 3" type="success" size="small">已完成</el-tag>
<el-button v-if="canComplete" size="mini" type="success" plain icon="el-icon-circle-check" style="margin-left:12px;" @click="handleCompletePlan">完成计划</el-button>
</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.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>
</div>
<el-divider />
<div class="section-title">
<span>维修明细进度 <span class="en-sub">· Detail Progress</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="200" show-overflow-tooltip />
<el-table-column label="产线执行人员" align="center" prop="lineExecutor" min-width="130" show-overflow-tooltip />
<el-table-column label="设备执行人员" align="center" prop="equipmentExecutor" min-width="130" show-overflow-tooltip />
<el-table-column label="预计开始时间" align="center" prop="plannedStartTime" min-width="120" show-overflow-tooltip />
<el-table-column label="预计结束时间" align="center" prop="plannedCompleteTime" min-width="120" show-overflow-tooltip />
<el-table-column label="实际开始时间" align="center" prop="actualStartTime" min-width="120" show-overflow-tooltip />
<el-table-column label="实际结束时间" align="center" prop="actualCompleteTime" min-width="120" show-overflow-tooltip />
<el-table-column label="总耗时h" align="center" prop="totalHours" min-width="100" show-overflow-tooltip />
<el-table-column label="验收人" align="center" prop="acceptanceUser" min-width="100" show-overflow-tooltip />
<el-table-column label="进度" align="center" width="160">
<template slot-scope="scope">
<el-progress :percentage="scope.row.progressRate || 0" :status="scope.row.detailStatus === 2 ? 'success' : ''" :stroke-width="12" style="padding: 4px 0;" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.detailStatus === 0" type="info" size="mini">未开始</el-tag>
<el-tag v-else-if="scope.row.detailStatus === 1" type="warning" size="mini">进行中</el-tag>
<el-tag v-else-if="scope.row.detailStatus === 2" type="success" size="mini">已完成</el-tag>
</template>
</el-table-column>
<el-table-column label="实际成本" align="center" width="100">
<template slot-scope="scope">¥{{ scope.row.actualCost || 0 }}</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80" fixed="right" v-if="currentRow.planStatus !== 3">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditProgress(scope.row)">进度</el-button>
</template>
</el-table-column>
</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>
<!-- 进度编辑弹窗 -->
<el-dialog title="更新维修进度" :visible.sync="progressDialogVisible" width="550px" append-to-body>
<el-form ref="progressForm" :model="progressForm" label-width="100px">
<el-form-item label="设备部件">
<span>{{ progressForm.componentName }}</span>
</el-form-item>
<el-form-item label="维修内容">
<span>{{ progressForm.repairContent }}</span>
</el-form-item>
<el-form-item label="完成进度" prop="progressRate">
<el-slider v-model="progressForm.progressRate" :min="0" :max="100" :step="5" show-input style="padding:0 10px;" />
</el-form-item>
<el-form-item label="完成状态" prop="detailStatus">
<el-select v-model="progressForm.detailStatus" placeholder="请选择" style="width:100%">
<el-option label="未开始" :value="0" />
<el-option label="进行中" :value="1" />
<el-option label="已完成" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="实际成本(元)" prop="actualCost">
<el-input-number v-model="progressForm.actualCost" :min="0" :precision="2" style="width:100%" />
</el-form-item>
<el-form-item label="负责人" prop="repairUser">
<el-input v-model="progressForm.repairUser" placeholder="请输入维修负责人" />
</el-form-item>
<el-form-item label="完成时间" prop="completeTime" v-if="progressForm.detailStatus === 2">
<el-date-picker clearable v-model="progressForm.completeTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择完成时间" style="width:100%" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="progressDialogVisible = false"> </el-button>
<el-button type="primary" :loading="progressButtonLoading" @click="submitProgressForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listMaintenancePlan, getMaintenancePlan, updateMaintenancePlan } from "@/api/flow/maintenancePlan";
import { listMaintenancePlanDetail, updateMaintenancePlanDetail } from "@/api/flow/maintenancePlanDetail";
import DragResizePanel from "@/components/DragResizePanel/index.vue";
import { parseTime } from '@/utils/klp'
export default {
name: "EqpExecute",
components: { DragResizePanel },
data() {
return {
loading: false,
detailLoading: false,
progressButtonLoading: false,
total: 0,
queryParams: { pageNum: 1, pageSize: 10, planNo: undefined, approvalStatus: 2, planStatus: undefined },
dataList: [],
currentRow: null,
detailList: [],
progressDialogVisible: false,
progressForm: {}
};
},
computed: {
canComplete() {
if (!this.currentRow || this.currentRow.planStatus === 3) return false;
if (this.detailList.length === 0) return false;
return this.detailList.every(function(d) { return d.detailStatus === 2; });
}
},
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);
},
// ---- Progress editing ----
handleEditProgress(row) {
this.progressForm = Object.assign({}, row, {
completeTime: row.completeTime || undefined
});
this.progressDialogVisible = true;
},
submitProgressForm() {
var self = this;
var data = {
detailId: this.progressForm.detailId,
progressRate: this.progressForm.progressRate,
detailStatus: this.progressForm.detailStatus,
actualCost: this.progressForm.actualCost,
repairUser: this.progressForm.repairUser
};
if (this.progressForm.detailStatus === 2) {
data.completeTime = this.progressForm.completeTime || this.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}');
}
this.progressButtonLoading = true;
updateMaintenancePlanDetail(data).then(function() {
self.$modal.msgSuccess("进度更新成功");
self.progressDialogVisible = false;
self.loadDetailList(self.currentRow.planId);
}).finally(function() { self.progressButtonLoading = false; });
},
// ---- Complete plan ----
handleCompletePlan() {
var self = this;
this.$modal.confirm('确认完成维修计划"' + this.currentRow.planNo + '"').then(function() {
return updateMaintenancePlan({
planId: self.currentRow.planId,
planStatus: 3,
actualCompleteTime: self.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
});
}).then(function() {
self.$modal.msgSuccess("维修计划已完成");
self.loadDetail(self.currentRow.planId);
self.getList();
}).catch(function() { });
}
}
};
</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; }
.header-filter { width: 120px; }
.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; }
.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>