Files
klp-oa/klp-ui/src/views/mes/qc/qualityReview/index.vue
王文昊 450fca0c45 feat: 新增质量评审流程全量功能
- 新增质量评审相关的实体、Mapper、Service、Controller接口与实现
- 新增前端页面与API接口,支持评审单增删改查、提交送审、审批驳回、改判执行
- 新增数据库初始化脚本与字典数据
- 修复vue.config热加载监视系统文件导致的EBUSY错误
- 清理HRM模块API导出注释
2026-06-30 17:58:21 +08:00

816 lines
31 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 quality-review-container">
<div class="left-panel">
<div class="panel-header">
<div class="header-title">
<i class="el-icon-document-checked"></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.flowStatus" placeholder="全部状态" clearable size="mini" @change="handleQuery" class="header-filter">
<el-option v-for="item in dict.type.quality_review_status" :key="item.value" :label="item.label" :value="parseInt(item.value)" />
</el-select>
</div>
<div class="search-row">
<el-input v-model="queryParams.reviewNo" placeholder="搜索评审单编号..." clearable prefix-icon="el-icon-search" size="small" @keyup.enter.native="handleQuery" @clear="handleQuery" />
<el-button type="primary" size="small" @click="handleAdd" v-hasPermi="['qc:qualityReview:add']">
<i class="el-icon-plus"></i>
</el-button>
</div>
<div v-loading="loading" class="list-body">
<div v-for="item in dataList" :key="item.reviewId" class="list-item"
:class="{ active: currentRow && currentRow.reviewId === item.reviewId }" @click="handleRowClick(item)">
<div class="item-main">
<span class="item-title">{{ item.reviewNo }}</span>
<span class="item-sub">{{ item.transmitUser }} · {{ item.productName }}</span>
</div>
<div class="item-meta">
<el-tag v-if="item.flowStatus === 1" type="info" size="mini">待提交</el-tag>
<el-tag v-else-if="item.flowStatus === 2" type="warning" size="mini">待审批</el-tag>
<el-tag v-else-if="item.flowStatus === 3" type="success" size="mini">已通过</el-tag>
<el-tag v-else-if="item.flowStatus === 4" type="danger" size="mini">已驳回</el-tag>
</div>
<div class="item-actions">
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(item)" v-hasPermi="['qc:qualityReview:edit']"></el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(item)" v-hasPermi="['qc:qualityReview:delete']"></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>
<!-- 右侧详情 -->
<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="detail-header">
<div class="detail-header-left">
<span class="detail-title">{{ currentRow.reviewNo }}</span>
<el-tag v-if="currentRow.flowStatus === 1" type="info" size="small">待提交</el-tag>
<el-tag v-else-if="currentRow.flowStatus === 2" type="warning" size="small">待审批</el-tag>
<el-tag v-else-if="currentRow.flowStatus === 3" type="success" size="small">已通过</el-tag>
<el-tag v-else-if="currentRow.flowStatus === 4" type="danger" size="small">已驳回</el-tag>
</div>
<div class="detail-header-actions">
<!-- 待提交提交送审 -->
<el-button v-if="currentRow.flowStatus === 1" size="mini" type="primary" plain icon="el-icon-s-promotion"
@click="handleSubmit" v-hasPermi="['qc:qualityReview:submit']">提交送审</el-button>
<!-- 待审批审批/驳回 -->
<el-button v-if="currentRow.flowStatus === 2" size="mini" type="success" plain icon="el-icon-check"
@click="handleApprove" v-hasPermi="['qc:qualityReview:approve']">审批通过</el-button>
<el-button v-if="currentRow.flowStatus === 2" size="mini" type="danger" plain icon="el-icon-close"
@click="handleReject" v-hasPermi="['qc:qualityReview:approve']">驳回</el-button>
<!-- 已通过执行改判 -->
<el-button v-if="currentRow.flowStatus === 3" size="mini" type="warning" plain icon="el-icon-setting"
@click="handleExecute" v-hasPermi="['qc:qualityReview:execute']">执行改判</el-button>
<!-- 已驳回重新提交 -->
<el-button v-if="currentRow.flowStatus === 4" size="mini" type="primary" plain icon="el-icon-s-promotion"
@click="handleReSubmit" v-hasPermi="['qc:qualityReview:submit']">重新提交</el-button>
<!-- 通用 -->
<el-button size="mini" type="text" icon="el-icon-refresh" @click="handleRefreshDetail">刷新</el-button>
<el-button v-if="currentRow.flowStatus === 1 || currentRow.flowStatus === 4" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(currentRow)">编辑</el-button>
<el-button v-if="currentRow.flowStatus === 1 || currentRow.flowStatus === 4" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(currentRow)">删除</el-button>
</div>
</div>
<!-- ===== 基本信息 ===== -->
<el-card class="detail-section" shadow="never">
<div slot="header"><span><i class="el-icon-info"></i> 基本信息</span></div>
<el-form :model="currentRow" label-width="100px" size="small" class="basic-form">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="评审单编号">{{ currentRow.reviewNo }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="产品名称">{{ currentRow.productName }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="传递部门">{{ currentRow.transmitDept }}</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="传递人">{{ currentRow.transmitUser }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="传递日期">{{ currentRow.transmitDate }}</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="生产日期">{{ currentRow.prodDateRange }}</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- ===== 钢卷明细表详情只读 ===== -->
<el-card class="detail-section" shadow="never">
<div slot="header">
<span><i class="el-icon-s-management"></i> 问题钢卷明细</span>
</div>
<el-table :data="coilList" border style="width: 100%" size="small" max-height="400">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="currentCoilNo" label="产品卷号" min-width="140" />
<el-table-column prop="supplierCoilNo" label="原料卷号" min-width="140" />
<el-table-column prop="spec" label="规格(mm)" width="110" />
<el-table-column prop="netWeight" label="卷重(t)" width="90" align="right" />
<el-table-column prop="defectDesc" label="缺陷描述" min-width="200" show-overflow-tooltip />
<el-table-column prop="beforeQuality" label="改判前" width="80" align="center">
<template slot-scope="scope">
<el-tag size="mini" type="danger">{{ scope.row.beforeQuality }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="regradeQuality" label="改判后" width="110" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.regradeQuality" size="mini" type="warning">{{ scope.row.regradeQuality }}</el-tag>
<span v-else class="text-muted">待指定</span>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- ===== 品质部评审意见 ===== -->
<el-card class="detail-section" shadow="never">
<div slot="header"><span><i class="el-icon-edit-outline"></i> 品质部评审意见</span></div>
<div v-if="editing" class="section-editor">
<el-input type="textarea" :rows="3" v-model="editForm.deptOpinion" placeholder="请填写品质部评审意见..." />
<div style="margin-top:8px; display:flex; gap:8px;">
<el-input v-model="editForm.deptSign" placeholder="签字人" style="width:160px;" size="small" />
<el-date-picker v-model="editForm.deptSignDate" type="date" placeholder="签字日期" size="small" />
</div>
<el-button size="small" type="primary" @click="saveOpinion" style="margin-top:8px;">保存意见</el-button>
</div>
<div v-else class="section-display">
<div class="opinion-text">{{ currentRow.deptOpinion || '暂未填写' }}</div>
<div class="opinion-footer" v-if="currentRow.deptOpinion">
<span>签字{{ currentRow.deptSign || '-' }}</span>
<span>日期{{ currentRow.deptSignDate || '-' }}</span>
</div>
</div>
</el-card>
<!-- ===== 领导审批意见 ===== -->
<el-card class="detail-section" shadow="never">
<div slot="header"><span><i class="el-icon-s-check"></i> 领导审批意见</span></div>
<!-- flowStatus=2时显示审批表单权限由后端API控制前端不做限制 -->
<div v-if="currentRow.flowStatus === 2" class="section-editor">
<!-- 审批通过模式 -->
<el-radio-group v-model="approveAction" style="margin-bottom:12px;">
<el-radio-button label="approve">通过</el-radio-button>
<el-radio-button label="reject">驳回</el-radio-button>
</el-radio-group>
<template v-if="approveAction === 'approve'">
<el-input type="textarea" :rows="3" v-model="approveForm.leaderOpinion" placeholder="请输入领导审批意见..." />
<div style="margin:8px 0;">
<el-input v-model="approveForm.leaderSign" placeholder="签字人" style="width:160px;" size="small" />
</div>
<div class="approve-coil-section">
<div class="approve-coil-title">请为每个钢卷指定改判后质量状态</div>
<div v-for="coil in coilList" :key="coil.detailId" class="approve-coil-row">
<span class="approve-coil-label">{{ coil.currentCoilNo }}{{ coil.spec }}</span>
<el-select v-model="approveCoilMap[coil.detailId]" placeholder="请选择改判后状态" size="small" style="width:180px;">
<el-option v-for="item in regradeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
<el-button size="small" type="success" @click="doApprove">确认通过</el-button>
</template>
<template v-else>
<el-input type="textarea" :rows="3" v-model="rejectReason" placeholder="请输入驳回原因..." />
<el-button size="small" type="danger" @click="doReject" style="margin-top:8px;">确认驳回</el-button>
</template>
</div>
<div v-else class="section-display">
<div class="opinion-text">{{ currentRow.leaderOpinion || '暂无审批意见' }}</div>
<div class="opinion-footer" v-if="currentRow.leaderOpinion">
<span>签字{{ currentRow.leaderSign || '-' }}</span>
<span>日期{{ currentRow.leaderSignDate || '-' }}</span>
</div>
<div v-if="currentRow.flowStatus === 4 && currentRow.rejectReason" class="reject-reason">
<el-alert :title="'驳回原因:' + currentRow.rejectReason" type="error" show-icon :closable="false" />
</div>
</div>
</el-card>
<!-- ===== 审批日志 ===== -->
<el-card class="detail-section" shadow="never">
<div slot="header"><span><i class="el-icon-time"></i> 审批记录</span></div>
<div v-if="logList.length === 0" class="text-muted" style="padding:12px;">暂无审批记录</div>
<el-timeline v-else>
<el-timeline-item v-for="log in logList" :key="log.logId"
:timestamp="log.createTime"
:type="log.action === 'submit' ? 'primary' : (log.action === 'approve' ? 'success' : 'danger')"
:icon="log.action === 'submit' ? 'el-icon-s-promotion' : (log.action === 'approve' ? 'el-icon-check' : 'el-icon-close')">
<div>
<strong>{{ log.action === 'submit' ? '提交送审' : (log.action === 'approve' ? '审批通过' : '驳回') }}</strong>
<span v-if="log.operator"> {{ log.operator }}</span>
</div>
<div v-if="log.opinion" class="timeline-opinion">{{ log.opinion }}</div>
</el-timeline-item>
</el-timeline>
</el-card>
</div>
</div>
<!-- 编辑弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" :close-on-click-modal="false" append-to-body>
<el-form ref="form" :model="editForm" :rules="rules" label-width="100px" size="small">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="editForm.productName" placeholder="如:镀锌产品" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="传递部门" prop="transmitDept">
<el-input v-model="editForm.transmitDept" placeholder="品质部" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="传递人" prop="transmitUser">
<el-input v-model="editForm.transmitUser" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="传递日期" prop="transmitDate">
<el-date-picker v-model="editForm.transmitDate" type="date" placeholder="选择日期" style="width:100%;" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="生产日期" prop="prodDateRange">
<el-input v-model="editForm.prodDateRange" placeholder="如14-15" />
</el-form-item>
</el-col>
</el-row>
<!-- ===== 钢卷选择 ===== -->
<div class="dialog-section">
<div class="dialog-section-header">
<span>问题钢卷</span>
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="showCoilSelector = true">选择O级钢卷</el-button>
</div>
<el-table :data="editForm.coilList" border style="width:100%" size="small" max-height="250">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="currentCoilNo" label="产品卷号" min-width="130" />
<el-table-column prop="supplierCoilNo" label="原料卷号" min-width="130" />
<el-table-column prop="spec" label="规格(mm)" width="100" />
<el-table-column prop="netWeight" label="卷重(t)" width="80" align="right" />
<el-table-column prop="defectDesc" label="缺陷描述" min-width="160">
<template slot-scope="scope">
<el-input v-model="scope.row.defectDesc" size="mini" placeholder="输入缺陷描述" />
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="editForm.coilList.splice(scope.$index, 1)"></el-button>
</template>
</el-table-column>
</el-table>
<div v-if="editForm.coilList.length === 0" class="dialog-table-empty">请点击上方按钮选择O级钢卷</div>
</div>
<!-- ===== 钢卷选择器嵌套在弹窗内 ===== -->
<CoilSelector
:visible.sync="showCoilSelector"
:multiple="true"
:filters="{ qualityStatusCsv: 'O', status: 0 }"
@confirm="onDialogCoilConfirm" />
<el-divider />
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="品质部意见" prop="deptOpinion">
<el-input type="textarea" :rows="3" v-model="editForm.deptOpinion" placeholder="请填写品质部评审意见" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="签字人">
<el-input v-model="editForm.deptSign" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="签字日期">
<el-date-picker v-model="editForm.deptSignDate" type="date" placeholder="选择日期" style="width:100%;" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveForm">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { listQualityReview, getQualityReview, addQualityReview, updateQualityReview, delQualityReview,
submitQualityReview, approveQualityReview, rejectQualityReview, executeQualityReview,
listQualityReviewCoil, listQualityReviewLog } from '@/api/mes/qc/qualityReview'
import CoilSelector from '@/components/CoilSelector'
export default {
name: 'QualityReview',
components: { CoilSelector },
dicts: ['quality_review_status', 'regrade_quality_type'],
data() {
return {
// 列表
loading: false,
dataList: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 20,
reviewNo: undefined,
flowStatus: undefined
},
currentRow: null,
// 详情
detailLoading: false,
coilList: [],
logList: [],
// 钢卷选择
showCoilSelector: false,
// 编辑弹窗
dialogVisible: false,
dialogTitle: '',
editing: false,
editForm: {
reviewId: undefined,
productName: '',
transmitDept: '品质部',
transmitUser: '',
transmitDate: undefined,
prodDateRange: '',
deptOpinion: '',
deptSign: '',
deptSignDate: undefined,
coilList: []
},
rules: {
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }]
},
// 改判状态选项(硬编码后备,优先使用字典)
regradeOptions: [
{ label: '协议销售', value: 'protocol_sale' },
{ label: '转分剪', value: 'to_slitting' },
{ label: '降级', value: 'downgrade' },
{ label: '返修', value: 'rework' },
{ label: '报废', value: 'scrap' }
],
// 审批
approveAction: 'approve',
approveForm: {
reviewId: undefined,
leaderOpinion: '',
leaderSign: ''
},
approveCoilMap: {},
rejectReason: ''
}
},
created() {
this.getList()
},
methods: {
// ===== 列表 =====
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
getList() {
this.loading = true
listQualityReview(this.queryParams).then(res => {
this.dataList = res.rows || []
this.total = res.total || 0
this.loading = false
}).catch(() => { this.loading = false })
},
handleRowClick(row) {
this.currentRow = row
this.loadDetail(row.reviewId)
},
// ===== 详情 =====
loadDetail(reviewId) {
this.detailLoading = true
getQualityReview(reviewId).then(res => {
const data = res.data
this.currentRow = data
this.coilList = data.coilList || []
this.logList = data.logList || []
// 初始化审批表单数据先构建完整对象再赋值解决Vue2响应式问题
const map = {}
this.coilList.forEach(c => {
map[c.detailId] = c.regradeQuality || ''
})
this.approveCoilMap = map
this.approveForm.reviewId = reviewId
this.approveForm.leaderOpinion = ''
this.approveForm.leaderSign = ''
this.rejectReason = ''
this.detailLoading = false
}).catch(() => { this.detailLoading = false })
},
handleRefreshDetail() {
if (this.currentRow) {
this.loadDetail(this.currentRow.reviewId)
this.getList()
}
},
// ===== 新增/编辑 =====
handleAdd() {
this.dialogTitle = '新建质量评审单'
this.editForm = {
reviewId: undefined,
productName: '',
transmitDept: '品质部',
transmitUser: '',
transmitDate: undefined,
prodDateRange: '',
deptOpinion: '',
deptSign: '',
deptSignDate: undefined,
coilList: []
}
this.editing = true
this.dialogVisible = true
},
handleUpdate(row) {
this.dialogTitle = '编辑质量评审单'
this.editForm = {
reviewId: row.reviewId,
productName: row.productName,
transmitDept: row.transmitDept,
transmitUser: row.transmitUser,
transmitDate: row.transmitDate,
prodDateRange: row.prodDateRange,
deptOpinion: row.deptOpinion,
deptSign: row.deptSign,
deptSignDate: row.deptSignDate,
coilList: this.coilList.map(c => ({ ...c }))
}
this.editing = true
this.dialogVisible = true
},
saveForm() {
this.$refs.form.validate(valid => {
if (!valid) return
const data = { ...this.editForm, coilList: this.editForm.coilList || [] }
if (this.editForm.reviewId) {
updateQualityReview(data).then(() => {
this.$modal.msgSuccess('修改成功')
this.dialogVisible = false
this.handleRefreshDetail()
})
} else {
addQualityReview(data).then(() => {
this.$modal.msgSuccess('创建成功')
this.dialogVisible = false
this.getList()
})
}
})
},
// ===== 钢卷选择(弹窗内) =====
onDialogCoilConfirm(selected) {
if (!selected || selected.length === 0) return
const startIdx = this.editForm.coilList.length || 0
const newCoils = selected.map((coil, idx) => ({
coilId: coil.coilId,
currentCoilNo: coil.currentCoilNo,
supplierCoilNo: coil.supplierCoilNo || coil.enterCoilNo,
spec: coil.spec,
netWeight: coil.netWeight,
defectDesc: '',
groupSeq: startIdx + idx + 1,
beforeQuality: coil.qualityStatus || 'O'
}))
this.editForm.coilList = [...(this.editForm.coilList || []), ...newCoils]
},
handleRemoveCoil(index) {
this.coilList.splice(index, 1)
},
// ===== 提交送审 =====
handleSubmit() {
this.$confirm('确认提交送审?', '提示', { type: 'warning' }).then(() => {
submitQualityReview(this.currentRow.reviewId).then(() => {
this.$modal.msgSuccess('送审成功')
this.handleRefreshDetail()
})
}).catch(() => {})
},
handleReSubmit() {
this.$confirm('确认重新提交?提交后将进入待审批状态。', '提示', { type: 'warning' }).then(() => {
submitQualityReview(this.currentRow.reviewId).then(() => {
this.$modal.msgSuccess('送审成功')
this.handleRefreshDetail()
})
}).catch(() => {})
},
// ===== 审批 =====
handleApprove() {
this.approveAction = 'approve'
this.approveForm = {
reviewId: this.currentRow.reviewId,
leaderOpinion: '',
leaderSign: ''
}
// 先构建完整对象再赋值
const map = {}
this.coilList.forEach(c => {
map[c.detailId] = c.regradeQuality || ''
})
this.approveCoilMap = map
},
doApprove() {
if (!this.approveForm.leaderOpinion) {
this.$modal.msgError('请输入审批意见')
return
}
const coilRegradeList = this.coilList.map(c => ({
detailId: c.detailId,
regradeQuality: this.approveCoilMap[c.detailId]
}))
const invalid = coilRegradeList.some(r => !r.regradeQuality)
if (invalid) {
this.$modal.msgError('请为每个钢卷指定改判后质量状态')
return
}
this.$confirm('确认审批通过?', '提示', { type: 'success' }).then(() => {
approveQualityReview({
reviewId: this.currentRow.reviewId,
leaderOpinion: this.approveForm.leaderOpinion,
leaderSign: this.approveForm.leaderSign,
coilRegradeList
}).then(() => {
this.$modal.msgSuccess('审批通过')
this.handleRefreshDetail()
})
}).catch(() => {})
},
handleReject() {
this.approveAction = 'reject'
this.rejectReason = ''
},
doReject() {
if (!this.rejectReason) {
this.$modal.msgError('请输入驳回原因')
return
}
this.$confirm('确认驳回该评审单?', '提示', { type: 'warning' }).then(() => {
rejectQualityReview(this.currentRow.reviewId, this.rejectReason).then(() => {
this.$modal.msgSuccess('已驳回')
this.handleRefreshDetail()
})
}).catch(() => {})
},
// ===== 执行改判 =====
handleExecute() {
this.$confirm('确认执行改判?执行后钢卷质量状态将被更新。', '提示', { type: 'warning' }).then(() => {
executeQualityReview(this.currentRow.reviewId).then(() => {
this.$modal.msgSuccess('改判执行成功')
this.handleRefreshDetail()
})
}).catch(() => {})
},
// ===== 删除 =====
handleDelete(row) {
this.$confirm('确认删除评审单【' + (row.reviewNo || row.reviewId) + '】?', '提示', { type: 'warning' }).then(() => {
delQualityReview(row.reviewId).then(() => {
this.$modal.msgSuccess('删除成功')
if (this.currentRow && this.currentRow.reviewId === row.reviewId) {
this.currentRow = null
this.coilList = []
this.logList = []
}
this.getList()
})
}).catch(() => {})
},
// ===== 辅助 =====
hasPermi(permi) {
return this.$store.getters.permissions && this.$store.getters.permissions.includes(permi)
},
saveOpinion() {
if (!this.currentRow) return
updateQualityReview({
reviewId: this.currentRow.reviewId,
deptOpinion: this.editForm.deptOpinion,
deptSign: this.editForm.deptSign,
deptSignDate: this.editForm.deptSignDate
}).then(() => {
this.$modal.msgSuccess('保存成功')
this.handleRefreshDetail()
})
}
}
}
</script>
<style lang="scss">
.quality-review-container {
display: flex;
height: calc(100vh - 84px);
gap: 12px;
padding: 0;
.left-panel {
width: 320px;
min-width: 320px;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 4px;
border: 1px solid #e4e7ed;
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 12px 0;
.header-title {
font-weight: 600;
font-size: 15px;
display: flex;
align-items: center;
gap: 6px;
}
.header-filter { width: 130px; }
}
.search-row {
display: flex;
padding: 8px 12px;
gap: 6px;
.el-input { flex: 1; }
}
.list-body {
flex: 1;
overflow-y: auto;
padding: 0 8px;
}
.list-item {
padding: 10px 12px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
border-radius: 4px;
margin: 2px 0;
&:hover { background: #f5f7fa; }
&.active { background: #ecf5ff; }
.item-main {
display: flex;
justify-content: space-between;
align-items: center;
.item-title { font-weight: 500; font-size: 13px; }
.item-sub { font-size: 12px; color: #909399; }
}
.item-meta { margin-top: 4px; }
.item-actions {
margin-top: 4px;
text-align: right;
}
}
.list-empty {
text-align: center;
padding: 40px 0;
color: #c0c4cc;
i { font-size: 36px; display: block; margin-bottom: 8px; }
}
.list-footer { padding: 8px; }
}
.right-panel {
flex: 1;
background: #fff;
border-radius: 4px;
border: 1px solid #e4e7ed;
overflow-y: auto;
padding: 16px;
.empty-tip {
text-align: center;
padding: 80px 0;
color: #c0c4cc;
i { font-size: 48px; display: block; margin-bottom: 12px; }
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 12px;
.detail-header-left {
display: flex;
align-items: center;
gap: 8px;
.detail-title { font-size: 16px; font-weight: 600; }
}
.detail-header-actions { display: flex; gap: 4px; flex-wrap: wrap; }
}
.detail-section {
margin-bottom: 12px;
.basic-form .el-form-item { margin-bottom: 0; }
}
.section-editor {
.approve-coil-section {
background: #fafafa;
padding: 12px;
border-radius: 4px;
margin: 8px 0;
.approve-coil-title { font-weight: 500; margin-bottom: 8px; }
.approve-coil-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 6px;
.approve-coil-label { min-width: 200px; font-size: 13px; }
}
}
}
.section-display {
.opinion-text {
background: #fafafa;
padding: 12px;
border-radius: 4px;
line-height: 1.6;
white-space: pre-wrap;
}
.opinion-footer {
margin-top: 8px;
display: flex;
gap: 20px;
color: #909399;
font-size: 13px;
}
.reject-reason { margin-top: 12px; }
}
.dialog-section {
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 12px;
margin-bottom: 16px;
.dialog-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-weight: 600;
font-size: 14px;
}
.dialog-table-empty {
text-align: center;
padding: 20px;
color: #c0c4cc;
font-size: 13px;
}
}
.text-muted { color: #c0c4cc; }
.timeline-opinion {
font-size: 13px;
color: #606266;
margin-top: 4px;
background: #f5f7fa;
padding: 6px 10px;
border-radius: 4px;
}
}
}
</style>