This commit is contained in:
jhd
2026-07-07 09:42:36 +08:00
118 changed files with 6616 additions and 178 deletions

View File

@@ -64,3 +64,13 @@ export function listTodayOrder(query) {
params: query
})
}
// 导出正式订单主列表(业务精简字段)
export function exportOrder(query) {
return request({
url: '/crm/order/exportDetail',
method: 'post',
data: query,
responseType: 'blob'
})
}

View File

@@ -42,3 +42,13 @@ export function delOrderItem(itemId) {
method: 'delete'
})
}
// 导出正式订单明细列表(业务精简字段)
export function exportOrderItem(query) {
return request({
url: '/crm/orderItem/exportDetail',
method: 'post',
data: query,
responseType: 'blob'
})
}

View File

@@ -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>

View File

@@ -66,6 +66,7 @@
/>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">筛选</el-button>
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
<span v-if="total > 0" class="result-count"> {{ total }} 条记录</span>
</div>
@@ -130,6 +131,14 @@
</div>
</template>
</el-table-column>
<!-- 业务员 -->
<el-table-column label="业务员" width="110">
<template slot-scope="scope">
<el-select v-model="scope.row.salesman" size="small" class="editable-cell" style="width: 100%" @change="saveRow(scope.row)">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<!-- 签订日期 -->
<el-table-column label="签订日期" prop="signTime" width="100">
<template slot-scope="scope">
@@ -421,8 +430,10 @@
</template>
<script>
import { listOrder, updateOrder } from '@/api/crm/order'
import { listOrder, updateOrder, exportOrder } from '@/api/crm/order'
import { listCustomer } from '@/api/crm/customer'
import { blobValidate } from "@/utils/klp";
import { saveAs } from 'file-saver'
const STATUS_MAP = {
'0': { label: '草稿', type: 'info' },
@@ -433,6 +444,7 @@ const STATUS_MAP = {
export default {
name: 'ContractDetailEdit',
dicts: ['wip_pack_saleman'],
data() {
return {
loading: false,
@@ -628,7 +640,38 @@ export default {
}
this.customerDialogVisible = false
this.saveRow(row)
}
},
// 导出合同明细(按当前筛选条件)
handleExport() {
const params = {}
if (this.contractQuery.contractCode) params.contractCode = this.contractQuery.contractCode
if (this.contractQuery.supplier) params.supplier = this.contractQuery.supplier
if (this.contractQuery.customer) params.customer = this.contractQuery.customer
if (this.contractQuery.status !== undefined && this.contractQuery.status !== null) params.status = this.contractQuery.status
if (this.contractQuery.signDateStart) params.signDateStart = this.contractQuery.signDateStart
if (this.contractQuery.signDateEnd) params.signDateEnd = this.contractQuery.signDateEnd
if (this.contractQuery.deliveryDateStart) params.deliveryDateStart = this.contractQuery.deliveryDateStart
if (this.contractQuery.deliveryDateEnd) params.deliveryDateEnd = this.contractQuery.deliveryDateEnd
if (this.contractQuery.signLocation) params.signLocation = this.contractQuery.signLocation
exportOrder(params).then((res) => {
this.handleExportBlob(res, '合同编辑详情.xlsx')
}).catch(() => {})
},
// 处理blob文件导出
async handleExportBlob(res, fileName) {
const isBlob = blobValidate(res);
if (isBlob) {
saveAs(res, fileName);
this.$message.success('导出成功!');
} else {
const resText = await res.text();
const rspObj = JSON.parse(resText);
const errMsg = rspObj.msg || '导出失败';
this.$message.error(errMsg);
}
},
}
}
</script>
@@ -727,6 +770,22 @@ export default {
white-space: normal;
}
/* 业务员下拉框样式 */
.select-salesman ::v-deep .el-input__inner {
background-color: #f3f0ff;
border-color: transparent;
padding: 0 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.select-salesman ::v-deep .el-input__inner:focus {
border-color: #5F7BA0;
background-color: #fff;
overflow: visible;
white-space: normal;
}
/* 日期选择器样式 */
.editable-cell-date ::v-deep .el-input__inner {
background-color: #e6f7ff;

View File

@@ -21,6 +21,7 @@
<div class="toolbar">
<el-button type="danger" size="small" icon="el-icon-delete" :disabled="!selection.length" @click="handleBatchDelete">批量删除</el-button>
<el-button type="primary" size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
<span class="toolbar-tip">合同列为只读明细列失焦自动保存需有修改权限</span>
</div>
@@ -50,6 +51,9 @@
<el-table-column label="供方" min-width="120" show-overflow-tooltip>
<template slot-scope="{ row }">{{ (row.orderInfo && row.orderInfo.supplier) || '-' }}</template>
</el-table-column>
<el-table-column label="业务员" min-width="80" show-overflow-tooltip>
<template slot-scope="{ row }">{{ (row.orderInfo && row.orderInfo.salesman) || row.salesman || '-' }}</template>
</el-table-column>
<el-table-column label="签订时间" width="110">
<template slot-scope="{ row }">{{ row.orderInfo && row.orderInfo.signTime ? parseTime(row.orderInfo.signTime, '{y}-{m}-{d}') : '-' }}</template>
</el-table-column>
@@ -201,7 +205,9 @@
</template>
<script>
import { listOrderItem, updateOrderItem, delOrderItem } from '@/api/crm/orderItem'
import { listOrderItem, updateOrderItem, delOrderItem, exportOrderItem } from '@/api/crm/orderItem'
import { blobValidate } from "@/utils/klp";
import { saveAs } from 'file-saver'
const ITEM_PAYLOAD_KEYS = [
'itemId',
@@ -354,7 +360,36 @@ export default {
console.error('批量删除失败:', err)
}
})
}
},
// 导出订单明细(按当前筛选条件)
handleExport() {
const params = {}
if (this.queryParams.contractCode) params.contractCode = this.queryParams.contractCode
if (this.queryParams.customer) params.customer = this.queryParams.customer
if (this.queryParams.material) params.material = this.queryParams.material
if (this.queryParams.orderId) {
const n = Number(this.queryParams.orderId)
params.orderId = Number.isNaN(n) ? this.queryParams.orderId : n
}
exportOrderItem(params).then((res) => {
this.handleExportBlob(res, '正式订单明细.xlsx')
}).catch(() => {})
},
// 处理blob文件导出
async handleExportBlob(res, fileName) {
const isBlob = blobValidate(res);
if (isBlob) {
saveAs(res, fileName);
this.$modal.msgSuccess('导出成功!');
} else {
const resText = await res.text();
const rspObj = JSON.parse(resText);
const errMsg = rspObj.msg || '导出失败';
this.$modal.msgError(errMsg);
}
},
}
}
</script>

View File

@@ -52,6 +52,7 @@
/>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">筛选</el-button>
<el-button size="small" icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
<span class="sort-hint">已按合同签订日期默认当月结果按交货日期倒序排列</span>
</div>
@@ -96,6 +97,11 @@
<span class="contract-info" :title="row.customer">{{ row.customer }}</span>
</template>
</el-table-column>
<el-table-column label="业务员" width="80">
<template slot-scope="{ row }">
<span class="contract-info">{{ row._order && row._order.salesman ? row._order.salesman : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="签订日期" prop="signTime" width="100">
<template slot-scope="{ row }">
<span class="contract-info contract-date">{{ formatDate(row.signTime) }}</span>
@@ -270,8 +276,10 @@
</template>
<script>
import { listOrder, updateOrder } from '@/api/crm/order'
import { listOrder, updateOrder, exportOrder } from '@/api/crm/order'
import { parseProductContent, stringifyProductContent, calculateProductFields, recalculateTotals } from '@/utils/productContent'
import { blobValidate } from "@/utils/klp";
import { saveAs } from 'file-saver'
export default {
name: 'OrderItemList',
@@ -555,6 +563,34 @@ export default {
this.getList()
},
// 导出订单明细(按当前筛选条件)
handleExport() {
const params = {}
if (this.queryParams.contractCode) params.contractCode = this.queryParams.contractCode
if (this.queryParams.customer) params.customer = this.queryParams.customer
if (this.queryParams.signDateStart) params.signDateStart = this.queryParams.signDateStart
if (this.queryParams.signDateEnd) params.signDateEnd = this.queryParams.signDateEnd
if (this.queryParams.deliveryDateStart) params.deliveryDateStart = this.queryParams.deliveryDateStart
if (this.queryParams.deliveryDateEnd) params.deliveryDateEnd = this.queryParams.deliveryDateEnd
exportOrder(params).then((res) => {
this.handleExportBlob(res, '订单明细列表.xlsx')
}).catch(() => {})
},
// 处理blob文件导出
async handleExportBlob(res, fileName) {
const isBlob = blobValidate(res);
if (isBlob) {
saveAs(res, fileName);
this.$message.success('导出成功!');
} else {
const resText = await res.text();
const rspObj = JSON.parse(resText);
const errMsg = rspObj.msg || '导出失败';
this.$message.error(errMsg);
}
},
groupRowClassName({ row, rowIndex }) {
if (row.productIndex === 0) {
return 'group-row-a'

View File

@@ -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 {

View File

@@ -0,0 +1,40 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DR_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DrInventory',
components: {
BasePage,
},
data() {
return {
querys: {
dataType: 1,
status: 0,
},
warehouseOptions: DR_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { DR_PROCESSING_TABS } from '../config'
export default {
name: 'DrProcessing',
components: {
DoPage,
},
data() {
return {
activeTab: 'process',
tabs: DR_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DR_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DrQuality',
components: {
BasePage,
},
data() {
return {
activeTab: 'abnormal',
abnormalQuerys: {
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
dataType: 1,
status: 0,
qualityStatus: 'O',
},
warehouseOptions: DR_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/shuang/comprehensive.vue'
import DayReport from '@/views/wms/report/shuang/day.vue'
import MonthReport from '@/views/wms/report/shuang/month.vue'
import YearReport from '@/views/wms/report/shuang/year.vue'
import LossReport from '@/views/wms/report/shuang/loss.vue'
import OutReport from '@/views/wms/report/shuang/out.vue'
import TeamReport from '@/views/wms/report/shuang/team.vue'
export default {
name: 'DrReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DR_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DrShipping',
components: {
BasePage,
},
data() {
return {
activeTab: 'waybill',
waybillQuerys: {
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
dataType: 1,
status: 1,
},
warehouseOptions: DR_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,28 @@
export const DR_WAREHOUSE_OPTIONS = [
{ value: '1992873386047643650', label: '双机架原料库' },
{ value: '1992873437713080322', label: '双机架成品库' },
{ value: '1988151076996706306', label: '镀铬原料库' },
]
export const DR_PROCESSING_TABS = [
{
title: '双机架工序',
name: 'process',
label: '双机架工序',
tabs: DR_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
{
title: '合卷',
name: 'merge',
label: '双机架合卷',
useSpecialSplit: false,
},
{
title: '双机架修复',
name: 'repair',
label: '双机架修复工序',
tabs: DR_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
]

View File

@@ -2,6 +2,26 @@
<div class="dr-container">
<div class="dr-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
<el-menu-item index="plan">
<i class="el-icon-s-order"></i>
<span slot="title">计划</span>
@@ -23,19 +43,42 @@
</template>
<script>
import Plan from './components/Plan.vue'
import Plan from './components/Plan.vue'
import ProcessSpec from './components/ProcessSpec.vue'
import Actual from './components/Actual.vue'
import Actual from './components/Actual.vue'
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'DrSystem',
components: { Plan, ProcessSpec, Actual },
components: {
Plan,
ProcessSpec,
Actual,
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return { activeMenu: 'plan' }
return { activeMenu: 'inventory' }
},
computed: {
currentComponent() {
return { plan: 'Plan', processSpec: 'ProcessSpec', actual: 'Actual' }[this.activeMenu]
return {
plan: 'Plan',
processSpec: 'ProcessSpec',
actual: 'Actual',
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}[this.activeMenu]
}
},
methods: {
@@ -45,6 +88,10 @@ export default {
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.dr-container {
display: flex;
width: 100%;

View File

@@ -0,0 +1,40 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DUGE_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DugeInventory',
components: {
BasePage,
},
data() {
return {
querys: {
dataType: 1,
status: 0,
},
warehouseOptions: DUGE_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { DUGE_PROCESSING_TABS } from '../config'
export default {
name: 'DugeProcessing',
components: {
DoPage,
},
data() {
return {
activeTab: 'process',
tabs: DUGE_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DUGE_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DugeQuality',
components: {
BasePage,
},
data() {
return {
activeTab: 'abnormal',
abnormalQuerys: {
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
dataType: 1,
status: 0,
qualityStatus: 'O',
},
warehouseOptions: DUGE_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/duge/comprehensive.vue'
import DayReport from '@/views/wms/report/duge/day.vue'
import MonthReport from '@/views/wms/report/duge/month.vue'
import YearReport from '@/views/wms/report/duge/year.vue'
import LossReport from '@/views/wms/report/duge/loss.vue'
import OutReport from '@/views/wms/report/duge/out.vue'
import TeamReport from '@/views/wms/report/duge/team.vue'
export default {
name: 'DugeReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { DUGE_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'DugeShipping',
components: {
BasePage,
},
data() {
return {
activeTab: 'waybill',
waybillQuerys: {
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
dataType: 1,
status: 1,
},
warehouseOptions: DUGE_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,27 @@
export const DUGE_WAREHOUSE_OPTIONS = [
{ value: '1988151076996706306', label: '镀铬原料库' },
{ value: '1988151132361519105', label: '镀铬成品库' },
]
export const DUGE_PROCESSING_TABS = [
{
title: '镀铬工序',
name: 'process',
label: '镀铬工序',
tabs: DUGE_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
{
title: '合卷',
name: 'merge',
label: '镀铬合卷',
useSpecialSplit: false,
},
{
title: '镀铬修复',
name: 'repair',
label: '镀铬修复工序',
tabs: DUGE_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
]

View File

@@ -0,0 +1,129 @@
<template>
<div class="duge-container">
<div class="duge-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
</el-menu>
</div>
<div class="duge-content">
<component :is="currentComponent" />
</div>
</div>
</template>
<script>
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'DugeSystem',
components: {
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return {
activeMenu: 'inventory',
}
},
computed: {
currentComponent() {
const componentMap = {
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}
return componentMap[this.activeMenu]
},
},
methods: {
handleMenuSelect(index) {
this.activeMenu = index
},
},
}
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.duge-container {
display: flex;
width: 100%;
height: calc(100vh - 84px);
background-color: #f5f7fa;
}
.duge-sidebar {
width: 100px;
background-color: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
.sidebar-menu {
border: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
border-right: none;
::v-deep .el-menu-item {
color: #606266;
padding: 0 20px;
font-size: 14px;
&:hover {
background-color: #ecf5ff;
color: #409eff;
}
&.is-active {
background-color: #ecf5ff;
color: #409eff;
border-right: 3px solid #409eff;
}
i {
font-size: 18px;
margin-right: 8px;
}
}
}
}
.duge-content {
flex: 1;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { LAJIAO_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'LajiaoInventory',
components: {
BasePage,
},
data() {
return {
// 拉矫平整集成库存直接复用钢卷基础查询面板,仅切换逻辑库区范围。
querys: {
dataType: 1,
status: 0,
},
// 这里使用拉矫平整主链路库区,避免把技术部/废品库等外围库区混入集成入口。
warehouseOptions: LAJIAO_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { LAJIAO_PROCESSING_TABS } from '../config'
export default {
name: 'LajiaoProcessing',
components: {
DoPage,
},
data() {
return {
// 拉矫平整 WIP 复用现有工序待办组件,只切换为拉矫平整工序标签与对应逻辑库区。
activeTab: 'process',
// 配置集中定义在 config.js便于后续同步调整工序名称与库区映射。
tabs: LAJIAO_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { LAJIAO_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'LajiaoQuality',
components: {
BasePage,
},
data() {
return {
// 品质页复用现有异常/次品/O 卷查询逻辑,只切换拉矫平整库区范围。
activeTab: 'abnormal',
abnormalQuerys: {
// 异常钢卷默认按异常信息排序,便于品质人员优先处理。
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
// 次品钢卷使用现有 scrap 口径。
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
// O 卷通过质量状态过滤,不额外增加专用接口。
dataType: 1,
status: 0,
qualityStatus: 'O',
},
// 品质板块的库区范围与拉矫平整集成其余板块一致,保证筛选口径统一。
warehouseOptions: LAJIAO_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/lajiao/comprehensive.vue'
import DayReport from '@/views/wms/report/lajiao/day.vue'
import MonthReport from '@/views/wms/report/lajiao/month.vue'
import YearReport from '@/views/wms/report/lajiao/year.vue'
import LossReport from '@/views/wms/report/lajiao/loss.vue'
import OutReport from '@/views/wms/report/lajiao/out.vue'
import TeamReport from '@/views/wms/report/lajiao/team.vue'
export default {
name: 'LajiaoReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
// 报表页直接复用 wms/report/lajiao 下的既有报表资源,
// 集成页只负责聚合入口,不额外改写报表查询逻辑。
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { LAJIAO_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'LajiaoShipping',
components: {
BasePage,
},
data() {
return {
// 发货板块沿用现有钢卷发运能力,仅将过滤库区限定为拉矫平整工序链路。
activeTab: 'waybill',
waybillQuerys: {
// 配卷情况需要带上发货绑定信息,便于直接查看配卷结果。
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
// 发货记录仅查看已发货数据。
dataType: 1,
status: 1,
},
// 发货板块与库存/WIP 保持相同库区口径,避免统计口径不一致。
warehouseOptions: LAJIAO_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,40 @@
// 拉矫平整集成页公共配置。
// 这里统一维护五个板块共用的逻辑库区保持库存、WIP、发货、品质口径一致。
// 拉矫平整主链路逻辑库区:
// 1. 拉矫原料库:拉矫平整工序入口原料
// 2. 拉矫成品库:拉矫平整工序产出成品
// 3. 双机架原料库:拉矫下游衔接库区
export const LAJIAO_WAREHOUSE_OPTIONS = [
{ value: '1988150854442741762', label: '拉矫原料库' },
{ value: '1988150915591499777', label: '拉矫成品库' },
{ value: '1992873386047643650', label: '双机架原料库' },
]
// 拉矫平整 WIP 直接复用现有 WMS 工序组件:
// - 拉矫平整工序、拉矫修复工序走 specialSplit 流程
// - 拉矫平整合卷走普通待办流程
export const LAJIAO_PROCESSING_TABS = [
{
title: '拉矫平整工序',
name: 'process',
label: '拉矫平整工序',
// 拉矫平整工序和修复工序都限定在主链路库区内操作。
tabs: LAJIAO_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
{
title: '合卷',
name: 'merge',
label: '拉矫平整合卷',
// 合卷沿用待办单模式,不需要传入库区页签。
useSpecialSplit: false,
},
{
title: '拉矫修复',
name: 'repair',
label: '拉矫修复工序',
tabs: LAJIAO_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
]

View File

@@ -0,0 +1,133 @@
<template>
<div class="lajiao-container">
<div class="lajiao-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<!-- 拉矫平整集成页按需求只保留五个核心业务板块 -->
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
</el-menu>
</div>
<div class="lajiao-content">
<component :is="currentComponent" />
</div>
</div>
</template>
<script>
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'LajiaoSystem',
components: {
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return {
// 默认先展示库存板块,与酸轧、镀锌、脱脂集成入口保持一致。
activeMenu: 'inventory',
}
},
computed: {
currentComponent() {
// 集成页只作为业务导航壳,具体业务能力由各子组件承载。
const componentMap = {
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}
return componentMap[this.activeMenu]
},
},
methods: {
handleMenuSelect(index) {
// 左侧菜单切换时,仅替换右侧业务面板,不做额外路由跳转。
this.activeMenu = index
},
},
}
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.lajiao-container {
display: flex;
width: 100%;
height: calc(100vh - 84px);
background-color: #f5f7fa;
}
.lajiao-sidebar {
width: 100px;
background-color: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
.sidebar-menu {
border: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
border-right: none;
::v-deep .el-menu-item {
color: #606266;
padding: 0 20px;
font-size: 14px;
&:hover {
background-color: #ecf5ff;
color: #409eff;
}
&.is-active {
background-color: #ecf5ff;
color: #409eff;
border-right: 3px solid #409eff;
}
i {
font-size: 18px;
margin-right: 8px;
}
}
}
}
.lajiao-content {
flex: 1;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { SPLIT_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'SplitInventory',
components: {
BasePage,
},
data() {
return {
// 分条集成库存直接复用钢卷基础查询面板,仅切换到分条成品链路库区。
querys: {
dataType: 1,
status: 0,
},
warehouseOptions: SPLIT_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { SPLIT_PROCESSING_TABS } from '../config'
export default {
name: 'SplitProcessing',
components: {
DoPage,
},
data() {
return {
// 纵剪分条 WIP 目前仅复用现有“纵剪分条工序”能力,不额外引入不存在的合卷/修复页签。
activeTab: 'process',
tabs: SPLIT_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { SPLIT_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'SplitQuality',
components: {
BasePage,
},
data() {
return {
// 品质页复用现有异常/次品/O 卷查询逻辑,只切换分条成品链路库区范围。
activeTab: 'abnormal',
abnormalQuerys: {
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
dataType: 1,
status: 0,
qualityStatus: 'O',
},
warehouseOptions: SPLIT_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/split/comprehensive.vue'
import DayReport from '@/views/wms/report/split/day.vue'
import MonthReport from '@/views/wms/report/split/month.vue'
import YearReport from '@/views/wms/report/split/year.vue'
import LossReport from '@/views/wms/report/split/loss.vue'
import OutReport from '@/views/wms/report/split/out.vue'
import TeamReport from '@/views/wms/report/split/team.vue'
export default {
name: 'SplitReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
// 报表页直接复用 wms/report/split 下的既有报表资源。
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { SPLIT_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'SplitShipping',
components: {
BasePage,
},
data() {
return {
// 发货板块沿用现有钢卷发运能力,仅将过滤库区限定为分条成品链路。
activeTab: 'waybill',
waybillQuerys: {
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
dataType: 1,
status: 1,
},
warehouseOptions: SPLIT_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,34 @@
// 纵剪分条集成页公共配置。
// 分条模块与镀锌/脱脂不同WIP 关注待分条工序链路,库存/发货/品质更关注分条成品去向。
// 纵剪分条成品链路逻辑库区:
// 1. 酸连轧分条成品
// 2. 退火分条成品
// 3. 镀锌分条成品
// 4. 拉矫分条成品
// 5. 包装用内部仓
export const SPLIT_WAREHOUSE_OPTIONS = [
{ value: '1988150210872930306', label: '酸连轧分条成品' },
{ value: '1988150800092950529', label: '退火分条成品' },
{ value: '1988150380649967617', label: '镀锌分条成品' },
{ value: '1988151027466170370', label: '拉矫分条成品' },
{ value: '2027272581575487489', label: '包装用内部仓' },
]
// 纵剪分条 WIP 复用现有 WMS 工序组件。
// 这里沿用现有工序页签定义:首项“原料库”由原模块保留空 value 作为前置入口占位。
export const SPLIT_PROCESSING_TABS = [
{
title: '纵剪分条工序',
name: 'process',
label: '纵剪分条工序',
tabs: [
{ value: '', label: '原料库' },
{ value: '1988150210872930306', label: '酸连轧分条成品' },
{ value: '1988150800092950529', label: '退火分条成品' },
{ value: '1988150380649967617', label: '镀锌分条成品' },
{ value: '1988151027466170370', label: '拉矫分条成品' },
],
useSpecialSplit: true,
},
]

View File

@@ -0,0 +1,131 @@
<template>
<div class="split-container">
<div class="split-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<!-- 纵剪分条集成页按需求只保留五个核心业务板块 -->
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
</el-menu>
</div>
<div class="split-content">
<component :is="currentComponent" />
</div>
</div>
</template>
<script>
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'SplitSystem',
components: {
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return {
// 默认先展示库存板块,与其他集成入口保持一致。
activeMenu: 'inventory',
}
},
computed: {
currentComponent() {
const componentMap = {
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}
return componentMap[this.activeMenu]
},
},
methods: {
handleMenuSelect(index) {
this.activeMenu = index
},
},
}
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.split-container {
display: flex;
width: 100%;
height: calc(100vh - 84px);
background-color: #f5f7fa;
}
.split-sidebar {
width: 100px;
background-color: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
.sidebar-menu {
border: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
border-right: none;
::v-deep .el-menu-item {
color: #606266;
padding: 0 20px;
font-size: 14px;
&:hover {
background-color: #ecf5ff;
color: #409eff;
}
&.is-active {
background-color: #ecf5ff;
color: #409eff;
border-right: 3px solid #409eff;
}
i {
font-size: 18px;
margin-right: 8px;
}
}
}
}
.split-content {
flex: 1;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { TUOZHI_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'TuozhiInventory',
components: {
BasePage,
},
data() {
return {
// 脱脂集成库存直接复用钢卷基础查询面板,仅切换逻辑库区范围。
querys: {
dataType: 1,
status: 0,
},
// 这里使用脱脂主链路库区,避免把技术部/废品库等外围库区混入集成入口。
warehouseOptions: TUOZHI_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { TUOZHI_PROCESSING_TABS } from '../config'
export default {
name: 'TuozhiProcessing',
components: {
DoPage,
},
data() {
return {
// 脱脂 WIP 复用现有工序待办组件,只切换为脱脂工序标签与对应逻辑库区。
activeTab: 'process',
// 配置集中定义在 config.js便于后续同步调整工序名称与库区映射。
tabs: TUOZHI_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { TUOZHI_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'TuozhiQuality',
components: {
BasePage,
},
data() {
return {
// 品质页复用现有异常/次品/O 卷查询逻辑,只切换脱脂库区范围。
activeTab: 'abnormal',
abnormalQuerys: {
// 异常钢卷默认按异常信息排序,便于品质人员优先处理。
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
// 次品钢卷使用现有 scrap 口径。
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
// O 卷通过质量状态过滤,不额外增加专用接口。
dataType: 1,
status: 0,
qualityStatus: 'O',
},
// 品质板块的库区范围与脱脂集成其余板块一致,保证筛选口径统一。
warehouseOptions: TUOZHI_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/tuozhi/comprehensive.vue'
import DayReport from '@/views/wms/report/tuozhi/day.vue'
import MonthReport from '@/views/wms/report/tuozhi/month.vue'
import YearReport from '@/views/wms/report/tuozhi/year.vue'
import LossReport from '@/views/wms/report/tuozhi/loss.vue'
import OutReport from '@/views/wms/report/tuozhi/out.vue'
import TeamReport from '@/views/wms/report/tuozhi/team.vue'
export default {
name: 'TuozhiReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
// 报表页直接复用 wms/report/tuozhi 下的既有报表资源,
// 集成页只负责聚合入口,不额外改写报表查询逻辑。
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { TUOZHI_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'TuozhiShipping',
components: {
BasePage,
},
data() {
return {
// 发货板块沿用现有钢卷发运能力,仅将过滤库区限定为脱脂工序链路。
activeTab: 'waybill',
waybillQuerys: {
// 配卷情况需要带上发货绑定信息,便于直接查看配卷结果。
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
// 发货记录仅查看已发货数据。
dataType: 1,
status: 1,
},
// 发货板块与库存/WIP 保持相同库区口径,避免统计口径不一致。
warehouseOptions: TUOZHI_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,40 @@
// 脱脂工序集成页公共配置。
// 这里统一维护五个板块共用的逻辑库区保持库存、WIP、发货、品质口径一致。
// 脱脂工序主链路逻辑库区:
// 1. 脱脂原料库:脱脂工序入口原料
// 2. 脱脂成品库:脱脂工序产出成品
// 3. 罩式退火原料库:脱脂下游衔接库区
export const TUOZHI_WAREHOUSE_OPTIONS = [
{ value: '1988150545175736322', label: '脱脂原料库' },
{ value: '1988150586938421250', label: '脱脂成品库' },
{ value: '1988150648993148929', label: '罩式退火原料库' },
]
// 脱脂工序 WIP 直接复用现有 WMS 工序组件:
// - 脱脂工序、脱脂修复工序走 specialSplit 流程
// - 脱脂合卷走普通待办流程
export const TUOZHI_PROCESSING_TABS = [
{
title: '脱脂工序',
name: 'process',
label: '脱脂工序',
// 脱脂工序和修复工序都限定在脱脂主链路库区内操作。
tabs: TUOZHI_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
{
title: '合卷',
name: 'merge',
label: '脱脂合卷',
// 合卷沿用待办单模式,不需要传入库区页签。
useSpecialSplit: false,
},
{
title: '脱脂修复',
name: 'repair',
label: '脱脂修复工序',
tabs: TUOZHI_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
]

View File

@@ -0,0 +1,133 @@
<template>
<div class="tuozhi-container">
<div class="tuozhi-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<!-- 脱脂集成页按需求只保留五个核心业务板块 -->
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
</el-menu>
</div>
<div class="tuozhi-content">
<component :is="currentComponent" />
</div>
</div>
</template>
<script>
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'TuozhiSystem',
components: {
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return {
// 默认先展示库存板块,与酸轧、镀锌集成入口保持一致。
activeMenu: 'inventory',
}
},
computed: {
currentComponent() {
// 集成页只作为业务导航壳,具体业务能力由各子组件承载。
const componentMap = {
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}
return componentMap[this.activeMenu]
},
},
methods: {
handleMenuSelect(index) {
// 左侧菜单切换时,仅替换右侧业务面板,不做额外路由跳转。
this.activeMenu = index
},
},
}
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.tuozhi-container {
display: flex;
width: 100%;
height: calc(100vh - 84px);
background-color: #f5f7fa;
}
.tuozhi-sidebar {
width: 100px;
background-color: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
.sidebar-menu {
border: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
border-right: none;
::v-deep .el-menu-item {
color: #606266;
padding: 0 20px;
font-size: 14px;
&:hover {
background-color: #ecf5ff;
color: #409eff;
}
&.is-active {
background-color: #ecf5ff;
color: #409eff;
border-right: 3px solid #409eff;
}
i {
font-size: 18px;
margin-right: 8px;
}
}
}
}
.tuozhi-content {
flex: 1;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<div class="inventory-container">
<BasePage
:qrcode="false"
:querys="querys"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { ZINC_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'ZincInventory',
components: {
BasePage,
},
data() {
return {
// 镀锌集成库存直接复用钢卷基础查询面板,仅切换逻辑库区范围。
querys: {
dataType: 1,
status: 0,
},
// 这里使用镀锌主链路库区,避免把技术部/废品库等外围库区混入集成入口。
warehouseOptions: ZINC_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.inventory-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div class="processing-container">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.title" :name="tab.name">
<DoPage
v-if="activeTab === tab.name"
:label="tab.label"
:tabs="tab.tabs"
:useSpecialSplit="tab.useSpecialSplit"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import DoPage from '@/views/wms/coil/panels/do.vue'
import { ZINC_PROCESSING_TABS } from '../config'
export default {
name: 'ZincProcessing',
components: {
DoPage,
},
data() {
return {
// 镀锌 WIP 复用现有工序待办组件,只切换为镀锌工序标签与对应逻辑库区。
activeTab: 'process',
// 配置集中定义在 config.js便于后续同步调整工序名称与库区映射。
tabs: ZINC_PROCESSING_TABS,
}
},
}
</script>
<style scoped lang="scss">
.processing-container {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="quality-container">
<el-tabs v-model="activeTab" class="quality-tabs">
<el-tab-pane label="异常钢卷" name="abnormal">
<div v-if="activeTab === 'abnormal'" class="tab-content">
<BasePage
:qrcode="false"
:querys="abnormalQuerys"
:labelType="'2'"
:hideType="false"
:showAbnormal="true"
:showControl="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="次品钢卷" name="defect">
<div v-if="activeTab === 'defect'" class="tab-content">
<BasePage
:qrcode="false"
:querys="defectQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="O卷" name="oil">
<div v-if="activeTab === 'oil'" class="tab-content">
<BasePage
:qrcode="false"
:querys="oilQuerys"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { ZINC_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'ZincQuality',
components: {
BasePage,
},
data() {
return {
// 品质页复用现有异常/次品/O 卷查询逻辑,只切换镀锌库区范围。
activeTab: 'abnormal',
abnormalQuerys: {
// 异常钢卷默认按异常信息排序,便于品质人员优先处理。
dataType: 1,
orderByAbnormal: true,
},
defectQuerys: {
// 次品钢卷使用现有 scrap 口径。
dataType: 1,
OnlyScrap: true,
},
oilQuerys: {
// O 卷通过质量状态过滤,不额外增加专用接口。
dataType: 1,
status: 0,
qualityStatus: 'O',
},
// 品质板块的库区范围与镀锌集成其余板块一致,保证筛选口径统一。
warehouseOptions: ZINC_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.quality-container {
width: 100%;
height: 100%;
.quality-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="report-container">
<el-tabs v-model="activeTab" class="report-tabs">
<el-tab-pane label="综合报表" name="comprehensive">
<Comprehensive v-if="activeTab === 'comprehensive'" />
</el-tab-pane>
<el-tab-pane label="日报表" name="day">
<DayReport v-if="activeTab === 'day'" />
</el-tab-pane>
<el-tab-pane label="月报表" name="month">
<MonthReport v-if="activeTab === 'month'" />
</el-tab-pane>
<el-tab-pane label="年报表" name="year">
<YearReport v-if="activeTab === 'year'" />
</el-tab-pane>
<el-tab-pane label="损耗报表" name="loss">
<LossReport v-if="activeTab === 'loss'" />
</el-tab-pane>
<el-tab-pane label="产出报表" name="out">
<OutReport v-if="activeTab === 'out'" />
</el-tab-pane>
<el-tab-pane label="班报表" name="team">
<TeamReport v-if="activeTab === 'team'" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Comprehensive from '@/views/wms/report/zinc/comprehensive.vue'
import DayReport from '@/views/wms/report/zinc/day.vue'
import MonthReport from '@/views/wms/report/zinc/month.vue'
import YearReport from '@/views/wms/report/zinc/year.vue'
import LossReport from '@/views/wms/report/zinc/loss.vue'
import OutReport from '@/views/wms/report/zinc/out.vue'
import TeamReport from '@/views/wms/report/zinc/team.vue'
export default {
name: 'ZincReport',
components: {
Comprehensive,
DayReport,
MonthReport,
YearReport,
LossReport,
OutReport,
TeamReport,
},
data() {
return {
// 报表页直接复用 wms/report/zinc 下的既有报表资源,
// 集成页只负责聚合入口,不额外改写报表查询逻辑。
activeTab: 'comprehensive',
}
},
}
</script>
<style scoped lang="scss">
.report-container {
width: 100%;
height: 100%;
.report-tabs {
height: 100%;
overflow-y: auto;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div class="shipping-container">
<el-tabs v-model="activeTab" class="shipping-tabs">
<el-tab-pane label="配卷情况" name="waybill">
<div class="tab-content">
<BasePage
:qrcode="false"
:showWaybill="true"
:showNewExport="true"
:querys="waybillQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
<el-tab-pane label="发货记录" name="ship">
<div class="tab-content">
<BasePage
:qrcode="false"
:querys="shipQuerys"
:showStatus="false"
:hideType="false"
:useWarehouseIds="true"
:warehouseOptions="warehouseOptions"
:showControl="false"
:showExportTime="true"
:showMaterialType="true"
/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import BasePage from '@/views/wms/coil/panels/base.vue'
import { ZINC_WAREHOUSE_OPTIONS } from '../config'
export default {
name: 'ZincShipping',
components: {
BasePage,
},
data() {
return {
// 发货板块沿用现有钢卷发运能力,仅将过滤库区限定为镀锌工序链路。
activeTab: 'waybill',
waybillQuerys: {
// 配卷情况需要带上发货绑定信息,便于直接查看配卷结果。
dataType: 1,
includeBindInfo: true,
},
shipQuerys: {
// 发货记录仅查看已发货数据。
dataType: 1,
status: 1,
},
// 发货板块与库存/WIP 保持相同库区口径,避免统计口径不一致。
warehouseOptions: ZINC_WAREHOUSE_OPTIONS,
}
},
}
</script>
<style scoped lang="scss">
.shipping-container {
width: 100%;
height: 100%;
.shipping-tabs {
height: 100%;
overflow-y: auto;
}
.tab-content {
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,42 @@
// 镀锌工序集成页公共配置。
// 这里统一维护五个板块共用的逻辑库区,避免各子页面重复定义后产生偏差。
// 镀锌工序相关逻辑库区:
// 1. 镀锌原料库:镀锌工序入口原料
// 2. 镀锌成品库:镀锌工序产出成品
// 3. 镀锌纵剪分条原料库:后续分条前的原料库区
// 4. 镀锌待打包:镀锌工序产出后待包装库区
export const ZINC_WAREHOUSE_OPTIONS = [
{ value: '1988150263284953089', label: '镀锌原料库' },
{ value: '1988150323162836993', label: '镀锌成品库' },
{ value: '1988150487185289217', label: '镀锌纵剪分条原料库' },
{ value: '2056545127927787522', label: '镀锌待打包' },
]
// 镀锌工序 WIP 直接复用现有 WMS 工序组件:
// - 镀锌工序、镀锌修复工序走 specialSplit 流程
// - 镀锌合卷走普通待办流程
export const ZINC_PROCESSING_TABS = [
{
title: '镀锌工序',
name: 'process',
label: '镀锌工序',
// 镀锌工序和修复工序都需要限制在镀锌主链路库区范围内操作。
tabs: ZINC_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
{
title: '合卷',
name: 'merge',
label: '镀锌合卷',
// 合卷沿用待办单模式,不需要传入库区页签。
useSpecialSplit: false,
},
{
title: '镀锌修复',
name: 'repair',
label: '镀锌修复工序',
tabs: ZINC_WAREHOUSE_OPTIONS,
useSpecialSplit: true,
},
]

View File

@@ -1,5 +1,133 @@
<template>
<div>
镀锌子系统
<div class="zinc-container">
<div class="zinc-sidebar">
<el-menu :default-active="activeMenu" class="sidebar-menu" @select="handleMenuSelect">
<!-- 镀锌集成页按需求只保留五个核心业务板块 -->
<el-menu-item index="inventory">
<i class="el-icon-box"></i>
<span slot="title">库存</span>
</el-menu-item>
<el-menu-item index="processing">
<i class="el-icon-s-operation"></i>
<span slot="title">WIP</span>
</el-menu-item>
<el-menu-item index="report">
<i class="el-icon-document"></i>
<span slot="title">报表</span>
</el-menu-item>
<el-menu-item index="shipping">
<i class="el-icon-truck"></i>
<span slot="title">发货</span>
</el-menu-item>
<el-menu-item index="quality">
<i class="el-icon-warning-outline"></i>
<span slot="title">品质</span>
</el-menu-item>
</el-menu>
</div>
<div class="zinc-content">
<component :is="currentComponent" />
</div>
</div>
</template>
</template>
<script>
import Inventory from './components/Inventory.vue'
import Processing from './components/Processing.vue'
import Report from './components/Report.vue'
import Shipping from './components/Shipping.vue'
import Quality from './components/Quality.vue'
export default {
name: 'ZincSystem',
components: {
Inventory,
Processing,
Report,
Shipping,
Quality,
},
data() {
return {
// 默认先展示库存板块,与酸轧集成入口保持一致。
activeMenu: 'inventory',
}
},
computed: {
currentComponent() {
// 集成页只作为业务导航壳,具体业务能力由各子组件承载。
const componentMap = {
inventory: 'Inventory',
processing: 'Processing',
report: 'Report',
shipping: 'Shipping',
quality: 'Quality',
}
return componentMap[this.activeMenu]
},
},
methods: {
handleMenuSelect(index) {
// 左侧菜单切换时,仅替换右侧业务面板,不做额外路由跳转。
this.activeMenu = index
},
},
}
</script>
<style scoped lang="scss">
.app-container {
padding: 0 !important;
}
.zinc-container {
display: flex;
width: 100%;
height: calc(100vh - 84px);
background-color: #f5f7fa;
}
.zinc-sidebar {
width: 100px;
background-color: #fff;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
.sidebar-menu {
border: none;
background-color: transparent;
flex: 1;
overflow-y: auto;
border-right: none;
::v-deep .el-menu-item {
color: #606266;
padding: 0 20px;
font-size: 14px;
&:hover {
background-color: #ecf5ff;
color: #409eff;
}
&.is-active {
background-color: #ecf5ff;
color: #409eff;
border-right: 3px solid #409eff;
}
i {
font-size: 18px;
margin-right: 8px;
}
}
}
}
.zinc-content {
flex: 1;
overflow: hidden;
}
</style>

View File

@@ -334,16 +334,13 @@ export default {
/** 打印标签 */
handlePrintLabel(row) {
this.labelRender.data = row;
this.labelRender.type = getCoilTagPrintType(row);
this.labelRender.data = {
...row,
updateTime: row.updateTime?.split(' ')[0] || '',
};
this.$nextTick(() => {
const el = this.$refs.labelRender && this.$refs.labelRender.$el;
if (el) {
el.style.display = 'block';
window.print();
el.style.display = 'none';
}
});
this.$refs.labelRender.printLabel();
})
},
handleExport() {