const api = require('../../utils/api') Page({ data: { keyword: '', selectedMold: null, moldOptions: [], filteredMoldOptions: [], batchMoldOptions: [], batchKeyword: '', dropdownVisible: false, selectedMoldKeys: [], selectedMoldCount: 0, allFilteredSelected: false, batchModalVisible: false, batchGenerating: false, generatedMoldName: '', qrPath: '', qrUrl: '', qrImageUrl: '', qrLoadError: '', taskRows: [], taskPage: 1, taskPageInput: '1', taskPageSize: 5, taskTotal: 0, taskTotalPages: 1, taskLoading: false, swipedTaskId: null, }, onShow() { this.loadMolds() this.loadBatchTasks() this.startTaskPolling() }, onHide() { this.stopTaskPolling() }, onUnload() { this.stopTaskPolling() }, startTaskPolling() { this.stopTaskPolling() this.taskTimer = setInterval(() => { this.loadBatchTasks({ silent: true }) }, 5000) }, stopTaskPolling() { if (this.taskTimer) { clearInterval(this.taskTimer) this.taskTimer = null } }, async loadMolds() { try { const moldOptions = await api.listMoldNames({ limit: 500 }) const availableKeys = new Set(moldOptions.map(item => item.uniqueKey)) const selectedMoldKeys = this.data.selectedMoldKeys.filter(key => availableKeys.has(key)) this.setData({ moldOptions, selectedMoldKeys, selectedMoldCount: selectedMoldKeys.length, ...this.buildFilteredState(this.data.keyword, moldOptions, selectedMoldKeys), }) } catch (error) { wx.showToast({ title: error.message || '加载模具失败', icon: 'none' }) } }, async loadBatchTasks(options = {}) { if (this.data.taskLoading && options.silent) { return } if (!options.silent) { this.setData({ taskLoading: true }) } try { const result = await api.listMoldQrBatchTasks({ page: this.data.taskPage, pageSize: this.data.taskPageSize, }) this.setData({ taskRows: result.rows, taskPage: result.page, taskPageInput: String(result.page || 1), taskTotal: result.total, taskTotalPages: result.totalPages, swipedTaskId: result.rows.some(row => row.id === this.data.swipedTaskId) ? this.data.swipedTaskId : null, }) } catch (error) { if (!options.silent) { wx.showToast({ title: error.message || '加载任务失败', icon: 'none' }) } } finally { if (!options.silent) { this.setData({ taskLoading: false }) } } }, filterMolds(keyword, source = this.data.moldOptions) { const text = String(keyword || '').trim().toLowerCase() if (!text) { return source.slice(0, 500) } return source.filter(item => ( String(item.displayName || '').toLowerCase().includes(text) || String(item.moldName || item.name || '').toLowerCase().includes(text) || String(item.processName || '').toLowerCase().includes(text) || String(item.stampingMethod || '').toLowerCase().includes(text) || String(item.attendancePointName || '').toLowerCase().includes(text) )).slice(0, 500) }, buildFilteredState(keyword, source = this.data.moldOptions, selectedKeys = this.data.selectedMoldKeys) { const selectedSet = new Set(selectedKeys || []) const filteredMoldOptions = this.filterMolds(keyword, source).map(item => ({ ...item, checked: selectedSet.has(item.uniqueKey), })) return { filteredMoldOptions, allFilteredSelected: filteredMoldOptions.length > 0 && filteredMoldOptions.every(item => item.checked), } }, buildBatchState(selectedKeys = this.data.selectedMoldKeys, keyword = this.data.batchKeyword) { const selectedSet = new Set(selectedKeys || []) const batchMoldOptions = this.filterMolds(keyword, this.data.moldOptions).map(item => ({ ...item, checked: selectedSet.has(item.uniqueKey), })) return { batchMoldOptions, allFilteredSelected: batchMoldOptions.length > 0 && batchMoldOptions.every(item => item.checked), } }, setBatchSelection(selectedMoldKeys) { this.setData({ selectedMoldKeys, selectedMoldCount: selectedMoldKeys.length, ...this.buildFilteredState(this.data.keyword, this.data.moldOptions, selectedMoldKeys), ...this.buildBatchState(selectedMoldKeys), }) }, onInput(e) { const keyword = e.detail.value this.setData({ keyword, selectedMold: null, ...this.buildFilteredState(keyword), dropdownVisible: true, }) }, showDropdown() { this.setData({ dropdownVisible: true, ...this.buildFilteredState(this.data.keyword), }) }, selectMold(e) { const index = Number(e.currentTarget.dataset.index) const mold = this.data.filteredMoldOptions[index] if (!mold) { return } this.setData({ selectedMold: mold, keyword: mold.displayName, ...this.buildFilteredState(mold.displayName), dropdownVisible: false, }) }, noop() {}, openBatchModal() { this.setData({ batchModalVisible: true, dropdownVisible: false, batchKeyword: '', ...this.buildBatchState(this.data.selectedMoldKeys, ''), }) }, closeBatchModal() { if (this.data.batchGenerating) { return } this.setData({ batchModalVisible: false }) }, onBatchSelectionChange(e) { const visibleKeys = this.data.batchMoldOptions.map(item => item.uniqueKey) const selectedVisibleKeys = e.detail.value || [] const selectedSet = new Set(this.data.selectedMoldKeys.filter(key => !visibleKeys.includes(key))) selectedVisibleKeys.forEach(key => selectedSet.add(key)) this.setBatchSelection(Array.from(selectedSet)) }, onBatchInput(e) { const batchKeyword = e.detail.value this.setData({ batchKeyword, ...this.buildBatchState(this.data.selectedMoldKeys, batchKeyword), }) }, toggleSelectAll() { const batchKeys = this.data.batchMoldOptions.map(item => item.uniqueKey) if (!batchKeys.length) { wx.showToast({ title: '当前没有可选模具', icon: 'none' }) return } let selectedKeys = [] const selectedSet = new Set(this.data.selectedMoldKeys) if (this.data.allFilteredSelected) { batchKeys.forEach(key => selectedSet.delete(key)) } else { batchKeys.forEach(key => selectedSet.add(key)) } selectedKeys = Array.from(selectedSet) this.setBatchSelection(selectedKeys) }, getSelectedMolds() { const selectedSet = new Set(this.data.selectedMoldKeys) return this.data.moldOptions.filter(item => selectedSet.has(item.uniqueKey)) }, batchZipFilePath(fileName) { const safeName = String(fileName || `模具二维码_${Date.now()}.zip`).replace(/[\\/:*?"<>|]/g, '_') return `${wx.env.USER_DATA_PATH}/${safeName}` }, shareZipFile(filePath, fileName) { return new Promise((resolve, reject) => { if (!wx.shareFileMessage) { reject(new Error('当前微信版本不支持转发ZIP文件')) return } wx.shareFileMessage({ filePath, fileName, success: resolve, fail: reject, }) }) }, isRequestTimeout(error) { const message = `${error && error.message || ''} ${error && error.errMsg || ''}`.toLowerCase() return message.includes('timeout') || message.includes('超时') }, async createBatchTask() { const molds = this.getSelectedMolds() if (!molds.length) { wx.showToast({ title: '请先勾选模具', icon: 'none' }) return } this.setData({ batchGenerating: true }) wx.showLoading({ title: '创建任务中' }) try { const task = await api.createMoldQrBatchTask(molds) wx.hideLoading() wx.showToast({ title: `已创建${task.itemCount || molds.length}个二维码任务`, icon: 'success' }) this.setData({ batchModalVisible: false, selectedMoldKeys: [], selectedMoldCount: 0, taskPage: 1, ...this.buildFilteredState(this.data.keyword, this.data.moldOptions, []), ...this.buildBatchState([], this.data.batchKeyword), }) this.loadBatchTasks() } catch (error) { wx.hideLoading() if (this.isRequestTimeout(error)) { this.setData({ batchModalVisible: false, taskPage: 1, }) this.loadBatchTasks() wx.showModal({ title: '创建结果待确认', content: '请求超时,但任务可能已经创建。已刷新任务列表,请先查看列表状态,避免重复创建。', showCancel: false, }) } else { wx.showToast({ title: error.message || '创建任务失败', icon: 'none' }) } } finally { this.setData({ batchGenerating: false }) } }, changeTaskPage(e) { const direction = Number(e.currentTarget.dataset.direction || 0) const nextPage = Math.max(1, Math.min(this.data.taskTotalPages, this.data.taskPage + direction)) if (nextPage === this.data.taskPage) { return } this.setData({ taskPage: nextPage }, () => this.loadBatchTasks()) }, onTaskPageInput(e) { this.setData({ taskPageInput: e.detail.value }) }, jumpTaskPage(e) { wx.hideKeyboard() const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.taskPageInput const target = Math.max(1, Math.min(Number(inputValue) || this.data.taskPage, this.data.taskTotalPages)) if (target === this.data.taskPage) { this.setData({ taskPageInput: String(this.data.taskPage || 1) }) return } this.setData({ taskPage: target }, () => this.loadBatchTasks()) }, refreshBatchTasks() { this.loadBatchTasks() }, onTaskTouchStart(e) { const touch = e.touches && e.touches[0] if (!touch) { return } this.taskTouchStartX = touch.clientX this.taskTouchStartY = touch.clientY }, onTaskTouchMove(e) { const touch = e.touches && e.touches[0] if (!touch) { return } const deltaX = touch.clientX - this.taskTouchStartX const deltaY = touch.clientY - this.taskTouchStartY if (Math.abs(deltaX) < 45 || Math.abs(deltaX) < Math.abs(deltaY) * 1.2) { return } const id = Number(e.currentTarget.dataset.id) if (deltaX < 0) { this.setData({ swipedTaskId: id }) } else if (this.data.swipedTaskId === id) { this.setData({ swipedTaskId: null }) } }, onTaskTouchEnd() { this.taskTouchStartX = 0 this.taskTouchStartY = 0 }, async stopBatchTask(e) { const index = Number(e.currentTarget.dataset.index) const task = this.data.taskRows[index] if (!task) { return } wx.showLoading({ title: '停止中' }) try { await api.stopMoldQrBatchTask(task.id) wx.hideLoading() wx.showToast({ title: '已停止', icon: 'success' }) this.loadBatchTasks() } catch (error) { wx.hideLoading() wx.showToast({ title: error.message || '停止失败', icon: 'none' }) } }, async resumeBatchTask(e) { const index = Number(e.currentTarget.dataset.index) const task = this.data.taskRows[index] if (!task) { return } wx.showLoading({ title: '继续中' }) try { await api.resumeMoldQrBatchTask(task.id) wx.hideLoading() wx.showToast({ title: '已继续生成', icon: 'success' }) this.loadBatchTasks() } catch (error) { wx.hideLoading() wx.showToast({ title: error.message || '继续失败', icon: 'none' }) } }, async shareBatchTask(e) { const index = Number(e.currentTarget.dataset.index) const task = this.data.taskRows[index] if (!task || !task.zipUrl) { wx.showToast({ title: 'ZIP还未生成完成', icon: 'none' }) return } wx.showLoading({ title: '准备转发' }) try { const fileName = task.fileName || `模具二维码_${Date.now()}.zip` const filePath = await api.downloadFileByUrl(task.zipUrl, this.batchZipFilePath(fileName), 120000) wx.hideLoading() await this.shareZipFile(filePath, fileName) } catch (error) { wx.hideLoading() const message = error && error.errMsg && error.errMsg.includes('cancel') ? '已取消转发' : (error.message || '转发失败') wx.showToast({ title: message, icon: 'none' }) } }, async deleteBatchTask(e) { const index = Number(e.currentTarget.dataset.index) const task = this.data.taskRows[index] if (!task) { return } wx.showModal({ title: '删除ZIP任务', content: '删除后会同时删除这条记录及对应ZIP包,是否确定删除?', cancelText: '否', confirmText: '删除', confirmColor: '#d92d20', success: async res => { if (!res.confirm) { return } wx.showLoading({ title: '删除中' }) try { await api.deleteMoldQrBatchTask(task.id) wx.hideLoading() wx.showToast({ title: '已删除', icon: 'success' }) this.setData({ swipedTaskId: null }) this.loadBatchTasks() } catch (error) { wx.hideLoading() wx.showToast({ title: error.message || '删除失败', icon: 'none' }) } }, }) }, async generate() { const mold = this.data.selectedMold if (!mold || !mold.moldName || !mold.processName) { wx.showToast({ title: '请选择产品名称、工序和冲压方式', icon: 'none' }) return } wx.showLoading({ title: '生成中' }) try { const result = await api.generateMoldQr(mold) const qrUrl = this.withCacheBuster(result.qrUrl) this.setData({ generatedMoldName: result.displayName || result.moldName || result.deviceNo, qrPath: result.qrPath, qrUrl, qrImageUrl: '', qrLoadError: '', }) if (!result.qrUrl) { wx.showToast({ title: '微信配置未生成图片', icon: 'none' }) } else { this.prepareQrImage(qrUrl) } } catch (error) { wx.showToast({ title: error.message, icon: 'none' }) } finally { wx.hideLoading() } }, withCacheBuster(url) { if (!url) { return '' } const separator = url.indexOf('?') === -1 ? '?' : '&' return `${url}${separator}_t=${Date.now()}` }, prepareQrImage(url) { wx.downloadFile({ url, success: res => { if (res.statusCode === 200 && res.tempFilePath) { this.setData({ qrImageUrl: res.tempFilePath, qrLoadError: '' }) return } this.setData({ qrImageUrl: url }) }, fail: () => { this.setData({ qrImageUrl: url, qrLoadError: '二维码图片下载失败,已切换为直接显示', }) }, }) }, onQrLoad() { this.setData({ qrLoadError: '' }) }, onQrError() { this.setData({ qrLoadError: '二维码图片加载失败,点击空白区域可放大查看' }) }, preview() { if (!this.data.qrUrl) { return } wx.previewImage({ urls: [this.data.qrUrl], current: this.data.qrUrl, }) }, })