feat: 细码改独立页并优化首屏,修改密码简化并升至1.1.27

This commit is contained in:
2026-11-13 15:15:58 +08:00
parent b1648bcb30
commit d848e63091
11 changed files with 847 additions and 141 deletions
+2
View File
@@ -80,6 +80,8 @@ const install = (Vue, vm) => {
// PDA登录接口 // PDA登录接口
pdaLogin: (params = {}) => vm.$u.postJson('/login', params), pdaLogin: (params = {}) => vm.$u.postJson('/login', params),
pdaLogout: (params = {}) => vm.$u.postJson('/logout', params), pdaLogout: (params = {}) => vm.$u.postJson('/logout', params),
// 修改密码:PUT pda/v1/user/updatePassword(system.UpdateUserPasswordParam: password, user_id)
updatePassword: (params = {}) => vm.$u.putJson('/user/updatePassword', params),
// 获取配布单列表 // 获取配布单列表
getFpmArrangeOrderList: (params = {}) => vm.$u.get('/product/fpmArrangeOrder/getFpmArrangeOrderList', params), getFpmArrangeOrderList: (params = {}) => vm.$u.get('/product/fpmArrangeOrder/getFpmArrangeOrderList', params),
// 获取成品配布单详情 // 获取成品配布单详情
+78 -3
View File
@@ -506,8 +506,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];
// 操作列放前面,避免 PDA 小屏横向滚动后才看到删除 // 非只读:勾选列放前面,支持多选批量删除
if (!readonly) cols.splice(1, 0, { label: '操作', key: 'action', width: 160 }); if (!readonly) cols.unshift({ label: '选择', key: 'select', width: 100 });
return cols; return cols;
} }
@@ -519,7 +519,7 @@ export function mapFineCodeTableRows(list, readonly) {
weight: item.weight != null && item.weight !== '' ? `${item.weight}` : '-', weight: item.weight != null && item.weight !== '' ? `${item.weight}` : '-',
bar_code: item.bar_code || '-', bar_code: item.bar_code || '-',
position: item.position || '-', position: item.position || '-',
action: readonly ? '' : '删除', select: readonly ? '' : 'select',
})); }));
} }
@@ -528,6 +528,81 @@ export function getFineCodeTableWidth(columns) {
return `${total}rpx`; return `${total}rpx`;
} }
/** 按 group_key 在汇总表或明细表中定位细码分组 */
export function findFineCodeGroup(order, groupKey) {
if (!order || !groupKey) return null;
const fromSummary = (order.summary_rows || []).find((row) => row.group_key === groupKey);
if (fromSummary) return fromSummary;
return (order.item_data || []).find(
(row) => row.group_key === groupKey || String(row.id) === String(groupKey),
) || null;
}
/** 细码列表合计(条数 / 匹数 / 重量,数值已是展示单位) */
export function sumFineCodeList(list) {
let roll = 0;
let weight = 0;
const count = (list || []).length;
(list || []).forEach((fc) => {
roll += Number(fc.roll) || 0;
weight += Number(fc.weight) || 0;
});
return {
count,
roll: Number.isInteger(roll) ? roll : Number(roll.toFixed(2)),
weight: Number(weight.toFixed(2)),
};
}
/** 细码明细页入参缓存(避免进页再拉整单详情卡住;必须带 orderId 防串单) */
export const FINE_CODE_PAGE_CACHE_KEY = 'fabric_out_fine_code_page';
export function buildFineCodePagePayload(orderId, row) {
if (!orderId || !row) return null;
return {
order_id: Number(orderId),
group_key: row.group_key || String(row.id || ''),
grey_fabric_code: row.grey_fabric_code || '',
grey_fabric_name: row.grey_fabric_name || '',
yarn_batch: row.yarn_batch || '',
machine_number: row.machine_number || '',
fine_codes: [...(row.fine_codes || [])],
};
}
export function saveFineCodePagePayload(orderId, row) {
const payload = buildFineCodePagePayload(orderId, row);
if (!payload) return null;
try {
uni.setStorageSync(FINE_CODE_PAGE_CACHE_KEY, payload);
} catch (e) {
// 存储失败时仍可通过 eventChannel 传参
}
return payload;
}
/** 读取并清除缓存;仅当 orderId + groupKey 同时匹配时返回,避免串单 */
export function takeFineCodePagePayload(orderId, groupKey) {
try {
const payload = uni.getStorageSync(FINE_CODE_PAGE_CACHE_KEY) || null;
uni.removeStorageSync(FINE_CODE_PAGE_CACHE_KEY);
if (!payload) return null;
if (Number(payload.order_id) !== Number(orderId)) return null;
if (String(payload.group_key || '') !== String(groupKey || '')) return null;
return payload;
} catch (e) {
return null;
}
}
export function clearFineCodePagePayload() {
try {
uni.removeStorageSync(FINE_CODE_PAGE_CACHE_KEY);
} catch (e) {
// ignore
}
}
/** /**
* 确认并执行审核状态变更(新增/编辑/查看页共用) * 确认并执行审核状态变更(新增/编辑/查看页共用)
* @param {Vue} vm 页面实例,需有 $u.api 与 showError * @param {Vue} vm 页面实例,需有 $u.api 与 showError
+98 -33
View File
@@ -3,35 +3,60 @@
<view class="popupWrap"> <view class="popupWrap">
<view class="popupTitle">细码</view> <view class="popupTitle">细码</view>
<view v-if="!list.length" class="emptyTip">暂无细码数据</view> <view v-if="!list.length" class="emptyTip">暂无细码数据</view>
<scroll-view v-else scroll-x class="tableScroll"> <template v-else>
<scroll-view scroll-x class="tableScroll">
<view class="fineTable" :style="{ width: tableWidth }"> <view class="fineTable" :style="{ width: tableWidth }">
<view class="tableRow tableHead"> <view class="tableRow tableHead">
<view <view
v-for="col in columns" v-for="col in columns"
:key="col.key" :key="col.key"
class="tableCell headCell" class="tableCell headCell"
:class="{ selectCell: col.key === 'select' }"
:style="cellStyle(col)" :style="cellStyle(col)"
>{{ col.label }}</view> @click="col.key === 'select' && !readonly ? toggleSelectAll() : null"
>
<checkbox
v-if="col.key === 'select' && !readonly"
:checked="isAllSelected"
@click.stop="toggleSelectAll"
/>
<template v-else>{{ col.label }}</template>
</view> </view>
<view v-for="(row, rowIndex) in tableRows" :key="rowIndex" class="tableRow"> </view>
<view
v-for="(row, rowIndex) in tableRows"
:key="rowIndex"
class="tableRow"
@click="!readonly && toggleRow(rowIndex)"
>
<view <view
v-for="col in columns" v-for="col in columns"
:key="col.key" :key="col.key"
class="tableCell" class="tableCell"
:class="{ actionCell: col.key === 'action' && !readonly }" :class="{ selectCell: col.key === 'select' && !readonly }"
:style="cellStyle(col)" :style="cellStyle(col)"
@click="onCellTap(col.key, rowIndex)"
> >
<text <checkbox
v-if="col.key === 'action' && !readonly" v-if="col.key === 'select' && !readonly"
class="deleteBtn" :checked="isRowSelected(rowIndex)"
>{{ row[col.key] }}</text> @click.stop="toggleRow(rowIndex)"
/>
<template v-else>{{ row[col.key] }}</template> <template v-else>{{ row[col.key] }}</template>
</view> </view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view v-if="!readonly && list.length" class="tableTip">点击「删除」将调用扫码删除接口</view> <view v-if="!readonly" class="batchBar">
<text class="batchHint">已选 {{ selectedCount }} 条</text>
<u-button
type="error"
size="medium"
:disabled="selectedCount === 0"
@click="emitBatchDelete"
>删除选中({{ selectedCount }})</u-button>
</view>
<view v-if="!readonly" class="tableTip">勾选细码后点击「删除选中」批量删除</view>
</template>
</view> </view>
</u-popup> </u-popup>
</template> </template>
@@ -49,6 +74,11 @@ export default {
list: { type: Array, default: () => [] }, list: { type: Array, default: () => [] },
readonly: { type: Boolean, default: false }, readonly: { type: Boolean, default: false },
}, },
data() {
return {
selectedIndexes: [],
};
},
computed: { computed: {
visible: { visible: {
get() { return this.show; }, get() { return this.show; },
@@ -63,11 +93,19 @@ export default {
tableWidth() { tableWidth() {
return getFineCodeTableWidth(this.columns); return getFineCodeTableWidth(this.columns);
}, },
tableScrollHeight() { selectedCount() {
const rowHeight = 56; return this.selectedIndexes.length;
const headerHeight = 56; },
const total = headerHeight + this.list.length * rowHeight; isAllSelected() {
return `${Math.min(Math.max(total, 112), 480)}rpx`; return this.list.length > 0 && this.selectedIndexes.length === this.list.length;
},
},
watch: {
show(val) {
if (!val) this.clearSelection();
},
list() {
this.clearSelection();
}, },
}, },
methods: { methods: {
@@ -78,10 +116,38 @@ export default {
maxWidth: `${col.width}rpx`, maxWidth: `${col.width}rpx`,
}; };
}, },
onCellTap(key, rowIndex) { clearSelection() {
if (this.readonly || key !== 'action') return; this.selectedIndexes = [];
const item = this.list[rowIndex]; },
if (item) this.$emit('delete', item); isRowSelected(rowIndex) {
return this.selectedIndexes.indexOf(rowIndex) !== -1;
},
toggleRow(rowIndex) {
if (this.readonly) return;
const idx = this.selectedIndexes.indexOf(rowIndex);
if (idx === -1) {
this.selectedIndexes = this.selectedIndexes.concat(rowIndex);
} else {
this.selectedIndexes = this.selectedIndexes.filter((i) => i !== rowIndex);
}
},
toggleSelectAll() {
if (this.readonly) return;
if (this.isAllSelected) {
this.clearSelection();
return;
}
this.selectedIndexes = this.list.map((_, index) => index);
},
emitBatchDelete() {
if (this.readonly || !this.selectedCount) return;
const items = this.selectedIndexes
.slice()
.sort((a, b) => a - b)
.map((index) => this.list[index])
.filter(Boolean);
if (!items.length) return;
this.$emit('batch-delete', items);
}, },
}, },
}; };
@@ -115,6 +181,7 @@ export default {
.tableScroll { .tableScroll {
width: 100%; width: 100%;
flex-shrink: 0; flex-shrink: 0;
max-height: 52vh;
} }
.fineTable { .fineTable {
display: inline-block; display: inline-block;
@@ -161,25 +228,23 @@ export default {
color: #3e3e3e; color: #3e3e3e;
min-height: 80rpx; min-height: 80rpx;
} }
.actionCell { .selectCell {
padding: 12rpx 8rpx; padding: 12rpx 8rpx;
} }
.deleteBtn { .batchBar {
display: inline-flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: space-between;
min-width: 120rpx; gap: 20rpx;
min-height: 72rpx; margin-top: 24rpx;
padding: 0 20rpx; flex-shrink: 0;
border-radius: 8rpx; }
background: #fff1f0; .batchHint {
border: 1rpx solid #ffa39e; font-size: $pda-font-popup-sub;
color: #f5222d; color: #666;
font-size: $pda-font-action;
line-height: 1.2;
} }
.tableTip { .tableTip {
margin-top: 20rpx; margin-top: 16rpx;
font-size: $pda-font-popup-sub; font-size: $pda-font-popup-sub;
color: #999; color: #999;
text-align: center; text-align: center;
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "浩拓织厂", "name" : "浩拓织厂",
"appid" : "__UNI__F79F300", "appid" : "__UNI__F79F300",
"description" : "周尚-浩拓织厂", "description" : "周尚-浩拓织厂",
"versionName" : "1.1.26", "versionName" : "1.1.27",
"versionCode" : 126, "versionCode" : 127,
"transformPx" : false, "transformPx" : false,
"app-plus" : { "app-plus" : {
"optimization" : { "optimization" : {
+7
View File
@@ -605,6 +605,13 @@
"navigationBarTitleText": "查看坯布出仓单", "navigationBarTitleText": "查看坯布出仓单",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
},{
"path" : "pages/storefabric/storeFabricBusinessOutFineCode",
"style" :
{
"navigationBarTitleText": "细码明细",
"enablePullDownRefresh": false
}
},{ },{
"path" : "pages/storefabric/storeFabricWarehouseGroupList", "path" : "pages/storefabric/storeFabricWarehouseGroupList",
"style" : "style" :
@@ -92,14 +92,12 @@
@confirm="selectConfirmFun" @confirm="selectConfirmFun"
/> />
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" /> <FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="false" @delete="onDeleteFineCode" />
</view> </view>
</template> </template>
<script> <script>
import formMixin from './storeFabricBusinessOutMixin.js'; import formMixin from './storeFabricBusinessOutMixin.js';
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue'; import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue'; import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue'; import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue'; import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
@@ -111,7 +109,6 @@ import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
export default { export default {
components: { components: {
FabricInfoPopup, FabricInfoPopup,
FineCodePopup,
OrderStatusBar, OrderStatusBar,
FabricOutScanBar, FabricOutScanBar,
FabricOutScanDetail, FabricOutScanDetail,
@@ -75,14 +75,12 @@
@confirm="selectConfirmFun" @confirm="selectConfirmFun"
/> />
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" /> <FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="false" @delete="onDeleteFineCode" />
</view> </view>
</template> </template>
<script> <script>
import formMixin from './storeFabricBusinessOutMixin.js'; import formMixin from './storeFabricBusinessOutMixin.js';
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue'; import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue'; import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue'; import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue'; import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
@@ -94,7 +92,6 @@ import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
export default { export default {
components: { components: {
FabricInfoPopup, FabricInfoPopup,
FineCodePopup,
OrderStatusBar, OrderStatusBar,
FabricOutScanBar, FabricOutScanBar,
FabricOutScanDetail, FabricOutScanDetail,
@@ -0,0 +1,566 @@
<template>
<view class="fine-code-page">
<scroll-view
scroll-y
class="pageScroll"
:style="{ height: scrollHeight }"
refresher-enabled
:refresher-triggered="triggered"
@refresherrefresh="onRefresh"
@scrolltolower="onScrollToLower"
>
<view class="infoCard">
<view class="infoTitle">汇总信息</view>
<view class="infoRow">
<text class="infoLabel">坯布编号</text>
<text class="infoValue">{{ summary.grey_fabric_code || '-' }}</text>
</view>
<view class="infoRow">
<text class="infoLabel">坯布名称</text>
<text class="infoValue">{{ summary.grey_fabric_name || '-' }}</text>
</view>
<view class="infoRow">
<text class="infoLabel">纱  批</text>
<text class="infoValue">{{ summary.yarn_batch || '-' }}</text>
</view>
<view class="infoRow">
<text class="infoLabel">机 台 号</text>
<text class="infoValue">{{ summary.machine_number || '-' }}</text>
</view>
<view class="infoRow">
<text class="infoLabel">合计条数</text>
<text class="infoValue">{{ totals.count }} 条</text>
</view>
<view class="infoRow">
<text class="infoLabel">合计重量</text>
<text class="infoValue">{{ totals.weight }} KG</text>
</view>
</view>
<view class="summaryBar">
<text>细码明细:{{ totals.count }} 条 / {{ totals.roll }} 匹 / {{ totals.weight }} KG</text>
</view>
<view v-if="!fineCodes.length" class="emptyTip">{{ loading ? '加载中...' : '暂无细码数据' }}</view>
<scroll-view v-else scroll-x class="tableScroll">
<view class="fineTable" :style="{ width: tableWidth }">
<view class="tableRow tableHead">
<view
v-for="col in columns"
:key="col.key"
class="tableCell headCell"
:class="{ selectCell: col.key === 'select' }"
:style="cellStyle(col)"
@click="col.key === 'select' && !readonly ? toggleSelectAll() : null"
>
<checkbox
v-if="col.key === 'select' && !readonly"
:checked="isAllSelected"
@click.stop="toggleSelectAll"
/>
<template v-else>{{ col.label }}</template>
</view>
</view>
<view
v-for="(row, rowIndex) in tableRows"
:key="rowIndex"
class="tableRow"
@click="!readonly && toggleRow(rowIndex)"
>
<view
v-for="col in columns"
:key="col.key"
class="tableCell"
:class="{ selectCell: col.key === 'select' && !readonly }"
:style="cellStyle(col)"
>
<checkbox
v-if="col.key === 'select' && !readonly"
:checked="!!selectedMap[rowIndex]"
@click.stop="toggleRow(rowIndex)"
/>
<template v-else>{{ row[col.key] }}</template>
</view>
</view>
</view>
</scroll-view>
<view v-if="hasMore" class="moreTip">正在加载更多细码({{ visibleCount }}/{{ fineCodes.length }})...</view>
</scroll-view>
<view v-if="!readonly" class="pageFooter">
<view class="batchBar">
<text class="batchHint">已选 {{ selectedCount }} 条</text>
<u-button
type="error"
size="medium"
:disabled="selectedCount === 0"
@click="onBatchDelete"
>删除选中({{ selectedCount }})</u-button>
</view>
<view class="tableTip">勾选细码后点击「删除选中」批量删除</view>
</view>
</view>
</template>
<script>
import util from '@/common/util';
import {
normalizeOrder,
getFineCodeTableColumns,
getFineCodeTableWidth,
mapFineCodeTableRows,
getFineCodeScanCode,
buildScanUpdateParams,
findFineCodeGroup,
sumFineCodeList,
canScanAtStatus,
takeFineCodePagePayload,
clearFineCodePagePayload,
} from '@/common/storeFabricBusinessOut';
const RENDER_CHUNK = 80;
export default {
data() {
return {
orderId: 0,
groupKey: '',
readonly: true,
loading: false,
fineCodes: [],
visibleCount: 0,
summary: {
grey_fabric_code: '',
grey_fabric_name: '',
yarn_batch: '',
machine_number: '',
},
selectedMap: {},
scrollHeight: '100%',
triggered: false,
changed: false,
_chunkTimer: null,
};
},
computed: {
columns() {
return getFineCodeTableColumns(this.readonly);
},
visibleFineCodes() {
return this.fineCodes.slice(0, this.visibleCount);
},
tableRows() {
return mapFineCodeTableRows(this.visibleFineCodes, this.readonly);
},
tableWidth() {
return getFineCodeTableWidth(this.columns);
},
totals() {
return sumFineCodeList(this.fineCodes);
},
selectedCount() {
return Object.keys(this.selectedMap).filter((k) => this.selectedMap[k]).length;
},
isAllSelected() {
return this.fineCodes.length > 0 && this.selectedCount === this.fineCodes.length;
},
hasMore() {
return this.visibleCount < this.fineCodes.length;
},
},
onLoad(options) {
uni.setNavigationBarTitle({ title: '细码明细' });
this.orderId = Number(options.id || 0);
this.groupKey = decodeURIComponent(options.groupKey || '');
this.readonly = String(options.readonly || '1') === '1';
if (!this.orderId || !this.groupKey) {
uni.showToast({ title: '缺少参数', icon: 'none' });
return;
}
// 优先用上一页传入的数据立刻出屏;必须 orderId + groupKey 双匹配,防止串单
const cached = takeFineCodePagePayload(this.orderId, this.groupKey);
if (cached) this.applyPayload(cached);
const channel = this.getOpenerEventChannel && this.getOpenerEventChannel();
if (channel) {
channel.on('initData', (payload) => {
if (!this.isPayloadMatch(payload)) return;
this.applyPayload(payload);
});
}
// 无匹配缓存时再拉接口;有缓存则不阻塞首屏
if (!this.fineCodes.length) {
this.loadDetail();
}
},
onReady() {
this.updateScrollHeight();
},
onUnload() {
this.clearChunkTimer();
clearFineCodePagePayload();
if (!this.changed) return;
const channel = this.getOpenerEventChannel && this.getOpenerEventChannel();
if (channel) channel.emit('fineCodeChanged');
},
methods: {
updateScrollHeight() {
this.$nextTick(() => {
uni.getSystemInfo({
success: (sys) => {
if (this.readonly) {
this.scrollHeight = `${sys.windowHeight}px`;
return;
}
const query = uni.createSelectorQuery().in(this);
query.select('.pageFooter').boundingClientRect();
query.exec((rects) => {
const footerH = (rects[0] && rects[0].height) || 0;
const h = sys.windowHeight - footerH;
this.scrollHeight = `${Math.max(Math.floor(h), 200)}px`;
});
},
});
});
},
cellStyle(col) {
return {
width: `${col.width}rpx`,
minWidth: `${col.width}rpx`,
maxWidth: `${col.width}rpx`,
};
},
clearChunkTimer() {
if (this._chunkTimer) {
clearTimeout(this._chunkTimer);
this._chunkTimer = null;
}
},
/** 分批渲染,避免一次挂载几百行卡住 PDA */
scheduleChunkRender(reset) {
this.clearChunkTimer();
if (reset) this.visibleCount = 0;
const step = () => {
if (this.visibleCount >= this.fineCodes.length) {
this._chunkTimer = null;
return;
}
this.visibleCount = Math.min(this.visibleCount + RENDER_CHUNK, this.fineCodes.length);
if (this.visibleCount < this.fineCodes.length) {
this._chunkTimer = setTimeout(step, 16);
} else {
this._chunkTimer = null;
}
};
// 首批同步渲染,马上可见
this.visibleCount = Math.min(RENDER_CHUNK, this.fineCodes.length);
if (this.visibleCount < this.fineCodes.length) {
this._chunkTimer = setTimeout(step, 16);
}
},
onScrollToLower() {
if (!this.hasMore) return;
this.visibleCount = Math.min(this.visibleCount + RENDER_CHUNK, this.fineCodes.length);
},
isPayloadMatch(payload) {
if (!payload) return false;
if (Number(payload.order_id) !== Number(this.orderId)) return false;
if (String(payload.group_key || '') !== String(this.groupKey || '')) return false;
return true;
},
applyPayload(payload) {
if (!this.isPayloadMatch(payload)) return;
this.applyGroup({
grey_fabric_code: payload.grey_fabric_code,
grey_fabric_name: payload.grey_fabric_name,
yarn_batch: payload.yarn_batch,
machine_number: payload.machine_number,
fine_codes: payload.fine_codes || [],
});
},
applyGroup(row) {
if (!row) {
this.fineCodes = [];
this.visibleCount = 0;
this.summary = {
grey_fabric_code: '',
grey_fabric_name: '',
yarn_batch: '',
machine_number: '',
};
this.clearSelection();
this.clearChunkTimer();
return;
}
this.summary = {
grey_fabric_code: row.grey_fabric_code || '',
grey_fabric_name: row.grey_fabric_name || '',
yarn_batch: row.yarn_batch || '',
machine_number: row.machine_number || '',
};
this.fineCodes = [...(row.fine_codes || [])];
this.clearSelection();
this.scheduleChunkRender(true);
this.updateScrollHeight();
},
loadDetail() {
this.loading = true;
return this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId })
.then((res) => {
this.triggered = false;
this.loading = false;
const order = normalizeOrder(res);
if (!order) {
this.showError('单据不存在');
return null;
}
if (!this.readonly && !canScanAtStatus(order.audit_status)) {
this.readonly = true;
}
const row = findFineCodeGroup(order, this.groupKey);
if (!row) {
this.applyGroup(null);
uni.showToast({ title: '未找到对应细码分组', icon: 'none' });
return order;
}
this.applyGroup(row);
return order;
})
.catch((err) => {
this.triggered = false;
this.loading = false;
this.showError(err.message || '加载失败');
return null;
});
},
onRefresh() {
this.triggered = true;
this.loadDetail();
},
clearSelection() {
this.selectedMap = {};
},
toggleRow(rowIndex) {
if (this.readonly) return;
const next = { ...this.selectedMap };
if (next[rowIndex]) delete next[rowIndex];
else next[rowIndex] = true;
this.selectedMap = next;
},
toggleSelectAll() {
if (this.readonly) return;
if (this.isAllSelected) {
this.clearSelection();
return;
}
const next = {};
for (let i = 0; i < this.fineCodes.length; i += 1) next[i] = true;
this.selectedMap = next;
},
onBatchDelete() {
if (this.readonly || !this.selectedCount) return;
const indexes = Object.keys(this.selectedMap)
.map((k) => Number(k))
.filter((i) => this.selectedMap[i])
.sort((a, b) => a - b);
const targets = indexes
.map((index) => this.fineCodes[index])
.filter(Boolean)
.map((item) => ({
item,
scanCode: getFineCodeScanCode(item),
}))
.filter((row) => row.scanCode);
if (!targets.length) {
this.showError('选中细码缺少条码,无法删除');
return;
}
uni.showModal({
title: '提示',
content: `确认删除选中的 ${targets.length} 条细码?`,
success: async (res) => {
if (!res.confirm) return;
uni.showLoading({ title: `删除中 0/${targets.length}` });
let successCount = 0;
let failMessage = '';
for (let i = 0; i < targets.length; i += 1) {
uni.showLoading({ title: `删除中 ${i + 1}/${targets.length}` });
try {
await this.$u.api.gfmOtherDeliveryOrder.scanUpdate(buildScanUpdateParams({
id: this.orderId,
scanCode: targets[i].scanCode,
scanType: 2,
groupKeys: [],
detail: {},
}));
successCount += 1;
} catch (e) {
failMessage = (e && e.message) || '删除失败';
break;
}
}
uni.hideLoading();
if (successCount > 0) this.changed = true;
await this.loadDetail();
if (successCount === targets.length) {
this.showSuccess(`已删除 ${successCount} 条`, '删除成功');
return;
}
if (successCount > 0) {
this.showError(`已删除 ${successCount} 条,其余失败:${failMessage}`);
return;
}
this.showError(failMessage || '删除失败');
},
});
},
showError(message) {
util.playErrorAudio();
uni.showModal({ title: '提示', content: message, showCancel: false });
},
showSuccess(message, speakText) {
if (speakText) util.playSpeak(speakText);
else util.playSuccessAudio();
uni.showToast({ title: message, icon: 'success' });
},
},
};
</script>
<style>
page { background-color: #F8F8F8; height: 100%; }
.fine-code-page {
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.pageScroll {
width: 100%;
box-sizing: border-box;
}
.infoCard {
margin: 20rpx 24rpx 0;
padding: 20rpx 24rpx;
background: #fff;
border-radius: 12rpx;
}
.infoTitle {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 16rpx;
color: #333;
}
.infoRow {
display: flex;
font-size: 26rpx;
padding: 8rpx 0;
}
.infoLabel {
width: 160rpx;
color: #999;
flex-shrink: 0;
}
.infoValue {
flex: 1;
color: #333;
word-break: break-all;
}
.summaryBar {
margin: 16rpx 24rpx 0;
padding: 12rpx 16rpx;
font-size: 26rpx;
color: #666;
background: #fff;
border-radius: 8rpx;
}
.emptyTip {
text-align: center;
color: #999;
padding: 80rpx 0;
font-size: 28rpx;
}
.moreTip {
text-align: center;
color: #999;
padding: 16rpx 0 28rpx;
font-size: 24rpx;
}
.tableScroll {
width: auto;
margin: 16rpx 24rpx 8rpx;
background: #fff;
border-radius: 8rpx;
box-sizing: border-box;
}
.fineTable {
display: inline-block;
vertical-align: top;
border: 1rpx solid #e1e1e1;
box-sizing: border-box;
}
.tableRow {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: stretch;
border-bottom: 1rpx solid #e1e1e1;
}
.tableRow:last-child {
border-bottom: none;
}
.tableHead {
background: #f1f1f1;
}
.tableCell {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 72rpx;
padding: 12rpx 8rpx;
font-size: 22rpx;
color: #333;
text-align: center;
border-right: 1rpx solid #e1e1e1;
box-sizing: border-box;
word-break: break-all;
white-space: normal;
}
.tableCell:last-child {
border-right: none;
}
.headCell {
font-weight: bold;
color: #3e3e3e;
min-height: 80rpx;
}
.selectCell {
padding: 12rpx 8rpx;
}
.pageFooter {
flex-shrink: 0;
padding: 16rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
border-top: 1rpx solid #eee;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
}
.batchBar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
}
.batchHint {
font-size: 26rpx;
color: #666;
}
.tableTip {
margin-top: 12rpx;
font-size: 24rpx;
color: #999;
text-align: center;
}
</style>
@@ -16,7 +16,6 @@ import {
BUSINESS_UNIT_TYPE_DYEING_FACTORY, BUSINESS_UNIT_TYPE_DYEING_FACTORY,
normalizeOrder, normalizeOrder,
mapToSelectOptions, mapToSelectOptions,
getFineCodeScanCode,
canScanAtStatus, canScanAtStatus,
isLockedOrderStatus, isLockedOrderStatus,
getFormStatusActionButtons, getFormStatusActionButtons,
@@ -32,6 +31,7 @@ import {
buildScanUpdateParams, buildScanUpdateParams,
getOrderDisplayWeight, getOrderDisplayWeight,
getFineCodeDisplayWeight, getFineCodeDisplayWeight,
saveFineCodePagePayload,
} from '@/common/storeFabricBusinessOut'; } from '@/common/storeFabricBusinessOut';
export default { export default {
@@ -53,10 +53,7 @@ export default {
BillDataMessage: '', BillDataMessage: '',
currentScanItem: null, currentScanItem: null,
fabricInfoShow: false, fabricInfoShow: false,
fineCodeShow: false,
currentFabricInfo: {}, currentFabricInfo: {},
currentFineCodes: [],
currentGroupKey: '',
scanDetail: getEmptyScanDetail(), scanDetail: getEmptyScanDetail(),
scanGroupKeys: [], scanGroupKeys: [],
lastScanCode: '', lastScanCode: '',
@@ -342,8 +339,7 @@ export default {
this.BarCodeDelStatus = (e.detail.value || []).length > 0; this.BarCodeDelStatus = (e.detail.value || []).length > 0;
}, },
/** 扫码新增/删除细码,统一走 scanUpdate 接口 */ /** 扫码新增/删除细码,统一走 scanUpdate 接口 */
doScanUpdate(scanCode, scanType, options = {}) { doScanUpdate(scanCode, scanType) {
const { refreshFineCodes = false } = options;
uni.showLoading({ title: scanType === 2 ? '删除中...' : '处理中...' }); uni.showLoading({ title: scanType === 2 ? '删除中...' : '处理中...' });
return this.$u.api.gfmOtherDeliveryOrder.scanUpdate(buildScanUpdateParams({ return this.$u.api.gfmOtherDeliveryOrder.scanUpdate(buildScanUpdateParams({
id: this.orderId, id: this.orderId,
@@ -359,20 +355,17 @@ export default {
this.form.summary_rows = order.summary_rows || []; this.form.summary_rows = order.summary_rows || [];
this.auditStatus = order.audit_status; this.auditStatus = order.audit_status;
this.applyScanDetail(scanCode, order, scanType); this.applyScanDetail(scanCode, order, scanType);
if (refreshFineCodes) {
const rows = order.summary_rows || [];
const row = rows.find((i) => i.group_key === this.currentGroupKey);
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.playSpeak('扫码出仓成功'); else util.playSpeak('扫码出仓成功');
return order;
}) })
.catch((e) => { .catch((e) => {
uni.hideLoading(); uni.hideLoading();
this.showError(e.message || (scanType === 2 ? '删除失败' : '扫码失败')); this.showError(e.message || (scanType === 2 ? '删除失败' : '扫码失败'));
this.QRBarCode = ''; this.QRBarCode = '';
throw e;
}); });
}, },
openFabricInfo(row) { openFabricInfo(row) {
@@ -380,9 +373,30 @@ export default {
this.fabricInfoShow = true; this.fabricInfoShow = true;
}, },
openFineCode(row) { openFineCode(row) {
this.currentGroupKey = row.group_key || String(row.id); if (!this.orderId) {
this.currentFineCodes = [...(row.fine_codes || [])]; this.showError('请先提交保存后再查看细码');
this.fineCodeShow = true; return;
}
const groupKey = row.group_key || String(row.id || '');
if (!groupKey) {
this.showError('缺少细码分组信息');
return;
}
const readonly = (this.isReadonly || !this.canScan) ? 1 : 0;
const payload = saveFineCodePagePayload(this.orderId, row);
uni.navigateTo({
url: `./storeFabricBusinessOutFineCode?id=${this.orderId}&groupKey=${encodeURIComponent(groupKey)}&readonly=${readonly}`,
events: {
fineCodeChanged: () => {
if (this.orderId) this.loadOrder(this.orderId);
},
},
success: (res) => {
if (payload && res.eventChannel) {
res.eventChannel.emit('initData', payload);
}
},
});
}, },
onSummaryCellClick(event) { onSummaryCellClick(event) {
const row = event && event.lineData; const row = event && event.lineData;
@@ -393,22 +407,6 @@ export default {
} }
this.openFineCode(row); this.openFineCode(row);
}, },
onDeleteFineCode(fineCode) {
if (this.isReadonly || !this.canScan) return;
const scanCode = getFineCodeScanCode(fineCode);
if (!scanCode) {
this.showError('该细码缺少条码,无法删除');
return;
}
uni.showModal({
title: '提示',
content: `确认删除细码 ${scanCode}?`,
success: (res) => {
if (!res.confirm) return;
this.doScanUpdate(scanCode, 2, { refreshFineCodes: true });
},
});
},
handleStatusAction(action) { handleStatusAction(action) {
confirmOrderStatusAction(this, { confirmOrderStatusAction(this, {
orderId: this.orderId, orderId: this.orderId,
@@ -36,14 +36,12 @@
<FabricOutFooter :buttons="actionButtons" @action="handleAction" /> <FabricOutFooter :buttons="actionButtons" @action="handleAction" />
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" /> <FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="true" />
</view> </view>
</template> </template>
<script> <script>
import util from '@/common/util'; import util from '@/common/util';
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue'; import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue'; import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
import FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue'; import FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue';
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue'; import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
@@ -54,19 +52,18 @@ import {
confirmOrderStatusAction, confirmOrderStatusAction,
formatDisplayTime, formatDisplayTime,
STATUS_ACTION_VOICE, STATUS_ACTION_VOICE,
saveFineCodePagePayload,
} from '@/common/storeFabricBusinessOut'; } from '@/common/storeFabricBusinessOut';
export default { export default {
components: { FabricInfoPopup, FineCodePopup, OrderStatusBar, FabricOutDetailList, FabricOutFooter }, components: { FabricInfoPopup, OrderStatusBar, FabricOutDetailList, FabricOutFooter },
data() { data() {
return { return {
orderId: 0, orderId: 0,
order: {}, order: {},
billTypeName: (BILL_TYPES[0] || {}).label || '坯布其他出货单', billTypeName: (BILL_TYPES[0] || {}).label || '坯布其他出货单',
fabricInfoShow: false, fabricInfoShow: false,
fineCodeShow: false,
currentFabricInfo: {}, currentFabricInfo: {},
currentFineCodes: [],
}; };
}, },
computed: { computed: {
@@ -108,8 +105,24 @@ export default {
this.fabricInfoShow = true; this.fabricInfoShow = true;
}, },
openFineCode(row) { openFineCode(row) {
this.currentFineCodes = [...(row.fine_codes || [])]; if (!this.orderId) {
this.fineCodeShow = true; this.showError('单据未加载');
return;
}
const groupKey = row.group_key || String(row.id || '');
if (!groupKey) {
this.showError('缺少细码分组信息');
return;
}
const payload = saveFineCodePagePayload(this.orderId, row);
uni.navigateTo({
url: `./storeFabricBusinessOutFineCode?id=${this.orderId}&groupKey=${encodeURIComponent(groupKey)}&readonly=1`,
success: (res) => {
if (payload && res.eventChannel) {
res.eventChannel.emit('initData', payload);
}
},
});
}, },
handleAction(action) { handleAction(action) {
if (action === 'edit') { if (action === 'edit') {
+32 -46
View File
@@ -3,19 +3,10 @@
<view class="remind-text">请设置登录密码<br> <view class="remind-text">请设置登录密码<br>
定期更新密码提高安全性</view> 定期更新密码提高安全性</view>
<u-form class="form" :model="model" :rules="rules" ref="uForm"> <u-form class="form" :model="model" :rules="rules" ref="uForm">
<u-form-item label="旧密码" prop="oldPassword" label-width="180"> <u-form-item label="密码" prop="password" label-width="180">
<u-input type="password" v-model="model.oldPassword" placeholder="请输入旧密码"></u-input> <u-input type="password" v-model="model.password" placeholder="请输入密码"></u-input>
</u-form-item>
<u-form-item label="新密码" prop="newPassword" label-width="180">
<u-input type="password" v-model="model.newPassword" placeholder="请输入新密码"></u-input>
</u-form-item>
<u-form-item label="确认密码" prop="confirmPassword" label-width="180">
<u-input type="password" v-model="model.confirmPassword" placeholder="请确认新密码"></u-input>
</u-form-item> </u-form-item>
</u-form> </u-form>
<view class="remind-text">
<u-icon name="question-circle" color="#2767dc" size="28"></u-icon>
密码必须是8-16位的数字,字符组合(不能是纯数字)</view>
<view class="form-footer"> <view class="form-footer">
<u-button class="btn" type="primary" @click="submit">提交</u-button> <u-button class="btn" type="primary" @click="submit">提交</u-button>
<!-- <u-button class="btn" type="default" @click="cancel">关闭</u-button> --> <!-- <u-button class="btn" type="default" @click="cancel">关闭</u-button> -->
@@ -26,46 +17,17 @@
/** /**
* Copyright (c) 2013-Now http://aidex.vip All rights reserved. * Copyright (c) 2013-Now http://aidex.vip All rights reserved.
*/ */
import base64 from '@/common/base64.js';
export default { export default {
data() { data() {
return { return {
model: { model: {
oldPassword: '', password: ''
newPassword: '',
confirmPassword: ''
}, },
rules: { rules: {
oldPassword: [ password: [
{ {
required: true, required: true,
message: '请输入旧密码', message: '请输入密码',
trigger: ['change','blur'],
}
],
newPassword: [
{
required: true,
message: '请输入新密码',
trigger: ['change','blur'],
},
{
pattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,12}$/,
message: '需同时含有字母和数字,长度在6-12之间',
trigger: ['change','blur'],
}
],
confirmPassword: [
{
required: true,
message: '请重新输入密码',
trigger: ['change','blur'],
},
{
validator: (rule, value, callback) => {
return value === this.model.newPassword;
},
message: '两次输入的密码不相等',
trigger: ['change','blur'], trigger: ['change','blur'],
} }
], ],
@@ -76,16 +38,37 @@ export default {
this.$refs.uForm.setRules(this.rules); this.$refs.uForm.setRules(this.rules);
}, },
methods: { methods: {
getCurrentUserId() {
const app = getApp();
const loginId = app && app.globalData ? app.globalData.LoginID : 0;
if (loginId) return Number(loginId);
const loginData = uni.getStorageSync('RemoteTokenData');
if (loginData && loginData.user_id != null) return Number(loginData.user_id);
return 0;
},
submit() { submit() {
this.$refs.uForm.validate(valid => { this.$refs.uForm.validate(valid => {
if (!valid) { if (!valid) {
this.$u.toast('您填写的信息有误,请根据提示修正。'); this.$u.toast('您填写的信息有误,请根据提示修正。');
return; return;
} }
this.$u.api.user.infoSavePwd({ const userId = this.getCurrentUserId();
oldPassword: this.model.oldPassword, if (!userId) {
newPassword: this.model.newPassword this.$u.toast('缺少用户信息,请重新登录');
return;
}
// OpenAPI: system.UpdateUserPasswordParam
this.$u.api.updatePassword({
password: this.model.password,
user_id: userId,
}).then(() => { }).then(() => {
const saved = uni.getStorageSync('UserInfo');
if (saved && typeof saved === 'object') {
uni.setStorageSync('UserInfo', {
...saved,
password: this.model.password
});
}
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '密码修改成功', content: '密码修改成功',
@@ -122,4 +105,7 @@ page{
padding: 20rpx 30rpx; padding: 20rpx 30rpx;
color: #666666; color: #666666;
} }
.form-footer {
padding: 40rpx 30rpx;
}
</style> </style>