feat: 坯布出仓增加操作语音并精简扫码明细
This commit is contained in:
@@ -61,6 +61,12 @@ export const STATUS_ACTION_CONFIG = {
|
|||||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 审核 / 作废成功后播放的语音文案 */
|
||||||
|
export const STATUS_ACTION_VOICE = {
|
||||||
|
approve: '审核成功',
|
||||||
|
void: '作废成功',
|
||||||
|
};
|
||||||
|
|
||||||
// 出仓类型:当前后端仅提供「坯布其他出货单」一种类型的接口
|
// 出仓类型:当前后端仅提供「坯布其他出货单」一种类型的接口
|
||||||
export const BILL_TYPES = [
|
export const BILL_TYPES = [
|
||||||
{ value: 'other', label: '坯布其他出货单' },
|
{ value: 'other', label: '坯布其他出货单' },
|
||||||
@@ -147,17 +153,7 @@ export function normalizeOrder(order) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 扫码详情区表格列 */
|
/** 扫码详情区汇总维度(勾选后随扫码请求把对应 lock 字段传给后端) */
|
||||||
export const SCAN_DETAIL_TABLE_COLUMNS = [
|
|
||||||
{ label: '坯布编号', key: 'grey_fabric_code', width: 140 },
|
|
||||||
{ label: '坯布名称', key: 'grey_fabric_name', width: 140 },
|
|
||||||
{ label: '纱批', key: 'yarn_batch', width: 120 },
|
|
||||||
{ label: '机号', key: 'machine_number', width: 100 },
|
|
||||||
{ label: '条数', key: 'roll', width: 80 },
|
|
||||||
{ label: '重量', key: 'weight', width: 100 },
|
|
||||||
];
|
|
||||||
|
|
||||||
/** 扫码详情区汇总维度(勾选后参与表格分组;带 lock 的维度会随扫码请求传给后端) */
|
|
||||||
export const SCAN_GROUP_OPTIONS = [
|
export const SCAN_GROUP_OPTIONS = [
|
||||||
{ key: 'produce_order_no', label: '合同' },
|
{ key: 'produce_order_no', label: '合同' },
|
||||||
{ key: 'grey_fabric_code', label: '布编' },
|
{ key: 'grey_fabric_code', label: '布编' },
|
||||||
@@ -300,56 +296,6 @@ export function buildScanDetailFromContext(context, order) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function aggregateScanTableRows(rows, keys) {
|
|
||||||
const map = new Map();
|
|
||||||
rows.forEach((row) => {
|
|
||||||
const groupId = keys.map((k) => row[k] || '').join('|');
|
|
||||||
const existing = map.get(groupId);
|
|
||||||
if (!existing) {
|
|
||||||
map.set(groupId, { ...row, roll: 0, raw_weight: 0 });
|
|
||||||
}
|
|
||||||
const agg = map.get(groupId);
|
|
||||||
agg.roll = Number((agg.roll + Number(row.roll || 0)).toFixed(2));
|
|
||||||
agg.raw_weight = Number(agg.raw_weight || 0) + Number(row.raw_weight || 0);
|
|
||||||
});
|
|
||||||
return Array.from(map.values()).map((row) => ({
|
|
||||||
...row,
|
|
||||||
weight: formatWeightKg(row.raw_weight),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 构建扫码详情区汇总表格数据 */
|
|
||||||
export function buildScanDetailTableRows(itemData, groupKeys = []) {
|
|
||||||
const rows = [];
|
|
||||||
(itemData || []).forEach((item) => {
|
|
||||||
(item.fine_codes || []).forEach((fc) => {
|
|
||||||
rows.push({
|
|
||||||
customer_name: item.customer_name || '',
|
|
||||||
produce_order_no: item.produce_order_no || item.contract_no || '',
|
|
||||||
grey_fabric_code: item.grey_fabric_code || '',
|
|
||||||
grey_fabric_name: item.grey_fabric_name || '',
|
|
||||||
yarn_batch: item.yarn_batch || '',
|
|
||||||
machine_number: item.machine_number || '',
|
|
||||||
volume_number: fc.roll_no || fc.fabric_piece_code || '',
|
|
||||||
grey_fabric_level_name: item.grey_fabric_level_name || '',
|
|
||||||
roll: fc.roll || 0,
|
|
||||||
raw_weight: fc.raw_weight != null ? fc.raw_weight : 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (!rows.length) return [];
|
|
||||||
const keys = (groupKeys || []).filter(Boolean);
|
|
||||||
if (!keys.length) {
|
|
||||||
return aggregateScanTableRows(rows, ['grey_fabric_code', 'grey_fabric_name', 'yarn_batch', 'machine_number']);
|
|
||||||
}
|
|
||||||
return aggregateScanTableRows(rows, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getScanDetailTableWidth(columns) {
|
|
||||||
const total = (columns || []).reduce((sum, col) => sum + (col.width || 120), 0);
|
|
||||||
return `${total}rpx`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeItem(item) {
|
export function normalizeItem(item) {
|
||||||
const machineStockRaw = item.machine_stock_roll ?? item.machine_stock ?? item.machine_inventory_roll;
|
const machineStockRaw = item.machine_stock_roll ?? item.machine_stock ?? item.machine_inventory_roll;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -366,6 +366,56 @@ function playErrorAudio() {
|
|||||||
errorAudio.play();
|
errorAudio.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 坯布出仓等业务的固定中文提示语音
|
||||||
|
const SPEAK_AUDIO = {
|
||||||
|
'扫码出仓成功': '/static/audio/scan_out_ok.mp3',
|
||||||
|
'删除成功': '/static/audio/delete_ok.mp3',
|
||||||
|
'提交成功': '/static/audio/submit_ok.mp3',
|
||||||
|
'审核成功': '/static/audio/audit_ok.mp3',
|
||||||
|
'作废成功': '/static/audio/void_ok.mp3',
|
||||||
|
};
|
||||||
|
|
||||||
|
const speakAudioMap = {};
|
||||||
|
|
||||||
|
function stopOtherSpeak(exceptText) {
|
||||||
|
Object.keys(speakAudioMap).forEach((key) => {
|
||||||
|
if (key === exceptText || !speakAudioMap[key]) return;
|
||||||
|
try {
|
||||||
|
speakAudioMap[key].stop();
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
if (successAudio) {
|
||||||
|
try {
|
||||||
|
successAudio.stop();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playSpeak(text) {
|
||||||
|
const src = SPEAK_AUDIO[text];
|
||||||
|
if (!src) return;
|
||||||
|
stopOtherSpeak(text);
|
||||||
|
const prev = speakAudioMap[text];
|
||||||
|
if (prev) {
|
||||||
|
try {
|
||||||
|
prev.stop();
|
||||||
|
prev.destroy();
|
||||||
|
} catch (e) {}
|
||||||
|
speakAudioMap[text] = undefined;
|
||||||
|
}
|
||||||
|
const audio = uni.createInnerAudioContext();
|
||||||
|
audio.autoplay = true;
|
||||||
|
audio.src = src;
|
||||||
|
audio.onError((res) => {
|
||||||
|
console.log('语音播放失败', text, res && res.errMsg);
|
||||||
|
try {
|
||||||
|
audio.destroy();
|
||||||
|
} catch (e) {}
|
||||||
|
if (speakAudioMap[text] === audio) speakAudioMap[text] = undefined;
|
||||||
|
});
|
||||||
|
speakAudioMap[text] = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get apiurl() {
|
get apiurl() {
|
||||||
@@ -378,4 +428,5 @@ export default {
|
|||||||
parGetData,
|
parGetData,
|
||||||
playSuccessAudio,
|
playSuccessAudio,
|
||||||
playErrorAudio,
|
playErrorAudio,
|
||||||
|
playSpeak,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<view class="fabric-out-sectionTitle">{{ title }}</view>
|
<view v-if="title" class="fabric-out-sectionTitle">{{ title }}</view>
|
||||||
<slot name="before-list" />
|
<slot name="before-list" />
|
||||||
<view class="fabric-out-detailList">
|
<view class="fabric-out-detailList">
|
||||||
<view
|
<view
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
list: { type: Array, default: () => [] },
|
list: { type: Array, default: () => [] },
|
||||||
title: { type: String, default: '出仓细码' },
|
title: { type: String, default: '' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
<text class="fabric-out-scanDetailLabel">客户:</text>
|
<text class="fabric-out-scanDetailLabel">客户:</text>
|
||||||
<text class="fabric-out-scanDetailValue">{{ detail.customer_name || '' }}</text>
|
<text class="fabric-out-scanDetailValue">{{ detail.customer_name || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
<view class="fabric-out-scanDetailRow">
|
||||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('produce_order_no')">
|
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('produce_order_no')">
|
||||||
<checkbox :checked="isGroupChecked('produce_order_no')" />
|
<checkbox :checked="isGroupChecked('produce_order_no')" />
|
||||||
<text class="fabric-out-scanDetailLabel">生产通知单:</text>
|
<text class="fabric-out-scanDetailLabel">生产通知单:</text>
|
||||||
<text class="fabric-out-scanDetailValue">{{ detail.produce_order_no || '' }}</text>
|
<text class="fabric-out-scanDetailValue">{{ detail.produce_order_no || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="fabric-out-scanDetailRow">
|
||||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('grey_fabric_code')">
|
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('grey_fabric_code')">
|
||||||
<checkbox :checked="isGroupChecked('grey_fabric_code')" />
|
<checkbox :checked="isGroupChecked('grey_fabric_code')" />
|
||||||
<text class="fabric-out-scanDetailLabel">布编:</text>
|
<text class="fabric-out-scanDetailLabel">布编:</text>
|
||||||
@@ -58,67 +60,16 @@
|
|||||||
<text class="fabric-out-scanDetailValue">{{ formatWeight(detail.weight) }}Kg</text>
|
<text class="fabric-out-scanDetailValue">{{ formatWeight(detail.weight) }}Kg</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view scroll-x class="fabric-out-scanDetailTableScroll">
|
|
||||||
<view class="fabric-out-scanDetailTable" :style="{ width: tableWidth }">
|
|
||||||
<view class="fabric-out-scanDetailTableRow fabric-out-scanDetailTableHead">
|
|
||||||
<view
|
|
||||||
v-for="col in columns"
|
|
||||||
:key="col.key"
|
|
||||||
class="fabric-out-scanDetailTableCell"
|
|
||||||
:style="cellStyle(col)"
|
|
||||||
>{{ col.label }}</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="!tableRows.length" class="fabric-out-scanDetailEmpty">暂无数据~~</view>
|
|
||||||
<view
|
|
||||||
v-for="(row, rowIndex) in tableRows"
|
|
||||||
:key="rowIndex"
|
|
||||||
class="fabric-out-scanDetailTableRow"
|
|
||||||
>
|
|
||||||
<view
|
|
||||||
v-for="col in columns"
|
|
||||||
:key="col.key"
|
|
||||||
class="fabric-out-scanDetailTableCell"
|
|
||||||
:style="cellStyle(col)"
|
|
||||||
>{{ formatCell(row, col.key) }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
|
||||||
SCAN_DETAIL_TABLE_COLUMNS,
|
|
||||||
getScanDetailTableWidth,
|
|
||||||
buildScanDetailTableRows,
|
|
||||||
} from '@/common/storeFabricBusinessOut';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
detail: { type: Object, default: () => ({}) },
|
detail: { type: Object, default: () => ({}) },
|
||||||
itemData: { type: Array, default: () => [] },
|
|
||||||
groupKeys: { type: Array, default: () => [] },
|
groupKeys: { type: Array, default: () => [] },
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
columns() {
|
|
||||||
return SCAN_DETAIL_TABLE_COLUMNS;
|
|
||||||
},
|
|
||||||
tableWidth() {
|
|
||||||
return getScanDetailTableWidth(this.columns);
|
|
||||||
},
|
|
||||||
tableRows() {
|
|
||||||
return buildScanDetailTableRows(this.itemData, this.groupKeys);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
cellStyle(col) {
|
|
||||||
return {
|
|
||||||
width: `${col.width}rpx`,
|
|
||||||
minWidth: `${col.width}rpx`,
|
|
||||||
maxWidth: `${col.width}rpx`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isGroupChecked(key) {
|
isGroupChecked(key) {
|
||||||
return (this.groupKeys || []).includes(key);
|
return (this.groupKeys || []).includes(key);
|
||||||
},
|
},
|
||||||
@@ -140,11 +91,6 @@ export default {
|
|||||||
const n = Number(value || 0);
|
const n = Number(value || 0);
|
||||||
return Number(n.toFixed(2));
|
return Number(n.toFixed(2));
|
||||||
},
|
},
|
||||||
formatCell(row, key) {
|
|
||||||
if (key === 'roll') return this.formatRoll(row.roll);
|
|
||||||
if (key === 'weight') return this.formatWeight(row.weight);
|
|
||||||
return row[key] || '';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
<FabricOutScanDetail
|
<FabricOutScanDetail
|
||||||
slot="before-list"
|
slot="before-list"
|
||||||
:detail="scanDetail"
|
:detail="scanDetail"
|
||||||
:item-data="form.item_data"
|
|
||||||
:group-keys="scanGroupKeys"
|
:group-keys="scanGroupKeys"
|
||||||
@group-change="scanGroupKeys = $event"
|
@group-change="scanGroupKeys = $event"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
<FabricOutScanDetail
|
<FabricOutScanDetail
|
||||||
slot="before-list"
|
slot="before-list"
|
||||||
:detail="scanDetail"
|
:detail="scanDetail"
|
||||||
:item-data="form.item_data"
|
|
||||||
:group-keys="scanGroupKeys"
|
:group-keys="scanGroupKeys"
|
||||||
@group-change="scanGroupKeys = $event"
|
@group-change="scanGroupKeys = $event"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
getFormStatusActionButtons,
|
getFormStatusActionButtons,
|
||||||
orderToFormFields,
|
orderToFormFields,
|
||||||
confirmOrderStatusAction,
|
confirmOrderStatusAction,
|
||||||
|
STATUS_ACTION_VOICE,
|
||||||
DRAFT_AUDIT_STATUS,
|
DRAFT_AUDIT_STATUS,
|
||||||
DRAFT_AUDIT_STATUS_NAME,
|
DRAFT_AUDIT_STATUS_NAME,
|
||||||
getEmptyScanDetail,
|
getEmptyScanDetail,
|
||||||
@@ -242,7 +243,7 @@ export default {
|
|||||||
this.applyOrder(order);
|
this.applyOrder(order);
|
||||||
this.headerSaved = true;
|
this.headerSaved = true;
|
||||||
this.syncCanScanFromStatus(order.audit_status);
|
this.syncCanScanFromStatus(order.audit_status);
|
||||||
this.showSuccess('保存成功,可以扫码');
|
this.showSuccess('保存成功,可以扫码', '提交成功');
|
||||||
this.registerOrderScanBroadcast();
|
this.registerOrderScanBroadcast();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@@ -330,10 +331,10 @@ export default {
|
|||||||
const row = order.item_data.find((i) => (i.group_key || String(i.id)) === this.currentGroupKey);
|
const row = order.item_data.find((i) => (i.group_key || String(i.id)) === this.currentGroupKey);
|
||||||
this.currentFineCodes = row ? [...row.fine_codes] : [];
|
this.currentFineCodes = row ? [...row.fine_codes] : [];
|
||||||
}
|
}
|
||||||
this.BillDataMessage = scanType === 2 ? '删除成功' : '扫描成功';
|
this.BillDataMessage = scanType === 2 ? '删除成功' : '扫码出仓成功';
|
||||||
this.QRBarCode = '';
|
this.QRBarCode = '';
|
||||||
if (scanType === 2) this.showSuccess('删除成功');
|
if (scanType === 2) this.showSuccess('删除成功', '删除成功');
|
||||||
else util.playSuccessAudio();
|
else util.playSpeak('扫码出仓成功');
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
@@ -377,7 +378,8 @@ export default {
|
|||||||
if (!order) return;
|
if (!order) return;
|
||||||
this.applyOrder(order);
|
this.applyOrder(order);
|
||||||
this.syncCanScanFromStatus(order.audit_status);
|
this.syncCanScanFromStatus(order.audit_status);
|
||||||
this.showSuccess('操作成功');
|
const voice = STATUS_ACTION_VOICE[action] || '';
|
||||||
|
this.showSuccess(voice || '操作成功', voice);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -385,8 +387,9 @@ export default {
|
|||||||
util.playErrorAudio();
|
util.playErrorAudio();
|
||||||
uni.showModal({ title: '提示', content: message, showCancel: false });
|
uni.showModal({ title: '提示', content: message, showCancel: false });
|
||||||
},
|
},
|
||||||
showSuccess(message) {
|
showSuccess(message, speakText) {
|
||||||
util.playSuccessAudio();
|
if (speakText) util.playSpeak(speakText);
|
||||||
|
else util.playSuccessAudio();
|
||||||
uni.showToast({ title: message, icon: 'success' });
|
uni.showToast({ title: message, icon: 'success' });
|
||||||
},
|
},
|
||||||
getBillTypeName() {
|
getBillTypeName() {
|
||||||
|
|||||||
@@ -125,34 +125,6 @@
|
|||||||
color: #333;
|
color: #333;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.fabric-out-scanDetailTableScroll {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.fabric-out-scanDetailTable {
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
.fabric-out-scanDetailTableRow {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
}
|
|
||||||
.fabric-out-scanDetailTableHead {
|
|
||||||
background: #f5f5f5;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.fabric-out-scanDetailTableCell {
|
|
||||||
padding: 14rpx 8rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
.fabric-out-scanDetailEmpty {
|
|
||||||
padding: 24rpx;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.fabric-out-submitView {
|
.fabric-out-submitView {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16rpx 26rpx 26rpx;
|
padding: 16rpx 26rpx 26rpx;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import {
|
|||||||
getViewStatusActionButtons,
|
getViewStatusActionButtons,
|
||||||
confirmOrderStatusAction,
|
confirmOrderStatusAction,
|
||||||
formatDisplayTime,
|
formatDisplayTime,
|
||||||
|
STATUS_ACTION_VOICE,
|
||||||
} from '@/common/storeFabricBusinessOut';
|
} from '@/common/storeFabricBusinessOut';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -123,8 +124,10 @@ export default {
|
|||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
const order = normalizeOrder(res);
|
const order = normalizeOrder(res);
|
||||||
if (order) this.order = order;
|
if (order) this.order = order;
|
||||||
util.playSuccessAudio();
|
const voice = STATUS_ACTION_VOICE[action] || '';
|
||||||
uni.showToast({ title: '操作成功', icon: 'success' });
|
if (voice) util.playSpeak(voice);
|
||||||
|
else util.playSuccessAudio();
|
||||||
|
uni.showToast({ title: voice || '操作成功', icon: 'success' });
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user