feat: 优化手动进仓扫码流程并升至1.1.22

This commit is contained in:
2026-11-08 22:38:49 +08:00
parent 5b6690b4d8
commit 4d3f767e16
7 changed files with 216 additions and 139 deletions
+13 -9
View File
@@ -46,12 +46,12 @@ export const FABRIC_INFO_FIELDS = [
/** 细码表格列(不含操作列) */ /** 细码表格列(不含操作列) */
export const FINE_CODE_TABLE_COLUMNS = [ export const FINE_CODE_TABLE_COLUMNS = [
{ label: '序号', key: 'seq', width: 70 }, { label: '序号', key: 'seq', width: 90 },
{ label: '卷号', key: 'roll_no', width: 90 }, { label: '卷号', key: 'roll_no', width: 110 },
{ label: '匹数', key: 'roll', width: 90 }, { label: '匹数', key: 'roll', width: 100 },
{ label: '数量(重量)', key: 'weight', width: 120 }, { label: '数量(重量)', key: 'weight', width: 160 },
{ label: '条码', key: 'bar_code', width: 260 }, { label: '条码', key: 'bar_code', width: 280 },
{ label: '仓位', key: 'position', width: 110 }, { label: '仓位', key: 'position', width: 140 },
]; ];
/** 审核状态变更操作配置(apiKey 对应 gfmOtherDeliveryOrder 下的方法名) */ /** 审核状态变更操作配置(apiKey 对应 gfmOtherDeliveryOrder 下的方法名) */
@@ -490,7 +490,8 @@ export function orderToFormFields(order) {
export function getFineCodeTableColumns(readonly) { export function getFineCodeTableColumns(readonly) {
const cols = [...FINE_CODE_TABLE_COLUMNS]; const cols = [...FINE_CODE_TABLE_COLUMNS];
if (!readonly) cols.push({ label: '操作', key: 'action', width: 90 }); // 操作列放前面,避免 PDA 小屏横向滚动后才看到删除
if (!readonly) cols.splice(1, 0, { label: '操作', key: 'action', width: 160 });
return cols; return cols;
} }
@@ -516,7 +517,7 @@ export function getFineCodeTableWidth(columns) {
* @param {Vue} vm 页面实例,需有 $u.api 与 showError * @param {Vue} vm 页面实例,需有 $u.api 与 showError
* @param {{ orderId: number, action: string, onSuccess?: Function }} options * @param {{ orderId: number, action: string, onSuccess?: Function }} options
*/ */
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) { export function confirmOrderStatusAction(vm, { orderId, action, onSuccess, onCancel }) {
const cfg = STATUS_ACTION_CONFIG[action]; const cfg = STATUS_ACTION_CONFIG[action];
if (!cfg || !orderId) return; if (!cfg || !orderId) return;
const api = vm.$u.api.gfmOtherDeliveryOrder; const api = vm.$u.api.gfmOtherDeliveryOrder;
@@ -526,7 +527,10 @@ export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
title: '提示', title: '提示',
content: cfg.msg, content: cfg.msg,
success: (res) => { success: (res) => {
if (!res.confirm) return; if (!res.confirm) {
if (typeof onCancel === 'function') onCancel();
return;
}
uni.showLoading({ title: '处理中...' }); uni.showLoading({ title: '处理中...' });
requestFn(buildStatusParam(orderId, cfg.status)) requestFn(buildStatusParam(orderId, cfg.status))
.then(() => { .then(() => {
+15 -11
View File
@@ -10,14 +10,12 @@
@input="$emit('input', $event.detail.value)" @input="$emit('input', $event.detail.value)"
@confirm="$emit('scan')" @confirm="$emit('scan')"
/> />
<u-icon <view
v-if="showPhoneScan" class="fabric-out-toolbarIcon"
class="fabric-out-phoneScanIcon" @click.stop="$emit('phone-scan')"
name="scan" >
size="80" <u-icon name="scan" size="44" color="#595959" />
color="#2979ff" </view>
@click="$emit('phone-scan')"
/>
<checkbox-group @change="$emit('delete-mode-change', $event)"> <checkbox-group @change="$emit('delete-mode-change', $event)">
<label class="fabric-out-delCheck"> <label class="fabric-out-delCheck">
<checkbox :checked="deleteMode" />删除 <checkbox :checked="deleteMode" />删除
@@ -37,7 +35,6 @@ export default {
deleteMode: { type: Boolean, default: false }, deleteMode: { type: Boolean, default: false },
message: { type: String, default: '' }, message: { type: String, default: '' },
disabledTip: { type: String, default: '当前状态不可扫码' }, disabledTip: { type: String, default: '当前状态不可扫码' },
showPhoneScan: { type: Boolean, default: false },
}, },
}; };
</script> </script>
@@ -45,9 +42,16 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss'; @import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
.fabric-out-phoneScanIcon { .fabric-out-toolbarIcon {
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
margin-left: 8rpx; margin-left: 8rpx;
padding: 10rpx 16rpx; border-radius: 8rpx;
border: 1rpx solid #d9d9d9;
background: #f7f7f7;
flex-shrink: 0; flex-shrink: 0;
} }
</style> </style>
+40 -12
View File
@@ -21,11 +21,17 @@
:class="{ actionCell: col.key === 'action' && !readonly }" :class="{ actionCell: col.key === 'action' && !readonly }"
:style="cellStyle(col)" :style="cellStyle(col)"
@click="onCellTap(col.key, rowIndex)" @click="onCellTap(col.key, rowIndex)"
>{{ row[col.key] }}</view> >
<text
v-if="col.key === 'action' && !readonly"
class="deleteBtn"
>{{ row[col.key] }}</text>
<template v-else>{{ row[col.key] }}</template>
</view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view v-if="!readonly && list.length" class="tableTip">点击「操作」列删除,将调用扫码删除接口</view> <view v-if="!readonly && list.length" class="tableTip">点击「删除」将调用扫码删除接口</view>
</view> </view>
</u-popup> </u-popup>
</template> </template>
@@ -81,9 +87,11 @@ export default {
}; };
</script> </script>
<style scoped> <style scoped lang="scss">
@import '@/common/pda-typography.scss';
.popupWrap { .popupWrap {
padding: 24rpx 16rpx 30rpx; padding: 28rpx 16rpx 32rpx;
max-height: 80vh; max-height: 80vh;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@@ -91,18 +99,18 @@ export default {
overflow: hidden; overflow: hidden;
} }
.popupTitle { .popupTitle {
font-size: 32rpx; font-size: $pda-font-popup-title;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
margin-bottom: 20rpx; margin-bottom: 24rpx;
color: #333; color: #333;
flex-shrink: 0; flex-shrink: 0;
} }
.emptyTip { .emptyTip {
text-align: center; text-align: center;
color: #999; color: #999;
padding: 40rpx 0; padding: 48rpx 0;
font-size: 28rpx; font-size: $pda-font-popup-sub;
} }
.tableScroll { .tableScroll {
width: 100%; width: 100%;
@@ -118,6 +126,7 @@ export default {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
align-items: stretch;
border-bottom: 1rpx solid #e1e1e1; border-bottom: 1rpx solid #e1e1e1;
} }
.tableRow:last-child { .tableRow:last-child {
@@ -131,8 +140,12 @@ export default {
} }
.tableCell { .tableCell {
flex-shrink: 0; flex-shrink: 0;
padding: 12rpx 8rpx; display: flex;
font-size: 22rpx; align-items: center;
justify-content: center;
min-height: $pda-row-min-height;
padding: 16rpx 10rpx;
font-size: $pda-font-popup-item;
color: #333; color: #333;
text-align: center; text-align: center;
border-right: 1rpx solid #e1e1e1; border-right: 1rpx solid #e1e1e1;
@@ -146,13 +159,28 @@ export default {
.headCell { .headCell {
font-weight: bold; font-weight: bold;
color: #3e3e3e; color: #3e3e3e;
min-height: 80rpx;
} }
.actionCell { .actionCell {
padding: 12rpx 8rpx;
}
.deleteBtn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 120rpx;
min-height: 72rpx;
padding: 0 20rpx;
border-radius: 8rpx;
background: #fff1f0;
border: 1rpx solid #ffa39e;
color: #f5222d; color: #f5222d;
font-size: $pda-font-action;
line-height: 1.2;
} }
.tableTip { .tableTip {
margin-top: 16rpx; margin-top: 20rpx;
font-size: 22rpx; font-size: $pda-font-popup-sub;
color: #999; color: #999;
text-align: center; text-align: center;
flex-shrink: 0; flex-shrink: 0;
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "浩拓织厂", "name" : "浩拓织厂",
"appid" : "__UNI__F79F300", "appid" : "__UNI__F79F300",
"description" : "周尚-浩拓织厂", "description" : "周尚-浩拓织厂",
"versionName" : "1.1.21", "versionName" : "1.1.22",
"versionCode" : 121, "versionCode" : 122,
"transformPx" : false, "transformPx" : false,
"app-plus" : { "app-plus" : {
"optimization" : { "optimization" : {
@@ -48,7 +48,6 @@
:delete-mode="BarCodeDelStatus" :delete-mode="BarCodeDelStatus"
:message="BillDataMessage" :message="BillDataMessage"
disabled-tip="请先提交保存后再扫码" disabled-tip="请先提交保存后再扫码"
:show-phone-scan="usePhoneScanFallback"
@input="QRBarCode = $event" @input="QRBarCode = $event"
@scan="handleScan" @scan="handleScan"
@phone-scan="handlePhoneScan" @phone-scan="handlePhoneScan"
@@ -41,7 +41,6 @@
:value="QRBarCode" :value="QRBarCode"
:delete-mode="BarCodeDelStatus" :delete-mode="BarCodeDelStatus"
:message="BillDataMessage" :message="BillDataMessage"
:show-phone-scan="usePhoneScanFallback"
@input="QRBarCode = $event" @input="QRBarCode = $event"
@scan="handleScan" @scan="handleScan"
@phone-scan="handlePhoneScan" @phone-scan="handlePhoneScan"
+146 -103
View File
@@ -9,39 +9,15 @@
</template> </template>
</common-navbar> </common-navbar>
<view class="manual-in__scan">
<input
class="manual-in__scan-input"
type="text"
confirm-type="done"
:focus="scanFocus"
v-model="scanCode"
placeholder="请点击扫描/输入布票编码"
placeholder-class="manual-in__placeholder"
@confirm="onScanConfirm"
/>
</view>
<view class="manual-in__panel"> <view class="manual-in__panel">
<view class="manual-in__row"> <view class="manual-in__row manual-in__row--input" @click="openInspectorPicker">
<text class="manual-in__label">布票编码</text> <text class="manual-in__label">查布工</text>
<text class="manual-in__value">{{ detail.fabric_piece_code || '-' }}</text> <view class="manual-in__right">
</view> <text :class="detail.inspector_name ? 'manual-in__field-text' : 'manual-in__placeholder'">
<view class="manual-in__row"> {{ detail.inspector_name || '请选择查布工' }}
<text class="manual-in__label">生产单号</text> </text>
<text class="manual-in__value">{{ detail.production_order_no || '-' }}</text> <text class="manual-in__action" @click.stop="clearInspector">取消</text>
</view> </view>
<view class="manual-in__row">
<text class="manual-in__label">机台</text>
<text class="manual-in__value">{{ detail.machine_number || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">布种名称</text>
<text class="manual-in__value">{{ detail.grey_fabric_name || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">坯布编号</text>
<text class="manual-in__value">{{ detail.grey_fabric_code || '-' }}</text>
</view> </view>
<view class="manual-in__row manual-in__row--input"> <view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">重量</text> <text class="manual-in__label">重量</text>
@@ -66,15 +42,6 @@
<text class="manual-in__action" @click.stop="clearLevel">取消</text> <text class="manual-in__action" @click.stop="clearLevel">取消</text>
</view> </view>
</view> </view>
<view class="manual-in__row manual-in__row--input" @click="openInspectorPicker">
<text class="manual-in__label">查布工</text>
<view class="manual-in__right">
<text :class="detail.inspector_name ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ detail.inspector_name || '请选择查布工' }}
</text>
<text class="manual-in__action" @click.stop="clearInspector">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input"> <view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">存放仓位</text> <text class="manual-in__label">存放仓位</text>
<view class="manual-in__right"> <view class="manual-in__right">
@@ -88,22 +55,62 @@
<text class="manual-in__action" @click="editWarehouseBin">修改</text> <text class="manual-in__action" @click="editWarehouseBin">修改</text>
</view> </view>
</view> </view>
</view>
<view class="manual-in__scan">
<input
class="manual-in__scan-input"
type="text"
confirm-type="done"
:confirm-hold="true"
:focus="scanFocus"
v-model="scanCode"
placeholder="先选查布工并填写信息,再扫描布票编码"
placeholder-class="manual-in__placeholder"
@confirm="onScanConfirm"
/>
<view
v-if="scanCode"
class="manual-in__scan-icon"
@click.stop="clearScanCode"
>
<u-icon name="close-circle-fill" size="44" color="#bfbfbf" />
</view>
<view
class="manual-in__scan-icon"
@click.stop="handlePhoneScan"
>
<u-icon name="scan" size="44" color="#595959" />
</view>
</view>
<view class="manual-in__panel">
<view class="manual-in__row">
<text class="manual-in__label">布票编码</text>
<text class="manual-in__value">{{ detail.fabric_piece_code || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">生产单号</text>
<text class="manual-in__value">{{ detail.production_order_no || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">机台</text>
<text class="manual-in__value">{{ detail.machine_number || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">布种名称</text>
<text class="manual-in__value">{{ detail.grey_fabric_name || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">坯布编号</text>
<text class="manual-in__value">{{ detail.grey_fabric_code || '-' }}</text>
</view>
<view class="manual-in__row" v-if="detail.volume_number != null && detail.volume_number !== ''"> <view class="manual-in__row" v-if="detail.volume_number != null && detail.volume_number !== ''">
<text class="manual-in__label">卷号</text> <text class="manual-in__label">卷号</text>
<text class="manual-in__value">{{ detail.volume_number }}</text> <text class="manual-in__value">{{ detail.volume_number }}</text>
</view> </view>
</view> </view>
<view class="manual-in__footer">
<u-button
type="primary"
shape="circle"
:loading="submitting"
:disabled="submitting || loadingDetail"
@click="submitInWarehouse"
>确定进仓</u-button>
</view>
<SearchSelectPopup <SearchSelectPopup
:show.sync="levelPickerShow" :show.sync="levelPickerShow"
title="选择等级" title="选择等级"
@@ -217,12 +224,19 @@ export default {
this.loadInspectorOptions(); this.loadInspectorOptions();
}, },
onShow() { onShow() {
this.scanFocus = false; this.refocusScanInput(50);
this.$nextTick(() => { },
this.scanFocus = true; onUnload() {
}); clearTimeout(this._scanFocusTimer);
}, },
methods: { methods: {
refocusScanInput(delay = 120) {
clearTimeout(this._scanFocusTimer);
this.scanFocus = false;
this._scanFocusTimer = setTimeout(() => {
this.scanFocus = true;
}, delay);
},
navToSetting() { navToSetting() {
uni.navigateTo({ url: '/pages/sys/user/scan-settings' }); uni.navigateTo({ url: '/pages/sys/user/scan-settings' });
}, },
@@ -330,6 +344,22 @@ export default {
if (!code) return; if (!code) return;
this.fetchScanDetail(code); this.fetchScanDetail(code);
}, },
clearScanCode() {
this.scanCode = '';
this.refocusScanInput();
},
handlePhoneScan() {
const err = this.validateBeforeScan();
if (err) {
this.$u.toast(err);
return;
}
this.openPhoneScan({
callback: (scanResult) => {
this.handleScanCode(scanResult);
},
});
},
onScanConfirm() { onScanConfirm() {
const code = String(this.scanCode || '').trim(); const code = String(this.scanCode || '').trim();
if (!code) { if (!code) {
@@ -338,33 +368,60 @@ export default {
} }
this.fetchScanDetail(code); this.fetchScanDetail(code);
}, },
validateBeforeScan() {
const inspectorName = String(this.detail.inspector_name || '').trim();
if (!inspectorName && (this.detail.inspector_id == null || this.detail.inspector_id === '')) {
return '请先选择查布工';
}
if (toWeightInt(this.weightKg) == null) {
return '请先输入有效重量';
}
return '';
},
async fetchScanDetail(code) { async fetchScanDetail(code) {
if (this.loadingDetail) return; if (this.loadingDetail || this.submitting) return;
const err = this.validateBeforeScan();
if (err) {
this.$u.toast(err);
return;
}
this.loadingDetail = true; this.loadingDetail = true;
this.scanCode = code; this.scanCode = code;
let detailOk = false;
try { try {
const res = await this.$u.api.productionScheduleOrder.getScanFineCodeDetail({ const res = await this.$u.api.productionScheduleOrder.getScanFineCodeDetail({
fabric_piece_code: code, fabric_piece_code: code,
}); });
this.applyScanDetail(res, code); this.applyScanDetail(res, code);
detailOk = true;
} catch (error) { } catch (error) {
console.log('扫码获取细码失败', error); console.log('扫码获取细码失败', error);
const kept = this.keepFormFields();
this.detail = { this.detail = {
...emptyDetail(), ...emptyDetail(),
fabric_piece_code: code, fabric_piece_code: code,
inspector_id: this.detail.inspector_id, ...kept,
inspector_name: this.detail.inspector_name,
}; };
this.weightKg = ''; this.scanCode = '';
this.refocusScanInput(200);
} finally { } finally {
this.loadingDetail = false; this.loadingDetail = false;
} }
if (detailOk) {
await this.submitInWarehouse();
}
},
keepFormFields() {
return {
grey_fabric_level_id: normalizeLevelIds(this.detail.grey_fabric_level_id),
grey_fabric_level_name: this.detail.grey_fabric_level_name || '',
inspector_id: this.detail.inspector_id,
inspector_name: this.detail.inspector_name || '',
};
}, },
applyScanDetail(res, fallbackCode) { applyScanDetail(res, fallbackCode) {
const data = res || {}; const data = res || {};
const levelIds = normalizeLevelIds(data.grey_fabric_level_id); const kept = this.keepFormFields();
const hasIncomingInspector = data.inspector_id != null && data.inspector_id !== ''
|| String(data.inspector_name || '').trim();
this.detail = { this.detail = {
...emptyDetail(), ...emptyDetail(),
fabric_piece_code: data.fabric_piece_code || fallbackCode || '', fabric_piece_code: data.fabric_piece_code || fallbackCode || '',
@@ -372,21 +429,11 @@ export default {
machine_number: data.machine_number || '', machine_number: data.machine_number || '',
grey_fabric_name: data.grey_fabric_name || '', grey_fabric_name: data.grey_fabric_name || '',
grey_fabric_code: data.grey_fabric_code || '', grey_fabric_code: data.grey_fabric_code || '',
inspection_weight: formatDisplayWeightKg(data.inspection_weight) ? data.inspection_weight : null, inspection_weight: toWeightInt(this.weightKg),
grey_fabric_level_id: levelIds, volume_number: data.volume_number != null && data.volume_number !== '' ? data.volume_number : null,
grey_fabric_level_name: Array.isArray(data.grey_fabric_level_name) ...kept,
? data.grey_fabric_level_name.filter(Boolean).join('、')
: (data.grey_fabric_level_name || ''),
inspector_id: hasIncomingInspector && data.inspector_id != null && data.inspector_id !== ''
? data.inspector_id
: this.detail.inspector_id,
inspector_name: hasIncomingInspector
? (data.inspector_name || this.detail.inspector_name || '')
: (this.detail.inspector_name || ''),
}; };
this.weightKg = formatDisplayWeightKg(data.inspection_weight);
this.scanCode = this.detail.fabric_piece_code; this.scanCode = this.detail.fabric_piece_code;
util.playSpeak('扫码成功');
}, },
async submitInWarehouse() { async submitInWarehouse() {
const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim(); const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim();
@@ -440,26 +487,21 @@ export default {
this.resetForNextScan(); this.resetForNextScan();
} catch (error) { } catch (error) {
console.log('手动进仓失败', error); console.log('手动进仓失败', error);
this.scanCode = '';
this.refocusScanInput(200);
} finally { } finally {
this.submitting = false; this.submitting = false;
} }
}, },
resetForNextScan() { resetForNextScan() {
const inspectorId = this.detail.inspector_id; const kept = this.keepFormFields();
const inspectorName = this.detail.inspector_name;
this.scanCode = ''; this.scanCode = '';
this.detail = { this.detail = {
...emptyDetail(), ...emptyDetail(),
inspector_id: inspectorId, ...kept,
inspector_name: inspectorName,
}; };
this.weightKg = ''; // toast 会抢焦点,稍后再聚焦,方便连续扫码
this.$nextTick(() => { this.refocusScanInput(600);
this.scanFocus = false;
this.$nextTick(() => {
this.scanFocus = true;
});
});
}, },
}, },
}; };
@@ -471,7 +513,7 @@ export default {
.manual-in { .manual-in {
min-height: 100vh; min-height: 100vh;
background: #f5f6f8; background: #f5f6f8;
padding-bottom: 180rpx; padding-bottom: 40rpx;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -487,14 +529,31 @@ export default {
margin: 16rpx 0; margin: 16rpx 0;
padding: 0 28rpx; padding: 0 28rpx;
background: #fff; background: #fff;
display: flex;
align-items: center;
gap: 12rpx;
} }
.manual-in__scan-input { .manual-in__scan-input {
flex: 1;
min-width: 0;
height: $pda-scan-input-height; height: $pda-scan-input-height;
font-size: $pda-font-body; font-size: $pda-font-body;
color: #202328; color: #202328;
} }
.manual-in__scan-icon {
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
flex-shrink: 0;
border-radius: 8rpx;
border: 1rpx solid #d9d9d9;
background: #f7f7f7;
}
.manual-in__placeholder { .manual-in__placeholder {
color: #b0b4bb; color: #b0b4bb;
font-size: $pda-font-placeholder; font-size: $pda-font-placeholder;
@@ -569,20 +628,4 @@ export default {
color: #2f78e6; color: #2f78e6;
flex-shrink: 0; flex-shrink: 0;
} }
.manual-in__footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 40rpx calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
}
::v-deep .manual-in__footer .u-btn {
height: $pda-button-height !important;
line-height: $pda-button-height !important;
font-size: $pda-font-button !important;
}
</style> </style>