fix(APP): 倒计时期间确认按钮禁用虚化 + 收窄倒计时范围
- 5秒倒计时期间确认按钮虚化禁用(点不了),只有取消可用,倒计时结束按钮解锁再点击才执行 - 收窄范围:仅 删除记录/删除图片/结束分支/驳回 保留5秒等待;接收/转交/派发/保存/创建/撤回转交 恢复直接确认
This commit is contained in:
@ -96,7 +96,7 @@
|
||||
</view>
|
||||
<view class="field-label" style="margin-top:12px;">备注 <text class="required">*</text></view>
|
||||
<textarea v-model="firstForm.note" class="popup-textarea" placeholder="请填写备注说明(必填)" :maxlength="500" />
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="createFirstVisible = false">取消</button><button class="btn-primary" :class="{ 'btn-counting': confirming === 'first' }" :disabled="firstSaving || !firstForm.assignee_id || !firstForm.note.trim()" @tap="confirmBtn('first', doCreateFirstTask)">{{ confirmLabel('first', firstSaving ? '创建中...' : '确认创建') }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="createFirstVisible = false">取消</button><button class="btn-primary" :disabled="firstSaving || !firstForm.assignee_id || !firstForm.note.trim()" @tap="doCreateFirstTask">{{ firstSaving ? '创建中...' : '确认创建' }}</button></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 记录/拍照 -->
|
||||
@ -110,7 +110,7 @@
|
||||
<view v-for="n in recordForm.pendingCount" :key="'p'+n" class="img-cell img-cell-loading"><text class="img-loading-text">⏳</text></view>
|
||||
</view>
|
||||
<button v-if="recordForm.images.length + recordForm.pendingCount < 9" class="btn-upload" @tap="handleChooseImage" :disabled="isUploading">{{ isUploading ? '上传中...' : `📷 拍照/选图 (${recordForm.images.length + recordForm.pendingCount}/9)` }}</button>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeRecordPopup">取消</button><button class="btn-primary" :class="{ 'btn-counting': confirming === 'record' }" :disabled="recordSaving || isUploading" @tap="confirmBtn('record', doSaveRecord)">{{ confirmLabel('record', isUploading ? '上传中' : (recordSaving ? '保存中...' : (recordForm.recordId ? '更新记录' : '保存记录'))) }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeRecordPopup">取消</button><button class="btn-primary" :disabled="recordSaving || isUploading" @tap="doSaveRecord">{{ isUploading ? '上传中' : (recordSaving ? '保存中...' : (recordForm.recordId ? '更新记录' : '保存记录')) }}</button></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 任务操作 -->
|
||||
@ -127,13 +127,13 @@
|
||||
@tap="receiveTaskName = opt">{{ opt }}</view>
|
||||
</view>
|
||||
<textarea v-model="receiveRemark" class="popup-textarea" placeholder="接收备注(选填)" :maxlength="500" style="margin-top:12px;" />
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :class="{ 'btn-counting': confirming === 'receive' }" :disabled="actionLoading || !receiveTaskName" @tap="confirmBtn('receive', doReceive)">{{ confirmLabel('receive', '确认接收') }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :disabled="actionLoading || !receiveTaskName" @tap="doReceive">确认接收</button></view>
|
||||
</template>
|
||||
<template v-if="actionPopup.type === 'reject'">
|
||||
<text class="popup-title">品质驳回</text>
|
||||
<textarea v-model="rejectReason" class="popup-textarea" placeholder="请填写驳回原因(必填)" :maxlength="500" />
|
||||
<text class="popup-hint">⚠ 驳回后将自动创建返工任务</text>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-danger" :class="{ 'btn-counting': confirming === 'reject' }" :disabled="actionLoading || !rejectReason.trim()" @tap="confirmBtn('reject', doReject)">{{ confirmLabel('reject', '确认驳回') }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-danger" :class="{ 'btn-counting': confirming === 'reject' && confirmCount > 0 }" :disabled="actionLoading || !rejectReason.trim() || (confirming === 'reject' && confirmCount > 0)" @tap="confirmBtn('reject', doReject)">{{ confirmLabel('reject', '确认驳回') }}</button></view>
|
||||
</template>
|
||||
<template v-if="actionPopup.type === 'transfer'">
|
||||
<text class="popup-title">完工转交</text>
|
||||
@ -148,7 +148,7 @@
|
||||
<view class="field-label" style="margin-top:12px;">交接备注 <text class="required">*</text></view>
|
||||
<textarea v-model="transferForm.note" class="popup-textarea" placeholder="请填写交接备注(必填)" :maxlength="500" />
|
||||
<view v-if="transferForm.isWarehouse || transferForm.selectedUserId" class="preview-hint">{{ transferForm.isWarehouse ? '产品将入库并从个人待办中移除' : '将创建新任务指派给 ' + (transferUserName || '—') }}</view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :class="{ 'btn-counting': confirming === 'transfer' }" :disabled="actionLoading || (!transferForm.isWarehouse && !transferForm.selectedUserId) || !transferForm.note.trim()" @tap="confirmBtn('transfer', doTransfer)">{{ confirmLabel('transfer', actionLoading ? '提交中...' : (transferForm.isWarehouse ? '📦 确认入库' : '确认转交')) }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary" :disabled="actionLoading || (!transferForm.isWarehouse && !transferForm.selectedUserId) || !transferForm.note.trim()" @tap="doTransfer">{{ actionLoading ? '提交中...' : (transferForm.isWarehouse ? '📦 确认入库' : '确认转交') }}</button></view>
|
||||
</template>
|
||||
<template v-if="actionPopup.type === 'spawn'">
|
||||
<text class="popup-title">➕ 派发协助分支</text>
|
||||
@ -161,7 +161,7 @@
|
||||
</view>
|
||||
<view class="field-label" style="margin-top:12px;">派发备注 <text class="required">*</text></view>
|
||||
<textarea v-model="spawnForm.remark" class="popup-textarea" placeholder="请填写派发备注说明(必填)" :maxlength="500" />
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary btn-spawn" :class="{ 'btn-counting': confirming === 'spawn' }" :disabled="actionLoading || !spawnForm.assignee_id || !spawnForm.remark.trim()" @tap="confirmBtn('spawn', doSpawn)">{{ confirmLabel('spawn', actionLoading ? '提交中...' : '确认派发') }}</button></view>
|
||||
<view class="popup-btns"><button class="btn-cancel" @tap="closeActionPopup">取消</button><button class="btn-primary btn-spawn" :disabled="actionLoading || !spawnForm.assignee_id || !spawnForm.remark.trim()" @tap="doSpawn">{{ actionLoading ? '提交中...' : '确认派发' }}</button></view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
@ -197,15 +197,15 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 🛡️ 双重确认弹窗(5 秒倒计时防误触) -->
|
||||
<!-- 🛡️ 双重确认弹窗(删除/结束分支/驳回 5 秒倒计时防误触) -->
|
||||
<view v-if="confirmDlg.visible" class="overlay" @tap="confirmDlgCancel">
|
||||
<view class="popup" @tap.stop style="max-width:360px;border-radius:16px;">
|
||||
<text class="popup-title">{{ confirmDlg.title }}</text>
|
||||
<text class="popup-hint" style="display:block;margin-bottom:4px;">{{ confirmDlg.content }}</text>
|
||||
<text class="cd-tip">⚠️ 请再次确认:倒计时结束后点击「确认执行」才会生效</text>
|
||||
<text v-if="confirmDlg.countdown" class="cd-tip">⚠️ 5 秒确认等待中:请核对信息,倒计时结束后确认按钮才可点击</text>
|
||||
<view class="popup-btns">
|
||||
<button class="btn-cancel" @tap="confirmDlgCancel">取消</button>
|
||||
<button class="btn-primary" :class="{ 'btn-counting': confirming === 'dlg' }" @tap="confirmDlgConfirm">{{ confirmLabel('dlg', '确认') }}</button>
|
||||
<button class="btn-primary" :class="{ 'btn-counting': confirming === 'dlg' && confirmCount > 0 }" :disabled="confirming === 'dlg' && confirmCount > 0" @tap="confirmDlgConfirm">{{ confirmDlg.countdown ? confirmLabel('dlg', '确认') : '确认' }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -337,7 +337,7 @@ export default {
|
||||
closeRecordPopup() { this.recordPopup = { visible: false, task: null }; },
|
||||
async handleChooseImage() { const maxSlots = 9 - (this.recordForm.images.length + this.recordForm.pendingCount); if (maxSlots <= 0) return; const chooseRes = await new Promise((resolve, reject) => { uni.chooseImage({ count: maxSlots, sizeType: ["compressed"], sourceType: ["camera", "album"], success: resolve, fail: reject }); }).catch(() => null); if (!chooseRes || !chooseRes.tempFilePaths || !chooseRes.tempFilePaths.length) return; let compressSkipCount = 0; const compressedPaths = []; for (const p of chooseRes.tempFilePaths) { try { const compressed = await new Promise((resolve, reject) => { uni.compressImage({ src: p, quality: 60, success: resolve, fail: reject }); }); compressedPaths.push(compressed.tempFilePath); } catch { compressSkipCount++; } } if (!compressedPaths.length) return; this.isUploading = true; this.recordForm.pendingCount += compressedPaths.length; for (const path of compressedPaths) { const url = await this.uploadFile(path); if (url) this.recordForm.images.push(url); this.recordForm.pendingCount--; } this.isUploading = false; },
|
||||
uploadFile(filePath) { return new Promise((resolve) => { uni.uploadFile({ url: getBaseUrl() + "/upload/", filePath, name: "file", success(res) { try { const data = JSON.parse(res.data); resolve(data.url || null); } catch { resolve(null); } }, fail: () => resolve(null) }); }); },
|
||||
removeRecordImage(i) { this.openConfirmDlg({ title: "删除图片", content: "确定删除这张图片吗?", action: () => { this.recordForm.images.splice(i, 1); } }); },
|
||||
removeRecordImage(i) { this.openConfirmDlg({ title: "删除图片", content: "确定删除这张图片吗?", countdown: true, action: () => { this.recordForm.images.splice(i, 1); } }); },
|
||||
previewRecordImage(i) { uni.previewImage({ urls: this.recordForm.images, current: i }); },
|
||||
async doSaveRecord() { if (this.isUploading) return; this.recordSaving = true; try { const payload = { remark: this.recordForm.remark.trim(), images: this.recordForm.images }; if (this.recordForm.recordId) await put(`/records/${this.recordForm.recordId}`, payload); else await patch(`/tasks/${this.recordPopup.task.id}/records`, payload); uni.showToast({ title: "已保存", icon: "success" }); this.closeRecordPopup(); this.doQuery(this.product.serial_number); } catch {} finally { this.recordSaving = false; } },
|
||||
|
||||
@ -352,26 +352,27 @@ export default {
|
||||
this.spawnForm = { assignee_id: "", remark: "" };
|
||||
},
|
||||
handleViewRecords(task) { uni.navigateTo({ url: `/pages/scan/records?taskId=${task.id}` }); },
|
||||
async doDeleteRecord(record) { this.openConfirmDlg({ title: "删除记录", content: "确定删除这条记录吗?删除后不可恢复。", action: async () => { try { await request({ url: `/records/${record.id}`, method: "DELETE" }); uni.showToast({ title: "记录已删除", icon: "success" }); this.doQuery(this.product.serial_number); } catch (e) { uni.showToast({ title: (e && e.data && e.data.detail) || "删除失败", icon: "none" }); } } }); },
|
||||
confirmEndBranch(task) { this.openConfirmDlg({ title: "结束分支", content: `确定结束「${task.task_name}」吗?`, action: () => this.doEndBranch(task) }); },
|
||||
async doDeleteRecord(record) { this.openConfirmDlg({ title: "删除记录", content: "确定删除这条记录吗?删除后不可恢复。", countdown: true, action: async () => { try { await request({ url: `/records/${record.id}`, method: "DELETE" }); uni.showToast({ title: "记录已删除", icon: "success" }); this.doQuery(this.product.serial_number); } catch (e) { uni.showToast({ title: (e && e.data && e.data.detail) || "删除失败", icon: "none" }); } } }); },
|
||||
confirmEndBranch(task) { this.openConfirmDlg({ title: "结束分支", content: `确定结束「${task.task_name}」吗?`, countdown: true, action: () => this.doEndBranch(task) }); },
|
||||
confirmRecall(task) { this.openConfirmDlg({ title: "撤回转交", content: `确定撤回「${task.task_name}」吗?撤回后任务将回到您的手中。`, action: () => this.doRecall(task) }); },
|
||||
async doRecall(task) { try { const opId = this.currentUsername || this.currentUserId; await post(`/tasks/${task.id}/recall?operator_id=${encodeURIComponent(opId)}`); uni.showToast({ title: "已撤回", icon: "success" }); this.doQuery(this.product.serial_number); } catch {} },
|
||||
async doEndBranch(task) { try { const opId = this.currentUsername || this.currentUserId; await post(`/tasks/${task.id}/end?operator_id=${encodeURIComponent(opId)}`); uni.showToast({ title: "分支已结束", icon: "success" }); this.doQuery(this.product.serial_number); } catch {} },
|
||||
closeActionPopup() { this.actionPopup = { visible: false, type: "", task: null }; },
|
||||
|
||||
// ═══ 双重确认倒计时(防误触) ═══
|
||||
// 第一次点击进入 5 秒倒计时(期间再次点击 = 取消反悔),倒计时结束再点击才真正执行
|
||||
// 首次点击进入 5 秒倒计时:期间确认按钮虚化禁用(点不了),只有「取消」可用;
|
||||
// 5 秒结束后确认按钮解锁,点击才真正执行。
|
||||
confirmBtn(key, doAction) {
|
||||
if (this.confirming === key) {
|
||||
if (this.confirmCount <= 0) {
|
||||
// 倒计时结束,点击执行
|
||||
this.clearConfirm();
|
||||
doAction();
|
||||
} else {
|
||||
// 倒计时中再次点击 → 取消(反悔)
|
||||
this.clearConfirm();
|
||||
}
|
||||
// 倒计时中:按钮已禁用,忽略点击
|
||||
return;
|
||||
}
|
||||
// 首次点击,开始倒计时
|
||||
this.clearConfirm();
|
||||
this.confirming = key;
|
||||
this.confirmCount = 5;
|
||||
@ -387,27 +388,30 @@ export default {
|
||||
this.confirmCount = 5;
|
||||
},
|
||||
confirmLabel(key, baseText) {
|
||||
if (this.confirming === key) return this.confirmCount > 0 ? `再次确认 (${this.confirmCount}s)` : "确认执行";
|
||||
if (this.confirming === key && this.confirmCount > 0) return `${baseText} (${this.confirmCount}s)`;
|
||||
return baseText;
|
||||
},
|
||||
// 确认框类操作(结束分支/删除记录/撤回转交):弹倒计时确认弹窗
|
||||
openConfirmDlg({ title, content, action }) {
|
||||
// 确认框类操作(结束分支/删除记录/删除图片/撤回转交)
|
||||
// countdown=true 时确认按钮需要 5 秒等待(期间禁用);false 时立即确认
|
||||
openConfirmDlg({ title, content, action, countdown = false }) {
|
||||
this.clearConfirm();
|
||||
this.confirmDlg = { visible: true, title, content, action };
|
||||
this.confirming = "dlg";
|
||||
this.confirmCount = 5;
|
||||
this.confirmTimer = setInterval(() => {
|
||||
this.confirmCount -= 1;
|
||||
if (this.confirmCount <= 0) clearInterval(this.confirmTimer);
|
||||
}, 1000);
|
||||
this.confirmDlg = { visible: true, title, content, action, countdown };
|
||||
if (countdown) {
|
||||
this.confirming = "dlg";
|
||||
this.confirmCount = 5;
|
||||
this.confirmTimer = setInterval(() => {
|
||||
this.confirmCount -= 1;
|
||||
if (this.confirmCount <= 0) clearInterval(this.confirmTimer);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
confirmDlgConfirm() {
|
||||
this.confirmBtn("dlg", () => {
|
||||
const action = this.confirmDlg.action;
|
||||
this.confirmDlg.visible = false;
|
||||
this.confirmDlg.action = null;
|
||||
if (action) action();
|
||||
});
|
||||
if (this.confirming === "dlg" && this.confirmCount > 0) return; // 倒计时中:忽略
|
||||
if (this.confirming === "dlg") this.clearConfirm();
|
||||
const action = this.confirmDlg.action;
|
||||
this.confirmDlg.visible = false;
|
||||
this.confirmDlg.action = null;
|
||||
if (action) action();
|
||||
},
|
||||
confirmDlgCancel() {
|
||||
this.clearConfirm();
|
||||
|
||||
@ -54,10 +54,10 @@
|
||||
<view class="dlg-box" @tap.stop>
|
||||
<text class="dlg-title">{{ confirmDlg.title }}</text>
|
||||
<text class="dlg-content">{{ confirmDlg.content }}</text>
|
||||
<text class="dlg-tip">⚠️ 请再次确认:倒计时结束点击「确认删除」才会生效</text>
|
||||
<text class="dlg-tip">⚠️ 5 秒确认等待中:请核对信息,倒计时结束后确认按钮才可点击</text>
|
||||
<view class="dlg-btns">
|
||||
<button class="dlg-btn dlg-cancel" @tap="confirmDlgCancel">取消</button>
|
||||
<button class="dlg-btn dlg-danger" @tap="confirmDlgConfirm">{{ confirmLabel('删除') }}</button>
|
||||
<button class="dlg-btn dlg-danger" :disabled="confirming === 'dlg' && confirmCount > 0" @tap="confirmDlgConfirm">{{ confirmLabel('删除') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -184,40 +184,22 @@ export default {
|
||||
this.confirmCount = 5;
|
||||
},
|
||||
confirmLabel(baseText) {
|
||||
if (this.confirming === "dlg") return this.confirmCount > 0 ? `再次确认 (${this.confirmCount}s)` : "确认删除";
|
||||
if (this.confirming === "dlg" && this.confirmCount > 0) return `${baseText} (${this.confirmCount}s)`;
|
||||
return baseText;
|
||||
},
|
||||
confirmDlgConfirm() {
|
||||
this.confirmBtn(() => {
|
||||
const rec = this.confirmDlg.target;
|
||||
this.confirmDlg.visible = false;
|
||||
this.confirmDlg.target = null;
|
||||
if (rec) this.deleteRecord(rec);
|
||||
});
|
||||
if (this.confirming === "dlg" && this.confirmCount > 0) return; // 倒计时中:忽略
|
||||
if (this.confirming === "dlg") this.clearConfirm();
|
||||
const rec = this.confirmDlg.target;
|
||||
this.confirmDlg.visible = false;
|
||||
this.confirmDlg.target = null;
|
||||
if (rec) this.deleteRecord(rec);
|
||||
},
|
||||
confirmDlgCancel() {
|
||||
this.clearConfirm();
|
||||
this.confirmDlg.visible = false;
|
||||
this.confirmDlg.target = null;
|
||||
},
|
||||
confirmBtn(doAction) {
|
||||
if (this.confirming === "dlg") {
|
||||
if (this.confirmCount <= 0) {
|
||||
this.clearConfirm();
|
||||
doAction();
|
||||
} else {
|
||||
this.clearConfirm();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.clearConfirm();
|
||||
this.confirming = "dlg";
|
||||
this.confirmCount = 5;
|
||||
this.confirmTimer = setInterval(() => {
|
||||
this.confirmCount -= 1;
|
||||
if (this.confirmCount <= 0) clearInterval(this.confirmTimer);
|
||||
}, 1000);
|
||||
},
|
||||
async deleteRecord(rec) {
|
||||
try {
|
||||
await request({ url: `/records/${rec.id}`, method: "DELETE" });
|
||||
@ -272,4 +254,5 @@ export default {
|
||||
.dlg-btn::after { border: none; }
|
||||
.dlg-cancel { background: #f3f4f6; color: #6b7280; }
|
||||
.dlg-danger { background: #dc2626; color: #fff; }
|
||||
.dlg-danger[disabled] { opacity: 0.5; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user