chore: initialize mini program frontend
This commit is contained in:
@@ -0,0 +1,871 @@
|
||||
const api = require('../../utils/api')
|
||||
|
||||
const pad = value => {
|
||||
const text = String(value)
|
||||
return text.length > 1 ? text : `0${text}`
|
||||
}
|
||||
|
||||
const datePart = value => {
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||
}
|
||||
|
||||
const timePart = value => {
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
|
||||
const dateTimeValue = (date, time) => (date && time ? `${date}T${time}:00` : '')
|
||||
|
||||
const productChoiceKey = item => `${item.attendancePointName || ''}||${item.projectNo}||${item.productName}`
|
||||
|
||||
const optionKey = item => `${item.attendancePointName || ''}||${item.projectNo}||${item.productName}||${item.process || ''}||${api.normalizeDeviceNo(item.deviceNo)}`
|
||||
|
||||
const segmentKey = item => `${api.normalizeDeviceNo(item.moldName || item.productName || item.deviceNo)}||${String(item.process || '').trim()}`
|
||||
|
||||
const round2 = value => Math.round(Number(value || 0) * 100) / 100
|
||||
|
||||
const withCompareClasses = item => {
|
||||
const standardWorkload = item.isCleaning
|
||||
? 0
|
||||
: (item.isMisc ? 0 : (api.calculateStandardWorkload(item.allocatedMinutes, item.standardBeat) || Number(item.standardWorkload || 0)))
|
||||
const beatComparison = (item.isCleaning || item.isMisc) ? api.classifyBeat(0, 0) : api.classifyBeat(item.actualBeat, item.standardBeat)
|
||||
const workloadComparison = (item.isCleaning || item.isMisc) ? api.classifyWorkload(0, 0) : api.classifyWorkload(item.goodQty, standardWorkload)
|
||||
return {
|
||||
...item,
|
||||
standardWorkload,
|
||||
beatCompareClass: beatComparison.className,
|
||||
beatReason: beatComparison.reason,
|
||||
workloadCompareClass: workloadComparison.className,
|
||||
workloadReason: workloadComparison.reason,
|
||||
}
|
||||
}
|
||||
|
||||
const correctionOrCurrent = (corrections, key, currentValue) => (
|
||||
corrections && corrections[key] !== undefined ? corrections[key] : currentValue
|
||||
)
|
||||
|
||||
const itemActualBeat = item => {
|
||||
const outputQty = Number(item.goodQty || 0) + Number(item.defectQty || 0)
|
||||
return outputQty > 0 ? round2(Number(item.allocatedMinutes || 0) * 60 / outputQty) : 0
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
user: null,
|
||||
rows: [],
|
||||
page: 1,
|
||||
pageInput: '1',
|
||||
totalPages: 1,
|
||||
focusReportId: '',
|
||||
refreshing: false,
|
||||
scrollTop: 0,
|
||||
expandedReportId: '',
|
||||
detailExpandedAll: false,
|
||||
filterVoided: true,
|
||||
productModalVisible: false,
|
||||
productModalRows: [],
|
||||
productModalKeyword: '',
|
||||
productModalPage: 1,
|
||||
productModalPageInput: '1',
|
||||
productModalTotalPages: 1,
|
||||
productModalContext: null,
|
||||
productModalLoading: false,
|
||||
},
|
||||
onLoad(options) {
|
||||
this.setData({ focusReportId: options.reportId || '' })
|
||||
},
|
||||
onShow() {
|
||||
this.load()
|
||||
},
|
||||
noop() {},
|
||||
onScroll(e) {
|
||||
this.currentScrollTop = e.detail.scrollTop || 0
|
||||
},
|
||||
toggleDetails() {
|
||||
this.setData({
|
||||
detailExpandedAll: !this.data.detailExpandedAll,
|
||||
expandedReportId: '',
|
||||
})
|
||||
},
|
||||
queryReportTop(id) {
|
||||
return new Promise(resolve => {
|
||||
this.createSelectorQuery()
|
||||
.select(`#review-report-${id}`)
|
||||
.boundingClientRect(rect => {
|
||||
resolve(rect ? rect.top : null)
|
||||
})
|
||||
.exec()
|
||||
})
|
||||
},
|
||||
restoreReportPosition(id, beforeTop) {
|
||||
if (beforeTop === null || beforeTop === undefined) {
|
||||
return
|
||||
}
|
||||
wx.nextTick(() => {
|
||||
this.queryReportTop(id).then(afterTop => {
|
||||
if (afterTop === null || afterTop === undefined) {
|
||||
return
|
||||
}
|
||||
const nextScrollTop = Math.max(
|
||||
0,
|
||||
(this.currentScrollTop || 0) + afterTop - beforeTop,
|
||||
)
|
||||
this.currentScrollTop = nextScrollTop
|
||||
this.setData({ scrollTop: nextScrollTop })
|
||||
})
|
||||
})
|
||||
},
|
||||
toggleReportDetail(e) {
|
||||
const id = String(e.currentTarget.dataset.id || '')
|
||||
const switchingReport = this.data.detailExpandedAll
|
||||
|| (!!this.data.expandedReportId && this.data.expandedReportId !== id)
|
||||
this.queryReportTop(id).then(beforeTop => {
|
||||
this.setData({
|
||||
detailExpandedAll: false,
|
||||
expandedReportId: this.data.expandedReportId === id ? '' : id,
|
||||
}, () => {
|
||||
if (switchingReport) {
|
||||
this.restoreReportPosition(id, beforeTop)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
async load(page = this.data.page) {
|
||||
const user = api.getCurrentUser()
|
||||
try {
|
||||
let result
|
||||
if (this.data.focusReportId) {
|
||||
const report = await api.getReport(this.data.focusReportId)
|
||||
result = {
|
||||
rows: (report.status === 'pending' || report.canEdit || report.isVoided) ? [report] : [],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
}
|
||||
} else {
|
||||
result = await api.listReports({
|
||||
status: 'pending',
|
||||
page,
|
||||
pageSize: 6,
|
||||
includeVoided: !this.data.filterVoided,
|
||||
})
|
||||
}
|
||||
const rows = await this.buildReviewRows(result.rows || [])
|
||||
const defaultExpandedId = this.data.focusReportId && rows.length ? String(rows[0].id) : ''
|
||||
this.setData({
|
||||
user,
|
||||
rows,
|
||||
page: result.page,
|
||||
pageInput: String(result.page || 1),
|
||||
totalPages: result.totalPages,
|
||||
expandedReportId: defaultExpandedId,
|
||||
detailExpandedAll: false,
|
||||
})
|
||||
} catch (error) {
|
||||
this.setData({ user, rows: [] })
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
}
|
||||
},
|
||||
async onPullDownRefresh() {
|
||||
this.setData({ refreshing: true })
|
||||
try {
|
||||
await this.load(this.data.page)
|
||||
} finally {
|
||||
this.setData({ refreshing: false })
|
||||
if (wx.stopPullDownRefresh) {
|
||||
wx.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
},
|
||||
async buildReviewRows(rows) {
|
||||
const hydrated = []
|
||||
for (const row of rows) {
|
||||
const editItems = []
|
||||
const editDeviceSegments = (row.deviceSegments || []).map((segment, index) => this.makeEditDeviceSegment(segment, index))
|
||||
for (const detail of row.items || []) {
|
||||
const editItem = await this.buildReviewItem(detail)
|
||||
const itemKey = segmentKey(editItem)
|
||||
editItem.deviceSegments = editDeviceSegments.filter(segment => segmentKey(segment) === itemKey)
|
||||
editItems.push(editItem)
|
||||
}
|
||||
hydrated.push({
|
||||
...row,
|
||||
timeRangeText: row.isCleaning ? '清洗报工' : `${row.startAtText} 至 ${row.endAtText}`,
|
||||
editStartDate: datePart(row.startAt),
|
||||
editStartTime: timePart(row.startAt),
|
||||
editEndDate: datePart(row.endAt),
|
||||
editEndTime: timePart(row.endAt),
|
||||
editReviewRemark: row.reviewRemark || '',
|
||||
editItems,
|
||||
})
|
||||
}
|
||||
return hydrated
|
||||
},
|
||||
async buildReviewItem(detail) {
|
||||
const [products, equipmentOptions] = await Promise.all([
|
||||
this.loadProductOptions(detail.productName, detail.attendancePointName),
|
||||
detail.isMisc ? Promise.resolve([]) : this.loadEquipmentOptions(detail.attendancePointName, !!detail.isCleaning),
|
||||
])
|
||||
return this.makeEditItem(detail, products, equipmentOptions)
|
||||
},
|
||||
async loadProductOptions(productName, attendancePointName = '', keyword = '') {
|
||||
try {
|
||||
const result = await api.listProducts({
|
||||
productName: keyword ? '' : productName,
|
||||
keyword,
|
||||
attendancePointName,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
return result.rows || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
async loadEquipmentOptions(attendancePointName = '', isCleaning = false) {
|
||||
try {
|
||||
const result = await api.listEquipment({
|
||||
attendancePointName,
|
||||
deviceType: isCleaning ? '清洗设备' : '冲压设备',
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
})
|
||||
return result.rows || []
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
makeEditItem(detail, products, equipmentOptions = []) {
|
||||
const corrections = detail.corrections || {}
|
||||
const current = {
|
||||
attendancePointName: detail.attendancePointName || '',
|
||||
projectNo: detail.projectNo,
|
||||
productName: detail.productName,
|
||||
process: detail.process || '',
|
||||
stampingMethod: detail.stampingMethod || '',
|
||||
deviceNo: detail.deviceNo,
|
||||
materialCode: detail.materialCode || '',
|
||||
materialName: detail.materialName || '',
|
||||
rawMaterialBatchNo: detail.rawMaterialBatchNo || '',
|
||||
standardBeat: detail.standardBeat || 0,
|
||||
standardWorkload: detail.standardWorkload || 0,
|
||||
isCleaning: !!detail.isCleaning,
|
||||
isContinuousDie: !!detail.isContinuousDie,
|
||||
isMisc: !!detail.isMisc,
|
||||
isMultiPerson: !!detail.isMultiPerson,
|
||||
operatorCount: detail.operatorCount || 1,
|
||||
changeoverCount: detail.changeoverCount || 0,
|
||||
remark: detail.remark || '',
|
||||
}
|
||||
const originalProduct = {
|
||||
attendancePointName: current.attendancePointName,
|
||||
projectNo: correctionOrCurrent(corrections, 'project_no', current.projectNo),
|
||||
productName: correctionOrCurrent(corrections, 'product_name', current.productName),
|
||||
process: correctionOrCurrent(corrections, 'process_name', current.process) || '',
|
||||
materialCode: current.materialCode,
|
||||
materialName: current.materialName,
|
||||
stampingMethod: correctionOrCurrent(corrections, 'stamping_method', current.stampingMethod) || '',
|
||||
operatorCount: current.operatorCount,
|
||||
standardBeat: current.standardBeat,
|
||||
isCleaning: current.isCleaning,
|
||||
isContinuousDie: current.isContinuousDie,
|
||||
isMisc: current.isMisc,
|
||||
isMultiPerson: current.isMultiPerson,
|
||||
}
|
||||
const optionMap = {}
|
||||
;(products || []).forEach(product => {
|
||||
optionMap[optionKey(product)] = product
|
||||
})
|
||||
optionMap[optionKey(current)] = current
|
||||
const options = Object.keys(optionMap).map(key => optionMap[key])
|
||||
const productMap = {}
|
||||
options.forEach(product => {
|
||||
productMap[productChoiceKey(product)] = {
|
||||
attendancePointName: product.attendancePointName || current.attendancePointName,
|
||||
projectNo: product.projectNo,
|
||||
productName: product.productName,
|
||||
}
|
||||
})
|
||||
const productChoices = Object.keys(productMap).map(key => productMap[key])
|
||||
const productLabels = productChoices.map(item => `${item.projectNo} · ${item.productName}`)
|
||||
let productIndex = productChoices.findIndex(item => item.projectNo === current.projectNo && item.productName === current.productName)
|
||||
if (productIndex < 0) {
|
||||
productIndex = 0
|
||||
}
|
||||
const selectedProduct = productChoices[productIndex] || current
|
||||
const processOptions = options.filter(item => (
|
||||
item.attendancePointName === selectedProduct.attendancePointName
|
||||
&& item.projectNo === selectedProduct.projectNo
|
||||
&& item.productName === selectedProduct.productName
|
||||
))
|
||||
const processLabels = processOptions.map(item => item.process || '未填写工序')
|
||||
let processIndex = processOptions.findIndex(item => (item.process || '') === current.process)
|
||||
if (processIndex < 0) {
|
||||
processIndex = 0
|
||||
}
|
||||
const selectedOption = processOptions[processIndex] || current
|
||||
const selectedDeviceNo = api.normalizeDeviceNo(detail.deviceNo)
|
||||
const deviceIndex = selectedDeviceNo
|
||||
? equipmentOptions.findIndex(item => api.normalizeDeviceNo(item.deviceNo) === selectedDeviceNo)
|
||||
: -1
|
||||
return withCompareClasses({
|
||||
id: detail.id,
|
||||
attendancePointName: current.attendancePointName,
|
||||
startAt: detail.startAt || '',
|
||||
isCleaning: !!detail.isCleaning,
|
||||
isContinuousDie: !!selectedOption.isContinuousDie,
|
||||
isMisc: !!detail.isMisc,
|
||||
isMultiPerson: !!selectedOption.isMultiPerson,
|
||||
operatorCount: selectedOption.operatorCount || detail.operatorCount || 1,
|
||||
changeoverCount: detail.changeoverCount || 0,
|
||||
changeoverCountError: false,
|
||||
remark: detail.remark || '',
|
||||
deviceError: false,
|
||||
deviceNo: detail.deviceNo,
|
||||
deviceIndex,
|
||||
equipmentOptions,
|
||||
projectNo: selectedOption.projectNo,
|
||||
productName: selectedOption.productName,
|
||||
process: selectedOption.process || '',
|
||||
stampingMethod: selectedOption.stampingMethod || current.stampingMethod || '',
|
||||
rawMaterialBatchNo: detail.rawMaterialBatchNo || '',
|
||||
productSearchKeyword: '',
|
||||
productOptions: options,
|
||||
productChoices,
|
||||
productLabels,
|
||||
productIndex,
|
||||
processOptions,
|
||||
processLabels,
|
||||
processIndex,
|
||||
originalProduct,
|
||||
goodQty: detail.goodQty,
|
||||
defectQty: detail.defectQty,
|
||||
allocatedMinutes: detail.allocatedMinutes || 0,
|
||||
workTimeText: detail.workTimeText || '0秒',
|
||||
actualBeat: detail.actualBeat || 0,
|
||||
standardBeat: selectedOption.standardBeat || detail.standardBeat || 0,
|
||||
standardWorkload: selectedOption.standardWorkload || detail.standardWorkload || 0,
|
||||
deviceSegments: detail.deviceSegments || [],
|
||||
})
|
||||
},
|
||||
productToEditItem(item, product) {
|
||||
const selectedDeviceNo = api.normalizeDeviceNo(item.deviceNo)
|
||||
const deviceIndex = selectedDeviceNo
|
||||
? (item.equipmentOptions || []).findIndex(equipment => api.normalizeDeviceNo(equipment.deviceNo) === selectedDeviceNo)
|
||||
: -1
|
||||
const nextItem = {
|
||||
...item,
|
||||
attendancePointName: item.attendancePointName,
|
||||
projectNo: product.projectNo || item.projectNo,
|
||||
productName: product.productName || item.productName,
|
||||
materialCode: product.materialCode || '',
|
||||
materialName: product.materialName || '',
|
||||
process: product.process || '',
|
||||
stampingMethod: product.stampingMethod || '',
|
||||
standardBeat: Number(product.standardBeat || 0),
|
||||
standardWorkload: api.calculateStandardWorkload(item.allocatedMinutes, product.standardBeat),
|
||||
operatorCount: product.operatorCount || 1,
|
||||
isCleaning: !!product.isCleaning,
|
||||
isContinuousDie: !!product.isContinuousDie,
|
||||
isMisc: !!product.isMisc,
|
||||
isMultiPerson: !!product.isMultiPerson,
|
||||
changeoverCount: product.isContinuousDie ? (item.changeoverCount || 0) : 0,
|
||||
changeoverCountError: false,
|
||||
actualBeat: itemActualBeat(item),
|
||||
deviceIndex,
|
||||
productOptions: [product],
|
||||
productChoices: [product],
|
||||
productLabels: [`${product.projectNo || ''} · ${product.productName || ''}`],
|
||||
productIndex: 0,
|
||||
processOptions: [product],
|
||||
processLabels: [product.process || '未填写工序'],
|
||||
processIndex: 0,
|
||||
}
|
||||
return withCompareClasses(nextItem)
|
||||
},
|
||||
replaceEditProduct(reportIndex, itemIndex, product) {
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
if (!item || !product) {
|
||||
return
|
||||
}
|
||||
this.replaceEditItem(reportIndex, itemIndex, this.productToEditItem(item, product))
|
||||
},
|
||||
async openProductModal(e) {
|
||||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||||
const row = this.data.rows[reportIndex]
|
||||
const item = row && row.editItems ? row.editItems[itemIndex] : null
|
||||
if (!item) {
|
||||
wx.showToast({ title: '报工明细不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
productModalVisible: true,
|
||||
productModalContext: { reportIndex, itemIndex },
|
||||
productModalRows: [],
|
||||
productModalKeyword: '',
|
||||
productModalPage: 1,
|
||||
productModalPageInput: '1',
|
||||
productModalTotalPages: 1,
|
||||
})
|
||||
await this.loadProductModal(1)
|
||||
},
|
||||
closeProductModal() {
|
||||
this.setData({
|
||||
productModalVisible: false,
|
||||
productModalRows: [],
|
||||
productModalKeyword: '',
|
||||
productModalContext: null,
|
||||
})
|
||||
},
|
||||
onDeviceChange(e) {
|
||||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||||
const deviceIndex = Number(e.detail.value)
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
const equipment = (item.equipmentOptions || [])[deviceIndex]
|
||||
if (!equipment) {
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceIndex`]: deviceIndex,
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceNo`]: equipment.deviceNo,
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceError`]: false,
|
||||
})
|
||||
},
|
||||
async loadProductModal(page = this.data.productModalPage) {
|
||||
const context = this.data.productModalContext
|
||||
if (!context) {
|
||||
return
|
||||
}
|
||||
const item = this.data.rows[context.reportIndex].editItems[context.itemIndex]
|
||||
this.setData({ productModalLoading: true })
|
||||
try {
|
||||
const result = await api.listProducts({
|
||||
keyword: this.data.productModalKeyword,
|
||||
attendancePointName: item.attendancePointName,
|
||||
page,
|
||||
pageSize: 8,
|
||||
})
|
||||
this.setData({
|
||||
productModalRows: result.rows || [],
|
||||
productModalPage: result.page,
|
||||
productModalPageInput: String(result.page || 1),
|
||||
productModalTotalPages: result.totalPages,
|
||||
})
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '加载产品失败', icon: 'none' })
|
||||
} finally {
|
||||
this.setData({ productModalLoading: false })
|
||||
}
|
||||
},
|
||||
onProductModalKeywordInput(e) {
|
||||
this.setData({ productModalKeyword: e.detail.value })
|
||||
this.loadProductModal(1)
|
||||
},
|
||||
replaceProductFromModal(e) {
|
||||
const index = Number(e.currentTarget.dataset.index)
|
||||
const context = this.data.productModalContext
|
||||
const product = this.data.productModalRows[index]
|
||||
if (!context || !product) {
|
||||
return
|
||||
}
|
||||
const item = this.data.rows[context.reportIndex].editItems[context.itemIndex]
|
||||
if (product.isMisc) {
|
||||
wx.showToast({ title: '处理杂活不能替换到普通报工', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!!item.isCleaning !== !!product.isCleaning) {
|
||||
wx.showToast({ title: item.isCleaning ? '清洗报工只能替换清洗工序' : '普通报工不能替换为清洗工序', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.replaceEditProduct(context.reportIndex, context.itemIndex, product)
|
||||
this.closeProductModal()
|
||||
},
|
||||
productModalPrevPage() {
|
||||
if (this.data.productModalPage > 1) {
|
||||
this.loadProductModal(this.data.productModalPage - 1)
|
||||
}
|
||||
},
|
||||
productModalNextPage() {
|
||||
if (this.data.productModalPage < this.data.productModalTotalPages) {
|
||||
this.loadProductModal(this.data.productModalPage + 1)
|
||||
}
|
||||
},
|
||||
onProductModalPageInput(e) {
|
||||
this.setData({ productModalPageInput: e.detail.value })
|
||||
},
|
||||
jumpProductModalPage(e) {
|
||||
wx.hideKeyboard()
|
||||
const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.productModalPageInput
|
||||
const target = Math.max(1, Math.min(Number(inputValue) || this.data.productModalPage, this.data.productModalTotalPages))
|
||||
if (target === this.data.productModalPage) {
|
||||
this.setData({ productModalPageInput: String(this.data.productModalPage || 1) })
|
||||
return
|
||||
}
|
||||
this.loadProductModal(target)
|
||||
},
|
||||
async restoreProduct(e) {
|
||||
const reportIndex = Number(e.currentTarget.dataset.reportIndex)
|
||||
const itemIndex = Number(e.currentTarget.dataset.itemIndex)
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
if (!item || !item.originalProduct) {
|
||||
return
|
||||
}
|
||||
const original = item.originalProduct
|
||||
wx.showLoading({ title: '恢复中' })
|
||||
try {
|
||||
const products = await this.loadProductOptions(original.productName, item.attendancePointName)
|
||||
const product = (products || []).find(option => (
|
||||
option.projectNo === original.projectNo
|
||||
&& option.productName === original.productName
|
||||
&& (option.process || '') === (original.process || '')
|
||||
)) || original
|
||||
this.replaceEditProduct(reportIndex, itemIndex, {
|
||||
...product,
|
||||
projectNo: original.projectNo,
|
||||
productName: original.productName,
|
||||
process: original.process || '',
|
||||
stampingMethod: product.stampingMethod || original.stampingMethod || '',
|
||||
materialCode: product.materialCode || original.materialCode || '',
|
||||
materialName: product.materialName || original.materialName || '',
|
||||
operatorCount: product.operatorCount || original.operatorCount || 1,
|
||||
standardBeat: product.standardBeat === undefined ? original.standardBeat : product.standardBeat,
|
||||
isCleaning: product.isCleaning === undefined ? original.isCleaning : product.isCleaning,
|
||||
isContinuousDie: product.isContinuousDie === undefined ? original.isContinuousDie : product.isContinuousDie,
|
||||
isMisc: product.isMisc === undefined ? original.isMisc : product.isMisc,
|
||||
isMultiPerson: product.isMultiPerson === undefined ? original.isMultiPerson : product.isMultiPerson,
|
||||
changeoverCount: (product.isContinuousDie === undefined ? original.isContinuousDie : product.isContinuousDie) ? (item.changeoverCount || 0) : 0,
|
||||
})
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '恢复失败', icon: 'none' })
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
makeEditDeviceSegment(segment, index) {
|
||||
const editScanDate = datePart(segment.scannedAt)
|
||||
const editScanTime = timePart(segment.scannedAt)
|
||||
return {
|
||||
...segment,
|
||||
label: index === 0 ? '开始模具' : '换模具',
|
||||
editScanDate,
|
||||
editScanTime,
|
||||
originalScanDate: editScanDate,
|
||||
originalScanTime: editScanTime,
|
||||
}
|
||||
},
|
||||
replaceEditItem(reportIndex, itemIndex, item) {
|
||||
this.setData({
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}]`]: item,
|
||||
})
|
||||
},
|
||||
onDeviceSegmentFieldChange(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
const segmentIndex = e.currentTarget.dataset.segmentIndex
|
||||
const field = e.currentTarget.dataset.field
|
||||
this.setData({
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].deviceSegments[${segmentIndex}].${field}`]: e.detail.value,
|
||||
})
|
||||
},
|
||||
onReportFieldChange(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const field = e.currentTarget.dataset.field
|
||||
this.setData({
|
||||
[`rows[${reportIndex}].${field}`]: e.detail.value,
|
||||
})
|
||||
},
|
||||
onItemInput(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
const field = e.currentTarget.dataset.field
|
||||
const value = e.detail.value
|
||||
const updates = {
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].${field}`]: value,
|
||||
}
|
||||
if (field === 'goodQty' || field === 'defectQty') {
|
||||
const item = {
|
||||
...this.data.rows[reportIndex].editItems[itemIndex],
|
||||
[field]: value,
|
||||
}
|
||||
const outputQty = Number(item.goodQty || 0) + Number(item.defectQty || 0)
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].actualBeat`] = outputQty > 0
|
||||
? round2(Number(item.allocatedMinutes || 0) * 60 / outputQty)
|
||||
: 0
|
||||
const nextItem = {
|
||||
...item,
|
||||
actualBeat: updates[`rows[${reportIndex}].editItems[${itemIndex}].actualBeat`],
|
||||
}
|
||||
const compared = withCompareClasses(nextItem)
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].standardWorkload`] = compared.standardWorkload
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].beatCompareClass`] = compared.beatCompareClass
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].beatReason`] = compared.beatReason
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].workloadCompareClass`] = compared.workloadCompareClass
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].workloadReason`] = compared.workloadReason
|
||||
}
|
||||
if (field === 'changeoverCount') {
|
||||
updates[`rows[${reportIndex}].editItems[${itemIndex}].changeoverCountError`] = false
|
||||
}
|
||||
this.setData(updates)
|
||||
},
|
||||
async reloadItemOptions(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
wx.showLoading({ title: '匹配中' })
|
||||
try {
|
||||
const keyword = String(item.productSearchKeyword || '').trim()
|
||||
const products = await this.loadProductOptions(item.productName, item.attendancePointName, keyword)
|
||||
this.replaceEditItem(reportIndex, itemIndex, this.makeEditItem(item, products))
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
onProductSearchInput(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
this.setData({
|
||||
[`rows[${reportIndex}].editItems[${itemIndex}].productSearchKeyword`]: e.detail.value,
|
||||
})
|
||||
},
|
||||
onProductChange(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
const productIndex = Number(e.detail.value)
|
||||
const selectedProduct = item.productChoices[productIndex]
|
||||
const processOptions = item.productOptions.filter(option => (
|
||||
option.attendancePointName === selectedProduct.attendancePointName
|
||||
&& option.projectNo === selectedProduct.projectNo
|
||||
&& option.productName === selectedProduct.productName
|
||||
))
|
||||
const nextOption = processOptions[0] || selectedProduct
|
||||
this.replaceEditItem(reportIndex, itemIndex, withCompareClasses({
|
||||
...item,
|
||||
productIndex,
|
||||
attendancePointName: item.attendancePointName,
|
||||
projectNo: selectedProduct.projectNo,
|
||||
productName: selectedProduct.productName,
|
||||
processOptions,
|
||||
processLabels: processOptions.map(option => option.process || '未填写工序'),
|
||||
processIndex: 0,
|
||||
process: nextOption.process || '',
|
||||
stampingMethod: nextOption.stampingMethod || '',
|
||||
deviceNo: item.deviceNo,
|
||||
standardBeat: nextOption.standardBeat || 0,
|
||||
standardWorkload: nextOption.standardWorkload || 0,
|
||||
operatorCount: nextOption.operatorCount || 1,
|
||||
isMultiPerson: !!nextOption.isMultiPerson,
|
||||
}))
|
||||
},
|
||||
onProcessChange(e) {
|
||||
const reportIndex = e.currentTarget.dataset.reportIndex
|
||||
const itemIndex = e.currentTarget.dataset.itemIndex
|
||||
const item = this.data.rows[reportIndex].editItems[itemIndex]
|
||||
const processIndex = Number(e.detail.value)
|
||||
const selected = item.processOptions[processIndex]
|
||||
if (!selected) {
|
||||
return
|
||||
}
|
||||
this.replaceEditItem(reportIndex, itemIndex, withCompareClasses({
|
||||
...item,
|
||||
processIndex,
|
||||
attendancePointName: item.attendancePointName,
|
||||
deviceNo: item.deviceNo,
|
||||
projectNo: selected.projectNo,
|
||||
productName: selected.productName,
|
||||
process: selected.process || '',
|
||||
stampingMethod: selected.stampingMethod || '',
|
||||
standardBeat: selected.standardBeat || 0,
|
||||
standardWorkload: selected.standardWorkload || 0,
|
||||
operatorCount: selected.operatorCount || 1,
|
||||
isMultiPerson: !!selected.isMultiPerson,
|
||||
}))
|
||||
},
|
||||
async approve(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const user = this.data.user
|
||||
if (!user || user.role !== 'admin') {
|
||||
wx.showToast({ title: '无审核权限', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const row = this.data.rows.find(item => String(item.id) === String(id))
|
||||
if (!row) {
|
||||
wx.showToast({ title: '报工记录不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (row.isVoided) {
|
||||
wx.showToast({ title: '作废记录不能编辑', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!row.canEdit) {
|
||||
wx.showToast({ title: '已超过可编辑期限', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const invalidItem = (row.editItems || []).find(item => (
|
||||
Number(item.goodQty || 0) < 0
|
||||
|| Number(item.defectQty || 0) < 0
|
||||
|| !Number.isFinite(Number(item.goodQty || 0))
|
||||
|| !Number.isFinite(Number(item.defectQty || 0))
|
||||
))
|
||||
if (invalidItem) {
|
||||
wx.showToast({ title: '产出数量不能为负数', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const invalidChangeoverItemIndex = (row.editItems || []).findIndex(item => {
|
||||
if (!item.isContinuousDie) {
|
||||
return false
|
||||
}
|
||||
const value = Number(item.changeoverCount)
|
||||
return !Number.isInteger(value) || value < 0
|
||||
})
|
||||
if (invalidChangeoverItemIndex >= 0) {
|
||||
this.setData({
|
||||
[`rows[${this.data.rows.findIndex(item => String(item.id) === String(id))}].editItems[${invalidChangeoverItemIndex}].changeoverCountError`]: true,
|
||||
})
|
||||
wx.showToast({ title: '请填写非负整数换料次数', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const missingDeviceItemIndex = (row.editItems || []).findIndex(item => !item.isMisc && !api.normalizeDeviceNo(item.deviceNo))
|
||||
if (missingDeviceItemIndex >= 0) {
|
||||
this.setData({
|
||||
[`rows[${this.data.rows.findIndex(item => String(item.id) === String(id))}].editItems[${missingDeviceItemIndex}].deviceError`]: true,
|
||||
})
|
||||
wx.showToast({ title: '请选择设备号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
await this.submitApprove(id, row, user)
|
||||
},
|
||||
async submitApprove(id, row, user) {
|
||||
const deviceCorrections = (row.editItems || []).flatMap(item => item.deviceSegments || [])
|
||||
.filter(segment => (
|
||||
segment.editScanDate !== segment.originalScanDate
|
||||
|| segment.editScanTime !== segment.originalScanTime
|
||||
))
|
||||
.map(segment => ({
|
||||
id: segment.id,
|
||||
scannedAt: dateTimeValue(segment.editScanDate, segment.editScanTime),
|
||||
}))
|
||||
const invalidDeviceCorrection = deviceCorrections.find(item => !item.scannedAt)
|
||||
if (invalidDeviceCorrection) {
|
||||
wx.showToast({ title: '换模具时间不能为空', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const patch = {
|
||||
startAt: row.isCleaning ? null : dateTimeValue(row.editStartDate, row.editStartTime),
|
||||
endAt: row.isCleaning ? null : dateTimeValue(row.editEndDate, row.editEndTime),
|
||||
deviceCorrections: row.isCleaning ? [] : deviceCorrections,
|
||||
reviewRemark: row.editReviewRemark || '',
|
||||
itemCorrections: (row.editItems || []).map(item => ({
|
||||
id: item.id,
|
||||
device_no: api.normalizeDeviceNo(item.deviceNo),
|
||||
project_no: item.projectNo,
|
||||
product_name: item.productName,
|
||||
raw_material_batch_no: item.rawMaterialBatchNo || null,
|
||||
process_name: item.process || '',
|
||||
changeover_count: item.isContinuousDie ? Number(item.changeoverCount || 0) : 0,
|
||||
good_qty: Number(item.goodQty || 0),
|
||||
defect_qty: Number(item.defectQty || 0),
|
||||
scrap_qty: 0,
|
||||
remark: item.remark || null,
|
||||
})),
|
||||
}
|
||||
|
||||
try {
|
||||
await api.approveReport(id, user, patch)
|
||||
wx.showToast({ title: row.status === 'approved' ? '已保存更改' : '已审核通过', icon: 'success' })
|
||||
if (this.data.focusReportId) {
|
||||
setTimeout(() => {
|
||||
wx.navigateBack({
|
||||
delta: 1,
|
||||
fail: () => {
|
||||
wx.redirectTo({
|
||||
url: '/pages/records/records?mode=pending&status=pending',
|
||||
})
|
||||
},
|
||||
})
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
await this.load(this.data.page)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
}
|
||||
},
|
||||
prevPage() {
|
||||
if (this.data.page > 1) {
|
||||
this.load(this.data.page - 1)
|
||||
}
|
||||
},
|
||||
nextPage() {
|
||||
if (this.data.page < this.data.totalPages) {
|
||||
this.load(this.data.page + 1)
|
||||
}
|
||||
},
|
||||
onPageInput(e) {
|
||||
this.setData({ pageInput: e.detail.value })
|
||||
},
|
||||
jumpToPage(e) {
|
||||
wx.hideKeyboard()
|
||||
const inputValue = e && e.detail && e.detail.value !== undefined ? e.detail.value : this.data.pageInput
|
||||
const target = Math.max(1, Math.min(Number(inputValue) || this.data.page, this.data.totalPages))
|
||||
if (target === this.data.page) {
|
||||
this.setData({ pageInput: String(this.data.page || 1) })
|
||||
return
|
||||
}
|
||||
this.load(target)
|
||||
},
|
||||
toggleFilterVoided() {
|
||||
this.setData({ filterVoided: !this.data.filterVoided, page: 1 })
|
||||
this.load(1)
|
||||
},
|
||||
onFilterVoidedChange(e) {
|
||||
this.setData({ filterVoided: !!e.detail.value, page: 1 })
|
||||
this.load(1)
|
||||
},
|
||||
async voidReport(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.showModal({
|
||||
title: '确认作废',
|
||||
content: '作废的记录将在30天后被系统彻底删除,彻底删除前可随时撤销作废,是否确定删除',
|
||||
cancelText: '否',
|
||||
confirmText: '是',
|
||||
confirmColor: '#c5221f',
|
||||
success: async res => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '处理中' })
|
||||
try {
|
||||
await api.voidReport(id)
|
||||
wx.showToast({ title: '已作废', icon: 'success' })
|
||||
await this.load(this.data.page)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '作废失败', icon: 'none' })
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
async unvoidReport(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
wx.showLoading({ title: '处理中' })
|
||||
try {
|
||||
await api.unvoidReport(id)
|
||||
wx.showToast({ title: '已撤销作废', icon: 'success' })
|
||||
await this.load(this.data.page)
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message || '撤销失败', icon: 'none' })
|
||||
} finally {
|
||||
wx.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "报工审核"
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<scroll-view
|
||||
class="page safe-bottom"
|
||||
scroll-y
|
||||
type="list"
|
||||
scroll-top="{{scrollTop}}"
|
||||
scroll-anchoring="{{true}}"
|
||||
refresher-enabled
|
||||
refresher-triggered="{{refreshing}}"
|
||||
bindscroll="onScroll"
|
||||
bindrefresherrefresh="onPullDownRefresh"
|
||||
>
|
||||
<view class="header">
|
||||
<view class="review-header-row">
|
||||
<view class="review-header-main">
|
||||
<text class="title">报工审核</text>
|
||||
<text class="subtitle">复核并校正员工上报数据后通过</text>
|
||||
</view>
|
||||
<view class="top-action-group">
|
||||
<view class="void-filter-switch">
|
||||
<text>过滤作废</text>
|
||||
<switch checked="{{filterVoided}}" color="#1463ff" bindchange="onFilterVoidedChange" />
|
||||
</view>
|
||||
<button class="btn mini secondary detail-toggle" bindtap="toggleDetails">
|
||||
{{detailExpandedAll ? '收起明细' : '展开明细'}}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!rows.length}}" class="list-empty">暂无待审核报工</view>
|
||||
|
||||
<view
|
||||
wx:for="{{rows}}"
|
||||
wx:key="id"
|
||||
wx:for-index="reportIndex"
|
||||
id="review-report-{{item.id}}"
|
||||
class="card review-card {{item.isSystemAutoSubmitted ? 'system-auto' : ''}} {{item.isVoided ? 'voided' : ''}} {{detailExpandedAll || expandedReportId == item.id ? 'expanded' : ''}}"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="toggleReportDetail"
|
||||
>
|
||||
<text wx:if="{{item.isVoided}}" class="voided-watermark">作废</text>
|
||||
<text wx:elif="{{item.isModified}}" class="modified-watermark">更改</text>
|
||||
<text wx:if="{{item.isSystemAutoSubmitted}}" class="timeout-watermark">{{item.autoSubmitReason || '超时-系统自动提交'}}</text>
|
||||
<text wx:if="{{item.isCleaning}}" class="cleaning-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified ? 'lower-watermark' : ''}}">清洗</text>
|
||||
<text wx:if="{{item.hasMisc}}" class="misc-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified ? 'lower-watermark' : ''}}">包含杂活</text>
|
||||
<text wx:if="{{item.hasMultiPerson}}" class="multi-person-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified || item.hasMisc || item.isCleaning ? 'lower-watermark' : ''}}">多人协作</text>
|
||||
<text wx:if="{{item.hasContinuousDie}}" class="continuous-die-watermark {{item.isSystemAutoSubmitted || item.isVoided || item.isModified || item.hasMisc || item.isCleaning || item.hasMultiPerson ? 'lower-watermark' : ''}} {{item.hasMultiPerson && (item.isSystemAutoSubmitted || item.isVoided || item.isModified || item.hasMisc || item.isCleaning) ? 'third-watermark' : ''}}">连续模</text>
|
||||
<view class="row">
|
||||
<view class="row-left">
|
||||
<text class="value">{{item.employeeName}} · {{item.reportDate}}</text>
|
||||
<text class="subtitle">考勤点 {{item.attendancePointName || '-'}}</text>
|
||||
<text class="subtitle">{{item.timeRangeText}}</text>
|
||||
<text class="subtitle">{{item.metrics.shiftDistributionText || ''}}</text>
|
||||
</view>
|
||||
<text class="pill {{item.status === 'approved' ? 'success' : 'warn'}}">{{item.statusName}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{detailExpandedAll || expandedReportId == item.id}}" catchtap="noop">
|
||||
<view class="divider"></view>
|
||||
<view wx:if="{{!item.isCleaning && !item.isVoided && item.canEdit}}" class="review-edit-panel">
|
||||
<text class="panel-title">上下班时间校正</text>
|
||||
<view class="time-grid">
|
||||
<picker mode="date" value="{{item.editStartDate}}" data-report-index="{{reportIndex}}" data-field="editStartDate" bindchange="onReportFieldChange">
|
||||
<view class="picker compact-picker">{{item.editStartDate || '上班日期'}}</view>
|
||||
</picker>
|
||||
<picker mode="time" value="{{item.editStartTime}}" data-report-index="{{reportIndex}}" data-field="editStartTime" bindchange="onReportFieldChange">
|
||||
<view class="picker compact-picker">{{item.editStartTime || '上班时间'}}</view>
|
||||
</picker>
|
||||
<picker mode="date" value="{{item.editEndDate}}" data-report-index="{{reportIndex}}" data-field="editEndDate" bindchange="onReportFieldChange">
|
||||
<view class="picker compact-picker">{{item.editEndDate || '下班日期'}}</view>
|
||||
</picker>
|
||||
<picker mode="time" value="{{item.editEndTime}}" data-report-index="{{reportIndex}}" data-field="editEndTime" bindchange="onReportFieldChange">
|
||||
<view class="picker compact-picker">{{item.editEndTime || '下班时间'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="item-list">
|
||||
<view wx:for="{{item.editItems}}" wx:key="id" wx:for-item="detail" wx:for-index="itemIndex" class="detail-line {{detail.isMultiPerson || detail.isContinuousDie ? 'marked-detail-line' : ''}} {{detail.isMultiPerson && detail.isContinuousDie ? 'stacked-detail-line' : ''}}">
|
||||
<text wx:if="{{detail.isMultiPerson}}" class="detail-multi-stamp">多人协作</text>
|
||||
<text wx:if="{{detail.isContinuousDie}}" class="detail-continuous-stamp {{detail.isMultiPerson ? 'lower-detail-stamp' : ''}}">连续模</text>
|
||||
<text class="detail-title">{{detail.deviceNo}} · {{detail.productName}} / {{detail.process || '-'}} / {{detail.stampingMethod || '-'}}</text>
|
||||
<text class="muted">考勤点 {{detail.attendancePointName || item.attendancePointName || '-'}}</text>
|
||||
<text wx:if="{{detail.isMisc}}" class="muted">处理杂活 · {{detail.workTimeText}}</text>
|
||||
<text wx:elif="{{detail.isCleaning}}" class="muted">工序 {{detail.process || '-'}} · 冲压方式 {{detail.stampingMethod || '-'}} · 清洗数量 {{detail.goodQty}}</text>
|
||||
<text wx:else class="muted">工序 {{detail.process || '-'}} · 冲压方式 {{detail.stampingMethod || '-'}} · 成品 {{detail.goodQty}} · 不良 {{detail.defectQty}}</text>
|
||||
<text wx:if="{{!detail.isCleaning && !detail.isMisc}}" class="muted">材料库存批次号 {{detail.rawMaterialBatchNo || '-'}}</text>
|
||||
<view wx:if="{{!detail.isCleaning && !detail.isMisc && detail.deviceSegments.length && !item.isVoided && item.canEdit}}" class="device-time-inline">
|
||||
<text class="panel-title device-time-title">换模具时间校正</text>
|
||||
<view
|
||||
wx:for="{{detail.deviceSegments}}"
|
||||
wx:key="id"
|
||||
wx:for-item="segment"
|
||||
wx:for-index="segmentIndex"
|
||||
class="device-time-row"
|
||||
>
|
||||
<view class="device-time-main">
|
||||
<text class="value">{{segment.label}} · {{segment.displayName}}</text>
|
||||
<text class="subtitle">原记录 {{segment.scannedAtText}}</text>
|
||||
</view>
|
||||
<view class="device-time-pickers">
|
||||
<picker mode="date" value="{{segment.editScanDate}}" data-report-index="{{reportIndex}}" data-item-index="{{itemIndex}}" data-segment-index="{{segmentIndex}}" data-field="editScanDate" bindchange="onDeviceSegmentFieldChange">
|
||||
<view class="picker compact-picker">{{segment.editScanDate || '日期'}}</view>
|
||||
</picker>
|
||||
<picker mode="time" value="{{segment.editScanTime}}" data-report-index="{{reportIndex}}" data-item-index="{{itemIndex}}" data-segment-index="{{segmentIndex}}" data-field="editScanTime" bindchange="onDeviceSegmentFieldChange">
|
||||
<view class="picker compact-picker">{{segment.editScanTime || '时间'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{detail.isMisc}}" class="detail-metric-grid">
|
||||
<view>
|
||||
<text class="label">工时</text>
|
||||
<text class="value">{{detail.workTimeText}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">杂活事项</text>
|
||||
<text class="value">{{detail.remark || '待备注'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:elif="{{detail.isCleaning}}" class="detail-metric-grid">
|
||||
<view>
|
||||
<text class="label">清洗数量</text>
|
||||
<text class="value">{{detail.goodQty}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">工时</text>
|
||||
<text class="value">{{detail.workTimeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:else class="detail-metric-grid">
|
||||
<view>
|
||||
<text class="label">工时</text>
|
||||
<text class="value">{{detail.workTimeText}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">成品 / 标准工作量</text>
|
||||
<text class="value {{detail.workloadCompareClass}}">
|
||||
{{detail.goodQty}} / {{detail.standardWorkload}}
|
||||
<text wx:if="{{detail.workloadReason}}" class="metric-reason">{{detail.workloadReason}}</text>
|
||||
</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">实际节拍</text>
|
||||
<text class="value {{detail.beatCompareClass}}">
|
||||
{{detail.actualBeat}} 秒
|
||||
<text wx:if="{{detail.beatReason}}" class="metric-reason">{{detail.beatReason}}</text>
|
||||
</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="label">标准节拍</text>
|
||||
<text class="value">{{detail.standardBeat}} 秒</text>
|
||||
</view>
|
||||
<view wx:if="{{detail.isContinuousDie}}">
|
||||
<text class="label">换料次数</text>
|
||||
<text class="value">{{detail.changeoverCount || 0}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{detail.isMisc && !item.isVoided && item.canEdit}}" class="review-edit-panel misc-remark-panel">
|
||||
<text class="panel-title">杂活事项备注</text>
|
||||
<textarea
|
||||
class="input misc-remark-input"
|
||||
value="{{detail.remark}}"
|
||||
placeholder="请输入处理杂活事项,便于经理监督留痕"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-item-index="{{itemIndex}}"
|
||||
data-field="remark"
|
||||
bindinput="onItemInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!detail.isMisc && !item.isVoided && item.canEdit}}" class="form-grid review-form-grid">
|
||||
<view class="form-item">
|
||||
<text class="label">设备号</text>
|
||||
<picker
|
||||
wx:if="{{detail.equipmentOptions.length}}"
|
||||
mode="selector"
|
||||
range="{{detail.equipmentOptions}}"
|
||||
range-key="deviceNo"
|
||||
value="{{detail.deviceIndex}}"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-item-index="{{itemIndex}}"
|
||||
bindchange="onDeviceChange"
|
||||
>
|
||||
<view class="device-select-control {{detail.deviceError ? 'field-error' : ''}}">
|
||||
<text class="device-select-text">{{detail.deviceNo || (detail.isCleaning ? '请选择清洗设备号' : '请选择冲压设备号')}}</text>
|
||||
<text class="device-select-action">选择</text>
|
||||
</view>
|
||||
</picker>
|
||||
<view wx:else class="readonly {{detail.deviceError ? 'field-error' : ''}}">暂无可选设备</view>
|
||||
</view>
|
||||
<view class="form-item full-span product-correction-row">
|
||||
<text class="label">产品/工序更正</text>
|
||||
<view class="product-correction-actions">
|
||||
<button class="btn primary full" data-report-index="{{reportIndex}}" data-item-index="{{itemIndex}}" bindtap="openProductModal">更正产品</button>
|
||||
<button class="btn secondary full" data-report-index="{{reportIndex}}" data-item-index="{{itemIndex}}" bindtap="restoreProduct">恢复</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item full-span">
|
||||
<text class="label">产品名称</text>
|
||||
<view class="readonly product-readonly">{{detail.projectNo}} · {{detail.productName}}</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">工序</text>
|
||||
<view class="readonly product-readonly">{{detail.process || '未填写工序'}}</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">冲压方式</text>
|
||||
<view class="readonly product-readonly">{{detail.stampingMethod || '-'}}</view>
|
||||
</view>
|
||||
<view wx:if="{{detail.isContinuousDie}}" class="form-item">
|
||||
<text class="label">换料次数</text>
|
||||
<input
|
||||
class="input number-input {{detail.changeoverCountError ? 'field-error' : ''}}"
|
||||
type="number"
|
||||
value="{{detail.changeoverCount}}"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-item-index="{{itemIndex}}"
|
||||
data-field="changeoverCount"
|
||||
bindinput="onItemInput"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">{{detail.isCleaning ? '清洗数量' : '成品数量'}}</text>
|
||||
<input
|
||||
class="input number-input"
|
||||
type="digit"
|
||||
value="{{detail.goodQty}}"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-item-index="{{itemIndex}}"
|
||||
data-field="goodQty"
|
||||
bindinput="onItemInput"
|
||||
/>
|
||||
</view>
|
||||
<view wx:if="{{!detail.isCleaning}}" class="form-item">
|
||||
<text class="label">不良数量</text>
|
||||
<input
|
||||
class="input number-input"
|
||||
type="digit"
|
||||
value="{{detail.defectQty}}"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-item-index="{{itemIndex}}"
|
||||
data-field="defectQty"
|
||||
bindinput="onItemInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!item.isVoided && item.canEdit}}" class="review-edit-panel review-remark-panel">
|
||||
<text class="panel-title">审核备注</text>
|
||||
<textarea
|
||||
class="input review-remark-input"
|
||||
value="{{item.editReviewRemark}}"
|
||||
placeholder="请输入本条报工的审核备注"
|
||||
maxlength="500"
|
||||
data-report-index="{{reportIndex}}"
|
||||
data-field="editReviewRemark"
|
||||
bindinput="onReportFieldChange"
|
||||
/>
|
||||
</view>
|
||||
<view wx:elif="{{item.reviewRemark}}" class="review-edit-panel review-remark-panel">
|
||||
<text class="panel-title">审核备注</text>
|
||||
<text class="readonly-text">{{item.reviewRemark}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{(!item.isVoided && (item.canEdit || item.canVoid)) || (item.isVoided && item.canUnvoid)}}" class="button-row review-action-row">
|
||||
<button wx:if="{{!item.isVoided && item.canEdit}}" class="btn primary review-action-btn" data-id="{{item.id}}" bindtap="approve">{{item.status === 'approved' ? '保存更改' : '校正并审核通过'}}</button>
|
||||
<button wx:if="{{!item.isVoided && item.canVoid}}" class="btn danger review-action-btn" data-id="{{item.id}}" catchtap="voidReport">作废</button>
|
||||
<button wx:if="{{item.isVoided && item.canUnvoid}}" class="btn secondary review-action-btn" data-id="{{item.id}}" catchtap="unvoidReport">撤销作废</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-row pager-row" wx:if="{{rows.length && !focusReportId}}">
|
||||
<button class="btn secondary pager-nav" bindtap="prevPage" disabled="{{page <= 1}}">上一页</button>
|
||||
<view class="page-jump-control">
|
||||
<input class="page-jump-input" type="number" confirm-type="done" value="{{pageInput}}" bindinput="onPageInput" bindconfirm="jumpToPage" />
|
||||
<text class="page-total-label">/ {{totalPages}}</text>
|
||||
<text class="page-jump-action" bindtap="jumpToPage">跳</text>
|
||||
</view>
|
||||
<button class="btn secondary pager-nav" bindtap="nextPage" disabled="{{page >= totalPages}}">下一页</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view wx:if="{{productModalVisible}}" class="modal-mask product-select-mask" catchtap="closeProductModal">
|
||||
<view class="modal-panel product-select-panel" catchtap="noop">
|
||||
<view class="modal-head">
|
||||
<view>
|
||||
<text class="modal-title">产品清单</text>
|
||||
<text class="modal-subtitle">选择正确的产品、工序和冲压方式后替换当前报工明细</text>
|
||||
</view>
|
||||
<button class="btn mini secondary modal-close" bindtap="closeProductModal">关闭</button>
|
||||
</view>
|
||||
|
||||
<input
|
||||
class="input product-select-search"
|
||||
value="{{productModalKeyword}}"
|
||||
placeholder="搜索产品、物料、供应商、工序、冲压方式"
|
||||
bindinput="onProductModalKeywordInput"
|
||||
/>
|
||||
|
||||
<view wx:if="{{!productModalRows.length && !productModalLoading}}" class="list-empty">暂无可替换产品</view>
|
||||
<view wx:for="{{productModalRows}}" wx:key="uniqueKey" wx:for-index="productIndex" class="card product-select-card">
|
||||
<view class="row">
|
||||
<view class="row-left">
|
||||
<text class="value">{{item.productName}}</text>
|
||||
<text class="subtitle">考勤点 {{item.attendancePointName || '-'}}</text>
|
||||
<text class="subtitle">{{item.projectNo}} · {{item.materialCode || '-'}}</text>
|
||||
<text class="subtitle">工序 {{item.processDisplay || item.process || '-'}} · 冲压方式 {{item.stampingMethod || '-'}}</text>
|
||||
<text class="subtitle">物料名称 {{item.materialName || '-'}}</text>
|
||||
</view>
|
||||
<text class="pill primary">{{item.processDisplay || item.process || '工序'}}</text>
|
||||
<text wx:if="{{item.isCleaning}}" class="cleaning-watermark product-select-stamp">清洗</text>
|
||||
<text wx:if="{{item.isMisc}}" class="misc-watermark product-select-stamp">杂活</text>
|
||||
<text wx:if="{{item.isMultiPerson}}" class="multi-person-watermark product-select-stamp">多人协作</text>
|
||||
<text wx:if="{{item.isContinuousDie}}" class="continuous-die-watermark product-select-stamp">连续模</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
<view wx:if="{{!item.isCleaning && !item.isMisc}}" class="row">
|
||||
<text class="muted">标准节拍 {{item.standardBeat}} 秒</text>
|
||||
</view>
|
||||
<button class="btn primary full product-replace-btn" data-index="{{productIndex}}" bindtap="replaceProductFromModal">替换</button>
|
||||
</view>
|
||||
|
||||
<view class="button-row pager-row product-select-pager" wx:if="{{productModalRows.length}}">
|
||||
<button class="btn secondary pager-nav" bindtap="productModalPrevPage" disabled="{{productModalPage <= 1}}">上一页</button>
|
||||
<view class="page-jump-control">
|
||||
<input class="page-jump-input" type="number" confirm-type="done" value="{{productModalPageInput}}" bindinput="onProductModalPageInput" bindconfirm="jumpProductModalPage" />
|
||||
<text class="page-total-label">/ {{productModalTotalPages}}</text>
|
||||
<text class="page-jump-action" bindtap="jumpProductModalPage">跳</text>
|
||||
</view>
|
||||
<button class="btn secondary pager-nav" bindtap="productModalNextPage" disabled="{{productModalPage >= productModalTotalPages}}">下一页</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,487 @@
|
||||
.review-header-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.review-header-main {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.detail-toggle {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 0 0 auto;
|
||||
min-width: 150rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
margin: 4rpx 0 0;
|
||||
padding: 0 18rpx;
|
||||
border-radius: 14rpx;
|
||||
color: #1463ff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.review-header-row .top-action-group {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 240rpx minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.review-header-row .void-filter-switch {
|
||||
width: 240rpx;
|
||||
}
|
||||
|
||||
.review-header-row .detail-toggle {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 68rpx;
|
||||
line-height: 68rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-color: #aebbd0;
|
||||
}
|
||||
|
||||
.cleaning-watermark {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 18rpx;
|
||||
z-index: 1;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 2rpx solid rgba(18, 163, 117, 0.55);
|
||||
border-radius: 12rpx;
|
||||
color: rgba(8, 116, 67, 0.72);
|
||||
background: rgba(226, 255, 243, 0.82);
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
transform: rotate(10deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.misc-watermark {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 18rpx;
|
||||
z-index: 1;
|
||||
padding: 8rpx 18rpx;
|
||||
border: 2rpx solid rgba(217, 119, 6, 0.55);
|
||||
border-radius: 12rpx;
|
||||
color: rgba(154, 74, 5, 0.82);
|
||||
background: rgba(255, 247, 232, 0.88);
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
transform: rotate(10deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.review-card.expanded {
|
||||
border-color: #1463ff;
|
||||
box-shadow:
|
||||
0 0 0 2rpx rgba(255, 255, 255, 0.9),
|
||||
0 13rpx 0 rgba(20, 86, 194, 0.48),
|
||||
0 24rpx 36rpx rgba(20, 86, 194, 0.16),
|
||||
inset 0 0 0 1rpx rgba(20, 99, 255, 0.24),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
|
||||
.detail-metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14rpx 18rpx;
|
||||
margin: 12rpx 0 8rpx;
|
||||
padding: 16rpx;
|
||||
border: 2rpx solid #c5cfde;
|
||||
border-radius: 14rpx;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f4f8fd 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1rpx rgba(255, 255, 255, 0.86),
|
||||
0 8rpx 0 rgba(177, 191, 211, 0.52),
|
||||
0 14rpx 22rpx rgba(22, 34, 50, 0.07);
|
||||
}
|
||||
|
||||
.metric-danger {
|
||||
color: #c5221f;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.metric-success {
|
||||
color: #087443;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.review-edit-panel {
|
||||
margin: 20rpx 0;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #dfe7f2;
|
||||
border-radius: 16rpx;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(20, 99, 255, 0.06), transparent 46%),
|
||||
#f8fbff;
|
||||
box-shadow:
|
||||
0 8rpx 0 rgba(207, 218, 233, 0.58),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
color: #162232;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.time-grid,
|
||||
.review-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.review-form-grid .full-span {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.compact-picker {
|
||||
min-height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
}
|
||||
|
||||
.device-time-inline {
|
||||
margin: 6rpx 0 8rpx;
|
||||
padding: 16rpx;
|
||||
border: 2rpx solid #c5cfde;
|
||||
border-radius: 14rpx;
|
||||
background: #f6faff;
|
||||
}
|
||||
|
||||
.device-time-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
padding: 14rpx 0;
|
||||
border-top: 1rpx solid rgba(174, 188, 208, 0.72);
|
||||
}
|
||||
|
||||
.device-time-row:first-of-type {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.device-time-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.device-time-pickers {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.device-time-title {
|
||||
margin-bottom: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.item-list {
|
||||
margin: 18rpx 0;
|
||||
padding: 18rpx;
|
||||
border: 2rpx solid #c4d0e2;
|
||||
border-radius: 16rpx;
|
||||
background: #eef4fb;
|
||||
box-shadow:
|
||||
inset 0 0 0 1rpx rgba(255, 255, 255, 0.72),
|
||||
0 8rpx 0 rgba(174, 188, 208, 0.34);
|
||||
}
|
||||
|
||||
.detail-line {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 28rpx;
|
||||
padding: 24rpx 22rpx 24rpx 28rpx;
|
||||
border: 3rpx solid #9fb0c8;
|
||||
border-left: 9rpx solid #1463ff;
|
||||
border-radius: 18rpx;
|
||||
background:
|
||||
linear-gradient(180deg, #ffffff 0%, #f7fbff 100%);
|
||||
box-shadow:
|
||||
0 0 0 2rpx rgba(255, 255, 255, 0.86),
|
||||
0 12rpx 0 rgba(133, 152, 179, 0.55),
|
||||
0 20rpx 28rpx rgba(22, 34, 50, 0.13),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.96);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.detail-line:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.detail-line:nth-child(even) {
|
||||
border-left-color: #12a375;
|
||||
background:
|
||||
linear-gradient(180deg, #ffffff 0%, #f5fffb 100%);
|
||||
}
|
||||
|
||||
.detail-line.multi-detail-line,
|
||||
.detail-line.marked-detail-line {
|
||||
padding-top: 72rpx;
|
||||
}
|
||||
|
||||
.detail-line.stacked-detail-line {
|
||||
padding-top: 112rpx;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
color: #162232;
|
||||
padding-right: 24rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.detail-multi-stamp {
|
||||
position: absolute;
|
||||
left: 24rpx;
|
||||
top: 22rpx;
|
||||
z-index: 2;
|
||||
padding: 7rpx 14rpx;
|
||||
border: 2rpx solid rgba(217, 119, 6, 0.62);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(154, 74, 5, 0.88);
|
||||
background: rgba(255, 247, 232, 0.92);
|
||||
font-size: 23rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.detail-continuous-stamp {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
top: 22rpx;
|
||||
z-index: 2;
|
||||
padding: 7rpx 14rpx;
|
||||
border: 2rpx solid rgba(217, 119, 6, 0.62);
|
||||
border-radius: 10rpx;
|
||||
color: rgba(154, 74, 5, 0.88);
|
||||
background: rgba(255, 247, 232, 0.92);
|
||||
font-size: 23rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.detail-continuous-stamp.lower-detail-stamp {
|
||||
top: 64rpx;
|
||||
}
|
||||
|
||||
.product-correction-row {
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.product-correction-actions {
|
||||
display: flex;
|
||||
gap: 22rpx;
|
||||
align-items: center;
|
||||
padding-bottom: 10rpx;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.product-correction-actions .btn {
|
||||
flex: 1 1 0;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
height: 76rpx;
|
||||
line-height: 76rpx;
|
||||
margin: 0;
|
||||
padding: 0 18rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 27rpx;
|
||||
}
|
||||
|
||||
.product-correction-actions .btn.full {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.product-readonly {
|
||||
min-height: 76rpx;
|
||||
padding: 0 20rpx;
|
||||
border: 2rpx solid #dbe4f0;
|
||||
border-radius: 14rpx;
|
||||
color: #162232;
|
||||
background: linear-gradient(180deg, #f9fbff 0%, #eef4fb 100%);
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
line-height: 76rpx;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.device-select-control {
|
||||
min-height: 76rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
padding: 0 18rpx;
|
||||
border: 2rpx solid #dbe4f0;
|
||||
border-radius: 14rpx;
|
||||
color: #162232;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.device-select-control.field-error {
|
||||
border-color: #c5221f;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.device-select-text {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.device-select-action {
|
||||
flex: 0 0 auto;
|
||||
color: #1463ff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.review-action-row {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.review-action-btn {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.review-remark-panel,
|
||||
.misc-remark-panel {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.review-remark-input,
|
||||
.misc-remark-input {
|
||||
min-height: 150rpx;
|
||||
line-height: 1.45;
|
||||
padding-top: 18rpx;
|
||||
padding-bottom: 18rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.readonly-text {
|
||||
display: block;
|
||||
color: #334155;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.55;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.modal-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: #162232;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.product-select-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 24rpx 22rpx calc(env(safe-area-inset-bottom) + 24rpx);
|
||||
background: rgba(15, 23, 42, 0.5);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.product-select-panel {
|
||||
width: 100%;
|
||||
max-height: 92vh;
|
||||
overflow-y: auto;
|
||||
padding: 24rpx;
|
||||
border: 2rpx solid #c7d2e2;
|
||||
border-radius: 18rpx;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
|
||||
box-shadow:
|
||||
0 16rpx 0 rgba(170, 184, 204, 0.62),
|
||||
0 30rpx 64rpx rgba(15, 23, 42, 0.24),
|
||||
inset 0 2rpx 0 rgba(255, 255, 255, 0.96);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
color: #6b778c;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.product-select-search {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-select-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 22rpx;
|
||||
}
|
||||
|
||||
.product-select-card .row-left {
|
||||
padding-right: 100rpx;
|
||||
}
|
||||
|
||||
.product-select-stamp {
|
||||
right: 22rpx;
|
||||
top: 72rpx;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
|
||||
.product-replace-btn {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.product-select-pager {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user