Merge remote-tracking branch 'origin/0.8.X' into 0.8.X
This commit is contained in:
44
klp-ui/src/api/aps/process.js
Normal file
44
klp-ui/src/api/aps/process.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询工艺列表
|
||||
export function listProcess(query) {
|
||||
return request({
|
||||
url: '/flow/prodProcess/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询工艺详细
|
||||
export function getProcess(processId) {
|
||||
return request({
|
||||
url: '/flow/prodProcess/' + processId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增工艺
|
||||
export function addProcess(data) {
|
||||
return request({
|
||||
url: '/flow/prodProcess',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改工艺
|
||||
export function updateProcess(data) {
|
||||
return request({
|
||||
url: '/flow/prodProcess',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除工艺
|
||||
export function delProcess(processIds) {
|
||||
return request({
|
||||
url: '/flow/prodProcess/' + processIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
44
klp-ui/src/api/aps/processStep.js
Normal file
44
klp-ui/src/api/aps/processStep.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询工艺步骤列表
|
||||
export function listProcessStep(query) {
|
||||
return request({
|
||||
url: '/flow/prodProcessStep/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询工艺步骤详细
|
||||
export function getProcessStep(stepId) {
|
||||
return request({
|
||||
url: '/flow/prodProcessStep/' + stepId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增工艺步骤
|
||||
export function addProcessStep(data) {
|
||||
return request({
|
||||
url: '/flow/prodProcessStep',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改工艺步骤
|
||||
export function updateProcessStep(data) {
|
||||
return request({
|
||||
url: '/flow/prodProcessStep',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除工艺步骤
|
||||
export function delProcessStep(stepIds) {
|
||||
return request({
|
||||
url: '/flow/prodProcessStep/' + stepIds,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -73,12 +73,12 @@ export function delScheduleItem(ids) {
|
||||
|
||||
// ====== 排产单明细项 接收/合并 ======
|
||||
|
||||
// 接收产需单(后端全字段复制)
|
||||
export function receiveScheduleItem(scheduleId) {
|
||||
// 接收产需单(根据配置的工序步骤生成排产明细)
|
||||
export function receiveScheduleItem(data) {
|
||||
return request({
|
||||
url: '/flow/prodScheduleItem/receive',
|
||||
method: 'post',
|
||||
data: { scheduleId }
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -51,12 +51,11 @@ export function submitQualityReview(reviewId) {
|
||||
})
|
||||
}
|
||||
|
||||
// 审批通过
|
||||
export function approveQualityReview(data) {
|
||||
// 审批通过(创建时已填好内容,领导仅确认)
|
||||
export function approveQualityReview(reviewId) {
|
||||
return request({
|
||||
url: '/qc/qualityReview/approve',
|
||||
method: 'post',
|
||||
data: data
|
||||
url: '/qc/qualityReview/approve/' + reviewId,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,19 @@ export function exportAttendanceReport(params) {
|
||||
url: '/wms/attendanceRecords/exportReport',
|
||||
method: 'post',
|
||||
params: params,
|
||||
responseType: 'blob'
|
||||
responseType: 'blob',
|
||||
// enames 是数组,需要特殊序列化避免逗号被二次编码
|
||||
paramsSerializer: params => {
|
||||
const searchParams = new URLSearchParams();
|
||||
Object.keys(params).forEach(key => {
|
||||
const val = params[key];
|
||||
if (Array.isArray(val)) {
|
||||
searchParams.append(key, val.join(','));
|
||||
} else if (val !== undefined && val !== null && val !== '') {
|
||||
searchParams.append(key, val);
|
||||
}
|
||||
});
|
||||
return searchParams.toString();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,3 +42,20 @@ export function delEmployeeInfo(infoId) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取所有不重复的部门名称
|
||||
export function getDepts() {
|
||||
return request({
|
||||
url: '/wms/employeeInfo/depts',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据部门查询在职员工
|
||||
export function getEmployeesByDept(dept) {
|
||||
return request({
|
||||
url: '/wms/employeeInfo/employeesByDept',
|
||||
method: 'get',
|
||||
params: { dept }
|
||||
})
|
||||
}
|
||||
|
||||
112
klp-ui/src/components/KLPService/ProcessSelect/index.vue
Normal file
112
klp-ui/src/components/KLPService/ProcessSelect/index.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="process-select">
|
||||
<el-select
|
||||
v-model="processId"
|
||||
:placeholder="placeholder"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
filterable
|
||||
class="process-select-input"
|
||||
@clear="handleClear"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in processList"
|
||||
:key="item.processId"
|
||||
:label="item.processName"
|
||||
:value="item.processId"
|
||||
>
|
||||
<span class="process-option-name">{{ item.processName }}</span>
|
||||
<span v-if="item._steps" class="process-option-steps">{{ item._steps }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProcess } from '@/api/aps/process'
|
||||
|
||||
export default {
|
||||
name: 'ProcessSelect',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, undefined],
|
||||
default: undefined
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择工序'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
processList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
processId: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
const process = this.processList.find(item => item.processId === val) || null
|
||||
this.$emit('input', val)
|
||||
this.$emit('change', val, process)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getProcessList()
|
||||
},
|
||||
methods: {
|
||||
getProcessList() {
|
||||
listProcess({ pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
this.processList = rows.map(item => ({
|
||||
...item,
|
||||
_steps: (item.stepList && item.stepList.length > 0)
|
||||
? item.stepList.map(s => s.stepName).join(' → ')
|
||||
: ''
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.processList = []
|
||||
})
|
||||
},
|
||||
handleClear() {
|
||||
this.$emit('input', undefined)
|
||||
this.$emit('change', undefined, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.process-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.process-select-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.process-option-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.process-option-steps {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
@@ -20,9 +20,6 @@ export default {
|
||||
const { icon, title, hasChildren } = context.props
|
||||
// 函数式组件中 :style 绑定不会进 props,需要从 context.data.style 读取
|
||||
const customStyle = context.data.style || {}
|
||||
if (Object.keys(customStyle).length > 0) {
|
||||
console.log('[Item] ✅ 收到 style:', customStyle, '| title:', title)
|
||||
}
|
||||
const vnodes = []
|
||||
|
||||
if (icon) {
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- <el-alert :title="'已配置'+allCols.length+'个列'" type="info" :closable="false" show-icon style="margin-bottom:8px" /> -->
|
||||
<el-table v-loading="gridLoading" height="calc(100vh - 260px)" :data="gridRows" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle" :key="'tbl-'+inputMode">
|
||||
<el-table v-loading="gridLoading" height="calc(100vh - 260px)" :data="tableData" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle" :row-class-name="rowClassName" :key="'tbl-'+inputMode">
|
||||
<el-table-column label="日期" width="135" fixed>
|
||||
<template slot-scope="s"><el-date-picker v-model="s.row.detailDate" type="date" value-format="yyyy-MM-dd" size="mini" style="width:124px" @change="sortGrid" /></template>
|
||||
<template slot-scope="s"><span v-if="s.row.$isSummary" class="summary-label">{{ s.row.detailDate }}</span><el-date-picker v-else v-model="s.row.detailDate" type="date" value-format="yyyy-MM-dd" size="mini" style="width:124px" @change="sortGrid" /></template>
|
||||
</el-table-column>
|
||||
<template v-for="col in displayCols">
|
||||
<el-table-column v-if="col.$type==='detail' && !col.isShift" :key="'d'+col.itemId" align="center" width="130">
|
||||
@@ -40,7 +40,8 @@
|
||||
<div class="col-hd">{{ col.itemName }}{{ col.unit ? '('+col.unit+')' : '' }}</div>
|
||||
</template>
|
||||
<template slot-scope="s">
|
||||
<el-input v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<span v-if="s.row.$isSummary" class="summary-val">{{ s.row['q'+col.itemId] || '-' }}</span>
|
||||
<el-input v-else v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<!-- <span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions">
|
||||
<i title="反填" v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row)" />
|
||||
<i title="自动获取" :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row)" />
|
||||
@@ -53,27 +54,39 @@
|
||||
<div class="col-hd">{{ col.itemName }}{{ col.unit ? '('+col.unit+')' : '' }}</div>
|
||||
</template>
|
||||
<template slot-scope="s">
|
||||
<div class="shift-cell">
|
||||
<span class="shift-tag">甲</span>
|
||||
<el-input v-model="s.row['q'+col.itemId+'_1']" size="mini" class="shift-input" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<!-- <span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '1')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '1')" /></span> -->
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="shift-cell">
|
||||
<span class="shift-tag">乙</span>
|
||||
<el-input v-model="s.row['q'+col.itemId+'_2']" size="mini" class="shift-input" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<!-- <span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '2')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '2')" /></span> -->
|
||||
</el-input>
|
||||
</div>
|
||||
<template v-if="s.row.$isSummary">
|
||||
<div class="shift-cell"><span class="shift-tag">甲</span><span class="summary-val">{{ s.row['q'+col.itemId+'_1'] || '-' }}</span></div>
|
||||
<div class="shift-cell"><span class="shift-tag">乙</span><span class="summary-val">{{ s.row['q'+col.itemId+'_2'] || '-' }}</span></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="shift-cell">
|
||||
<span class="shift-tag">甲</span>
|
||||
<el-input v-model="s.row['q'+col.itemId+'_1']" size="mini" class="shift-input" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<!-- <span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '1')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '1')" /></span> -->
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="shift-cell">
|
||||
<span class="shift-tag">乙</span>
|
||||
<el-input v-model="s.row['q'+col.itemId+'_2']" size="mini" class="shift-input" @input="recalcAll" :class="{'anomaly-input': isAnomalyCell(s.row.detailDate, col.itemId)}">
|
||||
<!-- <span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '2')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '2')" /></span> -->
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else-if="col.$type==='metric' && !col.isShift" :key="'m'+col.mIdx" :label="col.metricName+(col.unit?'('+col.unit+')':'')" width="85" align="center">
|
||||
<template slot-scope="s">{{ s.row['mv'+col.mIdx]!=null ? s.row['mv'+col.mIdx] : '-' }}</template>
|
||||
<template slot-scope="s"><span v-if="s.row.$isSummary" class="summary-val">{{ s.row['mv'+col.mIdx]!=null ? s.row['mv'+col.mIdx] : '-' }}</span><template v-else>{{ s.row['mv'+col.mIdx]!=null ? s.row['mv'+col.mIdx] : '-' }}</template></template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else-if="col.$type==='metric' && col.isShift" :key="'ms'+col.mIdx" :label="col.metricName+(col.unit?'('+col.unit+')':'')" width="95" align="center">
|
||||
<template slot-scope="s">
|
||||
<div class="shift-metric">甲 {{ s.row['mv'+col.mIdx+'_1']!=null ? s.row['mv'+col.mIdx+'_1'] : '-' }}</div>
|
||||
<div class="shift-metric">乙 {{ s.row['mv'+col.mIdx+'_2']!=null ? s.row['mv'+col.mIdx+'_2'] : '-' }}</div>
|
||||
<template v-if="s.row.$isSummary">
|
||||
<div class="shift-metric"><span class="summary-val">甲 {{ s.row['mv'+col.mIdx+'_1']!=null ? s.row['mv'+col.mIdx+'_1'] : '-' }}</span></div>
|
||||
<div class="shift-metric"><span class="summary-val">乙 {{ s.row['mv'+col.mIdx+'_2']!=null ? s.row['mv'+col.mIdx+'_2'] : '-' }}</span></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="shift-metric">甲 {{ s.row['mv'+col.mIdx+'_1']!=null ? s.row['mv'+col.mIdx+'_1'] : '-' }}</div>
|
||||
<div class="shift-metric">乙 {{ s.row['mv'+col.mIdx+'_2']!=null ? s.row['mv'+col.mIdx+'_2'] : '-' }}</div>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
@@ -82,7 +95,7 @@
|
||||
<!-- <el-button size="mini" type="text" style="padding:0 2px;font-size:11px" @click="saveRow(s.row)">保存</el-button>
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px" @click="fetchRow(s.row)">抓取</el-button>
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px;color:#67c23a" @click="backfillRow(s.row)">反填</el-button> -->
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px;color:#f56c6c" @click="gridRows.splice(s.$index,1)">删除</el-button>
|
||||
<el-button v-if="!s.row.$isSummary" size="mini" type="text" style="padding:0 2px;font-size:11px;color:#f56c6c" @click="gridRows.splice(s.$index - summaryRows.length,1)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -494,6 +507,74 @@ export default {
|
||||
})
|
||||
return col && col.color ? { background: col.color, color: '#fff' } : {}
|
||||
}
|
||||
},
|
||||
summaryRows() {
|
||||
const rows = this.gridRows.filter(r => r.detailDate)
|
||||
if (!rows.length) return []
|
||||
const detailCols = this.allCols.filter(c => c.$type === 'detail')
|
||||
const metricCols = this.allCols.filter(c => c.$type === 'metric')
|
||||
const rp = this.activeReport || {}
|
||||
const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const evalF = (f) => { const s = f.replace(/[^0-9+\-*/.()\s]/g,''); if(!s) return null; try { const r = new Function('return ('+s+')')(); return isFinite(r)?(Math.round(r*100)/100).toFixed(2):null } catch(e){ return null } }
|
||||
const calc = (type) => {
|
||||
const row = { detailDate: type === 'sum' ? '总和' : '平均', $isSummary: true, $summaryType: type }
|
||||
const n = rows.length || 1
|
||||
detailCols.forEach(col => {
|
||||
if (col.isShift) {
|
||||
const s1 = rows.reduce((a, r) => a + (parseFloat(r['q' + col.itemId + '_1']) || 0), 0)
|
||||
const s2 = rows.reduce((a, r) => a + (parseFloat(r['q' + col.itemId + '_2']) || 0), 0)
|
||||
row['q' + col.itemId + '_1'] = type === 'sum' ? s1.toFixed(2) : (s1 / n).toFixed(2)
|
||||
row['q' + col.itemId + '_2'] = type === 'sum' ? s2.toFixed(2) : (s2 / n).toFixed(2)
|
||||
row['q' + col.itemId] = type === 'sum' ? (s1 + s2).toFixed(2) : ((s1 + s2) / n).toFixed(2)
|
||||
} else {
|
||||
const s = rows.reduce((a, r) => a + (parseFloat(r['q' + col.itemId]) || 0), 0)
|
||||
row['q' + col.itemId] = type === 'sum' ? s.toFixed(2) : (s / n).toFixed(2)
|
||||
}
|
||||
})
|
||||
metricCols.forEach((m, mi) => {
|
||||
if (!m.metricFormula) {
|
||||
if (m.isShift) { row['mv'+m.mIdx+'_1'] = null; row['mv'+m.mIdx+'_2'] = null }
|
||||
else row['mv'+m.mIdx] = null
|
||||
return
|
||||
}
|
||||
const ef = (shiftId) => {
|
||||
let f = m.metricFormula
|
||||
for (let pmi = 0; pmi < mi; pmi++) {
|
||||
const pm = metricCols[pmi]; if (!pm.metricName) continue
|
||||
const pn = esc(pm.metricName)
|
||||
if (pm.isShift) {
|
||||
const pv1 = row['mv'+pm.mIdx+'_1']; const pv2 = row['mv'+pm.mIdx+'_2']
|
||||
if (pv1 != null) f = f.replace(new RegExp('@\\{'+pn+'\\}\\.甲班','g'), pv1)
|
||||
if (pv2 != null) f = f.replace(new RegExp('@\\{'+pn+'\\}\\.乙班','g'), pv2)
|
||||
}
|
||||
const pv = pm.isShift ? (shiftId ? row['mv'+pm.mIdx+'_'+shiftId] : null) : row['mv'+pm.mIdx]
|
||||
if (pv != null) f = f.replace(new RegExp('@\\{'+pn+'\\}','g'), pv)
|
||||
}
|
||||
detailCols.forEach(c => {
|
||||
const item = this.allItems.find(i => String(i.itemId) === String(c.itemId))
|
||||
if (!item || !item.itemCode) return; const code = item.itemCode
|
||||
if (c.isShift) {
|
||||
const v1 = parseFloat(row['q'+c.itemId+'_1'])||0; const v2 = parseFloat(row['q'+c.itemId+'_2'])||0
|
||||
f = f.replace(new RegExp('@\\{'+code+'\\}\\.甲班','g'), v1).replace(new RegExp('@\\{'+code+'\\}\\.乙班','g'), v2)
|
||||
const v = shiftId ? (shiftId==='1'?v1:v2) : v1+v2
|
||||
f = f.replace(new RegExp('@\\{'+code+'\\}','g'), v)
|
||||
} else {
|
||||
const v = parseFloat(row['q'+c.itemId])||0
|
||||
f = f.replace(new RegExp('@\\{'+code+'\\}\\.甲班','g'), v).replace(new RegExp('@\\{'+code+'\\}\\.乙班','g'), v).replace(new RegExp('@\\{'+code+'\\}','g'), v)
|
||||
}
|
||||
})
|
||||
f = f.replace(/input_weight/g, rp.inputWeight||0).replace(/output_weight/g, rp.outputWeight||0).replace(/price/g, (m.usePrice == 1) ? (m.metricValue || 0) : 0)
|
||||
return evalF(f)
|
||||
}
|
||||
if (m.isShift) { row['mv'+m.mIdx+'_1']=ef('1'); row['mv'+m.mIdx+'_2']=ef('2') }
|
||||
else row['mv'+m.mIdx]=ef(null)
|
||||
})
|
||||
return row
|
||||
}
|
||||
return [calc('sum'), calc('avg')]
|
||||
},
|
||||
tableData() {
|
||||
return [...this.summaryRows, ...this.gridRows]
|
||||
}
|
||||
},
|
||||
watch: { configOpen(v) { if (!v) this.rpOpen = false } },
|
||||
@@ -509,6 +590,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowClassName({ row }) {
|
||||
if (row.$isSummary) return 'summary-row'
|
||||
return ''
|
||||
},
|
||||
/* report */
|
||||
getList() {
|
||||
this.loading = true
|
||||
@@ -1142,4 +1227,8 @@ export default {
|
||||
.ica-backfill { color: #67c23a; margin-right: 1px; }
|
||||
.ica:hover { opacity: 0.7; }
|
||||
/deep/ .anomaly-input .el-input__inner { background: #fef0f0 !important; border-color: #f56c6c !important; }
|
||||
/deep/ .summary-row td { background: #f0f7ff !important; font-weight: bold; }
|
||||
/deep/ .summary-row td .cell { color: #303133; }
|
||||
.summary-label { font-weight: bold; color: #303133; padding: 0 5px; }
|
||||
.summary-val { font-weight: bold; color: #303133; }
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="app-container cost-trend-page">
|
||||
<!-- 筛选区 -->
|
||||
<el-card class="mb8">
|
||||
<el-card class="mb8 filter-card">
|
||||
<div class="filter-bar">
|
||||
<el-radio-group
|
||||
v-model="selectedLine"
|
||||
@@ -16,7 +16,6 @@
|
||||
{{ line.lineName }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<el-button type="primary" size="small" icon="el-icon-search" :loading="loading" @click="fetchData">
|
||||
查询
|
||||
</el-button>
|
||||
@@ -47,10 +46,10 @@
|
||||
multiple
|
||||
collapse-tags
|
||||
placeholder="成本类别(全部)"
|
||||
size="small"
|
||||
style="width:160px"
|
||||
@change="onCategoryChange"
|
||||
clearable
|
||||
size="small"
|
||||
style="width:150px"
|
||||
@change="onCategoryChange"
|
||||
>
|
||||
<el-option label="原料" value="原料" />
|
||||
<el-option label="能耗" value="能耗" />
|
||||
@@ -67,10 +66,10 @@
|
||||
collapse-tags
|
||||
filterable
|
||||
placeholder="成本项(全部)"
|
||||
size="small"
|
||||
style="width:220px"
|
||||
@change="onItemChange"
|
||||
clearable
|
||||
size="small"
|
||||
style="width:200px"
|
||||
@change="onItemChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="it in filteredItemOptions"
|
||||
@@ -81,13 +80,21 @@
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 图表区 -->
|
||||
<div class="chart-box" v-loading="loading">
|
||||
<div ref="trendChart" class="chart-body" />
|
||||
</div>
|
||||
|
||||
<div v-if="summary.seriesCount > 15" class="legend-hint">
|
||||
<i class="el-icon-info" /> 图例较多,可在下方图例区滚动查看,或缩小筛选范围以减少趋势线数量
|
||||
<!-- 迷你图表网格 -->
|
||||
<div v-loading="loading" class="mini-charts-grid">
|
||||
<template v-if="seriesList.length">
|
||||
<div v-for="s in seriesList" :key="s.key" class="mini-chart-card">
|
||||
<div class="mini-chart-header">
|
||||
<span class="mini-chart-dot" :style="{ background: s.color }" />
|
||||
<span class="mini-chart-name">{{ s.name }}</span>
|
||||
<span class="mini-chart-max">峰值 {{ formatMiniMax(s.maxVal) }}</span>
|
||||
</div>
|
||||
<div ref="miniCharts" class="mini-chart-body" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="!loading && reports.length" class="empty-hint">
|
||||
暂无匹配的成本项数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -116,7 +123,8 @@ export default {
|
||||
selectedItems: [],
|
||||
allItems: [],
|
||||
loading: false,
|
||||
chart: null,
|
||||
chartInstances: [],
|
||||
seriesList: [],
|
||||
reports: [],
|
||||
cachedDetails: [],
|
||||
summary: { seriesCount: 0, maxLabel: '-' }
|
||||
@@ -128,10 +136,6 @@ export default {
|
||||
if (!this.selectedLine || !this.productionLines.length) return null
|
||||
return this.productionLines.find(l => l.lineId === this.selectedLine) || null
|
||||
},
|
||||
lineNameDisplay() {
|
||||
const line = this.selectedLineObj
|
||||
return line ? line.lineName : ''
|
||||
},
|
||||
filteredItemOptions() {
|
||||
if (!this.selectedCategories.length) return this.allItems
|
||||
return this.allItems.filter(it => this.selectedCategories.includes(it.category))
|
||||
@@ -143,12 +147,12 @@ export default {
|
||||
this.$nextTick(() => this.fetchData())
|
||||
})
|
||||
this.loadItems()
|
||||
this._resizeHandler = () => { if (this.chart) this.chart.resize() }
|
||||
this._resizeHandler = () => this.resizeAllCharts()
|
||||
window.addEventListener('resize', this._resizeHandler)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this._resizeHandler)
|
||||
if (this.chart) { this.chart.dispose(); this.chart = null }
|
||||
this.disposeAllCharts()
|
||||
},
|
||||
methods: {
|
||||
initDefaultLine() {
|
||||
@@ -186,11 +190,9 @@ export default {
|
||||
this.allItems = r.rows || []
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
|
||||
async fetchData() {
|
||||
if (this.selectedLine == null) {
|
||||
return
|
||||
}
|
||||
if (this.selectedLine == null) return
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
@@ -204,13 +206,13 @@ export default {
|
||||
const allReports = (reportRes.rows || []).sort((a, b) => {
|
||||
return (a.reportDate || '').localeCompare(b.reportDate || '')
|
||||
})
|
||||
console.log('[CostTrend] reports:', allReports.length, allReports.map(r => ({ id: r.reportId, title: r.reportTitle, date: r.reportDate })))
|
||||
|
||||
if (!allReports.length) {
|
||||
this.$modal.msgWarning('该产线暂无报表')
|
||||
this.reports = []
|
||||
this.seriesList = []
|
||||
this.summary = { seriesCount: 0, maxLabel: '-' }
|
||||
if (this.chart) this.chart.clear()
|
||||
this.disposeAllCharts()
|
||||
return
|
||||
}
|
||||
this.reports = allReports
|
||||
@@ -224,15 +226,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 成本项模式 ====================
|
||||
async fetchDetailsAndCache(reports) {
|
||||
const allDetailResults = await Promise.all(
|
||||
reports.map(r =>
|
||||
listProdDetail({ reportId: r.reportId, pageNum: 1, pageSize: 99999 })
|
||||
.then(res => {
|
||||
console.log('[CostTrend] detail for report', r.reportId, 'rows:', (res.rows || []).length)
|
||||
return { reportId: r.reportId, rows: res.rows || [] }
|
||||
})
|
||||
.then(res => ({ reportId: r.reportId, rows: res.rows || [] }))
|
||||
.catch(e => {
|
||||
console.error('[CostTrend] detail fetch error for report', r.reportId, e)
|
||||
return { reportId: r.reportId, rows: [] }
|
||||
@@ -279,7 +277,7 @@ export default {
|
||||
})
|
||||
|
||||
const xLabels = reports.map(r => this.reportLabel(r))
|
||||
const series = []
|
||||
const list = []
|
||||
let globalMax = 0
|
||||
|
||||
Array.from(allItemIds).forEach((key, idx) => {
|
||||
@@ -288,115 +286,121 @@ export default {
|
||||
const data = reports.map(r => {
|
||||
const sums = reportSums[r.reportId] || {}
|
||||
const val = sums[key] ?? null
|
||||
if (val != null && val > globalMax) globalMax = val
|
||||
return val
|
||||
})
|
||||
if (data.every(v => v === null)) return
|
||||
|
||||
series.push({
|
||||
const maxVal = Math.max(...data.filter(v => v != null))
|
||||
if (maxVal > globalMax) globalMax = maxVal
|
||||
|
||||
list.push({
|
||||
key,
|
||||
name,
|
||||
type: 'line',
|
||||
color: COLORS[idx % COLORS.length],
|
||||
data,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: { width: 2, color: COLORS[idx % COLORS.length] },
|
||||
itemStyle: { color: COLORS[idx % COLORS.length] },
|
||||
emphasis: { focus: 'series' }
|
||||
xLabels,
|
||||
maxVal
|
||||
})
|
||||
})
|
||||
|
||||
this.seriesList = list
|
||||
this.summary = {
|
||||
seriesCount: series.length,
|
||||
seriesCount: list.length,
|
||||
maxLabel: this.formatMoney(globalMax)
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => this.renderChart(xLabels, series, '数量'), 50)
|
||||
this.$nextTick(() => this.renderMiniCharts())
|
||||
},
|
||||
|
||||
// ==================== 迷你图表渲染 ====================
|
||||
disposeAllCharts() {
|
||||
this.chartInstances.forEach(c => {
|
||||
try { c.dispose() } catch (e) { /* ignore */ }
|
||||
})
|
||||
this.chartInstances = []
|
||||
},
|
||||
|
||||
resizeAllCharts() {
|
||||
this.chartInstances.forEach(c => {
|
||||
try { c.resize() } catch (e) { /* ignore */ }
|
||||
})
|
||||
},
|
||||
|
||||
// ==================== 图表渲染 ====================
|
||||
renderChart(xLabels, series, yAxisName) {
|
||||
console.log('[CostTrend] renderChart called, ref:', !!this.$refs.trendChart, 'series:', series.length, 'xLabels:', xLabels.length)
|
||||
if (!this.$refs.trendChart) return
|
||||
if (this.chart) this.chart.dispose()
|
||||
this.chart = echarts.init(this.$refs.trendChart)
|
||||
renderMiniCharts() {
|
||||
this.disposeAllCharts()
|
||||
|
||||
if (!series.length) {
|
||||
this.chart.setOption({
|
||||
title: { text: '暂无数据', left: 'center', top: 'center', textStyle: { color: '#999', fontSize: 14 } },
|
||||
xAxis: { show: false },
|
||||
yAxis: { show: false },
|
||||
series: []
|
||||
})
|
||||
return
|
||||
}
|
||||
const containers = this.$refs.miniCharts
|
||||
if (!containers || !containers.length) return
|
||||
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function (params) {
|
||||
if (!params || !params.length) return ''
|
||||
let html = '<b>' + params[0].axisValue + '</b><br/>'
|
||||
params.forEach(p => {
|
||||
if (p.value != null) {
|
||||
html += '<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:' +
|
||||
p.color + ';margin-right:5px;"></span>' +
|
||||
p.seriesName + ': <b>' +
|
||||
Number(p.value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) +
|
||||
'</b><br/>'
|
||||
}
|
||||
})
|
||||
return html
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
bottom: 0,
|
||||
textStyle: { fontSize: 11 },
|
||||
pageIconSize: 12,
|
||||
pageTextStyle: { fontSize: 11 }
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
top: '3%',
|
||||
bottom: series.length > 20 ? '18%' : (series.length > 10 ? '14%' : '10%'),
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xLabels,
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
rotate: xLabels.length > 8 ? 30 : 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: yAxisName,
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
formatter: function (v) {
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
return v
|
||||
this.seriesList.forEach((s, idx) => {
|
||||
const el = containers[idx]
|
||||
if (!el) return
|
||||
|
||||
const chart = echarts.init(el)
|
||||
chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: function(params) {
|
||||
if (!params || !params.length) return ''
|
||||
const p = params[0]
|
||||
return '<b>' + p.axisValue + '</b><br/>' +
|
||||
'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:' +
|
||||
p.color + ';margin-right:4px;"></span>' +
|
||||
p.seriesName + ': <b>' +
|
||||
Number(p.value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) +
|
||||
'</b>'
|
||||
}
|
||||
},
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#e8e8e8' } }
|
||||
},
|
||||
dataZoom: xLabels.length > 10 ? [
|
||||
{
|
||||
type: 'slider',
|
||||
bottom: series.length > 10 ? (series.length > 20 ? 60 : 50) : 35,
|
||||
height: 16,
|
||||
textStyle: { fontSize: 10 }
|
||||
grid: {
|
||||
left: 8,
|
||||
right: 12,
|
||||
top: 8,
|
||||
bottom: 24,
|
||||
containLabel: true
|
||||
},
|
||||
{ type: 'inside' }
|
||||
] : [],
|
||||
series
|
||||
}, true)
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: s.xLabels,
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
rotate: s.xLabels.length > 8 ? 25 : 0,
|
||||
interval: s.xLabels.length > 12 ? 'auto' : 0
|
||||
},
|
||||
axisTick: { show: false },
|
||||
axisLine: { lineStyle: { color: '#ddd' }}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
fontSize: 10,
|
||||
formatter: function(v) {
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
return v
|
||||
}
|
||||
},
|
||||
splitLine: { lineStyle: { type: 'dashed', color: '#eee' }},
|
||||
splitNumber: 3
|
||||
},
|
||||
series: [{
|
||||
name: s.name,
|
||||
type: 'line',
|
||||
data: s.data,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { width: 1.5, color: s.color },
|
||||
itemStyle: { color: s.color },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: s.color + '30' },
|
||||
{ offset: 1, color: s.color + '05' }
|
||||
])
|
||||
}
|
||||
}]
|
||||
}, true)
|
||||
|
||||
this.chartInstances.push(chart)
|
||||
})
|
||||
},
|
||||
|
||||
reportLabel(r) {
|
||||
@@ -411,78 +415,132 @@ export default {
|
||||
const num = Number(val)
|
||||
if (num >= 10000) return (num / 10000).toFixed(2) + '万'
|
||||
return num.toFixed(2)
|
||||
},
|
||||
|
||||
formatMiniMax(val) {
|
||||
if (val == null || isNaN(val)) return '-'
|
||||
if (val >= 10000) return (val / 10000).toFixed(1) + '万'
|
||||
return Number(val).toFixed(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 { margin-bottom: 10px; }
|
||||
.mb8 { margin-bottom: 8px; }
|
||||
|
||||
.filter-card {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
.filter-card >>> .el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 统计摘要 */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
min-width: 80px;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 2px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-val {
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.stat-blue .stat-val { color: #409eff; }
|
||||
.stat-orange .stat-val { color: #e6a23c; }
|
||||
.stat-green .stat-val { color: #67c23a; }
|
||||
|
||||
.chart-box {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* 图表筛选 */
|
||||
.chart-filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.chart-filter-bar .filter-label {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chart-body {
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
|
||||
/* 迷你图表网格 */
|
||||
.mini-charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.legend-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
|
||||
.mini-chart-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mini-chart-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
padding: 8px 10px 0 10px;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
}
|
||||
.mini-chart-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mini-chart-name {
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mini-chart-max {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mini-chart-body {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="发货状态" align="center" prop="status" width="150">
|
||||
<el-table-column label="发货状态" align="center" prop="status" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 1" type="success" size="mini">已发货</el-tag>
|
||||
<el-tag v-else type="info" size="mini">未发货</el-tag>
|
||||
@@ -72,7 +72,7 @@
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-search"
|
||||
@click="handleDigitalCoilNo(scope.row)">数字钢卷</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-search" @click="handleTrace(scope.row)">追溯</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-search" @click="handleTrace(scope.row)">钢卷追溯</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
@@ -282,4 +282,10 @@ export default {
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-table .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -39,49 +39,49 @@
|
||||
|
||||
<!-- 产品行 -->
|
||||
<div v-for="(item, index) in products" :key="index" class="table-row" :class="{ 'table-row-hover': !readonly }">
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<div class="serial-number">
|
||||
<span>{{ index + 1 }}</span>
|
||||
<el-button v-if="!readonly && products.length > 1" type="text" size="mini" icon="el-icon-delete"
|
||||
class="delete-btn" @click="removeProduct(index)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.spec" placeholder="请输入规格" :readonly="readonly" size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-select v-model="item.material" placeholder="请选择材质" :disabled="readonly" size="small" filterable allow-create clearable style="width:100%;">
|
||||
<el-option v-for="opt in materialOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.quantity" placeholder="请输入数量" :readonly="readonly" size="small"
|
||||
@change="onQuantityChange(item)" />
|
||||
</div>
|
||||
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.taxPrice" placeholder="请输入含税单价" :readonly="readonly" size="small"
|
||||
@change="onTaxPriceChange(item)" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.taxDivisor" placeholder="税率除数" :readonly="readonly"
|
||||
size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.noTaxPrice" placeholder="无税单价" :readonly="readonly"
|
||||
size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.taxTotal" placeholder="含税总额" :readonly="readonly" size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.noTaxTotal" placeholder="无税总额" :readonly="readonly"
|
||||
size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.taxAmount" placeholder="税额" :readonly="readonly" size="small" />
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
<div class="table-cell edit-cell">
|
||||
<el-input v-model="item.remark" placeholder="请输入备注" :readonly="readonly" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -397,6 +397,10 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.edit-cell {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.table-cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<p>从左侧选择一条采购合同进行审核</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="pa-view">
|
||||
<div v-else class="pa-view" v-loading="detailLoading">
|
||||
<div class="pa-d-head">
|
||||
<div>
|
||||
<span class="pa-d-title">{{ current.planNo }}</span>
|
||||
@@ -91,8 +91,9 @@
|
||||
<div class="pa-section">采购要求<span class="pa-section-hint" v-if="current.items && current.items.length">共 {{ current.items.length }} 项</span></div>
|
||||
<el-table :data="current.items || []" border size="mini" max-height="320" class="pa-table">
|
||||
<el-table-column label="#" type="index" width="44" align="center" />
|
||||
<el-table-column label="规格" prop="spec" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="110" align="right" />
|
||||
<el-table-column label="规格" prop="spec" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="100" align="right" />
|
||||
<el-table-column label="厂商" prop="manufacturer" min-width="180" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无采购要求</span></template>
|
||||
</el-table>
|
||||
@@ -131,6 +132,7 @@ export default {
|
||||
return {
|
||||
loading: true,
|
||||
buttonLoading: false,
|
||||
detailLoading: false,
|
||||
total: 0,
|
||||
pendingCount: 0,
|
||||
planList: [],
|
||||
@@ -175,10 +177,11 @@ export default {
|
||||
this.getList()
|
||||
},
|
||||
selectPlan(row) {
|
||||
this.detailLoading = true
|
||||
getPurchasePlan(row.planId).then(res => {
|
||||
this.current = res.data || {}
|
||||
this.auditOpinion = this.current.auditOpinion || ''
|
||||
})
|
||||
}).finally(() => { this.detailLoading = false })
|
||||
},
|
||||
doAudit(status) {
|
||||
this.buttonLoading = true
|
||||
@@ -192,10 +195,11 @@ export default {
|
||||
this.getList(true) // 刷新列表但保留右侧当前计划
|
||||
this.loadPendingCount()
|
||||
// 右侧停留在刚审核的计划:状态/历史即时更新,驳回后可直接重新审核
|
||||
this.detailLoading = true
|
||||
getPurchasePlan(pid).then(res => {
|
||||
this.current = res.data || {}
|
||||
this.auditOpinion = ''
|
||||
})
|
||||
}).finally(() => { this.detailLoading = false })
|
||||
}).finally(() => { this.buttonLoading = false })
|
||||
},
|
||||
auditText(s) {
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<p>从左侧选择一条采购合同</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-else v-loading="detailLoading">
|
||||
<div class="pd-d-head">
|
||||
<div>
|
||||
<span class="pd-d-title">{{ current.planNo }}</span>
|
||||
@@ -61,6 +61,7 @@
|
||||
</div>
|
||||
<div class="pd-head-act">
|
||||
<el-button size="small" icon="el-icon-refresh" :loading="refreshing" @click="doRefreshArrival">刷新到货</el-button>
|
||||
<el-button size="small" icon="el-icon-download" @click="downloadTemplate">下载模板</el-button>
|
||||
<el-upload
|
||||
:headers="upload.headers"
|
||||
:action="uploadUrl"
|
||||
@@ -90,19 +91,20 @@
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="pd-tabs">
|
||||
<el-tab-pane label="采购要求" name="items">
|
||||
<el-table :data="current.items" border size="mini" max-height="420">
|
||||
<el-tab-pane label="采购与到货" name="all">
|
||||
<div class="pd-sec-title">采购要求</div>
|
||||
<el-table :data="current.items" border size="mini" max-height="300">
|
||||
<el-table-column label="#" type="index" width="44" align="center" />
|
||||
<el-table-column label="规格" prop="spec" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="110" align="right" />
|
||||
<el-table-column label="厂商" prop="manufacturer" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="spec" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="100" align="right" />
|
||||
<el-table-column label="厂商" prop="manufacturer" min-width="150" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无采购要求</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="'到货明细(' + deliveryList.length + ')'" name="delivery">
|
||||
<div class="pd-sec-title" style="margin-top:16px">到货明细({{ deliveryList.length }})</div>
|
||||
<el-table
|
||||
:data="deliveryList" border stripe size="mini" max-height="420"
|
||||
:data="deliveryList" border stripe size="mini" max-height="300"
|
||||
v-loading="deliveryLoading" :row-class-name="rowClass"
|
||||
>
|
||||
<el-table-column label="日期" prop="arrivalDate" width="100" align="center" />
|
||||
@@ -121,6 +123,32 @@
|
||||
<template slot="empty"><span>暂无到货记录,点右上角「上传到货表格」</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="'关联合同(' + (current.contractInfos ? current.contractInfos.length : 0) + ')'" name="contracts">
|
||||
<el-table :data="current.contractInfos" border size="mini" max-height="420" style="width:100%">
|
||||
<el-table-column label="订单编号" prop="orderCode" width="150" show-overflow-tooltip fixed />
|
||||
<el-table-column label="合同号" prop="contractCode" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="合同名称" prop="contractName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="客户(需方)" prop="customer" width="130" show-overflow-tooltip />
|
||||
<el-table-column label="供方" prop="supplier" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="金额" prop="orderAmount" width="110" align="right" />
|
||||
<el-table-column label="销售员" prop="salesman" width="80" />
|
||||
<el-table-column label="签订时间" prop="signTime" width="105" align="center" />
|
||||
<el-table-column label="签订地点" prop="signLocation" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="交货日期" prop="deliveryDate" width="105" align="center" />
|
||||
<el-table-column label="合同状态" width="90" align="center">
|
||||
<template slot-scope="s">{{ contractStatusText(s.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付定金" prop="depositPayable" width="100" align="right" />
|
||||
<el-table-column label="已付定金" prop="depositPaid" width="100" align="right" />
|
||||
<el-table-column label="未结款" prop="unpaidAmount" width="100" align="right" />
|
||||
<el-table-column label="产品内容" prop="productContent" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="供方电话" prop="supplierPhone" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="需方电话" prop="customerPhone" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无关联合同</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</section>
|
||||
@@ -136,6 +164,7 @@ import {
|
||||
refreshArrival
|
||||
} from '@/api/erp/purchasePlan'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import * as XLSX from 'xlsx'
|
||||
|
||||
export default {
|
||||
name: 'ErpPurchaseDelivery',
|
||||
@@ -146,10 +175,11 @@ export default {
|
||||
planList: [],
|
||||
queryParams: { pageNum: 1, pageSize: 20, keyword: undefined },
|
||||
current: {},
|
||||
activeTab: 'items',
|
||||
activeTab: 'all',
|
||||
deliveryList: [],
|
||||
deliveryLoading: false,
|
||||
refreshing: false,
|
||||
detailLoading: false,
|
||||
upload: { headers: { Authorization: 'Bearer ' + getToken() } },
|
||||
progressColor: '#5b8db8'
|
||||
}
|
||||
@@ -180,10 +210,12 @@ export default {
|
||||
this.getList()
|
||||
},
|
||||
selectPlan(p) {
|
||||
this.activeTab = 'items'
|
||||
this.activeTab = 'all'
|
||||
this.current = { ...p }
|
||||
this.deliveryList = []
|
||||
// 打开即静默按钢卷表复核一次到货状态
|
||||
// 立即加载详情,不等待 WMS 刷新
|
||||
this.refreshDetail()
|
||||
// 后台静默按钢卷表复核一次到货状态
|
||||
const planId = p.planId
|
||||
refreshArrival(planId).catch(() => {}).finally(() => {
|
||||
if (this.current.planId === planId) this.refreshDetail()
|
||||
@@ -192,7 +224,8 @@ export default {
|
||||
refreshDetail() {
|
||||
const planId = this.current.planId
|
||||
if (!planId) return
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
|
||||
this.detailLoading = true
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } }).finally(() => { this.detailLoading = false })
|
||||
this.deliveryLoading = true
|
||||
listDelivery(planId).then(res => { this.deliveryList = res.data || [] }).finally(() => { this.deliveryLoading = false })
|
||||
},
|
||||
@@ -212,13 +245,11 @@ export default {
|
||||
},
|
||||
statusText(row) {
|
||||
if (row.arrived === 1) return '已到货'
|
||||
if (row.inTransit === 1) return '在途'
|
||||
return '未到'
|
||||
return '在途'
|
||||
},
|
||||
statusClass(row) {
|
||||
if (row.arrived === 1) return 'yes'
|
||||
if (row.inTransit === 1) return 'transit'
|
||||
return 'no'
|
||||
return 'transit'
|
||||
},
|
||||
handleUploadSuccess(res) {
|
||||
if (res.code === 200) {
|
||||
@@ -228,7 +259,7 @@ export default {
|
||||
} else {
|
||||
this.$modal.msgSuccess(res.msg || '导入成功')
|
||||
}
|
||||
this.activeTab = 'delivery'
|
||||
this.activeTab = 'all'
|
||||
this.refreshDetail()
|
||||
this.getList(true)
|
||||
} else {
|
||||
@@ -240,6 +271,18 @@ export default {
|
||||
},
|
||||
fmt(v) {
|
||||
return Number(v || 0).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 3 })
|
||||
},
|
||||
contractStatusText(v) {
|
||||
return { 0: '草稿', 1: '生效', 2: '作废', 3: '已完成' }[v] || '—'
|
||||
},
|
||||
downloadTemplate() {
|
||||
const headers = ['日期', '牌号', '规格', '卷号', '单卷重量', '车号', '数量', '件数', '销售', '钢厂到站']
|
||||
const sampleRow = ['2026-01-01', 'Q235B', '1.0*1250*C', 'COIL0001', '20.50', '辽A12345', '60.8', '3', 'XS001', '沈阳站']
|
||||
const wb = XLSX.utils.book_new()
|
||||
const ws = XLSX.utils.aoa_to_sheet([headers, sampleRow])
|
||||
ws['!cols'] = headers.map(h => ({ wch: h.length > 4 ? 14 : 10 }))
|
||||
XLSX.utils.book_append_sheet(wb, ws, '到货模板')
|
||||
XLSX.writeFile(wb, '到货导入模板.xlsx')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,6 +335,10 @@ $sub: #909399;
|
||||
&.wide { grid-column: span 2; }
|
||||
}
|
||||
.pd-tabs { padding: 0 18px 18px; }
|
||||
.pd-sec-title {
|
||||
font-size: 13px; font-weight: 600; color: $ink;
|
||||
border-left: 3px solid $accent; padding-left: 8px; margin-bottom: 10px;
|
||||
}
|
||||
.pd-mtag {
|
||||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub;
|
||||
&.yes { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||||
|
||||
@@ -124,10 +124,13 @@
|
||||
</div>
|
||||
<el-table :data="form.items" border size="mini" max-height="340">
|
||||
<el-table-column label="#" type="index" width="44" align="center" />
|
||||
<el-table-column label="规格" min-width="220">
|
||||
<el-table-column label="规格" min-width="160">
|
||||
<template slot-scope="s"><el-input v-model="s.row.spec" size="mini" placeholder="如 3.00×1230" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总重量(T)" min-width="120">
|
||||
<el-table-column label="材质" min-width="120">
|
||||
<template slot-scope="s"><el-input v-model="s.row.material" size="mini" placeholder="如 Q235B" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总重量(T)" min-width="110">
|
||||
<template slot-scope="s"><el-input v-model="s.row.weight" size="mini" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="厂商" min-width="200">
|
||||
@@ -157,7 +160,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 查看 -->
|
||||
<div v-else class="pp-view">
|
||||
<div v-else class="pp-view" v-loading="detailLoading">
|
||||
<div class="pp-d-head">
|
||||
<div>
|
||||
<span class="pp-d-title">{{ current.planNo }}</span>
|
||||
@@ -212,12 +215,38 @@
|
||||
<el-tab-pane label="采购要求" name="items">
|
||||
<el-table :data="current.items" border size="mini" max-height="340">
|
||||
<el-table-column label="#" type="index" width="44" align="center" />
|
||||
<el-table-column label="规格" prop="spec" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="110" align="right" />
|
||||
<el-table-column label="厂商" prop="manufacturer" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="spec" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="总重量(T)" prop="weight" min-width="100" align="right" />
|
||||
<el-table-column label="厂商" prop="manufacturer" min-width="150" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无采购要求</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'关联合同(' + (current.contractInfos ? current.contractInfos.length : 0) + ')'" name="contracts">
|
||||
<el-table :data="current.contractInfos" border size="mini" max-height="340" style="width:100%">
|
||||
<el-table-column label="订单编号" prop="orderCode" width="150" show-overflow-tooltip fixed />
|
||||
<el-table-column label="合同号" prop="contractCode" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="合同名称" prop="contractName" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="客户(需方)" prop="customer" width="130" show-overflow-tooltip />
|
||||
<el-table-column label="供方" prop="supplier" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="金额" prop="orderAmount" width="110" align="right" />
|
||||
<el-table-column label="销售员" prop="salesman" width="80" />
|
||||
<el-table-column label="签订时间" prop="signTime" width="105" align="center" />
|
||||
<el-table-column label="签订地点" prop="signLocation" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="交货日期" prop="deliveryDate" width="105" align="center" />
|
||||
<el-table-column label="合同状态" width="90" align="center">
|
||||
<template slot-scope="s">{{ contractStatusText(s.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付定金" prop="depositPayable" width="100" align="right" />
|
||||
<el-table-column label="已付定金" prop="depositPaid" width="100" align="right" />
|
||||
<el-table-column label="未结款" prop="unpaidAmount" width="100" align="right" />
|
||||
<el-table-column label="产品内容" prop="productContent" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="供方电话" prop="supplierPhone" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="需方电话" prop="customerPhone" width="120" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="150" show-overflow-tooltip />
|
||||
<template slot="empty"><span>无关联合同</span></template>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</section>
|
||||
@@ -342,6 +371,7 @@ export default {
|
||||
},
|
||||
buttonLoading: false,
|
||||
submitLoading: false,
|
||||
detailLoading: false,
|
||||
selectedContracts: [],
|
||||
// 合同选择器
|
||||
pickerOpen: false,
|
||||
@@ -394,7 +424,8 @@ export default {
|
||||
refreshDetail() {
|
||||
const planId = this.current.planId
|
||||
if (!planId) return
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
|
||||
this.detailLoading = true
|
||||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } }).finally(() => { this.detailLoading = false })
|
||||
},
|
||||
// ---- 新增 / 编辑 ----
|
||||
resetForm() {
|
||||
@@ -406,6 +437,7 @@ export default {
|
||||
this.mode = 'edit'
|
||||
},
|
||||
handleEdit() {
|
||||
this.detailLoading = true
|
||||
getPurchasePlan(this.current.planId).then(res => {
|
||||
const d = res.data || {}
|
||||
this.form = {
|
||||
@@ -415,7 +447,7 @@ export default {
|
||||
}
|
||||
this.selectedContracts = []
|
||||
this.mode = 'edit'
|
||||
})
|
||||
}).finally(() => { this.detailLoading = false })
|
||||
},
|
||||
cancelEdit() {
|
||||
this.mode = this.planList.length ? 'view' : 'empty'
|
||||
@@ -495,7 +527,7 @@ export default {
|
||||
}).catch(() => cb([]))
|
||||
},
|
||||
blankItem() {
|
||||
return { spec: '', weight: '', manufacturer: '' }
|
||||
return { spec: '', material: '', weight: '', manufacturer: '' }
|
||||
},
|
||||
addItem() {
|
||||
// 新行继承上一行的厂商(同批多为同厂),规格/重量留空
|
||||
@@ -555,6 +587,9 @@ export default {
|
||||
},
|
||||
auditText(s) {
|
||||
return { '0': '待审核', '1': '已通过', '2': '已驳回', '3': '待送审' }[s] || '—'
|
||||
},
|
||||
contractStatusText(v) {
|
||||
return { 0: '草稿', 1: '生效', 2: '作废', 3: '已完成' }[v] || '—'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,7 @@
|
||||
<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 === 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>
|
||||
@@ -105,9 +103,6 @@
|
||||
<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>
|
||||
@@ -129,27 +124,29 @@
|
||||
<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">
|
||||
<el-table-column prop="regradeQuality" label="改判后" width="100" 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-column prop="beforeWarehouseName" label="当前库区" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.beforeWarehouseName || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.targetWarehouseName || '不移动' }}</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="currentRow.flowStatus === 1 || currentRow.flowStatus === 4" class="section-editor">
|
||||
<el-input type="textarea" :rows="3" v-model="opinionForm.deptOpinion" placeholder="请填写品质部评审意见..." />
|
||||
<div style="margin-top:8px; display:flex; gap:8px;">
|
||||
<el-input v-model="opinionForm.deptSign" placeholder="签字人" style="width:160px;" size="small" />
|
||||
<el-date-picker v-model="opinionForm.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="section-display">
|
||||
<div class="opinion-text">{{ currentRow.deptOpinion || '暂未填写' }}</div>
|
||||
<div class="opinion-footer" v-if="currentRow.deptOpinion">
|
||||
<span>签字:{{ currentRow.deptSign || '-' }}</span>
|
||||
@@ -161,34 +158,57 @@
|
||||
<!-- ===== 领导审批意见 ===== -->
|
||||
<el-card class="detail-section" shadow="never">
|
||||
<div slot="header"><span><i class="el-icon-s-check"></i> 领导审批意见</span></div>
|
||||
<!-- flowStatus=2时显示审批表单,权限由后端API控制,前端不做限制 -->
|
||||
<!-- flowStatus=2时显示审批表单(创建时已填好内容,领导仅确认/驳回) -->
|
||||
<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>
|
||||
<!-- 领导意见(只读展示) -->
|
||||
<div class="approve-opinion-box">
|
||||
<label>领导审批意见:</label>
|
||||
<div class="opinion-content">{{ currentRow.leaderOpinion || '暂未填写' }}</div>
|
||||
<div class="opinion-footer" v-if="currentRow.leaderOpinion">
|
||||
<span>签字:{{ currentRow.leaderSign || '-' }}</span>
|
||||
<span>日期:{{ currentRow.leaderSignDate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 class="approve-coil-section">
|
||||
<div class="approve-coil-title">各钢卷改判后质量状态:</div>
|
||||
<el-table :data="coilList" size="small" border max-height="300">
|
||||
<el-table-column prop="currentCoilNo" label="产品卷号" width="140" />
|
||||
<el-table-column prop="spec" label="规格(mm)" width="100" />
|
||||
<el-table-column prop="defectDesc" label="缺陷描述" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="beforeQuality" label="当前等级" width="80" align="center">
|
||||
<template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="regradeQuality" label="改判后等级" width="100" align="center">
|
||||
<template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100" />
|
||||
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
|
||||
<template slot-scope="s">
|
||||
<span>{{ s.row.targetWarehouseName || '不移动' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="approve-actions">
|
||||
<el-radio-group v-model="approveAction" size="small">
|
||||
<el-radio-button label="approve">通过</el-radio-button>
|
||||
<el-radio-button label="reject">驳回</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<div class="approve-action-body">
|
||||
<template v-if="approveAction === 'approve'">
|
||||
<p class="approve-hint">确认审批通过后将直接执行改判,钢卷质量状态和库区将同步更新</p>
|
||||
<el-button type="success" @click="doApprove" :size="'small'" icon="el-icon-check" round>确认通过</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input type="textarea" :rows="2" v-model="rejectReason" placeholder="请输入驳回原因..." style="margin-bottom:8px;" />
|
||||
<el-button type="danger" @click="doReject" :size="'small'" icon="el-icon-close" round>确认驳回</el-button>
|
||||
</template>
|
||||
</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 dict.type.coil_quality_status" :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>
|
||||
<div v-else class="section-display">
|
||||
<div class="opinion-text">{{ currentRow.leaderOpinion || '暂无审批意见' }}</div>
|
||||
@@ -224,8 +244,14 @@
|
||||
</div>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" :close-on-click-modal="false" append-to-body>
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="1200px" :close-on-click-modal="false" append-to-body class="review-dialog">
|
||||
<el-form ref="form" :model="editForm" :rules="rules" label-width="100px" size="small">
|
||||
<!-- ===== 基本信息 ===== -->
|
||||
<div class="dialog-section">
|
||||
<div class="dialog-section-header">
|
||||
<i class="el-icon-info"></i>
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
@@ -239,23 +265,20 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="传递人" prop="transmitUser">
|
||||
<el-input v-model="editForm.transmitUser" />
|
||||
<el-input v-model="editForm.transmitUser" placeholder="填写传递人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<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>
|
||||
|
||||
<!-- ===== 钢卷选择 ===== -->
|
||||
<div class="dialog-section">
|
||||
<div class="dialog-section-header">
|
||||
@@ -267,18 +290,46 @@
|
||||
<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">
|
||||
<el-table-column prop="defectDesc" label="缺陷描述" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.defectDesc" size="mini" placeholder="输入缺陷描述" />
|
||||
<el-input v-model="scope.row.defectDesc" size="mini" placeholder="输入缺陷" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="60" align="center">
|
||||
<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="120">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.regradeQuality" placeholder="请选择" size="mini" style="width:100%;">
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.beforeWarehouseName || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="targetWarehouseId" label="改判后库区" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.targetWarehouseId" placeholder="请选择" size="mini" style="width:100%;" @change="onTargetWarehouseChange(scope.row)">
|
||||
<el-option label="不移动" :value="null" />
|
||||
<el-option v-for="w in warehouseOptions" v-if="w.warehouseType === 1" :key="w.warehouseId" :label="w.warehouseName" :value="w.warehouseId" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="55" 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" style="cursor:pointer;" @click="showCoilSelector = true">请选择O级钢卷</div>
|
||||
<div v-if="editForm.coilList.length === 0" class="dialog-table-empty" @click="showCoilSelector = true">
|
||||
<i class="el-icon-circle-plus-outline"></i>
|
||||
<span>点击选择O级钢卷</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 钢卷选择器(嵌套在弹窗内) ===== -->
|
||||
@@ -288,31 +339,63 @@
|
||||
:filters="{ qualityStatusCsv: 'O', status: 0 }"
|
||||
@confirm="onDialogCoilConfirm" />
|
||||
|
||||
<el-divider />
|
||||
<!-- ===== 品质部评审 ===== -->
|
||||
<div class="dialog-section">
|
||||
<div class="dialog-section-header">
|
||||
<i class="el-icon-edit-outline"></i>
|
||||
<span>品质部评审意见</span>
|
||||
</div>
|
||||
<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="12">
|
||||
<el-form-item label="签字人">
|
||||
<el-input v-model="editForm.deptSign" placeholder="签字人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签字日期">
|
||||
<el-date-picker v-model="editForm.deptSignDate" type="date" placeholder="选择日期" style="width:100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- ===== 领导审批 ===== -->
|
||||
<div class="dialog-section">
|
||||
<div class="dialog-section-header">
|
||||
<i class="el-icon-s-check"></i>
|
||||
<span>领导审批意见</span>
|
||||
</div>
|
||||
<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 label="审批意见" prop="leaderOpinion">
|
||||
<el-input type="textarea" :rows="3" v-model="editForm.leaderOpinion" placeholder="请填写领导审批意见" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签字人">
|
||||
<el-input v-model="editForm.deptSign" />
|
||||
<el-input v-model="editForm.leaderSign" placeholder="领导姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签字日期">
|
||||
<el-date-picker v-model="editForm.deptSignDate" type="date" placeholder="选择日期" style="width:100%;" />
|
||||
<el-date-picker v-model="editForm.leaderSignDate" type="date" placeholder="选择日期" style="width:100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form>
|
||||
<span slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveForm">确定</el-button>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" icon="el-icon-close">取消</el-button>
|
||||
<el-button type="primary" @click="saveForm" icon="el-icon-check">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -323,6 +406,7 @@ import { listQualityReview, getQualityReview, addQualityReview, updateQualityRev
|
||||
submitQualityReview, approveQualityReview, rejectQualityReview, executeQualityReview,
|
||||
listQualityReviewCoil, listQualityReviewLog } from '@/api/mes/qc/qualityReview'
|
||||
import CoilSelector from '@/components/CoilSelector'
|
||||
import { listWarehouse } from '@/api/wms/warehouse'
|
||||
|
||||
export default {
|
||||
name: 'QualityReview',
|
||||
@@ -349,13 +433,8 @@ export default {
|
||||
|
||||
// 钢卷选择
|
||||
showCoilSelector: false,
|
||||
warehouseOptions: [], // 库区下拉选项
|
||||
|
||||
// 品质部意见编辑(独立于editForm,避免串数据)
|
||||
opinionForm: {
|
||||
deptOpinion: '',
|
||||
deptSign: '',
|
||||
deptSignDate: undefined
|
||||
},
|
||||
// 编辑弹窗
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
@@ -370,6 +449,9 @@ export default {
|
||||
deptOpinion: '',
|
||||
deptSign: '',
|
||||
deptSignDate: undefined,
|
||||
leaderOpinion: '',
|
||||
leaderSign: '',
|
||||
leaderSignDate: undefined,
|
||||
coilList: []
|
||||
},
|
||||
rules: {
|
||||
@@ -390,6 +472,11 @@ export default {
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
watch: {
|
||||
dialogVisible(val) {
|
||||
if (val) this.loadWarehouseOptions()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// ===== 列表 =====
|
||||
handleQuery() {
|
||||
@@ -423,13 +510,6 @@ export default {
|
||||
map[c.detailId] = c.regradeQuality || ''
|
||||
})
|
||||
this.approveCoilMap = map
|
||||
// 初始化品质部意见编辑(独立于editForm)
|
||||
this.opinionForm = {
|
||||
deptOpinion: data.deptOpinion || '',
|
||||
deptSign: data.deptSign || '',
|
||||
deptSignDate: data.deptSignDate || undefined
|
||||
}
|
||||
this.approveForm.reviewId = reviewId
|
||||
this.approveForm.leaderOpinion = ''
|
||||
this.approveForm.leaderSign = ''
|
||||
this.rejectReason = ''
|
||||
@@ -456,6 +536,9 @@ export default {
|
||||
deptOpinion: '',
|
||||
deptSign: '',
|
||||
deptSignDate: undefined,
|
||||
leaderOpinion: '',
|
||||
leaderSign: '',
|
||||
leaderSignDate: undefined,
|
||||
coilList: []
|
||||
}
|
||||
this.editing = true
|
||||
@@ -473,6 +556,9 @@ export default {
|
||||
deptOpinion: row.deptOpinion,
|
||||
deptSign: row.deptSign,
|
||||
deptSignDate: row.deptSignDate,
|
||||
leaderOpinion: row.leaderOpinion || '',
|
||||
leaderSign: row.leaderSign || '',
|
||||
leaderSignDate: row.leaderSignDate || undefined,
|
||||
coilList: this.coilList.map(c => ({ ...c }))
|
||||
}
|
||||
this.editing = true
|
||||
@@ -501,23 +587,51 @@ export default {
|
||||
// ===== 钢卷选择(弹窗内) =====
|
||||
onDialogCoilConfirm(selected) {
|
||||
if (!selected || selected.length === 0) return
|
||||
const existingIds = new Set(this.editForm.coilList.map(c => c.coilId))
|
||||
const newSelected = selected.filter(coil => !existingIds.has(coil.coilId))
|
||||
const dupCount = selected.length - newSelected.length
|
||||
if (dupCount > 0) {
|
||||
this.$message.warning(`已跳过 ${dupCount} 个重复钢卷`)
|
||||
}
|
||||
if (newSelected.length === 0) return
|
||||
const startIdx = this.editForm.coilList.length || 0
|
||||
const newCoils = selected.map((coil, idx) => ({
|
||||
const newCoils = newSelected.map((coil, idx) => ({
|
||||
coilId: coil.coilId,
|
||||
currentCoilNo: coil.currentCoilNo,
|
||||
supplierCoilNo: coil.supplierCoilNo || coil.enterCoilNo,
|
||||
spec: coil.specification, // CoilSelector返回的字段名为specification
|
||||
spec: coil.specification,
|
||||
netWeight: coil.netWeight,
|
||||
defectDesc: '',
|
||||
regradeQuality: '',
|
||||
beforeWarehouseId: coil.warehouseId || null,
|
||||
beforeWarehouseName: this.getWarehouseName(coil.warehouseId),
|
||||
targetWarehouseId: null,
|
||||
targetWarehouseName: '',
|
||||
groupSeq: startIdx + idx + 1,
|
||||
beforeQuality: coil.qualityStatus || 'O'
|
||||
}))
|
||||
this.editForm.coilList = [...(this.editForm.coilList || []), ...newCoils]
|
||||
},
|
||||
getWarehouseName(warehouseId) {
|
||||
if (!warehouseId) return ''
|
||||
const wh = this.warehouseOptions.find(w => w.warehouseId === warehouseId)
|
||||
return wh ? wh.warehouseName : ''
|
||||
},
|
||||
handleRemoveCoil(index) {
|
||||
this.coilList.splice(index, 1)
|
||||
},
|
||||
|
||||
// ===== 库区选择 =====
|
||||
loadWarehouseOptions() {
|
||||
listWarehouse().then(res => {
|
||||
this.warehouseOptions = res.data || []
|
||||
})
|
||||
},
|
||||
onTargetWarehouseChange(row) {
|
||||
const selected = this.warehouseOptions.find(w => w.warehouseId === row.targetWarehouseId)
|
||||
row.targetWarehouseName = selected ? selected.warehouseName : ''
|
||||
},
|
||||
|
||||
// ===== 提交送审 =====
|
||||
handleSubmit() {
|
||||
this.$confirm('确认提交送审?', '提示', { type: 'warning' }).then(() => {
|
||||
@@ -538,26 +652,8 @@ export default {
|
||||
|
||||
// ===== 审批 =====
|
||||
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.$confirm('确认审批通过?审批后将直接执行改判,钢卷质量状态将同步更新。', '提示', { type: 'success' }).then(() => {
|
||||
approveQualityReview(this.currentRow.reviewId).then(() => {
|
||||
this.$modal.msgSuccess('审批通过')
|
||||
this.handleRefreshDetail()
|
||||
})
|
||||
@@ -605,20 +701,8 @@ export default {
|
||||
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.opinionForm.deptOpinion,
|
||||
deptSign: this.opinionForm.deptSign,
|
||||
deptSignDate: this.opinionForm.deptSignDate
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.handleRefreshDetail()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -782,6 +866,20 @@ export default {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.approve-actions {
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.approve-actions .approve-action-body {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.approve-actions .approve-hint {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
.text-muted { color: #c0c4cc; }
|
||||
.timeline-opinion {
|
||||
font-size: 13px;
|
||||
|
||||
@@ -44,21 +44,33 @@
|
||||
</el-radio-group>
|
||||
|
||||
<template v-if="approveAction === 'approve'">
|
||||
<el-input type="textarea" :rows="3" v-model="approveForm.leaderOpinion" placeholder="请输入领导审批意见..." style="margin-bottom:12px;" />
|
||||
<el-input v-model="approveForm.leaderSign" placeholder="签字人" style="width:160px; margin-bottom:12px;" size="small" />
|
||||
<!-- 领导意见(只读展示) -->
|
||||
<div class="approve-opinion-box">
|
||||
<label>领导审批意见:</label>
|
||||
<div class="opinion-content">{{ approveDialog.review.leaderOpinion || '暂未填写' }}</div>
|
||||
<div class="opinion-footer" v-if="approveDialog.review.leaderOpinion">
|
||||
<span>签字:{{ approveDialog.review.leaderSign || '-' }}</span>
|
||||
<span>日期:{{ approveDialog.review.leaderSignDate || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 各钢卷改判后等级(只读展示) -->
|
||||
<div class="coil-regrade-section">
|
||||
<div class="coil-regrade-title">请为以下钢卷指定改判后质量状态:</div>
|
||||
<div class="coil-regrade-title">各钢卷改判后质量状态:</div>
|
||||
<el-table :data="approveCoilList" size="small" border max-height="300">
|
||||
<el-table-column prop="currentCoilNo" label="产品卷号" width="140" />
|
||||
<el-table-column prop="spec" label="规格" width="100" />
|
||||
<el-table-column prop="beforeQuality" label="当前等级" width="80">
|
||||
<el-table-column prop="defectDesc" label="缺陷描述" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="beforeQuality" label="当前等级" width="80" align="center">
|
||||
<template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="改判后状态" width="160">
|
||||
<el-table-column prop="regradeQuality" label="改判后等级" width="100" align="center">
|
||||
<template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100" />
|
||||
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
|
||||
<template slot-scope="s">
|
||||
<el-select v-model="approveCoilMap[s.row.detailId]" placeholder="请选择" size="small">
|
||||
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<span>{{ s.row.targetWarehouseName || '不移动' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -97,13 +109,7 @@ export default {
|
||||
review: null
|
||||
},
|
||||
approveAction: 'approve',
|
||||
approveForm: {
|
||||
reviewId: undefined,
|
||||
leaderOpinion: '',
|
||||
leaderSign: ''
|
||||
},
|
||||
approveCoilList: [],
|
||||
approveCoilMap: {},
|
||||
approveLoading: false,
|
||||
rejectReason: ''
|
||||
}
|
||||
@@ -126,19 +132,13 @@ export default {
|
||||
handleQuickApprove(row) {
|
||||
this.approveAction = 'approve'
|
||||
this.approveDialog.review = row
|
||||
this.approveForm = { reviewId: row.reviewId, leaderOpinion: '', leaderSign: '' }
|
||||
this.rejectReason = ''
|
||||
this.approveDialog.visible = true
|
||||
|
||||
// 加载明细
|
||||
// 加载明细(只读展示)
|
||||
getQualityReview(row.reviewId).then(res => {
|
||||
const data = res.data
|
||||
this.approveCoilList = data.coilList || []
|
||||
const map = {}
|
||||
;(data.coilList || []).forEach(c => {
|
||||
map[c.detailId] = c.regradeQuality || ''
|
||||
})
|
||||
this.approveCoilMap = map
|
||||
})
|
||||
},
|
||||
handleQuickReject(row) {
|
||||
@@ -150,25 +150,8 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
doApprove() {
|
||||
if (!this.approveForm.leaderOpinion) {
|
||||
this.$modal.msgError('请输入审批意见')
|
||||
return
|
||||
}
|
||||
const coilRegradeList = this.approveCoilList.map(c => ({
|
||||
detailId: c.detailId,
|
||||
regradeQuality: this.approveCoilMap[c.detailId]
|
||||
}))
|
||||
if (coilRegradeList.some(r => !r.regradeQuality)) {
|
||||
this.$modal.msgError('请为每个钢卷指定改判后质量状态')
|
||||
return
|
||||
}
|
||||
this.approveLoading = true
|
||||
approveQualityReview({
|
||||
reviewId: this.approveDialog.review.reviewId,
|
||||
leaderOpinion: this.approveForm.leaderOpinion,
|
||||
leaderSign: this.approveForm.leaderSign,
|
||||
coilRegradeList
|
||||
}).then(() => {
|
||||
approveQualityReview(this.approveDialog.review.reviewId).then(() => {
|
||||
this.$modal.msgSuccess('审批通过')
|
||||
this.approveDialog.visible = false
|
||||
this.approveLoading = false
|
||||
@@ -205,6 +188,13 @@ export default {
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.opinion-footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.coil-regrade-section {
|
||||
margin-top: 12px;
|
||||
|
||||
@@ -56,7 +56,12 @@
|
||||
<el-col :span="14">
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<span class="chart-title"><i class="el-icon-data-line" /> 每日磨削趋势</span>
|
||||
<span class="chart-title"><i class="el-icon-data-line" /> {{ trendChartTitle }}</span>
|
||||
<el-radio-group v-model="trendDimension" size="mini" @change="onTrendDimensionChange" style="margin-left:12px">
|
||||
<el-radio-button label="day">按天</el-radio-button>
|
||||
<el-radio-button label="week">按周</el-radio-button>
|
||||
<el-radio-button label="month">按月</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div ref="trendChartRef" class="chart-container" style="height:340px"></div>
|
||||
<div v-if="!dailyTrend.length" class="chart-empty">暂无数据</div>
|
||||
@@ -131,6 +136,38 @@
|
||||
<el-empty v-else description="暂无磨削记录" />
|
||||
</div>
|
||||
|
||||
<!-- 月度产线汇总二维表 -->
|
||||
<div class="table-card mt16">
|
||||
<div class="card-header">
|
||||
<span class="card-title"><i class="el-icon-s-data" /> 月度产线汇总</span>
|
||||
<span class="card-subtitle" v-if="monthlyPivot.length">{{ monthlyPivotMonths.length }} 个月 · {{ monthlyPivot.length }} 条产线</span>
|
||||
<el-button size="mini" type="primary" plain icon="el-icon-download" style="margin-left:8px" @click="exportMonthlyPivot" :disabled="!monthlyPivot.length">导出</el-button>
|
||||
</div>
|
||||
<div style="overflow-x:auto" v-if="monthlyPivot.length">
|
||||
<el-table :data="monthlyPivot" size="small" border stripe style="min-width:100%"
|
||||
:header-cell-style="{ textAlign: 'center', fontWeight: 600 }">
|
||||
<el-table-column label="产线" prop="lineName" fixed="left" width="140" align="center" />
|
||||
<el-table-column v-for="m in monthlyPivotMonths" :key="m" :label="m" align="center">
|
||||
<el-table-column label="磨削次数" align="center" min-width="80">
|
||||
<template slot-scope="{row}">{{ row[m + '_count'] || 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="磨削量(mm)" align="right" min-width="110">
|
||||
<template slot-scope="{row}">{{ (row[m + '_amount'] || 0).toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计" align="center">
|
||||
<el-table-column label="磨削次数" align="center" min-width="80">
|
||||
<template slot-scope="{row}">{{ row.totalCount }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="磨削量(mm)" align="right" min-width="110">
|
||||
<template slot-scope="{row}">{{ row.totalAmount.toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else description="暂无磨削记录" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -139,12 +176,19 @@ import * as echarts from 'echarts'
|
||||
import * as XLSX from 'xlsx'
|
||||
import { getRollStats, listRollInfo } from '@/api/mes/roll/rollInfo'
|
||||
import { listRollGrindAll } from '@/api/mes/roll/rollGrind'
|
||||
import { mapGetters } from 'vuex'
|
||||
import rollLineMixin from '../rollLineMixin'
|
||||
|
||||
export default {
|
||||
name: 'RollReport',
|
||||
mixins: [rollLineMixin],
|
||||
dicts: ['mes_roll_operator'],
|
||||
computed: {
|
||||
...mapGetters(['productionLines']),
|
||||
trendChartTitle() {
|
||||
return { day: '每日磨削趋势', week: '每周磨削趋势', month: '每月磨削趋势' }[this.trendDimension] || '磨削趋势'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const now = new Date()
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
@@ -161,7 +205,10 @@ export default {
|
||||
trendChart: null,
|
||||
pieChart: null,
|
||||
_beginTime: fmt(sevenDaysAgo),
|
||||
_endTime: fmt(now)
|
||||
_endTime: fmt(now),
|
||||
monthlyPivot: [],
|
||||
monthlyPivotMonths: [],
|
||||
trendDimension: 'day'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -197,8 +244,8 @@ export default {
|
||||
this.stats = statsRes.data || {}
|
||||
}
|
||||
|
||||
// 2. 获取全部轧辊(用于辊型映射)
|
||||
const rollRes = await listRollInfo({ pageNum: 1, pageSize: 99999, lineId: this.lineId || undefined })
|
||||
// 2. 获取全部轧辊(用于辊型映射和产线关联)
|
||||
const rollRes = await listRollInfo({ pageNum: 1, pageSize: 99999 })
|
||||
const rolls = rollRes.rows || []
|
||||
const rollMap = {}
|
||||
rolls.forEach(r => { rollMap[r.rollId] = r })
|
||||
@@ -211,10 +258,20 @@ export default {
|
||||
const allRecords = grindRes.data || []
|
||||
|
||||
// 4. 汇总计算
|
||||
this.autoDetectTrendDimension()
|
||||
this.computeSummary(allRecords, rollMap)
|
||||
this.computeDailyTrend(allRecords)
|
||||
this.computeTrendData(allRecords)
|
||||
this.computeRollTypeDist(allRecords, rollMap)
|
||||
this.computeOperatorStats(allRecords, rollMap)
|
||||
|
||||
// 5. 月度产线汇总(跨全部产线,不受当前 lineId 限制)
|
||||
await this.$store.dispatch('productionLine/getProductionLines')
|
||||
const allParams = {}
|
||||
if (this._beginTime) allParams.beginTime = this._beginTime
|
||||
if (this._endTime) allParams.endTime = this._endTime + ' 23:59:59'
|
||||
const allGrindRes = await listRollGrindAll(allParams)
|
||||
this.computeMonthlyPivot(allGrindRes.data || [], rollMap)
|
||||
|
||||
this.updateCharts()
|
||||
} catch (e) {
|
||||
console.error('加载报表数据失败', e)
|
||||
@@ -249,6 +306,56 @@ export default {
|
||||
})
|
||||
this.dailyTrend = Object.values(dateMap).sort((a, b) => a.grindDate.localeCompare(b.grindDate))
|
||||
},
|
||||
computeWeeklyTrend(records) {
|
||||
const weekMap = {}
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
records.forEach(r => {
|
||||
if (!r.grindTime) return
|
||||
const d = new Date(r.grindTime)
|
||||
const dayOfWeek = d.getDay() || 7
|
||||
const monday = new Date(d)
|
||||
monday.setDate(d.getDate() - dayOfWeek + 1)
|
||||
const sunday = new Date(monday)
|
||||
sunday.setDate(monday.getDate() + 6)
|
||||
const key = `${monday.getFullYear()}-${pad(monday.getMonth() + 1)}-${pad(monday.getDate())}`
|
||||
const label = `${pad(monday.getMonth() + 1)}-${pad(monday.getDate())}~${pad(sunday.getMonth() + 1)}-${pad(sunday.getDate())}`
|
||||
if (!weekMap[key]) weekMap[key] = { grindDate: label, grindCount: 0, totalGrindAmount: 0, _sort: key }
|
||||
weekMap[key].grindCount++
|
||||
weekMap[key].totalGrindAmount += Number(r.grindAmount || 0)
|
||||
})
|
||||
this.dailyTrend = Object.values(weekMap).sort((a, b) => a._sort.localeCompare(b._sort))
|
||||
},
|
||||
computeMonthlyTrend(records) {
|
||||
const monthMap = {}
|
||||
records.forEach(r => {
|
||||
if (!r.grindTime) return
|
||||
const m = r.grindTime.substring(0, 7)
|
||||
if (!monthMap[m]) monthMap[m] = { grindDate: m, grindCount: 0, totalGrindAmount: 0 }
|
||||
monthMap[m].grindCount++
|
||||
monthMap[m].totalGrindAmount += Number(r.grindAmount || 0)
|
||||
})
|
||||
this.dailyTrend = Object.values(monthMap).sort((a, b) => a.grindDate.localeCompare(b.grindDate))
|
||||
},
|
||||
computeTrendData(records) {
|
||||
this._cachedRecords = records
|
||||
if (this.trendDimension === 'week') this.computeWeeklyTrend(records)
|
||||
else if (this.trendDimension === 'month') this.computeMonthlyTrend(records)
|
||||
else this.computeDailyTrend(records)
|
||||
},
|
||||
autoDetectTrendDimension() {
|
||||
const [b, e] = this.dateRange
|
||||
if (!b || !e) return
|
||||
const days = (new Date(e) - new Date(b)) / (24 * 60 * 60 * 1000)
|
||||
if (days <= 31) this.trendDimension = 'day'
|
||||
else if (days <= 90) this.trendDimension = 'week'
|
||||
else this.trendDimension = 'month'
|
||||
},
|
||||
onTrendDimensionChange() {
|
||||
if (this._cachedRecords) {
|
||||
this.computeTrendData(this._cachedRecords)
|
||||
this.updateTrendChart()
|
||||
}
|
||||
},
|
||||
computeRollTypeDist(records, rollMap) {
|
||||
const typeMap = {}
|
||||
records.forEach(r => {
|
||||
@@ -420,6 +527,79 @@ export default {
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
},
|
||||
computeMonthlyPivot(records, rollMap) {
|
||||
const lineMonthMap = {}
|
||||
const monthSet = new Set()
|
||||
records.forEach(r => {
|
||||
if (!r.grindTime) return
|
||||
const month = r.grindTime.substring(0, 7)
|
||||
const roll = rollMap[r.rollId]
|
||||
const lid = Number(roll?.lineId) || 0
|
||||
if (!lineMonthMap[lid]) {
|
||||
const line = this.productionLines.find(l => Number(l.lineId) === lid)
|
||||
lineMonthMap[lid] = { lineId: lid, lineName: line ? line.lineName : (r.lineName || '未知产线'), months: {} }
|
||||
}
|
||||
if (!lineMonthMap[lid].months[month]) {
|
||||
lineMonthMap[lid].months[month] = { grindCount: 0, totalGrindAmount: 0 }
|
||||
}
|
||||
lineMonthMap[lid].months[month].grindCount++
|
||||
lineMonthMap[lid].months[month].totalGrindAmount += Number(r.grindAmount || 0)
|
||||
monthSet.add(month)
|
||||
})
|
||||
const months = [...monthSet].sort()
|
||||
const rows = this.productionLines.map(line => {
|
||||
const ld = lineMonthMap[line.lineId]
|
||||
const row = { lineId: line.lineId, lineName: line.lineName, totalCount: 0, totalAmount: 0 }
|
||||
months.forEach(m => {
|
||||
if (ld && ld.months[m]) {
|
||||
row[m + '_count'] = ld.months[m].grindCount
|
||||
row[m + '_amount'] = Number(ld.months[m].totalGrindAmount.toFixed(2))
|
||||
row.totalCount += ld.months[m].grindCount
|
||||
row.totalAmount += ld.months[m].totalGrindAmount
|
||||
} else {
|
||||
row[m + '_count'] = 0
|
||||
row[m + '_amount'] = 0
|
||||
}
|
||||
})
|
||||
row.totalAmount = Number(row.totalAmount.toFixed(2))
|
||||
return row
|
||||
})
|
||||
this.monthlyPivotMonths = months
|
||||
this.monthlyPivot = rows
|
||||
},
|
||||
exportMonthlyPivot() {
|
||||
const months = this.monthlyPivotMonths
|
||||
const header1 = ['产线']
|
||||
const header2 = ['']
|
||||
months.forEach(m => { header1.push(m, ''); header2.push('磨削次数', '磨削量(mm)') })
|
||||
header1.push('合计', '')
|
||||
header2.push('磨削次数', '磨削量(mm)')
|
||||
const rows = [header1, header2]
|
||||
this.monthlyPivot.forEach(row => {
|
||||
const r = [row.lineName]
|
||||
months.forEach(m => { r.push(row[m + '_count'] || 0, (row[m + '_amount'] || 0).toFixed(2)) })
|
||||
r.push(row.totalCount, row.totalAmount.toFixed(2))
|
||||
rows.push(r)
|
||||
})
|
||||
const ws = XLSX.utils.aoa_to_sheet(rows)
|
||||
// merge header row 1 for month groups
|
||||
ws['!merges'] = []
|
||||
let col = 1
|
||||
months.forEach(() => { ws['!merges'].push({ s: { r: 0, c: col }, e: { r: 0, c: col + 1 } }); col += 2 })
|
||||
ws['!merges'].push({ s: { r: 0, c: col }, e: { r: 0, c: col + 1 } })
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '月度产线汇总')
|
||||
const buf = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
|
||||
const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `月度产线汇总_${this._beginTime || ''}_${this._endTime ? this._endTime.substring(0, 10) : ''}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,6 +611,7 @@ export default {
|
||||
min-height: 100%;
|
||||
}
|
||||
.mb16 { margin-bottom: 16px; }
|
||||
.mt16 { margin-top: 16px; }
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-panel {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<template slot-scope="{ row }">{{ row.CREW || row.crew || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="钢种" min-width="80" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.ORDER_QUALITY || row.order_quality || row.GRADE || row.grade || '—' }}</template>
|
||||
<template slot-scope="{ row }">{{ row.GRADE || row.grade || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料厚度" width="68" align="right">
|
||||
<template slot-scope="{ row }">{{ row.ENTRY_THICK || row.entry_thick || '—' }}</template>
|
||||
|
||||
@@ -1,145 +1,134 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 统计卡片 -->
|
||||
<el-row :gutter="16" class="stat-row" v-if="activeTab === 'all'">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ stats.totalFiles }}</div>
|
||||
<div class="stat-label">文件总数</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card public">
|
||||
<div class="stat-num">{{ stats.publicFiles }}</div>
|
||||
<div class="stat-label">公开文件</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card private">
|
||||
<div class="stat-num">{{ stats.privateFiles }}</div>
|
||||
<div class="stat-label">私有文件</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-num">{{ stats.myFiles }}</div>
|
||||
<div class="stat-label">我上传的</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 左右分栏布局(可拖拽调节宽度) -->
|
||||
<div class="file-layout" ref="layoutContainer">
|
||||
<!-- 左侧:文件列表 -->
|
||||
<div class="file-left" :style="{ width: leftPanelWidth }">
|
||||
<!-- 搜索栏 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="文件名称" prop="fileName">
|
||||
<el-input
|
||||
v-model="queryParams.fileName"
|
||||
placeholder="请输入文件名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单编号" prop="orderNo">
|
||||
<el-input
|
||||
v-model="queryParams.orderNo"
|
||||
placeholder="请输入订单编号"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门" prop="dept">
|
||||
<el-input
|
||||
v-model="queryParams.dept"
|
||||
placeholder="请输入部门"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型" prop="fileType">
|
||||
<el-select
|
||||
v-model="queryParams.fileType"
|
||||
placeholder="请选择文件类型"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dict.type.sys_file_type"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="可见范围" prop="scopeType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||||
<el-select
|
||||
v-model="queryParams.scopeType"
|
||||
placeholder="请选择可见范围"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option label="公开" :value="1" />
|
||||
<el-option label="私有" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="['00:00:00', '23:59:59']"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 统计卡片(两行每行两个) -->
|
||||
<div class="summary-stat-container">
|
||||
<el-card class="summary-card" shadow="hover">
|
||||
<el-row :gutter="6">
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">文件总数</span>
|
||||
<span class="item-value">{{ stats.totalFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">公开文件</span>
|
||||
<span class="item-value">{{ stats.publicFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="6" style="margin-top: 4px;">
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">私有文件</span>
|
||||
<span class="item-value">{{ stats.privateFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="summary-item">
|
||||
<span class="item-label">我上传的</span>
|
||||
<span class="item-value">{{ stats.myFiles }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 文件类型筛选(两行) -->
|
||||
<div class="file-type-tabs">
|
||||
<span
|
||||
v-for="opt in fileTypeOptions"
|
||||
:key="opt.value"
|
||||
class="type-tab"
|
||||
:class="{ active: (queryParams.fileType || '') === opt.value }"
|
||||
@click="handleTypeTabClick(opt.value)"
|
||||
>{{ opt.label }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||||
<!-- 操作按钮 + 名称搜索 -->
|
||||
<div class="toolbar-row">
|
||||
<div class="toolbar-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
v-if="activeTab === 'my' || activeTab === 'all'"
|
||||
@click="handleAdd"
|
||||
>上传文件</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||||
</div>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
v-if="activeTab === 'my' || activeTab === 'all'"
|
||||
:disabled="multiple"
|
||||
style="margin-right: auto;"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
<div class="toolbar-search">
|
||||
<el-input
|
||||
v-model="queryParams.fileName"
|
||||
style="flex: 1;"
|
||||
prefix-icon="el-icon-search"
|
||||
placeholder="按名称搜索"
|
||||
clearable
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
@change="handleQuery"
|
||||
@keyup.enter.native="handleQuery"
|
||||
@clear="handleQuery"
|
||||
></el-input>
|
||||
<el-button icon="el-icon-sort" @click="toggleQuery"></el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表表格 -->
|
||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" highlight-current-row @row-click="handleRowClick">
|
||||
<!-- 高级搜索区域 -->
|
||||
<div v-show="showQuery" class="advanced-search">
|
||||
<el-form :model="queryParams" inline size="mini" class="advanced-search-form">
|
||||
<el-form-item label="订单编号">
|
||||
<el-input v-model="queryParams.orderNo" placeholder="订单编号" clearable @change="handleQuery" style="width: 140px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门">
|
||||
<el-input v-model="queryParams.dept" placeholder="所属部门" clearable @change="handleQuery" style="width: 140px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型">
|
||||
<el-select v-model="queryParams.fileType" placeholder="文件类型" clearable @change="handleQuery" style="width: 120px;">
|
||||
<el-option v-for="item in dict.type.sys_file_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="可见范围">
|
||||
<el-select v-model="queryParams.scopeType" placeholder="可见范围" clearable @change="handleQuery" style="width: 120px;">
|
||||
<el-option label="公开" :value="1" />
|
||||
<el-option label="私有" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@change="handleQuery"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表表格(固定高度可滚动) -->
|
||||
<div class="file-table-wrapper">
|
||||
<div class="file-table-scroll">
|
||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" highlight-current-row @row-click="handleRowClick">
|
||||
<el-table-column type="selection" width="55" align="center" v-if="activeTab === 'my' || activeTab === 'all'" />
|
||||
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
@@ -158,6 +147,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
@@ -166,6 +156,8 @@
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- 拖拽分隔条 -->
|
||||
<div class="resize-handle" @mousedown="startResize" :class="{ dragging: isDragging }"></div>
|
||||
@@ -179,19 +171,13 @@
|
||||
<div class="preview-header">
|
||||
<span class="preview-filename" :title="selectedFile.fileName">{{ selectedFile.fileName }}</span>
|
||||
<div>
|
||||
<el-button size="mini" type="text" @click="handlePreview(selectedFile)">预览</el-button>
|
||||
<el-button size="mini" type="warning" plain icon="el-icon-download" @click="handleExport">导出</el-button>
|
||||
<el-button size="mini" plain icon="el-icon-info" @click="handleShowInfo(selectedFile)">查看</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-download" @click="downloadFile(selectedFile)">下载</el-button>
|
||||
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(selectedFile)">编辑</el-button>
|
||||
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(selectedFile)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-meta">
|
||||
<span>{{ formatFileSize(selectedFile.fileSize) }}</span>
|
||||
<dict-tag :options="dict.type.sys_file_type" :value="selectedFile.fileType" />
|
||||
<span>{{ selectedFile.createBy }}</span>
|
||||
<span>{{ parseTime(selectedFile.createTime) }}</span>
|
||||
</div>
|
||||
<div class="preview-comment">
|
||||
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||||
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||||
@@ -362,6 +348,8 @@ export default {
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 显示高级搜索
|
||||
showQuery: false,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 文件列表
|
||||
@@ -441,6 +429,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 文件类型筛选选项 */
|
||||
fileTypeOptions() {
|
||||
const dict = this.dict.type.sys_file_type || []
|
||||
return [{ value: '', label: '全部' }, ...dict]
|
||||
},
|
||||
/** 根据文件后缀分类,决定右侧用哪个预览组件 */
|
||||
fileTypeCategory() {
|
||||
if (!this.selectedFile) return null
|
||||
@@ -564,16 +557,25 @@ export default {
|
||||
this.form.visibleUsers = []
|
||||
}
|
||||
},
|
||||
/** 搜索按钮 */
|
||||
/** 搜索 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 切换高级搜索 */
|
||||
toggleQuery() {
|
||||
this.showQuery = !this.showQuery
|
||||
},
|
||||
/** 文件类型筛选切换 */
|
||||
handleTypeTabClick(value) {
|
||||
this.queryParams.fileType = value || undefined
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.selectedFile = null
|
||||
this.resetForm('queryForm')
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
@@ -583,7 +585,7 @@ export default {
|
||||
fileType: undefined,
|
||||
scopeType: undefined
|
||||
}
|
||||
this.handleQuery()
|
||||
this.getList()
|
||||
},
|
||||
/** 多选框 */
|
||||
handleSelectionChange(selection) {
|
||||
@@ -821,38 +823,111 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-row {
|
||||
/* 操作工具栏 */
|
||||
.toolbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
.toolbar-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-left: auto;
|
||||
flex: 0 1 420px;
|
||||
}
|
||||
|
||||
/* 高级搜索区域 */
|
||||
.advanced-search {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.advanced-search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.advanced-search-form /deep/ .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 文件类型筛选(两行) */
|
||||
.file-type-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-tab:hover {
|
||||
color: #5F7BA0;
|
||||
border-color: #5F7BA0;
|
||||
}
|
||||
|
||||
.type-tab.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
border-color: #5F7BA0;
|
||||
}
|
||||
|
||||
/* 统计卡片 - 销售看板风格 */
|
||||
.summary-stat-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.summary-stat-container .summary-card {
|
||||
border: none;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.summary-stat-container .summary-card /deep/ .el-card__body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
border-left: 3px solid #409eff;
|
||||
padding: 6px 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.stat-card.public {
|
||||
border-left-color: #67c23a;
|
||||
}
|
||||
|
||||
.stat-card.private {
|
||||
border-left-color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-num {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
.item-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.upload-demo {
|
||||
@@ -862,12 +937,15 @@ export default {
|
||||
/* 左右分栏布局 */
|
||||
.file-layout {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
.file-left {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.file-right {
|
||||
@@ -915,12 +993,9 @@ export default {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
height: calc(100vh - 280px);
|
||||
min-height: 450px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.preview-empty {
|
||||
@@ -948,17 +1023,6 @@ export default {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.preview-comment {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
@@ -1042,7 +1106,6 @@ export default {
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
}
|
||||
@@ -1057,6 +1120,21 @@ export default {
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* 左侧表格区域撑满剩余空间,分页置底 */
|
||||
.file-table-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-table-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<!-- 表格选中行高亮(非 scoped,穿透 el-table) -->
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<div class="file-layout" ref="layoutContainer">
|
||||
<!-- 左侧:文件列表 -->
|
||||
<div class="file-left" :style="{ width: activeTab === 'share' || activeTab === 'related' ? leftPanelWidth : '100%' }">
|
||||
<!-- 搜索栏 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<!-- 搜索栏 - 我的文件 -->
|
||||
<el-form v-if="activeTab === 'my'" :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||
<el-form-item label="文件名称" prop="fileName">
|
||||
<el-input
|
||||
v-model="queryParams.fileName"
|
||||
@@ -112,8 +112,61 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<!-- 搜索栏 - 共享文件 / 与我相关 -->
|
||||
<div v-else class="share-search-area">
|
||||
<div class="toolbar-row">
|
||||
<div class="toolbar-buttons">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||||
</div>
|
||||
<div class="toolbar-search">
|
||||
<el-input
|
||||
v-model="queryParams.fileName"
|
||||
style="flex: 1;"
|
||||
prefix-icon="el-icon-search"
|
||||
placeholder="按名称搜索"
|
||||
clearable
|
||||
size="mini"
|
||||
@change="handleQuery"
|
||||
@keyup.enter.native="handleQuery"
|
||||
@clear="handleQuery"
|
||||
></el-input>
|
||||
<el-button icon="el-icon-sort" @click="toggleQuery"></el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="showQuery" class="advanced-search">
|
||||
<el-form :model="queryParams" inline size="mini" class="advanced-search-form">
|
||||
<el-form-item label="订单编号">
|
||||
<el-input v-model="queryParams.orderNo" placeholder="订单编号" clearable @change="handleQuery" style="width: 140px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门">
|
||||
<el-input v-model="queryParams.dept" placeholder="所属部门" clearable @change="handleQuery" style="width: 140px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型">
|
||||
<el-select v-model="queryParams.fileType" placeholder="文件类型" clearable @change="handleQuery" style="width: 120px;">
|
||||
<el-option v-for="item in dict.type.sys_file_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@change="handleQuery"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮(仅我的文件 tab) -->
|
||||
<el-row v-if="activeTab === 'my'" :gutter="10" class="mb8">
|
||||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -145,8 +198,10 @@
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 文件列表表格 -->
|
||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" :highlight-current-row="activeTab === 'share' || activeTab === 'related'" @row-click="handleRowClick">
|
||||
<!-- 文件列表表格(固定高度可滚动) -->
|
||||
<div :class="['file-table-wrapper', { scrolling: activeTab === 'share' || activeTab === 'related' }]">
|
||||
<div class="file-table-scroll">
|
||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" :highlight-current-row="activeTab === 'share' || activeTab === 'related'" @row-click="handleRowClick">
|
||||
<el-table-column type="selection" width="55" align="center" v-if="activeTab === 'my' || activeTab === 'all'" />
|
||||
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
@@ -209,6 +264,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
@@ -217,6 +273,8 @@
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 拖拽分隔条(共享文件 / 与我相关 tab) -->
|
||||
@@ -238,12 +296,6 @@
|
||||
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(selectedFile)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-meta">
|
||||
<span>{{ formatFileSize(selectedFile.fileSize) }}</span>
|
||||
<dict-tag :options="dict.type.sys_file_type" :value="selectedFile.fileType" />
|
||||
<span>{{ selectedFile.createBy }}</span>
|
||||
<span>{{ parseTime(selectedFile.createTime) }}</span>
|
||||
</div>
|
||||
<div class="preview-comment">
|
||||
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||||
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||||
@@ -417,6 +469,8 @@ export default {
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 显示高级搜索
|
||||
showQuery: false,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 文件列表
|
||||
@@ -623,10 +677,15 @@ export default {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 切换高级搜索 */
|
||||
toggleQuery() {
|
||||
this.showQuery = !this.showQuery
|
||||
},
|
||||
/** 重置按钮 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.selectedFile = null
|
||||
this.showQuery = false
|
||||
this.resetForm('queryForm')
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
@@ -928,12 +987,15 @@ export default {
|
||||
/* 左右分栏布局 */
|
||||
.file-layout {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
.file-left {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.file-right {
|
||||
@@ -981,12 +1043,9 @@ export default {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
height: calc(100vh - 280px);
|
||||
min-height: 450px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.preview-empty {
|
||||
@@ -1014,17 +1073,6 @@ export default {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.preview-meta {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.preview-comment {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
@@ -1108,7 +1156,6 @@ export default {
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
}
|
||||
@@ -1123,6 +1170,64 @@ export default {
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* 左侧表格区域撑满剩余空间可滚动 + 边框 */
|
||||
.file-table-wrapper.scrolling {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-table-wrapper.scrolling .file-table-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 搜索栏(共享文件 / 与我相关 tab) */
|
||||
.share-search-area {
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.toolbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.toolbar-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-left: auto;
|
||||
flex: 0 1 420px;
|
||||
}
|
||||
|
||||
.advanced-search {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.advanced-search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.advanced-search-form /deep/ .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 表格选中行高亮(非 scoped,穿透 el-table) -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="app-container acid-do-container">
|
||||
<div class="search-section">
|
||||
<h3 class="page-title">综合钢卷修正</h3>
|
||||
<h3 class="page-title">钢卷追踪</h3>
|
||||
<el-form :model="materialQueryParams" ref="materialQueryForm" size="small" :inline="true" class="search-form">
|
||||
<el-form-item label="入场钢卷号" prop="enterCoilNo">
|
||||
<el-input v-model="materialQueryParams.enterCoilNo" placeholder="请输入入场钢卷号" clearable
|
||||
|
||||
@@ -197,7 +197,7 @@ export default {
|
||||
total: 0,
|
||||
coilList: [],
|
||||
statistics: {},
|
||||
overdueDays: 20,
|
||||
overdueDays: 60,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 50,
|
||||
|
||||
@@ -163,7 +163,10 @@
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 钢卷明细表格 -->
|
||||
<h4 style="margin-top: 20px">钢卷明细</h4>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-top: 20px">
|
||||
<h4 style="margin: 0">钢卷明细</h4>
|
||||
<el-button type="primary" size="small" icon="el-icon-download" @click="handleExportDetail">导出</el-button>
|
||||
</div>
|
||||
<el-table :data="dialogList" border stripe max-height="500" style="width: 100%">
|
||||
<el-table-column prop="enterCoilNo" label="入场钢卷号" align="center" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="currentCoilNo" label="当前钢卷号" align="center" min-width="150" show-overflow-tooltip />
|
||||
@@ -518,6 +521,15 @@ export default {
|
||||
.finally(() => { this.dialogLoading = false; });
|
||||
},
|
||||
|
||||
handleExportDetail() {
|
||||
if (!this.currentWarehouse) return;
|
||||
this.download('wms/materialCoil/export', {
|
||||
warehouseId: this.currentWarehouse.warehouseId,
|
||||
dataType: 1,
|
||||
status: 0,
|
||||
}, `钢卷明细_${this.currentWarehouse.warehouseName}_${new Date().getTime()}.xlsx`);
|
||||
},
|
||||
|
||||
// ============ 弹窗数据透视图(班组 × 品质) ============
|
||||
getQualityGroup(qs) {
|
||||
if (!qs) return '未知';
|
||||
|
||||
@@ -211,7 +211,14 @@
|
||||
<el-input v-model="exportFormData.ename" placeholder="请输入姓名(可选)" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="deptname">
|
||||
<el-input v-model="exportFormData.deptname" placeholder="请输入部门(可选)" clearable />
|
||||
<el-select v-model="exportFormData.deptname" placeholder="请选择部门(可选)" clearable filterable @change="handleDeptChange" style="width: 100%">
|
||||
<el-option v-for="dept in deptList" :key="dept" :label="dept" :value="dept" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="enames" v-if="exportFormData.deptname">
|
||||
<el-select v-model="exportFormData.enames" placeholder="请选择员工(可选,不选则导出整个部门)" multiple clearable filterable style="width: 100%">
|
||||
<el-option v-for="emp in employeeList" :key="emp.name" :label="emp.name + ' (' + emp.jobType + ')'" :value="emp.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -224,6 +231,7 @@
|
||||
|
||||
<script>
|
||||
import { listRecords, getRecords, delRecords, addRecords, updateRecords, syncRecords, exportAttendanceReport } from "@/api/wms/attendance";
|
||||
import { getDepts, getEmployeesByDept } from "@/api/wms/employeeInfo";
|
||||
|
||||
export default {
|
||||
name: "Records",
|
||||
@@ -247,12 +255,17 @@ export default {
|
||||
exportDialogVisible: false,
|
||||
// 导出loading
|
||||
exportLoading: false,
|
||||
// 部门列表
|
||||
deptList: [],
|
||||
// 员工列表(根据部门动态加载)
|
||||
employeeList: [],
|
||||
// 导出表单
|
||||
exportFormData: {
|
||||
dateRange: [],
|
||||
pin: "",
|
||||
ename: "",
|
||||
deptname: ""
|
||||
deptname: "",
|
||||
enames: []
|
||||
},
|
||||
// 导出表单校验
|
||||
exportRules: {
|
||||
@@ -474,6 +487,12 @@ export default {
|
||||
this.exportFormData.pin = "";
|
||||
this.exportFormData.ename = "";
|
||||
this.exportFormData.deptname = "";
|
||||
this.exportFormData.enames = [];
|
||||
this.employeeList = [];
|
||||
// 加载部门列表
|
||||
getDepts().then(res => {
|
||||
this.deptList = res.data || [];
|
||||
});
|
||||
this.exportDialogVisible = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.exportForm && this.$refs.exportForm.clearValidate();
|
||||
@@ -484,35 +503,66 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 部门变更时加载对应员工 */
|
||||
handleDeptChange(dept) {
|
||||
this.exportFormData.enames = [];
|
||||
if (dept) {
|
||||
getEmployeesByDept(dept).then(res => {
|
||||
this.employeeList = res.data || [];
|
||||
});
|
||||
} else {
|
||||
this.employeeList = [];
|
||||
}
|
||||
},
|
||||
/** 执行导出考勤记录表 */
|
||||
doExportReport() {
|
||||
this.$refs.exportForm.validate(valid => {
|
||||
if (!valid) return;
|
||||
const [startDate, endDate] = this.exportFormData.dateRange;
|
||||
this.exportLoading = true;
|
||||
exportAttendanceReport({
|
||||
async doExportReport() {
|
||||
const valid = await this.$refs.exportForm.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
|
||||
const [startDate, endDate] = this.exportFormData.dateRange;
|
||||
this.exportLoading = true;
|
||||
|
||||
try {
|
||||
let enames = undefined;
|
||||
|
||||
if (this.exportFormData.deptname) {
|
||||
// 选了部门:通过 wms_employee_info 查员工姓名来过滤
|
||||
if (this.exportFormData.enames && this.exportFormData.enames.length > 0) {
|
||||
enames = this.exportFormData.enames;
|
||||
} else {
|
||||
const res = await getEmployeesByDept(this.exportFormData.deptname);
|
||||
const allEmps = res.data || [];
|
||||
enames = allEmps.map(e => e.name);
|
||||
if (enames.length === 0) {
|
||||
this.$modal.msgWarning('该部门下没有在职员工');
|
||||
this.exportLoading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await exportAttendanceReport({
|
||||
startTime: startDate,
|
||||
endTime: endDate,
|
||||
pin: this.exportFormData.pin || undefined,
|
||||
ename: this.exportFormData.ename || undefined,
|
||||
deptname: this.exportFormData.deptname || undefined
|
||||
}).then(res => {
|
||||
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `考勤记录表_${startDate}_${endDate}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
this.$modal.msgSuccess("导出成功");
|
||||
this.exportLoading = false;
|
||||
this.exportDialogVisible = false;
|
||||
}).catch(() => {
|
||||
this.exportLoading = false;
|
||||
pin: !enames ? (this.exportFormData.pin || undefined) : undefined,
|
||||
ename: !enames ? (this.exportFormData.ename || undefined) : undefined,
|
||||
enames: enames
|
||||
});
|
||||
});
|
||||
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `考勤记录表_${startDate}_${endDate}.xlsx`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
this.$modal.msgSuccess("导出成功");
|
||||
this.exportLoading = false;
|
||||
this.exportDialogVisible = false;
|
||||
} catch (e) {
|
||||
this.exportLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
589
klp-ui/src/views/wms/post/aps/processes.vue
Normal file
589
klp-ui/src/views/wms/post/aps/processes.vue
Normal file
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<div class="app-container" style="height: calc(100vh - 84px); display: flex;">
|
||||
<!-- 左侧工艺列表 -->
|
||||
<div class="left-panel" v-loading="processLoading"
|
||||
style="width: 35%; border-right: 1px solid #e4e7ed; overflow-y: auto;">
|
||||
<!-- 筛选区 -->
|
||||
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||||
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||||
<el-input v-model="queryParams.processName" placeholder="工艺名称" clearable @keyup.enter.native="handleSearch"
|
||||
style="width: 160px;" />
|
||||
<el-button class="aps-btn-red" icon="el-icon-search" size="mini" @click="handleSearch">筛选</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" class="aps-btn-silver" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-button class="aps-btn-red" icon="el-icon-plus" size="mini" @click="handleAddProcess">新增工艺</el-button>
|
||||
<span style="font-size: 12px; color: #909399;">
|
||||
共 <span class="aps-total-count">{{ total }}</span> 条
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表区域 -->
|
||||
<div class="custom-list">
|
||||
<div class="list-body">
|
||||
<div v-for="item in processList" :key="item.processId" class="list-item"
|
||||
:class="{ 'list-item-active': currentProcess && currentProcess.processId === item.processId }"
|
||||
@click="handleProcessClick(item)">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div style="font-weight: bold; font-size: 14px;">{{ item.processName }}</div>
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-button type="text" icon="el-icon-edit" size="mini" @click.stop="handleUpdateProcess(item)"></el-button>
|
||||
<el-button type="text" icon="el-icon-delete" size="mini" style="color: #F56C6C;" @click.stop="handleDeleteProcess(item)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #909399; margin-bottom: 4px;">
|
||||
{{ item.processDesc || '暂无描述' }}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #909399;">
|
||||
备注: {{ item.remark || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="processList.length === 0 && !processLoading"
|
||||
style="padding: 40px; text-align: center; color: #909399;">
|
||||
暂无工艺数据
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getProcessList" style="padding: 10px; margin-bottom: 10px !important;" />
|
||||
</div>
|
||||
|
||||
<!-- 右侧工艺步骤区域 -->
|
||||
<div class="right-panel" v-if="currentProcess && currentProcess.processId"
|
||||
style="flex: 1; display: flex; flex-direction: column;" v-loading="stepLoading">
|
||||
<div class="detail-panel">
|
||||
<!-- 工艺基本信息卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
<span>工艺基本信息</span>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-field"><label>工艺名称</label>
|
||||
<div class="field-value">{{ currentProcess.processName }}</div>
|
||||
</div>
|
||||
<div class="form-field"><label>工艺描述</label>
|
||||
<div class="field-value">{{ currentProcess.processDesc || '-' }}</div>
|
||||
</div>
|
||||
<div class="form-field" style="grid-column:1/3;"><label>备注</label>
|
||||
<div class="field-value">{{ currentProcess.remark || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工艺步骤卡片 -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">
|
||||
<span>工艺步骤({{ stepList.length }} 步)</span>
|
||||
<el-button type="text" icon="el-icon-plus" size="mini" style="color: white;" @click="handleAddStep">新增步骤</el-button>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div v-if="stepList.length > 0" class="process-timeline-wrap">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(step, index) in stepList"
|
||||
:key="step.stepId"
|
||||
:timestamp="'步骤 ' + step.stepOrder"
|
||||
placement="top"
|
||||
:type="index === stepList.length - 1 ? 'success' : 'primary'"
|
||||
:hollow="index !== stepList.length - 1"
|
||||
size="large"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-header">
|
||||
<span class="step-name">{{ step.stepName }}</span>
|
||||
<div class="step-actions">
|
||||
<el-button type="text" icon="el-icon-edit" size="mini" @click="handleUpdateStep(step)"></el-button>
|
||||
<el-button type="text" icon="el-icon-delete" size="mini" style="color: #F56C6C;" @click="handleDeleteStep(step)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="step.remark" class="step-remark">{{ step.remark }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<el-empty v-else description="暂无工艺步骤" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else style="flex: 1; display: flex; flex-direction: column;">
|
||||
<el-empty description="选择工艺后查看步骤" />
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑工艺弹窗 -->
|
||||
<el-dialog :title="processTitle" :visible.sync="processOpen" width="500px" append-to-body>
|
||||
<el-form ref="processForm" :model="processForm" :rules="processRules" label-width="80px">
|
||||
<el-form-item label="工艺名称" prop="processName">
|
||||
<el-input v-model="processForm.processName" placeholder="请输入工艺名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工艺描述" prop="processDesc">
|
||||
<el-input v-model="processForm.processDesc" type="textarea" :rows="3" placeholder="请输入工艺描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="processForm.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitProcessForm">确 定</el-button>
|
||||
<el-button @click="cancelProcess">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 新增/编辑工艺步骤弹窗 -->
|
||||
<el-dialog :title="stepTitle" :visible.sync="stepOpen" width="500px" append-to-body>
|
||||
<el-form ref="stepForm" :model="stepForm" :rules="stepRules" label-width="80px">
|
||||
<el-form-item label="步骤顺序" prop="stepOrder">
|
||||
<el-input-number v-model="stepForm.stepOrder" :min="1" :max="99" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="步骤名称" prop="stepName">
|
||||
<el-input v-model="stepForm.stepName" placeholder="请输入步骤名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="stepForm.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitStepForm">确 定</el-button>
|
||||
<el-button @click="cancelStep">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProcess, getProcess, addProcess, updateProcess, delProcess } from '@/api/aps/process'
|
||||
import { listProcessStep, addProcessStep, updateProcessStep, delProcessStep } from '@/api/aps/processStep'
|
||||
|
||||
export default {
|
||||
name: 'ApsProcesses',
|
||||
data() {
|
||||
return {
|
||||
// 工艺相关
|
||||
processLoading: false,
|
||||
processList: [],
|
||||
total: 0,
|
||||
currentProcess: null,
|
||||
queryParams: {
|
||||
processName: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
processTitle: '',
|
||||
processOpen: false,
|
||||
processForm: {},
|
||||
processRules: {
|
||||
processName: [
|
||||
{ required: true, message: '工艺名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
// 工艺步骤相关
|
||||
stepLoading: false,
|
||||
submitLoading: false,
|
||||
stepList: [],
|
||||
stepTitle: '',
|
||||
stepOpen: false,
|
||||
stepForm: {},
|
||||
stepRules: {
|
||||
stepOrder: [
|
||||
{ required: true, message: '步骤顺序不能为空', trigger: 'blur' }
|
||||
],
|
||||
stepName: [
|
||||
{ required: true, message: '步骤名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getProcessList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询工艺列表 */
|
||||
getProcessList() {
|
||||
this.processLoading = true
|
||||
listProcess(this.queryParams).then(res => {
|
||||
this.processList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).catch(() => {
|
||||
this.processList = []
|
||||
this.total = 0
|
||||
}).finally(() => {
|
||||
this.processLoading = false
|
||||
})
|
||||
},
|
||||
/** 搜索 */
|
||||
handleSearch() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getProcessList()
|
||||
},
|
||||
/** 重置 */
|
||||
resetQuery() {
|
||||
this.queryParams.processName = ''
|
||||
this.handleSearch()
|
||||
},
|
||||
/** 点击工艺 */
|
||||
handleProcessClick(process) {
|
||||
this.currentProcess = process
|
||||
this.getStepList(process.processId)
|
||||
},
|
||||
/** 查询工艺步骤列表 */
|
||||
getStepList(processId) {
|
||||
this.stepLoading = true
|
||||
listProcessStep({ processId }).then(res => {
|
||||
this.stepList = res.rows || []
|
||||
}).catch(() => {
|
||||
this.stepList = []
|
||||
}).finally(() => {
|
||||
this.stepLoading = false
|
||||
})
|
||||
},
|
||||
/** 新增工艺 */
|
||||
handleAddProcess() {
|
||||
this.resetProcessForm()
|
||||
this.processTitle = '新增工艺'
|
||||
this.processOpen = true
|
||||
},
|
||||
/** 修改工艺 */
|
||||
handleUpdateProcess(row) {
|
||||
this.resetProcessForm()
|
||||
const processId = row.processId || this.currentProcess.processId
|
||||
getProcess(processId).then(res => {
|
||||
this.processForm = res.data
|
||||
this.processTitle = '修改工艺'
|
||||
this.processOpen = true
|
||||
})
|
||||
},
|
||||
/** 提交工艺表单 */
|
||||
submitProcessForm() {
|
||||
this.$refs['processForm'].validate(valid => {
|
||||
if (valid) {
|
||||
this.submitLoading = true
|
||||
if (this.processForm.processId != undefined) {
|
||||
updateProcess(this.processForm).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.processOpen = false
|
||||
this.getProcessList()
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
} else {
|
||||
addProcess(this.processForm).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.processOpen = false
|
||||
this.getProcessList()
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除工艺 */
|
||||
handleDeleteProcess(row) {
|
||||
const processIds = row.processId || this.currentProcess.processId
|
||||
this.$modal.confirm('是否确认删除该工艺?').then(() => {
|
||||
return delProcess(processIds)
|
||||
}).then(() => {
|
||||
this.getProcessList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.currentProcess = null
|
||||
this.stepList = []
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 取消工艺弹窗 */
|
||||
cancelProcess() {
|
||||
this.processOpen = false
|
||||
this.resetProcessForm()
|
||||
},
|
||||
/** 重置工艺表单 */
|
||||
resetProcessForm() {
|
||||
this.processForm = {
|
||||
processId: undefined,
|
||||
processName: undefined,
|
||||
processDesc: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('processForm')
|
||||
},
|
||||
/** 新增步骤 */
|
||||
handleAddStep() {
|
||||
this.resetStepForm()
|
||||
this.stepForm.processId = this.currentProcess.processId
|
||||
// 自动计算下一步骤顺序
|
||||
if (this.stepList.length > 0) {
|
||||
const maxOrder = Math.max(...this.stepList.map(s => s.stepOrder || 0))
|
||||
this.stepForm.stepOrder = maxOrder + 1
|
||||
} else {
|
||||
this.stepForm.stepOrder = 1
|
||||
}
|
||||
this.stepTitle = '新增工艺步骤'
|
||||
this.stepOpen = true
|
||||
},
|
||||
/** 修改步骤 */
|
||||
handleUpdateStep(row) {
|
||||
this.resetStepForm()
|
||||
this.stepForm = { ...row }
|
||||
this.stepTitle = '修改工艺步骤'
|
||||
this.stepOpen = true
|
||||
},
|
||||
/** 提交步骤表单 */
|
||||
submitStepForm() {
|
||||
this.$refs['stepForm'].validate(valid => {
|
||||
if (valid) {
|
||||
this.submitLoading = true
|
||||
if (this.stepForm.stepId != undefined) {
|
||||
updateProcessStep(this.stepForm).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.stepOpen = false
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
} else {
|
||||
addProcessStep(this.stepForm).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.stepOpen = false
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
}).finally(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除步骤 */
|
||||
handleDeleteStep(row) {
|
||||
const stepIds = row.stepId
|
||||
this.$modal.confirm('是否确认删除该工艺步骤?').then(() => {
|
||||
return delProcessStep(stepIds)
|
||||
}).then(() => {
|
||||
this.getStepList(this.currentProcess.processId)
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 取消步骤弹窗 */
|
||||
cancelStep() {
|
||||
this.stepOpen = false
|
||||
this.resetStepForm()
|
||||
},
|
||||
/** 重置步骤表单 */
|
||||
resetStepForm() {
|
||||
this.stepForm = {
|
||||
stepId: undefined,
|
||||
processId: undefined,
|
||||
stepOrder: undefined,
|
||||
stepName: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('stepForm')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './scss/aps-theme.scss';
|
||||
|
||||
.app-container {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
height: calc(100vh - 84px);
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
height: calc(100vh - 84px);
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.custom-list {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 12px;
|
||||
border-bottom: 2px solid #dddddd;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.list-item:hover {
|
||||
background-color: $aps-silver-1;
|
||||
}
|
||||
|
||||
.list-item-active {
|
||||
background-color: $aps-red-1;
|
||||
border-left: 3px solid $aps-red-2;
|
||||
}
|
||||
|
||||
.aps-total-count {
|
||||
color: $aps-red-2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.aps-btn-red {
|
||||
@include aps-btn-red;
|
||||
}
|
||||
|
||||
.aps-btn-silver {
|
||||
@include aps-btn-silver;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: $aps-bg;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: $aps-white;
|
||||
border: 1px solid $aps-border;
|
||||
border-radius: $aps-radius;
|
||||
box-shadow: $aps-shadow-sm;
|
||||
}
|
||||
|
||||
.detail-card-header {
|
||||
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
|
||||
color: white;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.detail-card-body {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.form-grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px 16px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-size: 11px;
|
||||
color: $aps-text-muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-field .field-value {
|
||||
font-size: 13px;
|
||||
color: $aps-text;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid $aps-silver-mid;
|
||||
}
|
||||
|
||||
.aps-product-table {
|
||||
width: 100%;
|
||||
|
||||
::v-deep th {
|
||||
background: $aps-silver-1 !important;
|
||||
color: $aps-silver-5 !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 工艺步骤时间线 ======
|
||||
.process-timeline-wrap {
|
||||
padding: 20px 10px;
|
||||
|
||||
::v-deep .el-timeline {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__wrapper {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__timestamp {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $aps-text-muted;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__tail {
|
||||
border-left-color: $aps-silver-3;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__node--primary {
|
||||
background-color: $aps-red-2;
|
||||
border-color: $aps-red-2;
|
||||
}
|
||||
|
||||
::v-deep .el-timeline-item__node--success {
|
||||
background-color: #67C23A;
|
||||
border-color: #67C23A;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: $aps-white;
|
||||
border: 1px solid $aps-border;
|
||||
border-radius: $aps-radius;
|
||||
padding: 12px 16px;
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $aps-red-2;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $aps-text;
|
||||
}
|
||||
|
||||
.step-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.timeline-content:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.step-remark {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: $aps-text-muted;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed $aps-silver-3;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
||||
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||||
<el-input v-model="queryParams.scheduleNo" placeholder="排产单号" clearable @keyup.enter.native="handleSearch"
|
||||
<el-input v-model="queryParams.scheduleNo" placeholder="产需单号" clearable @keyup.enter.native="handleSearch"
|
||||
style="width: 130px;" size="small" />
|
||||
<el-input v-model="queryParams.customerName" placeholder="客户名" clearable
|
||||
@keyup.enter.native="handleSearch" style="width: 130px;" size="small" />
|
||||
@@ -58,15 +58,19 @@
|
||||
<div class="detail-card-header">
|
||||
<span>产需单信息</span>
|
||||
<div style="display:flex; gap:6px;">
|
||||
<button class="header-btn" :disabled="currentReq.scheduleStatus !== 0 && currentReq.scheduleStatus !== 3" @click="handleEdit(currentReq)">编辑</button>
|
||||
<button v-if="currentReq.scheduleStatus === 0 || currentReq.scheduleStatus === 3" class="header-btn" style="background:rgba(255,255,255,0.4);border-color:rgba(255,255,255,0.7);" @click="handleDispatch">{{ currentReq.scheduleStatus === 3 ? '重新下发' : '下发' }}</button>
|
||||
<button class="header-btn"
|
||||
:disabled="currentReq.scheduleStatus !== 0 && currentReq.scheduleStatus !== 3"
|
||||
@click="handleEdit(currentReq)">编辑</button>
|
||||
<button v-if="currentReq.scheduleStatus === 0 || currentReq.scheduleStatus === 3" class="header-btn"
|
||||
style="background:rgba(255,255,255,0.4);border-color:rgba(255,255,255,0.7);"
|
||||
@click="handleDispatch">{{ currentReq.scheduleStatus === 3 ? '重新下发' : '下发' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<table class="req-info-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="req-td-label">排产单号</td>
|
||||
<td class="req-td-label">产需单号</td>
|
||||
<td class="req-td-value">{{ currentReq.scheduleNo }}</td>
|
||||
<td class="req-td-label">生产日期</td>
|
||||
<td class="req-td-value">{{ currentReq.prodDate }}</td>
|
||||
@@ -75,7 +79,7 @@
|
||||
<td class="req-td-label">排产状态</td>
|
||||
<td class="req-td-value"><span class="aps-status-tag"
|
||||
:class="'status-' + (currentReq.scheduleStatus || 0)">{{ statusMap[currentReq.scheduleStatus] ||
|
||||
'未知' }}</span></td>
|
||||
'未知' }}</span></td>
|
||||
<td class="req-td-label">业务员</td>
|
||||
<td class="req-td-value">{{ currentReq.businessUser }}</td>
|
||||
</tr>
|
||||
@@ -139,7 +143,8 @@
|
||||
</tr>
|
||||
<tr v-if="currentReq.returnReason">
|
||||
<td class="req-td-label" style="color:#e74c3c;">驳回原因</td>
|
||||
<td class="req-td-value" colspan="3" style="color:#e74c3c;background:#fdecea;">{{ currentReq.returnReason }}</td>
|
||||
<td class="req-td-value" colspan="3" style="color:#e74c3c;background:#fdecea;">{{
|
||||
currentReq.returnReason }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -190,8 +195,10 @@
|
||||
<el-table-column label="操作" width="130" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<span style="white-space:nowrap;">
|
||||
<el-button v-if="canEdit" type="text" size="small" style="color:#409EFF;" @click="handleDetailEdit(scope.row)">编辑</el-button>
|
||||
<el-button v-if="canEdit" type="text" size="small" style="color:#ff4d4f;" @click="handleDetailDelete(scope.row)">删除</el-button>
|
||||
<el-button v-if="canEdit" type="text" size="small" style="color:#409EFF;"
|
||||
@click="handleDetailEdit(scope.row)">编辑</el-button>
|
||||
<el-button v-if="canEdit" type="text" size="small" style="color:#ff4d4f;"
|
||||
@click="handleDetailDelete(scope.row)">删除</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -204,13 +211,15 @@
|
||||
</el-row>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="700px" append-to-body
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" append-to-body
|
||||
:close-on-click-modal="false">
|
||||
<el-form ref="reqForm" :model="reqForm" :rules="reqRules" label-width="110px" size="small">
|
||||
<!-- 第一行:必填项 -->
|
||||
<div style="display: flex; gap: 16px;">
|
||||
<div style="flex: 1; min-width: 0;">
|
||||
<el-form ref="reqForm" :model="reqForm" :rules="reqRules" label-width="80px" size="small">
|
||||
<!-- 第一行:必填项 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排产单号" prop="scheduleNo">
|
||||
<el-form-item label="产需单号" prop="scheduleNo">
|
||||
<el-input v-model="reqForm.scheduleNo" placeholder="自动生成或手动填写" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -282,19 +291,42 @@
|
||||
<el-form-item label="交货重量偏差" prop="weightDeviation">
|
||||
<el-input v-model="reqForm.weightDeviation" />
|
||||
</el-form-item>
|
||||
<el-form-item label="其他技术要求" prop="otherTechReq">
|
||||
<el-input v-model="reqForm.otherTechReq" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="付款情况说明" prop="paymentDesc">
|
||||
<el-input v-model="reqForm.paymentDesc" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="其他技术要求" prop="otherTechReq">
|
||||
<el-input v-model="reqForm.otherTechReq" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="reqForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 右侧:历史记录 -->
|
||||
<div style="width: 200px; flex-shrink: 0;" v-if="!reqForm.scheduleId && scheduleNoHistory.length > 0">
|
||||
<div style="font-size: 13px; font-weight: 600; color: #2c3e50; margin-bottom: 8px; padding-left: 8px; border-left: 3px solid #ff4d4f;">
|
||||
历史记录
|
||||
</div>
|
||||
<div style="border: 1px solid #e4e7ed; border-radius: 4px; max-height: 450px; overflow-y: auto;">
|
||||
<div v-for="item in scheduleNoHistory" :key="item.scheduleNo" class="history-item"
|
||||
@click="useHistoryRecord(item)"
|
||||
style="padding: 8px 10px; border-bottom: 1px solid #ecf0f1; cursor: pointer; font-size: 12px; transition: background 0.15s;">
|
||||
<div style="font-weight: 500; color: #2c3e50;">{{ item.scheduleNo }}</div>
|
||||
<div style="color: #7f8c8d; margin-top: 2px;">{{ item.customerName || '-' }}</div>
|
||||
<div style="color: #b0b0b0; font-size: 11px;">{{ item.prodDate || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="btnLoading" type="danger" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
@@ -376,7 +408,8 @@
|
||||
<el-input v-model="detailForm.material" placeholder="请输入材质" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排产吨数" prop="scheduleWeight">
|
||||
<el-input-number v-model="detailForm.scheduleWeight" :min="0" :precision="3" :controls="false" style="width:100%" />
|
||||
<el-input-number v-model="detailForm.scheduleWeight" :min="0" :precision="3" :controls="false"
|
||||
style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="detailForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
@@ -458,9 +491,10 @@ export default {
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增产需单',
|
||||
btnLoading: false,
|
||||
scheduleNoHistory: [],
|
||||
reqForm: this.getEmptyForm(),
|
||||
reqRules: {
|
||||
scheduleNo: [{ required: true, message: '排产单号不能为空', trigger: 'blur' }],
|
||||
scheduleNo: [{ required: true, message: '产需单号不能为空', trigger: 'blur' }],
|
||||
prodDate: [{ required: true, message: '生产日期不能为空', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
@@ -564,6 +598,17 @@ export default {
|
||||
|
||||
handleAdd() {
|
||||
this.resetForm()
|
||||
this.reqForm.scheduleNo = this.generateScheduleNo()
|
||||
this.reqForm.prodDate = this.getTodayStr()
|
||||
const lastForm = this.getLastFormData()
|
||||
if (lastForm) {
|
||||
Object.keys(lastForm).forEach(key => {
|
||||
if (key !== 'scheduleId' && key !== 'scheduleNo' && key !== 'prodDate' && lastForm[key] !== undefined && lastForm[key] !== '' && lastForm[key] !== null) {
|
||||
this.reqForm[key] = lastForm[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.scheduleNoHistory = this.getScheduleNoHistory()
|
||||
this.dialogTitle = '新增产需单'
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
@@ -586,9 +631,14 @@ export default {
|
||||
this.$refs.reqForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.btnLoading = true
|
||||
const action = this.reqForm.scheduleId ? updateRequirement : addRequirement
|
||||
const isNew = !this.reqForm.scheduleId
|
||||
const action = isNew ? addRequirement : updateRequirement
|
||||
action(this.reqForm).then(() => {
|
||||
this.$modal.msgSuccess(this.reqForm.scheduleId ? '修改成功' : '新增成功')
|
||||
this.$modal.msgSuccess(isNew ? '新增成功' : '修改成功')
|
||||
if (isNew) {
|
||||
this.saveHistoryRecord(this.reqForm)
|
||||
this.saveLastFormData(this.reqForm)
|
||||
}
|
||||
this.dialogVisible = false
|
||||
this.getList()
|
||||
}).catch(() => {
|
||||
@@ -615,6 +665,71 @@ export default {
|
||||
}).catch(() => { })
|
||||
},
|
||||
|
||||
getTodayStr() {
|
||||
return new Date().toISOString().split('T')[0]
|
||||
},
|
||||
|
||||
generateScheduleNo() {
|
||||
const now = new Date()
|
||||
const dateStr = now.getFullYear().toString() +
|
||||
(now.getMonth() + 1).toString().padStart(2, '0') +
|
||||
now.getDate().toString().padStart(2, '0')
|
||||
const prefix = 'PCXD' + dateStr
|
||||
const existingNums = this.reqList
|
||||
.filter(item => item.scheduleNo && item.scheduleNo.startsWith(prefix))
|
||||
.map(item => {
|
||||
const seq = parseInt(item.scheduleNo.substring(prefix.length), 10)
|
||||
return isNaN(seq) ? 0 : seq
|
||||
})
|
||||
const maxSeq = existingNums.length > 0 ? Math.max(...existingNums) : 0
|
||||
const newSeq = (maxSeq + 1).toString().padStart(3, '0')
|
||||
return prefix + newSeq
|
||||
},
|
||||
|
||||
getScheduleNoHistory() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('aps_requirement_history') || '[]')
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
|
||||
saveHistoryRecord(formData) {
|
||||
if (!formData.scheduleNo) return
|
||||
const history = this.getScheduleNoHistory()
|
||||
const idx = history.findIndex(item => item.scheduleNo === formData.scheduleNo)
|
||||
if (idx >= 0) history.splice(idx, 1)
|
||||
history.unshift({ ...formData })
|
||||
delete history[0].scheduleId
|
||||
if (history.length > 20) history.pop()
|
||||
localStorage.setItem('aps_requirement_history', JSON.stringify(history))
|
||||
},
|
||||
|
||||
useHistoryRecord(item) {
|
||||
Object.keys(item).forEach(key => {
|
||||
if (key !== 'scheduleId' && item[key] !== undefined) {
|
||||
this.reqForm[key] = item[key]
|
||||
}
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.$refs.reqForm && this.$refs.reqForm.clearValidate()
|
||||
})
|
||||
},
|
||||
|
||||
getLastFormData() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('aps_requirement_last_form') || 'null')
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
saveLastFormData(formData) {
|
||||
const data = { ...formData }
|
||||
delete data.scheduleId
|
||||
localStorage.setItem('aps_requirement_last_form', JSON.stringify(data))
|
||||
},
|
||||
|
||||
openBindDialog() {
|
||||
this.bindDialogVisible = true
|
||||
this.selectedBindOrders = []
|
||||
@@ -1223,4 +1338,8 @@ export default {
|
||||
.aps-bind-dialog ::v-deep .el-dialog__body {
|
||||
padding: 16px 20px 0 20px;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
background: #fdecea;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<el-button size="small" class="aps-btn-red" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-tabs v-model="activeTab" size="small" style="margin:0 0 0 16px;" @tab-click="handleQuery">
|
||||
<el-tab-pane label="待审核" name="pending" />
|
||||
<el-tab-pane label="已接收" name="accepted" />
|
||||
<el-tab-pane label="已排产" name="scheduled" />
|
||||
</el-tabs>
|
||||
<div v-if="summaryText" class="aps-sch-summary">
|
||||
@@ -161,6 +162,104 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已接收 Tab -->
|
||||
<div v-loading="acceptedLoading" v-show="activeTab === 'accepted'" class="detail-card aps-sch-card">
|
||||
<div class="detail-card-header">
|
||||
<span>已接收产需单</span>
|
||||
<span v-if="acceptedScheduleList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
|
||||
共 {{ acceptedScheduleList.length }} 个产需单
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-card-body" style="padding:0;">
|
||||
<div v-if="acceptedScheduleList.length > 0" class="aps-sch-list">
|
||||
<div v-for="sch in acceptedScheduleList" :key="sch.scheduleId" class="aps-sch-item">
|
||||
<div class="aps-sch-item-header">
|
||||
<div class="aps-sch-item-header-left">
|
||||
<span class="aps-sch-no">{{ sch.scheduleNo }}</span>
|
||||
<span class="aps-status-tag status-2">已接收</span>
|
||||
<span class="aps-sch-header-extra">
|
||||
{{ sch.customerName }} · {{ sch.businessUser }} · {{ sch.deliveryCycle }}天
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="req-info-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="req-td-label">排产单号</td><td class="req-td-value">{{ sch.scheduleNo }}</td>
|
||||
<td class="req-td-label">生产日期</td><td class="req-td-value">{{ sch.prodDate }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">业务员</td><td class="req-td-value">{{ sch.businessUser }}</td>
|
||||
<td class="req-td-label">联系电话</td><td class="req-td-value">{{ sch.businessPhone }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">订货单位</td><td class="req-td-value">{{ sch.customerName }}</td>
|
||||
<td class="req-td-label">交货期(天)</td><td class="req-td-value">{{ sch.deliveryCycle }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">品名</td><td class="req-td-value" colspan="3">{{ sch.productType }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">产品用途</td><td class="req-td-value" colspan="3">{{ sch.usePurpose }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">厚度公差</td><td class="req-td-value">{{ sch.thicknessTolerance }}</td>
|
||||
<td class="req-td-label">宽度公差</td><td class="req-td-value">{{ sch.widthTolerance }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">表面质量</td><td class="req-td-value">{{ sch.surfaceQuality }}</td>
|
||||
<td class="req-td-label">表面处理</td><td class="req-td-value">{{ sch.surfaceTreatment }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">内径尺寸</td><td class="req-td-value">{{ sch.innerDiameter }}</td>
|
||||
<td class="req-td-label">外径要求</td><td class="req-td-value">{{ sch.outerDiameter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">包装要求</td><td class="req-td-value">{{ sch.packReq }}</td>
|
||||
<td class="req-td-label">切边要求</td><td class="req-td-value">{{ sch.cutEdgeReq }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="req-td-label">单件重量</td><td class="req-td-value">{{ sch.singleCoilWeight }}</td>
|
||||
<td class="req-td-label">交货重量偏差</td><td class="req-td-value">{{ sch.weightDeviation }}</td>
|
||||
</tr>
|
||||
<tr v-if="sch.otherTechReq">
|
||||
<td class="req-td-label">其他技术要求</td><td class="req-td-value" colspan="3">{{ sch.otherTechReq }}</td>
|
||||
</tr>
|
||||
<tr v-if="sch.paymentDesc">
|
||||
<td class="req-td-label">付款情况说明</td><td class="req-td-value" colspan="3">{{ sch.paymentDesc }}</td>
|
||||
</tr>
|
||||
<tr v-if="sch.remark">
|
||||
<td class="req-td-label">备注</td><td class="req-td-value" colspan="3">{{ sch.remark }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="(sch.detailList || []).length > 0" class="aps-sch-item-details">
|
||||
<div class="aps-sch-detail-header">
|
||||
<span class="aps-sch-detail-col col-spec">规格</span>
|
||||
<span class="aps-sch-detail-col col-material">材质</span>
|
||||
<span class="aps-sch-detail-col col-weight">排产吨数</span>
|
||||
<span class="aps-sch-detail-col col-type">品名</span>
|
||||
<span class="aps-sch-detail-col col-remark">备注</span>
|
||||
</div>
|
||||
<div v-for="(d, di) in sch.detailList" :key="di" class="aps-sch-detail-row" @click="handleDetailClick(sch, d)">
|
||||
<span class="aps-sch-detail-col col-spec">{{ d.spec }}</span>
|
||||
<span class="aps-sch-detail-col col-material">{{ d.material }}</span>
|
||||
<span class="aps-sch-detail-col col-weight">{{ d.scheduleWeight }}</span>
|
||||
<span class="aps-sch-detail-col col-type">{{ sch.productType || d.productType || '' }}</span>
|
||||
<span class="aps-sch-detail-col col-remark">{{ d.remark }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="aps-sch-item-empty">暂无明细</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!acceptedLoading" style="padding:40px;text-align:center;color:#909399;">
|
||||
{{ hasQueried ? '该日期暂无已接收产需单' : '请选择日期查询' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已排产 Tab -->
|
||||
<div v-loading="schLoading" v-show="activeTab === 'scheduled'" class="detail-card aps-sch-card">
|
||||
<div class="detail-card-header">
|
||||
@@ -169,85 +268,73 @@
|
||||
<span v-if="scheduledItemList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
|
||||
共 {{ scheduledItemList.length }} 条
|
||||
</span>
|
||||
<span v-if="selectedItems.length >= 2" style="margin-left: auto;">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="warning"
|
||||
icon="el-icon-link"
|
||||
@click.stop="handleMergePrepare"
|
||||
>
|
||||
合并选中项 ({{ selectedItems.length }})
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-else style="margin-left: auto;">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="warning"
|
||||
icon="el-icon-link"
|
||||
disabled
|
||||
>
|
||||
合并选中项
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div element-loading-text="正在加载已排产数据..." class="detail-card-body" style="padding:0;">
|
||||
<el-table
|
||||
v-if="scheduledItemList.length > 0"
|
||||
ref="scheduledTable"
|
||||
:data="scheduledItemList"
|
||||
border
|
||||
size="small"
|
||||
class="aps-table"
|
||||
:row-class-name="getItemRowClassName"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="45" align="center" />
|
||||
<el-table-column label="排产单号" prop="scheduleNo" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="生产日期" prop="prodDate" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="工序类型" prop="actionId" width="100" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ getActionIdName(scope.row.actionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排产状态" prop="scheduleStatus" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span class="aps-status-tag" :class="'status-' + (scope.row.scheduleStatus || 1)">{{ statusMap[scope.row.scheduleStatus] || '未知' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" show-overflow-tooltip />
|
||||
<el-table-column label="品名项" prop="productItem" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="订货单位" prop="customerName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="业务员" prop="businessUser" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="联系电话" prop="businessPhone" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="交货期(天)" prop="deliveryCycle" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="产品用途" prop="usePurpose" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="厚度公差" prop="thicknessTolerance" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="宽度公差" prop="widthTolerance" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="表面质量" prop="surfaceQuality" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="表面处理" prop="surfaceTreatment" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="内径尺寸" prop="innerDiameter" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="外径要求" prop="outerDiameter" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="包装要求" prop="packReq" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="切边要求" prop="cutEdgeReq" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="单件重量" prop="singleCoilWeight" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="交货重量偏差" prop="weightDeviation" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="其他技术要求" prop="otherTechReq" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="付款情况说明" prop="paymentDesc" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="单行排产备注" prop="rowRemark" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<div style="display:flex; justify-content:center; gap:0;">
|
||||
<el-button type="text" size="small" style="color:#409EFF;padding:0 6px;" @click="handleEditScheduled(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="small" style="color:#ff4d4f;padding:0 6px;" @click="handleDeleteScheduled(scope.row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template v-if="scheduledItemList.length > 0">
|
||||
<!-- 步骤工序 Tab -->
|
||||
<div class="aps-step-tabs">
|
||||
<span
|
||||
v-for="tab in scheduledStepTabs"
|
||||
:key="tab"
|
||||
class="aps-step-tab"
|
||||
:class="{ 'aps-step-tab-active': scheduledStepTab === tab }"
|
||||
@click="scheduledStepTab = tab"
|
||||
>
|
||||
{{ tab }}
|
||||
<span class="aps-step-tab-count">{{ scheduledItemList.filter(i => normalizeStepName(i.stepName) === tab).length }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="scheduledTable"
|
||||
:data="currentStepItems"
|
||||
border
|
||||
size="small"
|
||||
class="aps-table"
|
||||
>
|
||||
<el-table-column label="排产单号" prop="scheduleNo" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="生产日期" prop="prodDate" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="工序步骤" prop="stepName" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="排产状态" prop="scheduleStatus" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span class="aps-status-tag" :class="'status-' + (scope.row.scheduleStatus || 1)">{{ statusMap[scope.row.scheduleStatus] || '未知' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" show-overflow-tooltip />
|
||||
<el-table-column label="品名项" prop="productItem" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="订货单位" prop="customerName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="业务员" prop="businessUser" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="联系电话" prop="businessPhone" width="110" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="交货期(天)" prop="deliveryCycle" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="产品用途" prop="usePurpose" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="厚度公差" prop="thicknessTolerance" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="宽度公差" prop="widthTolerance" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="表面质量" prop="surfaceQuality" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="表面处理" prop="surfaceTreatment" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="内径尺寸" prop="innerDiameter" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="外径要求" prop="outerDiameter" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="包装要求" prop="packReq" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="切边要求" prop="cutEdgeReq" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="单件重量" prop="singleCoilWeight" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="交货重量偏差" prop="weightDeviation" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column label="其他技术要求" prop="otherTechReq" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="付款情况说明" prop="paymentDesc" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="单行排产备注" prop="rowRemark" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<div style="display:flex; justify-content:center; gap:0;">
|
||||
<el-button type="text" size="small" style="color:#409EFF;padding:0 6px;" @click="handleEditScheduled(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="small" style="color:#ff4d4f;padding:0 6px;" @click="handleDeleteScheduled(scope.row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<div v-else-if="!schLoading" style="padding:40px;text-align:center;color:#909399;">
|
||||
{{ hasQueried ? '该日期暂无已排产数据' : '请选择日期查询' }}
|
||||
</div>
|
||||
@@ -493,149 +580,48 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 合并排产明细对话框 -->
|
||||
<!-- 接收产需单弹窗 - 配置工序 -->
|
||||
<el-dialog
|
||||
title="合并排产明细"
|
||||
:visible.sync="mergeDialogVisible"
|
||||
width="850px"
|
||||
title="接收产需单 - 配置工序"
|
||||
:visible.sync="receiveDialogVisible"
|
||||
width="900px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div style="margin-bottom:12px;font-size:13px;color:#e67e22;">
|
||||
选中 {{ mergeForm.itemCount }} 条明细,请选择一个作为合并模板,下方字段将自动填充,您可修改后确认合并。
|
||||
请为每条明细配置工序,所有明细配置完成后才能接收。
|
||||
</div>
|
||||
|
||||
<!-- 选择模板 -->
|
||||
<el-table :data="mergeSourceRows" border size="small" style="margin-bottom:12px;" @row-click="pickMergeTemplate">
|
||||
<el-table-column width="50" align="center">
|
||||
|
||||
<el-table :data="receiveDetailList" border size="small" class="aps-table" max-height="400">
|
||||
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" />
|
||||
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="配置工序" min-width="200">
|
||||
<template slot-scope="scope">
|
||||
<el-radio v-model="mergeTemplateIndex" :label="scope.$index" @click.native.stop />
|
||||
<ProcessSelect
|
||||
v-model="scope.row.processId"
|
||||
placeholder="请选择工序"
|
||||
@change="(val, data) => handleReceiveProcessChange(scope.row, val, data)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工序步骤" min-width="300">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="scope.row.stepList && scope.row.stepList.length > 0" class="receive-steps-flow">
|
||||
<template v-for="(step, index) in scope.row.stepList">
|
||||
<span :key="step.stepId" class="receive-step-tag">{{ step.stepName }}</span>
|
||||
<span v-if="index < scope.row.stepList.length - 1" :key="'arrow-' + index" class="receive-step-arrow">→</span>
|
||||
</template>
|
||||
</div>
|
||||
<span v-else style="color:#909399;font-size:12px;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排产单号" prop="scheduleNo" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="spec" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="材质" prop="material" width="80" show-overflow-tooltip />
|
||||
<el-table-column label="排产吨数" prop="scheduleWeight" width="90" align="right" />
|
||||
<el-table-column label="品名" prop="productType" min-width="80" show-overflow-tooltip />
|
||||
<el-table-column label="订货单位" prop="customerName" min-width="120" show-overflow-tooltip />
|
||||
</el-table>
|
||||
|
||||
<!-- 合并表单 -->
|
||||
<el-form ref="mergeFormRef" :model="mergeForm" label-width="110px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排产单号"><el-input v-model="mergeForm.scheduleNo" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工序类型">
|
||||
<el-select v-model="mergeForm.actionId" placeholder="请选择工序类型" clearable filterable style="width:100%">
|
||||
<el-option v-for="p in processOptions" :key="p.actionId" :label="p.name" :value="p.actionId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="订货单位"><el-input v-model="mergeForm.customerName" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规格"><el-input v-model="mergeForm.spec" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="材质"><el-input v-model="mergeForm.material" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排产吨数(累加)">
|
||||
<el-input-number v-model="mergeForm.scheduleWeight" :min="0" :precision="3" :controls="false" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="品名"><el-input v-model="mergeForm.productType" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="品名项"><el-input v-model="mergeForm.productItem" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="业务员"><el-input v-model="mergeForm.businessUser" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话"><el-input v-model="mergeForm.businessPhone" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="交货期(天)">
|
||||
<el-input-number v-model="mergeForm.deliveryCycle" :min="0" :controls="false" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品用途"><el-input v-model="mergeForm.usePurpose" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="厚度公差"><el-input v-model="mergeForm.thicknessTolerance" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="宽度公差"><el-input v-model="mergeForm.widthTolerance" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表面质量"><el-input v-model="mergeForm.surfaceQuality" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表面处理"><el-input v-model="mergeForm.surfaceTreatment" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="内径尺寸"><el-input v-model="mergeForm.innerDiameter" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="外径要求"><el-input v-model="mergeForm.outerDiameter" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="包装要求"><el-input v-model="mergeForm.packReq" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="切边要求"><el-input v-model="mergeForm.cutEdgeReq" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单件重量"><el-input v-model="mergeForm.singleCoilWeight" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="交货重量偏差"><el-input v-model="mergeForm.weightDeviation" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="其他技术要求"><el-input v-model="mergeForm.otherTechReq" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="付款情况说明"><el-input v-model="mergeForm.paymentDesc" /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="备注"><el-input v-model="mergeForm.remark" /></el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div style="margin-top:8px;font-size:12px;color:#909399;">
|
||||
合并后 schedule_detail_ids:{{ mergeForm.scheduleDetailIds }}
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="mergeBtnLoading" type="warning" @click="confirmMerge">确 定 合 并</el-button>
|
||||
<el-button @click="mergeDialogVisible = false">取 消</el-button>
|
||||
<el-button :loading="receiveBtnLoading" type="primary" :disabled="!canConfirmReceive" @click="confirmReceive">确 定 接 收</el-button>
|
||||
<el-button @click="receiveDialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -651,13 +637,14 @@ import {
|
||||
listScheduleItem,
|
||||
updateScheduleItem,
|
||||
delScheduleItem,
|
||||
receiveScheduleItem,
|
||||
mergeScheduleItem
|
||||
receiveScheduleItem
|
||||
} from '@/api/aps/schedule'
|
||||
import { PROCESSES } from '@/utils/meta'
|
||||
import ProcessSelect from '@/components/KLPService/ProcessSelect/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ApsSchedule',
|
||||
components: { ProcessSelect },
|
||||
data() {
|
||||
const today = new Date()
|
||||
const y = today.getFullYear()
|
||||
@@ -676,26 +663,13 @@ export default {
|
||||
statusMap: { 1: '待审核', 2: '已接收', 3: '已驳回' },
|
||||
processOptions: PROCESSES,
|
||||
|
||||
// 已接收
|
||||
acceptedScheduleList: [],
|
||||
acceptedLoading: false,
|
||||
|
||||
// 已排产
|
||||
scheduledItemList: [],
|
||||
selectedItems: [],
|
||||
sourceColorMap: {},
|
||||
|
||||
// 合并对话框
|
||||
mergeDialogVisible: false,
|
||||
mergeBtnLoading: false,
|
||||
mergeTemplateIndex: 0,
|
||||
mergeSourceRows: [],
|
||||
mergeForm: {
|
||||
itemCount: 0, scheduleNo: '', actionId: '', customerName: '', spec: '', material: '',
|
||||
scheduleWeight: 0, productType: '', productItem: '', businessUser: '',
|
||||
businessPhone: '', deliveryCycle: undefined, usePurpose: '',
|
||||
thicknessTolerance: '', widthTolerance: '', surfaceQuality: '',
|
||||
surfaceTreatment: '', innerDiameter: '', outerDiameter: '',
|
||||
packReq: '', cutEdgeReq: '', singleCoilWeight: '',
|
||||
weightDeviation: '', otherTechReq: '', paymentDesc: '',
|
||||
remark: '', scheduleDetailIds: ''
|
||||
},
|
||||
scheduledStepTab: '',
|
||||
|
||||
// 驳回
|
||||
rejectDialogVisible: false,
|
||||
@@ -714,14 +688,37 @@ export default {
|
||||
|
||||
// 下钻
|
||||
drillDialogVisible: false,
|
||||
drillOrder: null
|
||||
drillOrder: null,
|
||||
|
||||
// 接收产需单弹窗
|
||||
receiveDialogVisible: false,
|
||||
receiveBtnLoading: false,
|
||||
receiveScheduleId: null,
|
||||
receiveDetailList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
this.selectedItems = []
|
||||
computed: {
|
||||
canConfirmReceive() {
|
||||
if (!this.receiveDetailList || this.receiveDetailList.length === 0) return false
|
||||
return this.receiveDetailList.every(d => d.processId)
|
||||
},
|
||||
// 已排产按步骤分组:去掉括号及括号内文字
|
||||
scheduledStepTabs() {
|
||||
const names = this.scheduledItemList
|
||||
.map(item => this.normalizeStepName(item.stepName))
|
||||
.filter(Boolean)
|
||||
return [...new Set(names)]
|
||||
},
|
||||
// 当前选中的步骤tab对应的数据
|
||||
currentStepItems() {
|
||||
if (!this.scheduledStepTab) return this.scheduledItemList
|
||||
const tab = this.scheduledStepTab
|
||||
return this.scheduledItemList.filter(
|
||||
item => this.normalizeStepName(item.stepName) === tab
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
this.handleQuery()
|
||||
},
|
||||
@@ -775,6 +772,8 @@ export default {
|
||||
this.hasQueried = true
|
||||
if (this.activeTab === 'pending') {
|
||||
this.queryPending()
|
||||
} else if (this.activeTab === 'accepted') {
|
||||
this.queryAccepted()
|
||||
} else {
|
||||
this.queryScheduled()
|
||||
}
|
||||
@@ -808,25 +807,82 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
// ====== 已接收 ======
|
||||
queryAccepted() {
|
||||
this.acceptedLoading = true
|
||||
this.acceptedScheduleList = []
|
||||
|
||||
listRequirement({
|
||||
prodDate: this.queryDate,
|
||||
scheduleStatus: 2,
|
||||
pageNum: 1,
|
||||
pageSize: 999
|
||||
}).then(res => {
|
||||
this.acceptedScheduleList = res.rows || []
|
||||
const totalCount = this.acceptedScheduleList.reduce((sum, sch) => sum + (sch.detailList || []).length, 0)
|
||||
this.summaryText = `共 ${this.acceptedScheduleList.length} 个已接收产需单,${totalCount} 条明细`
|
||||
}).catch(() => {
|
||||
this.acceptedScheduleList = []
|
||||
this.summaryText = ''
|
||||
}).finally(() => {
|
||||
this.acceptedLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleAccept(sch) {
|
||||
const details = sch.detailList || []
|
||||
if (details.length === 0) {
|
||||
this.$message.warning('该产需单无可排产的明细')
|
||||
return
|
||||
}
|
||||
this.$confirm(
|
||||
`确认接收产需单「${sch.scheduleNo}」的全部排产明细吗?共 ${details.length} 条明细`,
|
||||
'提示',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
).then(() => {
|
||||
this.$message.info('正在处理接收...')
|
||||
receiveScheduleItem(sch.scheduleId).then(() => {
|
||||
this.$modal.msgSuccess('接收成功,排产明细已写入')
|
||||
this.queryPending()
|
||||
}).catch(() => {
|
||||
this.$modal.msgError('接收失败')
|
||||
})
|
||||
}).catch(() => {})
|
||||
// 初始化接收明细列表,为每条明细添加 processId 和 stepList 字段
|
||||
this.receiveScheduleId = sch.scheduleId
|
||||
this.receiveDetailList = details.map(d => ({
|
||||
...d,
|
||||
processId: undefined,
|
||||
stepList: []
|
||||
}))
|
||||
this.receiveDialogVisible = true
|
||||
},
|
||||
|
||||
handleReceiveProcessChange(row, processId, processData) {
|
||||
if (processData && processData.stepList) {
|
||||
row.stepList = processData.stepList
|
||||
} else {
|
||||
row.stepList = []
|
||||
}
|
||||
},
|
||||
|
||||
confirmReceive() {
|
||||
// 验证所有明细都配置了工序
|
||||
const unconfigured = this.receiveDetailList.filter(d => !d.processId)
|
||||
if (unconfigured.length > 0) {
|
||||
this.$message.warning(`还有 ${unconfigured.length} 条明细未配置工序,请为所有明细配置工序`)
|
||||
return
|
||||
}
|
||||
// 构建接收数据,传递每条明细的 processId 和 stepList
|
||||
const receiveData = {
|
||||
scheduleId: this.receiveScheduleId,
|
||||
detailProcessList: this.receiveDetailList.map(d => ({
|
||||
scheduleDetailId: d.scheduleDetailId || d.scheduleDetailIds,
|
||||
processId: d.processId,
|
||||
stepList: (d.stepList || []).map(s => ({
|
||||
stepId: s.stepId,
|
||||
stepOrder: s.stepOrder,
|
||||
stepName: s.stepName
|
||||
}))
|
||||
}))
|
||||
}
|
||||
this.receiveBtnLoading = true
|
||||
receiveScheduleItem(receiveData).then(() => {
|
||||
this.$modal.msgSuccess('接收成功,排产明细已写入')
|
||||
this.receiveDialogVisible = false
|
||||
this.queryPending()
|
||||
}).catch(() => {
|
||||
this.$modal.msgError('接收失败')
|
||||
}).finally(() => {
|
||||
this.receiveBtnLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleReject(sch) {
|
||||
@@ -864,16 +920,14 @@ export default {
|
||||
queryScheduled() {
|
||||
this.schLoading = true
|
||||
this.scheduledItemList = []
|
||||
this.sourceColorMap = {}
|
||||
this.selectedItems = []
|
||||
this.scheduledStepTab = ''
|
||||
|
||||
listScheduleItem({ prodDate: this.queryDate, pageNum: 1, pageSize: 999 }).then(res => {
|
||||
this.scheduledItemList = (res.rows || []).sort((a, b) => (a.scheduleNo || '').localeCompare(b.scheduleNo || ''))
|
||||
try {
|
||||
this.sourceColorMap = this.buildGroupColorMap(this.scheduledItemList)
|
||||
} catch (e) {
|
||||
console.error('buildGroupColorMap error:', e)
|
||||
this.sourceColorMap = {}
|
||||
// 默认选中第一个步骤tab
|
||||
const tabs = this.scheduledStepTabs
|
||||
if (tabs.length > 0) {
|
||||
this.scheduledStepTab = tabs[0]
|
||||
}
|
||||
const totalWeight = this.scheduledItemList.reduce((sum, d) => {
|
||||
const w = parseFloat(d.scheduleWeight)
|
||||
@@ -934,98 +988,6 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ====== 合并 ======
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedItems = rows
|
||||
},
|
||||
|
||||
getItemRowClassName({ row }) {
|
||||
const key = row.scheduleNo
|
||||
if (!key) return ''
|
||||
const colorIndex = this.sourceColorMap[key]
|
||||
return colorIndex !== undefined ? `merge-source-${colorIndex}` : ''
|
||||
},
|
||||
|
||||
buildGroupColorMap(list) {
|
||||
const map = {}
|
||||
let colorIdx = 0
|
||||
list.forEach(item => {
|
||||
const key = item.scheduleNo
|
||||
if (!key) return
|
||||
if (!(key in map)) {
|
||||
map[key] = colorIdx % 5
|
||||
colorIdx++
|
||||
}
|
||||
})
|
||||
return map
|
||||
},
|
||||
|
||||
handleMergePrepare() {
|
||||
if (this.selectedItems.length < 2) {
|
||||
this.$message.warning('请至少选择2条排产明细进行合并')
|
||||
return
|
||||
}
|
||||
this.mergeSourceRows = [...this.selectedItems]
|
||||
this.mergeTemplateIndex = 0
|
||||
this.pickMergeTemplate(this.mergeSourceRows[0])
|
||||
this.mergeDialogVisible = true
|
||||
},
|
||||
|
||||
pickMergeTemplate(row) {
|
||||
const idx = this.mergeSourceRows.indexOf(row)
|
||||
if (idx >= 0) this.mergeTemplateIndex = idx
|
||||
this.mergeForm = {
|
||||
itemCount: this.mergeSourceRows.length,
|
||||
scheduleNo: row.scheduleNo || '',
|
||||
actionId: row.actionId != null ? Number(row.actionId) : '',
|
||||
customerName: row.customerName || '',
|
||||
spec: row.spec || '',
|
||||
material: row.material || '',
|
||||
scheduleWeight: this.mergeSourceRows.reduce((sum, r) => sum + (parseFloat(r.scheduleWeight) || 0), 0),
|
||||
productType: row.productType || '',
|
||||
productItem: row.productItem || '',
|
||||
businessUser: row.businessUser || '',
|
||||
businessPhone: row.businessPhone || '',
|
||||
deliveryCycle: row.deliveryCycle,
|
||||
usePurpose: row.usePurpose || '',
|
||||
thicknessTolerance: row.thicknessTolerance || '',
|
||||
widthTolerance: row.widthTolerance || '',
|
||||
surfaceQuality: row.surfaceQuality || '',
|
||||
surfaceTreatment: row.surfaceTreatment || '',
|
||||
innerDiameter: row.innerDiameter || '',
|
||||
outerDiameter: row.outerDiameter || '',
|
||||
packReq: row.packReq || '',
|
||||
cutEdgeReq: row.cutEdgeReq || '',
|
||||
singleCoilWeight: row.singleCoilWeight || '',
|
||||
weightDeviation: row.weightDeviation || '',
|
||||
otherTechReq: row.otherTechReq || '',
|
||||
paymentDesc: row.paymentDesc || '',
|
||||
remark: row.remark || '',
|
||||
scheduleDetailIds: this.mergeSourceRows.map(r => r.scheduleDetailIds || '').filter(Boolean).join(',')
|
||||
}
|
||||
},
|
||||
|
||||
confirmMerge() {
|
||||
this.mergeBtnLoading = true
|
||||
const ids = this.mergeSourceRows.map(r => r.scheduleId)
|
||||
const mergedBo = {
|
||||
...this.mergeForm,
|
||||
prodDate: this.queryDate,
|
||||
scheduleStatus: 2
|
||||
}
|
||||
delete mergedBo.itemCount
|
||||
mergeScheduleItem({ ids, mergedBo }).then(() => {
|
||||
this.$modal.msgSuccess(`合并成功:${ids.length} 条明细合并为 1 条`)
|
||||
this.mergeDialogVisible = false
|
||||
this.selectedItems = []
|
||||
this.queryScheduled()
|
||||
}).catch(() => {
|
||||
this.$modal.msgError('合并失败')
|
||||
}).finally(() => {
|
||||
this.mergeBtnLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
// ====== 下钻 & 辅助方法 ======
|
||||
scheduleTotalWeight(sch) {
|
||||
const details = sch.detailList || []
|
||||
@@ -1038,6 +1000,12 @@ export default {
|
||||
return p ? p.name : (actionId || '')
|
||||
},
|
||||
|
||||
// 去掉步骤名称中的括号及括号内文字:镀锌(毛化)=> 镀锌
|
||||
normalizeStepName(name) {
|
||||
if (!name) return ''
|
||||
return name.replace(/[((][^))]*[))]/g, '').trim()
|
||||
},
|
||||
|
||||
handleDetailClick(sch, detail) {
|
||||
// 点击明细行查看来源订单
|
||||
if (!sch || !sch.orderList || sch.orderList.length === 0) {
|
||||
@@ -1350,20 +1318,85 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// ====== 合并候选行颜色高亮(同一源排产单 → 同色) ======
|
||||
::v-deep .el-table__body tr.merge-source-0 > td {
|
||||
background-color: #f0f5ff !important;
|
||||
// ====== 接收弹窗步骤展示 ======
|
||||
.receive-steps-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
::v-deep .el-table__body tr.merge-source-1 > td {
|
||||
background-color: #f6ffed !important;
|
||||
|
||||
.receive-step-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: $aps-silver-1;
|
||||
border: 1px solid $aps-silver-mid;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
color: $aps-text;
|
||||
}
|
||||
::v-deep .el-table__body tr.merge-source-2 > td {
|
||||
background-color: #fff7e6 !important;
|
||||
|
||||
.receive-step-arrow {
|
||||
color: $aps-red-2;
|
||||
font-size: 14px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
::v-deep .el-table__body tr.merge-source-3 > td {
|
||||
background-color: #f9f0ff !important;
|
||||
|
||||
// ====== 已排产步骤工序 Tab ======
|
||||
.aps-step-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 12px;
|
||||
gap: 6px;
|
||||
background: $aps-silver-1;
|
||||
border-bottom: 1px solid $aps-border;
|
||||
}
|
||||
::v-deep .el-table__body tr.merge-source-4 > td {
|
||||
background-color: #fff0f6 !important;
|
||||
|
||||
.aps-step-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: $aps-text-muted;
|
||||
background: $aps-white;
|
||||
border: 1px solid $aps-silver-mid;
|
||||
border-radius: $aps-radius;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: $aps-red-2;
|
||||
border-color: $aps-red-2;
|
||||
}
|
||||
}
|
||||
|
||||
.aps-step-tab-active {
|
||||
color: #fff;
|
||||
background: $aps-red-2;
|
||||
border-color: $aps-red-2;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.aps-step-tab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 16px;
|
||||
padding: 0 5px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
|
||||
.aps-step-tab-active & {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,7 +81,7 @@ const NODE_EVENT_CONFIG = {
|
||||
{ id: 'H', label: '加工完工质检', handler: 'handleOpen', params: { componentPath: 'mes/qc/qualityReview/index' } },
|
||||
{ id: 'I', label: '登记质量缺陷', handler: 'handleOpen', params: { componentPath: 'wms/coil/abnormal/extend' } },
|
||||
{ id: 'J', label: '质量等级判定', handler: 'handleOpen', params: { componentPath: 'wms/coil/abnormal/extend' } },
|
||||
{ id: 'K', label: '单卷档案', handler: 'handleOpen', params: { componentPath: 'wms/coil/info/index' } },
|
||||
// { id: 'K', label: '单卷档案', handler: 'handleOpen', params: { componentPath: 'wms/coil/info/index' } },
|
||||
{ id: 'L', label: '编排发货计划', handler: 'handleOpen', params: { componentPath: 'wms/delivery/waybill/index' } },
|
||||
{ id: 'M', label: '生成发货单', handler: 'handleOpen', params: { componentPath: 'wms/delivery/bills/index' } },
|
||||
{ id: 'N', label: '发货质量校验', handler: 'handleOpen', params: { componentPath: 'mes/qc/qualityReview/index' } },
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div v-if="enabled" class="section-container">
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Complaint Content · 投诉情况与客户诉求</div>
|
||||
<div class="detail-section-text">{{ data.complaintContent || '-' }}</div>
|
||||
<div class="complaint-main">
|
||||
<div class="complaint-label">Complaint Content · 投诉情况与客户诉求</div>
|
||||
<div class="complaint-body">{{ data.complaintContent || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="customerInfo">
|
||||
@@ -35,29 +35,29 @@
|
||||
<div class="detail-section-text">-</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Downstream User · 下游使用用户</div>
|
||||
<div class="detail-section-text">{{ data.downstreamUserName || '-' }}</div>
|
||||
</div>
|
||||
<div class="divider-label">Other Info · 其他信息</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Phone · 电话</div>
|
||||
<div class="detail-section-text">{{ data.phone || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Project Name · 工程名称</div>
|
||||
<div class="detail-section-text">{{ data.projectName || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Project Location · 使用地点</div>
|
||||
<div class="detail-section-text">{{ data.projectLocation || '-' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-label">Product Usage · 产品用途</div>
|
||||
<div class="detail-section-text">{{ data.productUsage || '-' }}</div>
|
||||
<div class="secondary-grid">
|
||||
<div class="secondary-item">
|
||||
<span class="secondary-key">下游使用用户</span>
|
||||
<span class="secondary-val">{{ data.downstreamUserName || '-' }}</span>
|
||||
</div>
|
||||
<div class="secondary-item">
|
||||
<span class="secondary-key">电话</span>
|
||||
<span class="secondary-val">{{ data.phone || '-' }}</span>
|
||||
</div>
|
||||
<div class="secondary-item">
|
||||
<span class="secondary-key">工程名称</span>
|
||||
<span class="secondary-val">{{ data.projectName || '-' }}</span>
|
||||
</div>
|
||||
<div class="secondary-item">
|
||||
<span class="secondary-key">使用地点</span>
|
||||
<span class="secondary-val">{{ data.projectLocation || '-' }}</span>
|
||||
</div>
|
||||
<div class="secondary-item">
|
||||
<span class="secondary-key">产品用途</span>
|
||||
<span class="secondary-val">{{ data.productUsage || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" v-if="data.file">
|
||||
@@ -109,6 +109,37 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section-container {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ===== 投诉情况主体 — 突出显示 ===== */
|
||||
.complaint-main {
|
||||
margin-bottom: 18px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-left: 4px solid #1e293b;
|
||||
}
|
||||
|
||||
.complaint-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 0.4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.complaint-body {
|
||||
font-size: 15px;
|
||||
color: #1e293b;
|
||||
line-height: 1.8;
|
||||
word-break: break-all;
|
||||
min-height: 60px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
@@ -163,4 +194,45 @@ export default {
|
||||
font-size: 13px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
/* ===== 分隔标签 ===== */
|
||||
.divider-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
letter-spacing: 0.4px;
|
||||
text-transform: uppercase;
|
||||
margin: 16px 0 10px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
/* ===== 次要信息网格 ===== */
|
||||
.secondary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 16px;
|
||||
padding: 10px 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.secondary-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.secondary-key {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.secondary-val {
|
||||
font-size: 12px;
|
||||
color: #475569;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<el-tag v-else-if="item.flowStatus === 4" type="success" size="mini">全部办结</el-tag>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<el-button v-if="item.flowStatus === 4" size="mini" type="text" icon="el-icon-download"
|
||||
<el-button size="mini" type="text" icon="el-icon-download"
|
||||
@click.stop="handleExportPdf(item)" title="导出PDF"></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(item)"></el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(item)"></el-button>
|
||||
@@ -70,7 +70,7 @@
|
||||
<HeaderControlSection :complaintNo="currentRow.complaintNo" :flowStatus="currentRow.flowStatus"
|
||||
:meta="currentRow">
|
||||
<template #actions>
|
||||
<el-button v-if="currentRow.flowStatus === 4" size="mini" type="text" icon="el-icon-download"
|
||||
<el-button size="mini" type="text" icon="el-icon-download"
|
||||
@click="handleExportPdf(currentRow)" title="导出PDF">导出</el-button>
|
||||
<el-button v-if="currentRow.flowStatus === 1" size="mini" type="primary" plain
|
||||
icon="el-icon-s-promotion" @click="handleOpinionDispatch">意见下发</el-button>
|
||||
|
||||
@@ -149,7 +149,7 @@ export default {
|
||||
customExportVisible: false,
|
||||
columnGroups: {
|
||||
'基本信息': ['itemTypeDesc', 'warehouseName', 'actualWarehouseName', 'dataTypeText'],
|
||||
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo'],
|
||||
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo', 'coilId'],
|
||||
'时间': ['createTime', 'exportTime', 'exportBy'],
|
||||
'物理属性': ['netWeight', 'length', 'specification', 'actualThickness', 'actualLength', 'actualWidth', 'theoreticalThickness', 'theoreticalLength'],
|
||||
'材质属性': ['material', 'manufacturer', 'surfaceTreatmentDesc', 'zincLayer', 'packingStatus', 'temperGrade', 'coatingType', 'chromePlateCoilNo', 'rawMaterialThickness'],
|
||||
|
||||
@@ -560,7 +560,7 @@ export default {
|
||||
customExportType: 'output',
|
||||
columnGroups: {
|
||||
'基本信息': ['itemTypeDesc', 'warehouseName', 'actualWarehouseName', 'dataTypeText'],
|
||||
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo'],
|
||||
'钢卷号': ['enterCoilNo', 'supplierCoilNo', 'currentCoilNo', 'coilId'],
|
||||
'时间': ['createTime', 'exportTime', 'exportBy'],
|
||||
'物理属性': ['netWeight', 'length', 'specification', 'actualThickness', 'actualLength', 'actualWidth', 'theoreticalThickness', 'theoreticalLength', 'scheduleThickness'],
|
||||
'材质属性': ['material', 'manufacturer', 'surfaceTreatmentDesc', 'zincLayer', 'packingStatus', 'temperGrade', 'coatingType', 'chromePlateCoilNo', 'rawMaterialThickness'],
|
||||
|
||||
Reference in New Issue
Block a user