Files
klp-mono/apps/hand-factory/components/panels/code/trace.vue

853 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<view class="trace-container">
<!-- 扫码或输入 -->
<view class="search-section">
<view class="search-card">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">钢卷溯源</text>
</view>
<!-- 扫码按钮 -->
<view class="scan-btn" @click="handleScan">
<text class="scan-icon">📷</text>
<text class="scan-text">扫描二维码</text>
</view>
<view class="divider">
<view class="divider-line"></view>
<text class="divider-text"></text>
<view class="divider-line"></view>
</view>
<!-- 输入框 -->
<view class="input-group">
<view class="input-item">
<text class="input-label">入场钢卷号</text>
<input
v-model="searchForm.enterCoilNo"
placeholder="请输入入场钢卷号"
class="input-field"
/>
</view>
<view class="input-item">
<text class="input-label-optional">当前钢卷号可选</text>
<input
v-model="searchForm.currentCoilNo"
placeholder="可输入当前钢卷号筛选"
class="input-field"
/>
</view>
</view>
<!-- 查询按钮 -->
<button class="query-btn" @click="handleQuery" :disabled="loading">
{{ loading ? '查询中...' : '查询溯源' }}
</button>
</view>
</view>
<!-- 溯源结果 -->
<view class="result-section" v-if="traceResult">
<!-- 二维码信息 -->
<view class="result-card">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">二维码信息</text>
</view>
<view class="qrcode-info">
<view class="info-row">
<text class="info-label">主序列号</text>
<text class="info-value">{{ (traceResult.qrcode && traceResult.qrcode.serialNumber) || '-' }}</text>
</view>
<view class="info-row">
<text class="info-label">创建时间</text>
<text class="info-value">{{ (traceResult.qrcode && traceResult.qrcode.createTime) || '-' }}</text>
</view>
<view class="info-row" v-if="traceResult.all_qrcodes && traceResult.all_qrcodes.length > 1">
<text class="info-label">相关二维码</text>
<text class="info-value">{{ traceResult.all_qrcodes.length }} </text>
</view>
</view>
</view>
<!-- 操作步骤 -->
<view class="result-card" v-if="traceResult.steps && traceResult.steps.length > 0">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">操作步骤</text>
</view>
<view class="steps-timeline">
<view
class="step-item"
v-for="(step, index) in traceResult.steps"
:key="index"
>
<view class="step-marker">
<view class="step-number">{{ step.display_step || step.step }}</view>
<view class="step-line" v-if="index < traceResult.steps.length - 1"></view>
</view>
<view class="step-content">
<view class="step-header">
<text class="step-action">{{ step.action }}</text>
<text class="step-operation" v-if="step.operation">{{ step.operation }}</text>
</view>
<view class="step-details">
<view class="detail-row" v-if="step.current_coil_no">
<text class="detail-label">当前钢卷号</text>
<text class="detail-value">{{ step.current_coil_no }}</text>
</view>
<view class="detail-row" v-if="step.old_current_coil_no">
<text class="detail-label">原钢卷号</text>
<text class="detail-value">{{ step.old_current_coil_no }}</text>
</view>
<view class="detail-row" v-if="step.new_current_coil_no">
<text class="detail-label">新钢卷号</text>
<text class="detail-value">{{ step.new_current_coil_no }}</text>
</view>
<view class="detail-row" v-if="step.new_current_coil_nos">
<text class="detail-label">分卷列表</text>
<view class="detail-value">
<view class="coil-tags">
<text
class="coil-tag coil-tag-split"
v-for="coilNo in step.new_current_coil_nos.split(',')"
:key="coilNo"
>
{{ coilNo.trim() }}
</text>
</view>
</view>
</view>
<view class="detail-row" v-if="step.parent_coil_nos">
<text class="detail-label">合卷来源</text>
<view class="detail-value">
<view class="coil-tags">
<text
class="coil-tag coil-tag-merge"
v-for="coilNo in step.parent_coil_nos.split(',')"
:key="coilNo"
>
{{ coilNo.trim() }}
</text>
</view>
</view>
</view>
<view class="detail-row" v-if="step.child_coils && step.child_coils.length > 0">
<text class="detail-label">子钢卷</text>
<view class="detail-value">
<view class="coil-tags">
<text
class="coil-tag coil-tag-child"
v-for="coilNo in step.child_coils"
:key="coilNo"
>
{{ coilNo }}
</text>
</view>
</view>
</view>
<view class="detail-row" v-if="step.qrcode_serial">
<text class="detail-label">二维码序列</text>
<text class="detail-value">{{ step.qrcode_serial }}</text>
</view>
<view class="detail-row" v-if="step.operator">
<text class="detail-label">操作者</text>
<text class="detail-value">{{ step.operator }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 钢卷分支图 -->
<view class="result-card" v-if="traceResult.records && traceResult.records.length > 0">
<view class="card-title">
<text class="title-dot"></text>
<text class="title-text">钢卷分支图</text>
</view>
<view class="branch-view">
<view
class="branch-item"
v-for="(record, index) in groupedRecords"
:key="index"
>
<view class="branch-header">
<view class="branch-type" :class="['branch-' + record.type, 'operation-' + record.groupColor]">
{{ record.title }}
</view>
<text class="branch-count">{{ record.coils.length }} 个钢卷</text>
</view>
<view class="branch-coils">
<view
class="coil-card"
v-for="coil in record.coils"
:key="coil.coilId"
>
<view class="coil-header">
<text class="coil-no">{{ coil.currentCoilNo }}</text>
<view class="coil-status" :class="'status-' + coil.dataType">
{{ coil.dataType === 1 ? '当前' : '历史' }}
</view>
</view>
<view class="coil-info">
<text class="coil-detail">入场号{{ coil.enterCoilNo }}</text>
<text class="coil-detail" v-if="coil.team">班组{{ coil.team }}</text>
<text class="coil-detail" v-if="coil.warehouse">
库区{{ coil.warehouse.warehouseName }}
</text>
<text class="coil-detail">{{ coil.createTime }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 无记录提示 -->
<view class="empty-tip" v-if="!traceResult.records || traceResult.records.length === 0">
<text>未找到相关钢卷记录</text>
</view>
</view>
</view>
</template>
<script>
import { getGenerateRecord } from '@/api/wms/code.js'
import { getMaterialCoilTrace, getMaterialCoil } from '@/api/wms/coil.js'
export default {
data() {
return {
searchForm: {
enterCoilNo: '',
currentCoilNo: ''
},
traceResult: null,
loading: false
}
},
computed: {
// 按时间线和分支关系分组钢卷记录
groupedRecords() {
if (!this.traceResult || !this.traceResult.records) {
return [];
}
const groups = [];
// 按创建时间排序所有记录
const sortedRecords = [...this.traceResult.records].sort((a, b) =>
new Date(a.createTime) - new Date(b.createTime)
);
// 按数据类型分组,但保持时间顺序
const currentCoils = sortedRecords.filter(record => record.dataType === 1);
const historyCoils = sortedRecords.filter(record => record.dataType === 0);
// 当前数据分组
if (currentCoils.length > 0) {
// 进一步按操作类型细分当前数据
const currentByOperation = this.groupByOperation(currentCoils);
groups.push(...currentByOperation);
}
// 历史数据分组
if (historyCoils.length > 0) {
// 进一步按操作类型细分历史数据
const historyByOperation = this.groupByOperation(historyCoils, true);
groups.push(...historyByOperation);
}
return groups;
}
},
methods: {
// 按操作类型分组
groupByOperation(records, isHistory = false) {
const operationGroups = {};
for (const record of records) {
let operationType = '原始数据';
let groupColor = 'default';
if (record.hasMergeSplit === 1) {
operationType = '分卷结果';
groupColor = 'split';
} else if (record.hasMergeSplit === 2) {
operationType = '合卷结果';
groupColor = 'merge';
}
const groupKey = operationType;
if (!operationGroups[groupKey]) {
operationGroups[groupKey] = {
operationType: operationType,
groupColor: groupColor,
coils: []
};
}
operationGroups[groupKey].coils.push(record);
}
// 转换为数组
const groups = [];
for (const key in operationGroups) {
const group = operationGroups[key];
groups.push({
type: isHistory ? 'history' : 'current',
operationType: group.operationType,
groupColor: group.groupColor,
title: `${group.operationType}${isHistory ? '(历史)' : '(当前)'}`,
coils: group.coils
});
}
return groups;
},
// 扫码
handleScan() {
uni.scanCode({
success: async (res) => {
console.log('扫码结果:', res.result);
try {
const qrcodeId = res.result;
// 1. 获取二维码详情
const qrcodeRes = await getGenerateRecord(qrcodeId);
if (qrcodeRes.code !== 200) {
throw new Error('未找到二维码记录');
}
// 2. 解析content获取coil_id
const qrcodeRecord = qrcodeRes.data;
const content = JSON.parse(qrcodeRecord.content);
const coilId = content.coil_id;
if (!coilId || coilId === 'null' || coilId === 'undefined') {
throw new Error('二维码中未包含有效的钢卷ID');
}
// 3. 查询钢卷详情获取enterCoilNo
const coilRes = await getMaterialCoil(coilId);
if (coilRes.code !== 200 || !coilRes.data) {
throw new Error('未找到钢卷信息');
}
const coilData = coilRes.data;
this.searchForm.enterCoilNo = coilData.enterCoilNo;
this.searchForm.currentCoilNo = '';
// 4. 自动查询溯源
this.handleQuery();
} catch (err) {
console.error('扫码处理失败:', err);
uni.showToast({
title: err.message || '扫码信息解析失败',
icon: 'none',
duration: 2000
});
}
},
fail: (err) => {
console.error('扫码失败:', err);
uni.showToast({
title: '扫码失败,请重试',
icon: 'none'
});
}
});
},
// 查询溯源
handleQuery() {
// 验证
if (!this.searchForm.enterCoilNo) {
uni.showToast({
title: '请输入或扫描入场钢卷号',
icon: 'none'
});
return;
}
this.loading = true;
getMaterialCoilTrace(
this.searchForm.enterCoilNo,
this.searchForm.currentCoilNo || undefined
).then(res => {
if (res.code === 200) {
this.traceResult = res.data;
console.log('溯源结果:', this.traceResult);
if (!this.traceResult.records || this.traceResult.records.length === 0) {
uni.showToast({
title: '未找到相关记录',
icon: 'none'
});
} else {
uni.showToast({
title: '查询成功',
icon: 'success'
});
}
} else {
uni.showToast({
title: res.msg || '查询失败',
icon: 'none'
});
}
}).catch(err => {
console.error('查询失败:', err);
uni.showToast({
title: '查询失败,请重试',
icon: 'none'
});
}).finally(() => {
this.loading = false;
});
}
}
}
</script>
<style scoped lang="scss">
.trace-container {
padding-bottom: 30rpx;
}
/* 搜索区域 */
.search-section {
margin-bottom: 20rpx;
}
.search-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title {
display: flex;
align-items: center;
margin-bottom: 25rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.title-dot {
width: 8rpx;
height: 28rpx;
background: #5856d6;
border-radius: 4rpx;
margin-right: 12rpx;
}
.title-text {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
/* 扫码按钮 */
.scan-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
height: 100rpx;
background: linear-gradient(135deg, #5856d6 0%, #4840c4 100%);
border-radius: 12rpx;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(88, 86, 214, 0.3);
&:active {
transform: scale(0.98);
}
.scan-icon {
font-size: 40rpx;
}
.scan-text {
font-size: 28rpx;
font-weight: 500;
}
}
/* 分割线 */
.divider {
display: flex;
align-items: center;
margin: 30rpx 0;
.divider-line {
flex: 1;
height: 1rpx;
background: #e8e8e8;
}
.divider-text {
padding: 0 20rpx;
font-size: 24rpx;
color: #999;
}
}
/* 输入组 */
.input-group {
.input-item {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.input-label,
.input-label-optional {
display: block;
font-size: 26rpx;
color: #333;
margin-bottom: 12rpx;
font-weight: 500;
}
.input-label::before {
content: '*';
color: #ff4d4f;
margin-right: 6rpx;
}
.input-field {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
background: #f8f9fa;
border: 2rpx solid #e8e8e8;
border-radius: 10rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
&:focus {
background: #fff;
border-color: #5856d6;
}
}
}
}
/* 查询按钮 */
.query-btn {
width: 100%;
height: 88rpx;
margin-top: 30rpx;
background: linear-gradient(135deg, #5856d6 0%, #4840c4 100%);
color: #fff;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 500;
border: none;
box-shadow: 0 4rpx 16rpx rgba(88, 86, 214, 0.3);
&:active {
transform: scale(0.98);
}
&[disabled] {
opacity: 0.6;
}
}
/* 结果区域 */
.result-section {
.result-card {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
}
/* 二维码信息 */
.qrcode-info {
.info-row {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 26rpx;
color: #666;
min-width: 150rpx;
}
.info-value {
flex: 1;
font-size: 26rpx;
color: #333;
font-weight: 500;
}
}
}
/* 步骤时间线 */
.steps-timeline {
.step-item {
display: flex;
gap: 20rpx;
.step-marker {
display: flex;
flex-direction: column;
align-items: center;
.step-number {
width: 60rpx;
height: 60rpx;
background: #5856d6;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: bold;
flex-shrink: 0;
}
.step-line {
width: 2rpx;
flex: 1;
background: #e8e8e8;
margin: 10rpx 0;
}
}
.step-content {
flex: 1;
padding-bottom: 30rpx;
.step-header {
display: flex;
align-items: center;
gap: 15rpx;
margin-bottom: 15rpx;
.step-action {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.step-operation {
font-size: 24rpx;
color: #fff;
background: #5856d6;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
}
.step-details {
background: #f8f9fa;
border-radius: 10rpx;
padding: 20rpx;
.detail-row {
display: flex;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.detail-label {
font-size: 24rpx;
color: #666;
min-width: 140rpx;
}
.detail-value {
flex: 1;
font-size: 24rpx;
color: #333;
word-break: break-all;
}
}
.coil-tags {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
.coil-tag {
display: inline-block;
padding: 6rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
font-weight: 500;
&.coil-tag-split {
background: #fff3e0;
color: #ff9500;
border: 1rpx solid #ffd591;
}
&.coil-tag-merge {
background: #e8f5e8;
color: #34c759;
border: 1rpx solid #95de64;
}
&.coil-tag-child {
background: #f0f7ff;
color: #007aff;
border: 1rpx solid #91d5ff;
}
}
}
}
}
}
}
/* 分支视图 */
.branch-view {
.branch-item {
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.branch-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
padding: 15rpx 20rpx;
background: #f8f9fa;
border-radius: 10rpx;
.branch-type {
font-size: 26rpx;
font-weight: 600;
padding: 4rpx 16rpx;
border-radius: 20rpx;
&.branch-current {
background: #d1f2eb;
color: #0c6957;
}
&.branch-history {
background: #f8d7da;
color: #721c24;
}
&.operation-split {
background: #fff3e0;
color: #ff9500;
border: 1rpx solid #ffd591;
}
&.operation-merge {
background: #e8f5e8;
color: #34c759;
border: 1rpx solid #95de64;
}
&.operation-default {
background: #f0f7ff;
color: #007aff;
border: 1rpx solid #91d5ff;
}
}
.branch-count {
font-size: 24rpx;
color: #666;
}
}
.branch-coils {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
.coil-card {
flex: 1;
min-width: 300rpx;
background: #fff;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
padding: 20rpx;
.coil-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15rpx;
.coil-no {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.coil-status {
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
&.status-1 {
background: #d1f2eb;
color: #0c6957;
}
&.status-0 {
background: #f8d7da;
color: #721c24;
}
}
}
.coil-info {
.coil-detail {
display: block;
font-size: 22rpx;
color: #666;
margin-bottom: 8rpx;
line-height: 1.4;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
}
}
/* 空提示 */
.empty-tip {
text-align: center;
padding: 80rpx 0;
color: #999;
font-size: 28rpx;
}
</style>