chore: initialize mini program frontend
This commit is contained in:
@@ -0,0 +1,612 @@
|
||||
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() {},
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "报工打卡"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<view class="page safe-bottom">
|
||||
<view class="header">
|
||||
<text class="title">报工打卡</text>
|
||||
<view class="device-badge">
|
||||
<text class="device-label">考勤点</text>
|
||||
<text class="subtitle">{{attendancePointName || '-'}}</text>
|
||||
<text class="device-label">当前模具</text>
|
||||
<text class="device-no">{{moldDisplayName || moldName || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<block wx:if="{{locationAllowed}}">
|
||||
<view class="card">
|
||||
<view class="row">
|
||||
<view>
|
||||
<text class="label">上班打卡时间</text>
|
||||
<text class="value">{{state.startAtText || '-'}}</text>
|
||||
</view>
|
||||
<view class="text-right">
|
||||
<text class="label">下班打卡时间</text>
|
||||
<text class="value">{{state.endAtText || '-'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="row">
|
||||
<view class="row-left">
|
||||
<text class="label">当前人员</text>
|
||||
<block wx:if="{{user}}">
|
||||
<text class="value">{{user.name}} · {{user.phone}}</text>
|
||||
<text class="subtitle">{{user.role === 'worker' ? '冲压工人' : user.role === 'admin' ? '管理员' : '经理'}}</text>
|
||||
</block>
|
||||
<text wx:else class="value">未登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!user}}" class="card">
|
||||
<view class="button-row">
|
||||
<button class="btn primary full" open-type="getPhoneNumber" bindgetphonenumber="wechatLogin">手机号快捷登录</button>
|
||||
</view>
|
||||
<view wx:if="{{roleChoices.length}}" class="role-list">
|
||||
<text class="label">请选择角色</text>
|
||||
<button
|
||||
wx:for="{{roleChoices}}"
|
||||
wx:key="role"
|
||||
class="role-btn"
|
||||
data-role="{{item.role}}"
|
||||
bindtap="chooseRole"
|
||||
>
|
||||
{{item.name}} · {{item.roleName}}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{state && user && state.action === 'switch_or_finish'}}" class="clock-action-row">
|
||||
<button class="btn secondary clock-action-btn" bindtap="switchMold">换模具</button>
|
||||
<button class="btn primary clock-action-btn" bindtap="finishWork">打卡下班</button>
|
||||
</view>
|
||||
<button
|
||||
wx:elif="{{state && user}}"
|
||||
class="btn primary full clock-btn"
|
||||
bindtap="handleAction"
|
||||
>
|
||||
{{state.actionText}}
|
||||
</button>
|
||||
<view wx:elif="{{occupiedDetail}}" class="card occupied-card">
|
||||
<text class="value">模具已被占用</text>
|
||||
<text class="subtitle">{{occupiedDetail.message || '该模具已被其他员工占用,请联系管理员解除占用'}}</text>
|
||||
<view class="button-row">
|
||||
<button class="btn secondary full" bindtap="feedbackMoldOccupied">反馈给管理员</button>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:else class="list-empty">当前模具码不可用</view>
|
||||
</block>
|
||||
|
||||
<view wx:else class="card">
|
||||
<text class="value">{{locationChecking ? '正在校验位置' : '无法打开报工页面'}}</text>
|
||||
<text class="subtitle">{{locationMessage}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{toastVisible}}" class="clock-toast">
|
||||
<text>{{toastMessage}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{temporaryWorkerModalVisible}}" class="temp-worker-mask" catchtap="closeTemporaryWorkerModal">
|
||||
<view class="temp-worker-panel" catchtap="noop">
|
||||
<text class="temp-worker-title">创建临时工</text>
|
||||
<text class="temp-worker-tip">{{temporaryWorkerPrompt}}</text>
|
||||
<view class="temp-worker-field">
|
||||
<text class="temp-worker-label">姓名</text>
|
||||
<input
|
||||
class="input temp-worker-input"
|
||||
value="{{temporaryWorkerName}}"
|
||||
placeholder="请输入姓名"
|
||||
bindinput="onTemporaryWorkerNameInput"
|
||||
confirm-type="done"
|
||||
cursor-spacing="24"
|
||||
/>
|
||||
</view>
|
||||
<view class="button-row temp-worker-actions">
|
||||
<button class="btn secondary full" bindtap="closeTemporaryWorkerModal">取消</button>
|
||||
<button class="btn primary full" bindtap="submitTemporaryWorker">确认创建</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,105 @@
|
||||
.clock-btn {
|
||||
margin-top: 36rpx;
|
||||
height: 104rpx;
|
||||
line-height: 104rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 18rpx;
|
||||
}
|
||||
|
||||
.clock-action-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 20rpx;
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.clock-action-btn {
|
||||
width: 100%;
|
||||
height: 104rpx;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 104rpx;
|
||||
border-radius: 18rpx;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.occupied-card {
|
||||
margin-top: 30rpx;
|
||||
border-color: #d92d20;
|
||||
background: linear-gradient(180deg, #fffafa 0%, #fff1f1 100%);
|
||||
}
|
||||
|
||||
.device-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-top: 18rpx;
|
||||
padding: 14rpx 22rpx;
|
||||
border: 1rpx solid #b8c4d4;
|
||||
border-radius: 18rpx;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(225, 232, 241, 0.92)),
|
||||
#edf2f7;
|
||||
box-shadow:
|
||||
0 9rpx 0 rgba(173, 185, 201, 0.72),
|
||||
0 16rpx 22rpx rgba(22, 34, 50, 0.12),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.device-label {
|
||||
color: #667085;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.device-no {
|
||||
color: #162232;
|
||||
font-size: 46rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.role-list {
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
|
||||
.role-btn {
|
||||
margin-top: 12rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
border-radius: 14rpx;
|
||||
background: linear-gradient(180deg, #f6faff 0%, #e4efff 100%);
|
||||
color: #1456c2;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
box-shadow:
|
||||
0 7rpx 0 rgba(144, 178, 235, 0.62),
|
||||
0 12rpx 18rpx rgba(20, 86, 194, 0.1),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.84);
|
||||
}
|
||||
|
||||
.role-btn::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.clock-toast {
|
||||
position: fixed;
|
||||
left: 72rpx;
|
||||
right: 72rpx;
|
||||
top: 48%;
|
||||
z-index: 120;
|
||||
padding: 24rpx 28rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(32, 37, 45, 0.9);
|
||||
color: #ffffff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
box-shadow:
|
||||
0 12rpx 0 rgba(15, 23, 42, 0.2),
|
||||
0 22rpx 34rpx rgba(15, 23, 42, 0.2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
Reference in New Issue
Block a user