495 lines
15 KiB
JavaScript
495 lines
15 KiB
JavaScript
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,
|
||
})
|
||
},
|
||
})
|