JhHardwareWRS/pages/clock/clock.js
2026-06-24 15:19:14 +08:00

613 lines
18 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')
const locationGuard = require('../../utils/locationGuard')
Page({
data: {
user: null,
moldName: '',
processName: '',
attendancePointName: '',
stampingMethod: '',
isCleaning: false,
isMisc: false,
moldDisplayName: '',
sceneToken: '',
state: null,
roleChoices: [],
selectionToken: '',
toastVisible: false,
toastMessage: '',
locationChecking: true,
locationAllowed: false,
locationMessage: '正在校验当前位置',
currentLocation: null,
occupiedDetail: null,
temporaryWorkerModalVisible: false,
temporaryWorkerPrompt: '',
temporaryWorkerName: '',
},
toastTimer: null,
onLoad(options) {
this.resolveOpenMold(options)
},
async resolveOpenMold(options) {
const scene = this.decodeRepeated(options.scene || '')
let moldName = this.extractMoldName(options, scene)
let processName = this.extractProcessName(options, scene)
let attendancePointName = this.decodeRepeated(options.attendancePointName || options.attendance_point_name || '')
let stampingMethod = this.decodeRepeated(options.stampingMethod || options.stamping_method || '')
let isCleaning = false
let isMisc = false
let moldDisplayName = api.moldDisplayName(moldName, processName, stampingMethod)
const sceneToken = this.extractSceneToken(scene || options.moldName || '')
if (!moldName && sceneToken && api.getCurrentUser()) {
try {
const resolved = await api.resolveMoldScene(sceneToken)
moldName = resolved.moldName
processName = resolved.processName
attendancePointName = resolved.attendancePointName || ''
stampingMethod = resolved.stampingMethod || ''
isCleaning = !!resolved.isCleaning
isMisc = !!resolved.isMisc
moldDisplayName = resolved.displayName
} catch (error) {
this.showTip(error.message || '未找到模具二维码')
}
}
this.setData({ moldName, processName, attendancePointName, stampingMethod, isCleaning, isMisc, moldDisplayName, sceneToken })
this.verifyOpenLocation()
},
async resolveSceneAfterLogin() {
if (this.data.moldName || !this.data.sceneToken) {
return true
}
try {
const resolved = await api.resolveMoldScene(this.data.sceneToken)
this.setData({
moldName: resolved.moldName,
processName: resolved.processName,
attendancePointName: resolved.attendancePointName || '',
stampingMethod: resolved.stampingMethod || '',
isCleaning: !!resolved.isCleaning,
isMisc: !!resolved.isMisc,
moldDisplayName: resolved.displayName,
})
await this.verifyOpenLocation()
return false
} catch (error) {
this.showTip(error.message || '未找到模具二维码')
return false
}
},
extractMoldName(options, scene) {
const explicit = options.moldName || options.deviceNo
if (explicit) {
const value = this.decodeRepeated(explicit)
return value.startsWith('mold=') ? '' : value
}
const raw = this.decodeRepeated(scene)
const moldMatched = raw.match(/(?:^|[?&])?moldName=([^&]+)/)
if (moldMatched && moldMatched[1]) {
return this.decodeRepeated(moldMatched[1])
}
const oldMatched = raw.match(/(?:^|[?&])?deviceNo=([^&]+)/)
if (oldMatched && oldMatched[1]) {
return this.decodeRepeated(oldMatched[1])
}
if (raw.startsWith('mold=')) {
return ''
}
return raw
},
extractProcessName(options, scene) {
const explicit = options.processName || options.process_name
if (explicit) {
return this.decodeRepeated(explicit)
}
const raw = this.decodeRepeated(scene)
const matched = raw.match(/(?:^|[?&])?(?:processName|process_name)=([^&]+)/)
return matched && matched[1] ? this.decodeRepeated(matched[1]) : ''
},
extractSceneToken(scene) {
const raw = this.decodeRepeated(scene)
const matched = raw.match(/(?:^|[?&])?mold=([^&]+)/)
return matched && matched[1] ? this.decodeRepeated(matched[1]) : ''
},
decodeRepeated(value) {
let text = String(value || '').trim()
for (let index = 0; index < 3; index += 1) {
try {
const decoded = decodeURIComponent(text)
if (decoded === text) {
break
}
text = decoded
} catch (error) {
break
}
}
return text
},
isCleaningProcess() {
return !!this.data.isCleaning
|| String(this.data.stampingMethod || '').trim() === '清洗'
},
openCleaningReport() {
const moldName = encodeURIComponent(this.data.moldName || '')
const processName = encodeURIComponent(this.data.processName || '')
const stampingMethod = encodeURIComponent(this.data.stampingMethod || '')
const displayName = encodeURIComponent(this.data.moldDisplayName || '')
const attendancePointName = encodeURIComponent(this.data.attendancePointName || '')
wx.redirectTo({
url: `/pages/cleaningReport/cleaningReport?moldName=${moldName}&processName=${processName}&attendancePointName=${attendancePointName}&stampingMethod=${stampingMethod}&isCleaning=1&displayName=${displayName}`,
})
},
onShow() {
if (this.data.locationAllowed) {
this.load()
}
},
onUnload() {
if (this.toastTimer) {
clearTimeout(this.toastTimer)
this.toastTimer = null
}
},
showTip(message, duration = 5000) {
if (this.toastTimer) {
clearTimeout(this.toastTimer)
}
this.setData({
toastVisible: true,
toastMessage: String(message || ''),
})
this.toastTimer = setTimeout(() => {
this.setData({ toastVisible: false, toastMessage: '' })
this.toastTimer = null
}, duration)
},
async verifyOpenLocation() {
this.setData({
locationChecking: true,
locationAllowed: false,
locationMessage: '正在校验当前位置',
})
try {
const locationScope = api.getCurrentUser() ? 'accessible' : 'public'
const locationResult = await locationGuard.ensureFactoryLocation(this.data.attendancePointName, locationScope)
this.setData({
locationChecking: false,
locationAllowed: true,
locationMessage: '',
currentLocation: locationResult.current,
})
await this.load()
} catch (error) {
this.setData({
locationChecking: false,
locationAllowed: false,
locationMessage: error.message || '当前位置不在允许范围内,不能扫码报工',
currentLocation: null,
user: null,
state: null,
})
}
},
async load() {
let user = api.getCurrentUser()
let state = null
this.setData({ occupiedDetail: null })
if (user && !this.data.moldName && this.data.sceneToken) {
const shouldContinue = await this.resolveSceneAfterLogin()
if (!shouldContinue) {
return
}
}
if (user && this.data.moldName) {
try {
user = await api.refreshCurrentUser() || user
if (this.isCleaningProcess()) {
this.setData({ user, state: null }, () => {
this.openCleaningReport()
})
return
}
state = await api.getClockState(
this.data.moldName,
this.data.processName,
this.data.attendancePointName,
this.data.currentLocation,
)
if (state && state.isCleaning) {
this.setData({
user,
state: null,
stampingMethod: state.stampingMethod || '',
isCleaning: true,
isMisc: false,
}, () => {
this.openCleaningReport()
})
return
}
if (state && state.isMisc) {
this.setData({
stampingMethod: state.stampingMethod || '',
isMisc: true,
moldDisplayName: state.displayName || this.data.moldDisplayName,
})
}
} catch (error) {
if (await this.handleMoldOccupiedError(error)) {
user = api.getCurrentUser()
this.setData({ user, state: null })
return
}
if (await this.handleUnfinishedReportError(error)) {
user = api.getCurrentUser()
this.setData({ user, state: null })
return
}
this.showTip(error.message)
user = api.getCurrentUser()
}
}
this.setData({ user, state })
},
async wechatLogin(e) {
const phoneCode = e.detail && e.detail.code
if (!phoneCode) {
this.showTip('未获得手机号授权', 3000)
return
}
wx.showLoading({ title: '登录中' })
try {
const result = await api.loginWithPhoneCode(phoneCode)
if (result.needsTemporaryWorker) {
wx.hideLoading()
await this.confirmTemporaryWorker(result)
return
}
if (result.needsSelection) {
this.setData({
roleChoices: result.matchedPeople,
selectionToken: result.selectionToken,
})
return
}
await this.load()
} catch (error) {
this.showTip(error.message)
} finally {
wx.hideLoading()
}
},
confirmTemporaryWorker(result) {
return new Promise(resolve => {
this.temporaryWorkerResult = result
this.temporaryWorkerResolve = resolve
this.setData({
temporaryWorkerModalVisible: true,
temporaryWorkerPrompt: `该手机号${result.temporaryPhone || ''}不在人员清单中。临时工账号有效期只有24小时请填写姓名后创建。`,
temporaryWorkerName: '',
})
})
},
onTemporaryWorkerNameInput(e) {
this.setData({ temporaryWorkerName: e.detail.value })
},
closeTemporaryWorkerModal() {
this.setData({
temporaryWorkerModalVisible: false,
temporaryWorkerPrompt: '',
temporaryWorkerName: '',
})
this.temporaryWorkerResult = null
if (this.temporaryWorkerResolve) {
this.temporaryWorkerResolve()
this.temporaryWorkerResolve = null
}
},
async submitTemporaryWorker() {
const result = this.temporaryWorkerResult
const name = String(this.data.temporaryWorkerName || '').trim()
if (!name) {
this.showTip('请输入姓名', 3000)
return
}
if (!result || !result.temporaryToken) {
this.showTip('临时工注册信息已失效,请重新登录')
this.closeTemporaryWorkerModal()
return
}
this.setData({ temporaryWorkerModalVisible: false })
wx.showLoading({ title: '定位中' })
try {
const attendancePoint = await locationGuard.ensureTemporaryWorkerLocation()
wx.showLoading({ title: '创建中' })
await api.createTemporaryWorker(result.temporaryToken, name, attendancePoint)
await this.load()
wx.showToast({ title: '临时工已生效', icon: 'success' })
} catch (error) {
this.showTip(error.message)
} finally {
wx.hideLoading()
this.closeTemporaryWorkerModal()
}
},
async chooseRole(e) {
const role = e.currentTarget.dataset.role
wx.showLoading({ title: '登录中' })
try {
await api.selectRole(this.data.selectionToken, role)
this.setData({ roleChoices: [], selectionToken: '' })
await this.load()
} catch (error) {
this.showTip(error.message)
} finally {
wx.hideLoading()
}
},
canOperate() {
const { user, moldName, state } = this.data
if (!user) {
this.showTip('请先登录', 3000)
return false
}
if (user.role !== 'worker') {
this.showTip('只有冲压工人可以报工', 3000)
return false
}
if (!moldName) {
this.showTip('缺少模具名称', 3000)
return false
}
if (!state) {
this.showTip('当前模具码不可用', 3000)
return false
}
return true
},
formatAutoSubmitHours(value) {
const hours = Number(value || 15)
if (!Number.isFinite(hours)) {
return '15'
}
return Number.isInteger(hours) ? String(hours) : String(Math.round(hours * 100) / 100)
},
confirmStartWork() {
const hoursText = this.formatAutoSubmitHours(this.data.state && this.data.state.autoSubmitHours)
return new Promise(resolve => {
wx.showModal({
title: '开始上班',
content: `该班次时长最多为${hoursText}个小时。${hoursText}小时后未下班报工,系统将自动报工`,
confirmText: '开始上班',
cancelText: '取消',
success: res => resolve(!!res.confirm),
fail: () => resolve(false),
})
})
},
async switchMold() {
if (!this.canOperate()) {
return
}
try {
await api.switchDevice(
this.data.moldName,
this.data.processName,
this.data.attendancePointName,
this.data.currentLocation,
)
wx.showToast({ title: '已记录换模具', icon: 'success' })
await this.load()
} catch (error) {
if (await this.handleMoldOccupiedError(error)) {
return
}
if (await this.handleUnfinishedReportError(error)) {
return
}
this.showTip(error.message)
}
},
async finishWork() {
if (!this.canOperate()) {
return
}
try {
const session = await api.finishWork(
this.data.moldName,
this.data.processName,
this.data.attendancePointName,
this.data.currentLocation,
)
wx.navigateTo({
url: `/pages/reportForm/reportForm?sessionId=${session.id}`,
})
} catch (error) {
if (await this.handleMoldOccupiedError(error)) {
return
}
if (await this.handleUnfinishedReportError(error)) {
return
}
this.showTip(error.message)
}
},
async handleAction() {
if (!this.canOperate()) {
return
}
const { moldName, state } = this.data
try {
if (state.action === 'start') {
const confirmed = await this.confirmStartWork()
if (!confirmed) {
return
}
await api.startWork(moldName, this.data.processName, this.data.attendancePointName, this.data.currentLocation)
wx.showToast({ title: '已开始上班', icon: 'success' })
await this.load()
return
}
if (state.action === 'switch') {
await this.switchMold()
return
}
if (state.action === 'report' || (state.action === 'finish' && state.endAtText && state.session)) {
wx.navigateTo({
url: `/pages/reportForm/reportForm?sessionId=${state.session.id}`,
})
return
}
if (state.action === 'refinish') {
await this.finishWork()
return
}
await this.finishWork()
} catch (error) {
if (await this.handleMoldOccupiedError(error)) {
return
}
if (await this.handleUnfinishedReportError(error)) {
return
}
this.showTip(error.message)
}
},
resolveUnfinishedReportDetail(error) {
if (!error || error.statusCode !== 400) {
return null
}
let detail = error.detail
if (typeof detail === 'string') {
try {
detail = JSON.parse(detail)
} catch (parseError) {
detail = null
}
}
return detail && detail.code === 'unfinished_report_mold_mismatch' ? detail : null
},
async openUnfinishedReport(detail) {
let sessionId = detail.session_id
if (detail.reporting_expired) {
wx.showLoading({ title: '处理中' })
try {
const session = await api.finishWork(
this.data.moldName,
this.data.processName,
this.data.attendancePointName,
this.data.currentLocation,
)
sessionId = session.id
} finally {
wx.hideLoading()
}
}
wx.navigateTo({
url: `/pages/reportForm/reportForm?sessionId=${sessionId}`,
})
},
handleUnfinishedReportError(error) {
const detail = this.resolveUnfinishedReportDetail(error)
if (!detail || !detail.session_id) {
return Promise.resolve(false)
}
const message = detail.message || error.message || '当前有未完成的报工'
return new Promise(resolve => {
wx.showModal({
title: '未完成报工',
content: `${message}\n是否立刻前往未完成的报工?`,
cancelText: '否',
confirmText: '是',
confirmColor: '#1463ff',
success: async res => {
if (!res.confirm) {
resolve(true)
return
}
try {
await this.openUnfinishedReport(detail)
} catch (openError) {
this.showTip(openError.message || '打开未完成报工失败')
} finally {
resolve(true)
}
},
fail: () => resolve(true),
})
})
},
resolveMoldOccupiedDetail(error) {
if (!error || error.statusCode !== 423) {
return null
}
let detail = error.detail
if (typeof detail === 'string') {
try {
detail = JSON.parse(detail)
} catch (parseError) {
detail = null
}
}
return detail && detail.code === 'mold_occupied' ? detail : null
},
handleMoldOccupiedError(error) {
const detail = this.resolveMoldOccupiedDetail(error)
if (!detail) {
return Promise.resolve(false)
}
this.setData({ occupiedDetail: detail })
return new Promise(resolve => {
wx.showModal({
title: '模具已被占用',
content: detail.message || `已经被${detail.occupied_name || detail.occupied_phone || '其他员工'}占用,请联系管理员解除占用`,
confirmText: '反馈给管理员',
cancelText: '确定',
confirmColor: '#1463ff',
success: async res => {
if (!res.confirm) {
resolve(true)
return
}
wx.showLoading({ title: '反馈中' })
try {
await api.createMoldLockFeedback(detail.lock_id)
wx.showToast({ title: '反馈成功', icon: 'success' })
} catch (feedbackError) {
this.showTip(feedbackError.message || '反馈失败')
} finally {
wx.hideLoading()
resolve(true)
}
},
fail: () => resolve(true),
})
})
},
async feedbackMoldOccupied() {
const detail = this.data.occupiedDetail
if (!detail || !detail.lock_id) {
this.showTip('占用信息缺失,无法反馈')
return
}
wx.showLoading({ title: '反馈中' })
try {
await api.createMoldLockFeedback(detail.lock_id)
wx.showToast({ title: '反馈成功', icon: 'success' })
} catch (error) {
this.showTip(error.message || '反馈失败')
} finally {
wx.hideLoading()
}
},
noop() {},
})