JhHardwareWRS/pages/deviceQr/deviceQr.js
2026-07-10 10:12:03 +08:00

495 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
let message = error.message || '转发失败'
if (error && error.errMsg && error.errMsg.includes('cancel')) {
message = '已取消转发'
} else if (error && error.statusCode === 404) {
message = 'ZIP文件不存在或已过期请重新生成'
} else if (error && error.statusCode === 409) {
message = 'ZIP还未生成完成请刷新后重试'
}
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,
})
},
})