Files

1343 lines
30 KiB
Vue
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.
<template>
<view class="entry-page multi-machine-page" :class="`theme-${themeMode}`">
<view class="hero-card">
<view>
<text class="hero-title">一单多机</text>
<text class="hero-subtitle">按订单录入本次数量,提交后进入数量分摊流程</text>
</view>
<view class="hero-icon"><u-icon name="grid-fill" color="#ffffff" size="46" /></view>
</view>
<view class="config-card">
<view class="card-title">当前配置</view>
<view class="chip-row">
<view class="select-chip" @tap="showPicker('process')"><text class="chip-label">工序</text><text class="chip-value">{{ processLabel }}</text></view>
<view class="select-chip" @tap="showPicker('step')"><text class="chip-label">步骤</text><text class="chip-value">{{ stepLabel }}</text></view>
</view>
</view>
<view class="form-card">
<view class="form-title-row"><view><text class="card-title">扫码录入</text><text class="card-subtitle">一个订单一次录入本次完成数量</text></view><view class="progress-badge">本次录入</view></view>
<view class="field-card primary-field"><view class="field-main"><text class="field-label">订单号</text><u-input v-model="form.orderNo" :input-mode="pdaMode ? 'none' : 'text'" clearable placeholder="请扫描或输入订单号" placeholder-style="color: #a0a7b5;" @focus="handleOrderInputFocus" /></view><view class="scan-button" @tap="scanOrder"><u-icon name="scan" color="#ffffff" size="36" /></view></view>
<view class="quantity-card">
<view class="quantity-heading"><view><text class="field-label">本次数量</text><text class="quantity-tip">请输入本次需要分摊的数量</text></view><text class="quantity-unit">台</text></view>
<view class="quantity-control"><view class="qty-button" @tap="decreaseQuantity">-</view><input v-model="form.quantity" class="qty-input" type="number" /><view class="qty-button" @tap="increaseQuantity">+</view></view>
</view>
<view class="quantity-preview"><text>提交数量</text><text class="quantity-preview-value">{{ quantity }} 台</text></view>
<view class="action-row"><view class="submit-secondary" @tap="resetForm">重置</view><view class="submit-primary" :class="{ 'is-loading': submitting }" @tap="submitForm">{{ submitting ? '提交中...' : '提交录入' }}</view></view>
</view>
<view class="stats-card"><view class="stats-header"><view><text class="card-title">今日统计</text><text class="card-subtitle">当前工序/步骤本次录入量</text></view><view class="total-badge">{{ submittedTotal }}</view></view><view class="stats-grid"><view class="stat-item"><text class="stat-value success">{{ statistics.successTotal }}</text><text class="stat-label">成功</text></view><view class="stat-item"><text class="stat-value danger">{{ statistics.failedTotal }}</text><text class="stat-label">失败</text></view><view class="stat-item"><text class="stat-value">{{ submittedTotal }}</text><text class="stat-label">总数</text></view></view></view>
<view class="record-card"><view class="card-title">最近记录</view><view v-for="record in recentRecords" :key="record.id" class="record-item"><view><text class="record-main">{{ record.orderNo }}</text><text class="record-sub">本次数量:{{ record.quantity }} 台</text></view><view class="record-status">成功</view></view><view v-if="!recentRecords.length" class="empty-record">暂无录入记录</view></view>
</view>
</template>
<script>
// UTS 原生扫码插件仅在 App-Plus 真机/自定义基座中解析,Web 环境不加载。
// #ifdef APP-PLUS
import { openCodeScan } from '@/uni_modules/mas-code-scan'
// #endif
import { getDataEntryConfig, getOrderRecentRecords, getOrderStatistics, getProcessOptions, getStepOptions, submitOrderRecord } from '@/api/produce/data-entry.js'
import { getScanRegionSetting } from '@/utils/scan-settings.js'
export default {
data() {
return {
submitting: false,
scanning: false,
pdaMode: false,
currentUser: {
userCode: 'U001'
},
basicSettings: {},
specificRules: [],
processOptions: [],
stepOptions: [],
selectedProcessIndex: 0,
selectedStepIndex: 0,
form: {
orderNo: '',
quantity: 1
},
statistics: {
todayTotal: 0,
successTotal: 0,
failedTotal: 0
},
recentRecords: []
}
},
onLoad() {
this.loadPdaMode()
this.loadPageData()
},
onShow() {
this.loadPdaMode()
},
computed: {
quantity() {
return Math.max(1, Number(this.form.quantity) || 0)
},
processLabel() {
return this.processOptions[this.selectedProcessIndex]?.label || '请选择'
},
stepLabel() {
return this.stepOptions[this.selectedStepIndex]?.label || '请选择'
},
submittedTotal() {
return this.statistics.todayTotal
}
},
methods: {
getCurrentUserCode() {
const user = uni.getStorageSync('scanapp_user') || {}
return user.username || user.nickname || 'U001'
},
loadPdaMode() {
const userCode = this.getCurrentUserCode()
const settings = uni.getStorageSync(`scanapp_settings_${userCode}`)
this.pdaMode = settings?.pdaMode === true
},
handleOrderInputFocus() {
// #ifdef APP-PLUS
try {
const activity = plus.android.runtimeMainActivity()
const focusedView = activity.getWindow().getDecorView().findFocus()
if (focusedView) {
plus.android.invoke(focusedView, 'setShowSoftInputOnFocus', !this.pdaMode)
}
if (this.pdaMode) {
uni.hideKeyboard()
}
} catch (error) {
if (this.pdaMode) uni.hideKeyboard()
}
// #endif
},
getSelectionStorageKey() {
return `scanapp_entry_multi_machine_selection_${this.currentUser.userCode}`
},
getSavedSelection() {
const selection = uni.getStorageSync(this.getSelectionStorageKey())
return selection && typeof selection === 'object' ? selection : {}
},
findOptionIndex(options, value) {
const index = options.findIndex((item) => String(item.value) === String(value))
return index >= 0 ? index : 0
},
saveSelection() {
const processItem = this.processOptions[this.selectedProcessIndex]
const stepItem = this.stepOptions[this.selectedStepIndex]
uni.setStorageSync(this.getSelectionStorageKey(), {
processId: processItem?.value || '',
stepId: stepItem?.value || ''
})
},
async loadPageData() {
this.currentUser.userCode = this.getCurrentUserCode()
const savedSelection = this.getSavedSelection()
try {
const processOptions = await getProcessOptions()
this.processOptions = Array.isArray(processOptions) ? processOptions : []
this.selectedProcessIndex = this.findOptionIndex(this.processOptions, savedSelection.processId)
if (this.processOptions.length > 0) {
await this.loadStepOptions(this.processOptions[this.selectedProcessIndex].value, savedSelection.stepId, false)
}
await this.loadRecordData()
} catch (error) {
console.error('[scanapp] 一单多机页数据加载失败', error)
}
},
async loadRecordData() {
const processItem = this.processOptions[this.selectedProcessIndex]
const stepItem = this.stepOptions[this.selectedStepIndex]
const params = {
user_code: this.currentUser.userCode,
process_id: processItem?.value,
step_id: stepItem?.value
}
const [statistics, recent] = await Promise.all([
getOrderStatistics(params),
getOrderRecentRecords({ ...params, limit: 5 })
])
this.statistics = {
todayTotal: Number(statistics?.today_total || 0),
successTotal: Number(statistics?.success_total || 0),
failedTotal: Number(statistics?.failed_total || 0)
}
this.recentRecords = Array.isArray(recent?.items) ? recent.items.map((item, index) => ({
id: item.id || index,
orderNo: item.order_no || '-',
quantity: Number(item.quantity || 1)
})) : []
},
async loadStepOptions(processId, stepId = '', refreshRecords = true) {
try {
const options = await getStepOptions(processId)
this.stepOptions = Array.isArray(options) ? options : []
this.selectedStepIndex = this.findOptionIndex(this.stepOptions, stepId)
await this.loadDataEntryConfig()
this.saveSelection()
if (refreshRecords) await this.loadRecordData()
} catch (error) {
this.stepOptions = []
this.selectedStepIndex = 0
this.basicSettings = {}
this.specificRules = []
console.error('[scanapp] 一单多机步骤加载失败', error)
}
},
async loadDataEntryConfig() {
const processItem = this.processOptions[this.selectedProcessIndex]
const stepItem = this.stepOptions[this.selectedStepIndex]
if (!processItem || !stepItem) {
this.basicSettings = {}
this.specificRules = []
return
}
try {
const config = await getDataEntryConfig({
process_id: processItem.value,
step_id: stepItem.value,
apply_mode: 'order',
order_no: this.form.orderNo.trim(),
user_code: this.currentUser.userCode.trim()
})
this.basicSettings = config?.basic_setting || {}
this.specificRules = Array.isArray(config?.specific_rules) ? config.specific_rules : []
} catch (error) {
this.basicSettings = {}
this.specificRules = []
console.error('[scanapp] 一单多机规则加载失败', error)
}
},
matchSpecificRule(rule, valueMap) {
const matchField = rule.match_field || 'order_no'
const value = String(valueMap[matchField] || '').trim()
const pattern = String(rule.rule_pattern || '').trim()
if (!value || !pattern) return false
try {
return new RegExp(pattern).test(value)
} catch (error) {
return false
}
},
validateSpecificRules() {
const context = {
order_no: this.form.orderNo.trim(),
user_code: this.currentUser.userCode.trim()
}
const blockedRule = this.specificRules.find((rule) => {
if (rule.apply_mode && rule.apply_mode !== 'order' && rule.apply_mode !== 'both') return false
if (Array.isArray(rule.step_ids) && rule.step_ids.length && !rule.step_ids.map(String).includes(String(this.stepOptions[this.selectedStepIndex]?.value))) return false
return Number(rule.rule_type) === 2 && this.matchSpecificRule(rule, context)
})
if (blockedRule) {
uni.showToast({ title: blockedRule.rule_name || '当前订单命中特定规则,禁止提交', icon: 'none' })
return false
}
return true
},
validatePattern(value, settingKey, label) {
const pattern = String(this.basicSettings[settingKey] || '').trim()
if (!pattern) return true
try {
if (!new RegExp(pattern).test(value)) {
uni.showToast({ title: `${label}格式不符合规则`, icon: 'none' })
return false
}
} catch (error) {
console.error(`[scanapp] ${label}规则格式错误`, error)
}
return true
},
showPicker(type) {
const list = type === 'process' ? this.processOptions : this.stepOptions
if (!list.length) {
uni.showToast({ title: '当前没有可选项', icon: 'none' })
return
}
uni.showActionSheet({
itemList: list.map((item) => item.label),
success: async (res) => {
if (type === 'process') {
this.selectedProcessIndex = res.tapIndex
await this.loadStepOptions(this.processOptions[res.tapIndex]?.value)
} else {
this.selectedStepIndex = res.tapIndex
await this.loadDataEntryConfig()
this.saveSelection()
await this.loadRecordData()
}
}
})
},
scanOrder() {
if (this.scanning || this.submitting) return
// #ifdef APP-PLUS
this.scanning = true
openCodeScan({
smartZoom: true,
vibrate: true,
scanRegion: getScanRegionSetting(),
success: async (result) => {
const value = String(result?.content || '').trim()
if (!value) return
this.form.orderNo = value
await this.loadDataEntryConfig()
},
fail: (error) => {
if (Number(error?.errCode) === 9021003) return
uni.showToast({
title: Number(error?.errCode) === 9021002 ? '未获得扫码权限' : '未识别到有效条码',
icon: 'none'
})
},
complete: () => {
this.scanning = false
}
})
// #endif
// #ifndef APP-PLUS
uni.showToast({ title: '请在 Android App 中使用扫码功能', icon: 'none' })
// #endif
},
increaseQuantity() {
this.form.quantity = this.quantity + 1
},
decreaseQuantity() {
this.form.quantity = Math.max(1, this.quantity - 1)
},
validateForm() {
if (!this.form.orderNo.trim()) {
uni.showToast({ title: '请输入订单号', icon: 'none' })
return false
}
if (!this.processOptions[this.selectedProcessIndex]) {
uni.showToast({ title: '请选择工序', icon: 'none' })
return false
}
if (!this.stepOptions[this.selectedStepIndex]) {
uni.showToast({ title: '请选择步骤', icon: 'none' })
return false
}
if (this.quantity < 1 || !Number.isInteger(this.quantity)) {
uni.showToast({ title: '本次数量必须为正整数', icon: 'none' })
return false
}
const minLength = Number(this.basicSettings.order_no_min_length || 0)
if (minLength > 0 && this.form.orderNo.trim().length < minLength) {
uni.showToast({ title: `订单号长度不能少于 ${minLength} 位`, icon: 'none' })
return false
}
if (!this.validatePattern(this.form.orderNo.trim(), 'order_no_regex', '订单号')) return false
return this.validateSpecificRules()
},
resetForm() {
this.form.orderNo = ''
this.form.quantity = 1
this.loadDataEntryConfig()
},
async submitForm() {
if (this.submitting) return
await this.loadDataEntryConfig()
if (!this.validateForm()) return
this.submitting = true
try {
const processItem = this.processOptions[this.selectedProcessIndex]
const stepItem = this.stepOptions[this.selectedStepIndex]
await submitOrderRecord({
order_no: this.form.orderNo.trim(),
quantity: this.quantity,
user_code: this.currentUser.userCode,
process_id: processItem?.value,
step_id: stepItem?.value,
entry_mode: 'multi_machine',
scan_result: 1
})
uni.showToast({ title: '数量录入成功', icon: 'success' })
this.form.orderNo = ''
this.form.quantity = 1
await this.loadPageData()
} catch (error) {
console.error('[scanapp] 一单多机录入失败', error)
} finally {
this.submitting = false
}
}
}
}
</script>
<style lang="scss" scoped>
.multi-machine-page {
min-height: 100vh;
padding: 28rpx 24rpx 48rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eaf4ff 0%, #f4f6fb 34%, #f4f6fb 100%);
}
.hero-card,
.order-card,
.progress-card,
.machine-section,
.bottom-summary {
border: 1rpx solid rgba(255, 255, 255, 0.72);
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 16rpx 42rpx rgba(23, 35, 61, 0.06);
}
.hero-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 34rpx 30rpx;
background: linear-gradient(135deg, #1677ff 0%, #6b70e8 100%);
}
.hero-title,
.hero-subtitle,
.section-title,
.section-subtitle,
.field-label,
.meta-label,
.meta-value,
.machine-name,
.machine-hint,
.quantity-tip,
.summary-label,
.summary-value,
.summary-unit {
display: block;
}
.hero-title {
color: #ffffff;
font-size: 38rpx;
font-weight: 800;
line-height: 1.35;
}
.hero-subtitle {
margin-top: 10rpx;
color: rgba(255, 255, 255, 0.84);
font-size: 22rpx;
line-height: 1.4;
}
.hero-icon {
display: flex;
align-items: center;
justify-content: center;
width: 86rpx;
height: 86rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.18);
}
.order-card,
.progress-card,
.machine-section,
.bottom-summary {
margin-top: 24rpx;
padding: 28rpx;
}
.section-heading,
.progress-header,
.machine-card-header,
.machine-footer,
.bottom-summary,
.action-row {
display: flex;
align-items: center;
}
.section-heading,
.progress-header,
.machine-card-header,
.machine-footer,
.bottom-summary {
justify-content: space-between;
}
.section-title {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.section-subtitle {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
line-height: 1.4;
}
.order-status,
.machine-count,
.machine-state,
.summary-state {
padding: 8rpx 18rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-weight: 600;
}
.order-status,
.machine-state.is-ready,
.summary-state.is-complete {
background: rgba(25, 190, 107, 0.12);
color: #19be6b;
}
.machine-count {
background: rgba(22, 119, 255, 0.1);
color: #1677ff;
}
.machine-state.is-waiting,
.summary-state.is-warning {
background: rgba(247, 183, 51, 0.14);
color: #d99400;
}
.order-field,
.machine-field {
display: flex;
align-items: center;
width: 100%;
margin-top: 22rpx;
padding: 22rpx;
box-sizing: border-box;
border: 1rpx solid rgba(22, 119, 255, 0.18);
border-radius: 24rpx;
background: #f5f9ff;
}
.order-field-main,
.machine-field-main {
flex: 1;
min-width: 0;
}
.field-label {
margin-bottom: 6rpx;
color: #5f6b7a;
font-size: 22rpx;
font-weight: 600;
}
.scan-button {
display: flex;
align-items: center;
justify-content: center;
width: 76rpx;
height: 76rpx;
margin-left: 18rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #1677ff 0%, #6b70e8 100%);
box-shadow: 0 12rpx 26rpx rgba(22, 119, 255, 0.2);
}
.scan-button.small {
width: 68rpx;
height: 68rpx;
border-radius: 22rpx;
}
.order-meta {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
margin-top: 22rpx;
}
.meta-item {
padding: 18rpx 16rpx;
border-radius: 20rpx;
background: #f8fbff;
}
.meta-label {
color: #8a94a6;
font-size: 20rpx;
}
.meta-value {
margin-top: 8rpx;
color: #17233d;
font-size: 24rpx;
font-weight: 700;
word-break: break-all;
}
.progress-percent {
color: #1677ff;
font-size: 32rpx;
font-weight: 800;
}
.progress-track {
height: 16rpx;
margin-top: 22rpx;
overflow: hidden;
border-radius: 999rpx;
background: #eaf0f8;
}
.progress-fill {
height: 100%;
border-radius: 999rpx;
background: linear-gradient(90deg, #1677ff 0%, #6b70e8 100%);
transition: width 0.2s ease;
}
.machine-heading {
margin-bottom: 22rpx;
}
.machine-card {
padding: 24rpx;
border: 1rpx solid #edf1f7;
border-radius: 24rpx;
background: #f8fbff;
}
.machine-card + .machine-card {
margin-top: 18rpx;
}
.machine-index {
display: flex;
align-items: center;
justify-content: center;
width: 58rpx;
height: 58rpx;
margin-right: 16rpx;
border-radius: 18rpx;
background: rgba(22, 119, 255, 0.1);
color: #1677ff;
font-size: 22rpx;
font-weight: 800;
}
.machine-card-title {
flex: 1;
min-width: 0;
}
.machine-name {
color: #17233d;
font-size: 26rpx;
font-weight: 700;
}
.machine-hint {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
}
.machine-footer {
margin-top: 18rpx;
}
.quantity-copy .field-label {
margin-bottom: 6rpx;
}
.quantity-tip {
color: #a0a7b5;
font-size: 20rpx;
}
.quantity-control {
display: flex;
align-items: center;
overflow: hidden;
border-radius: 18rpx;
background: #ffffff;
}
.qty-button {
display: flex;
align-items: center;
justify-content: center;
width: 58rpx;
height: 58rpx;
color: #1677ff;
font-size: 28rpx;
font-weight: 700;
}
.qty-input {
width: 80rpx;
height: 58rpx;
color: #17233d;
font-size: 24rpx;
font-weight: 700;
text-align: center;
}
.remove-machine {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 18rpx;
color: #e91f3f;
font-size: 20rpx;
}
.remove-machine text {
margin-left: 8rpx;
}
.add-machine {
display: flex;
align-items: center;
justify-content: center;
margin-top: 18rpx;
padding: 22rpx;
border: 1rpx dashed rgba(22, 119, 255, 0.34);
border-radius: 22rpx;
background: #f8fbff;
color: #1677ff;
font-size: 24rpx;
font-weight: 700;
}
.add-machine text {
margin-left: 10rpx;
}
.add-machine--active {
background: #eef5ff;
}
.bottom-summary {
padding-top: 24rpx;
padding-bottom: 24rpx;
}
.summary-label {
color: #8a94a6;
font-size: 20rpx;
}
.summary-value {
margin-top: 6rpx;
color: #17233d;
font-size: 32rpx;
font-weight: 800;
}
.summary-unit {
display: inline;
color: #8a94a6;
font-size: 20rpx;
font-weight: 400;
}
.action-row {
margin-top: 24rpx;
}
.reset-button,
.submit-button {
display: flex;
align-items: center;
justify-content: center;
height: 86rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 700;
}
.reset-button {
width: 176rpx;
background: #f2f4f8;
color: #5f6b7a;
flex: none;
}
.submit-button {
margin-left: 14rpx;
flex: 1;
background: linear-gradient(135deg, #1677ff 0%, #6b70e8 100%);
color: #ffffff;
box-shadow: 0 14rpx 24rpx rgba(22, 119, 255, 0.18);
}
.submit-button.is-disabled {
opacity: 0.72;
}
.entry-page {
min-height: 100vh;
padding: 28rpx 24rpx 48rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eaf4ff 0%, #f4f6fb 38%, #f4f6fb 100%);
}
.hero-card,
.config-card,
.form-card,
.stats-card,
.record-card {
border: 1rpx solid rgba(255, 255, 255, 0.72);
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 16rpx 42rpx rgba(23, 35, 61, 0.06);
}
.hero-card {
padding: 34rpx 30rpx;
background: linear-gradient(135deg, #1677ff 0%, #35bfa3 100%);
}
.config-card,
.form-card,
.stats-card,
.record-card {
margin-top: 24rpx;
padding: 28rpx;
}
.card-title,
.card-subtitle,
.chip-label,
.chip-value,
.stat-value,
.stat-label,
.record-main,
.record-sub,
.empty-record,
.progress-badge {
display: block;
}
.card-title {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.card-subtitle {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
line-height: 1.4;
}
.chip-row,
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18rpx;
margin-top: 22rpx;
}
.select-chip,
.stat-item {
padding: 20rpx;
border-radius: 22rpx;
background: #f8fbff;
}
.chip-label,
.stat-label {
color: #8a94a6;
font-size: 20rpx;
}
.chip-value {
margin-top: 8rpx;
color: #17233d;
font-size: 26rpx;
font-weight: 700;
}
.form-title-row,
.stats-header,
.allocation-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
}
.progress-badge,
.total-badge {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: rgba(22, 119, 255, 0.1);
color: #1677ff;
font-size: 22rpx;
font-weight: 700;
}
.field-card {
display: flex;
align-items: center;
width: 100%;
padding: 22rpx;
box-sizing: border-box;
border: 1rpx solid #edf1f7;
border-radius: 24rpx;
background: #f8fbff;
}
.primary-field {
margin-top: 22rpx;
border-color: rgba(22, 119, 255, 0.18);
background: #f5f9ff;
}
.field-main {
flex: 1;
min-width: 0;
}
.field-label {
display: block;
margin-bottom: 6rpx;
color: #5f6b7a;
font-size: 22rpx;
font-weight: 600;
}
.progress-track {
height: 16rpx;
margin-top: 22rpx;
overflow: hidden;
border-radius: 999rpx;
background: #eaf0f8;
}
.progress-fill {
height: 100%;
border-radius: 999rpx;
background: linear-gradient(90deg, #1677ff 0%, #35bfa3 100%);
transition: width 0.2s ease;
}
.machine-card {
margin-top: 18rpx;
padding: 24rpx;
border: 1rpx solid #edf1f7;
border-radius: 24rpx;
background: #f8fbff;
}
.machine-card-header,
.machine-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.machine-index {
display: flex;
align-items: center;
justify-content: center;
width: 58rpx;
height: 58rpx;
margin-right: 16rpx;
border-radius: 18rpx;
background: rgba(22, 119, 255, 0.1);
color: #1677ff;
font-size: 22rpx;
font-weight: 800;
}
.machine-card-title {
flex: 1;
min-width: 0;
}
.machine-name,
.machine-hint,
.quantity-tip {
display: block;
}
.machine-name {
color: #17233d;
font-size: 26rpx;
font-weight: 700;
}
.machine-hint,
.quantity-tip {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
}
.machine-state,
.summary-state {
padding: 8rpx 18rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-weight: 600;
}
.machine-state.is-ready,
.summary-state.is-complete {
background: rgba(25, 190, 107, 0.12);
color: #19be6b;
}
.machine-state.is-waiting,
.summary-state.is-warning {
background: rgba(247, 183, 51, 0.14);
color: #d99400;
}
.machine-card .field-card {
margin-top: 18rpx;
}
.scan-button {
display: flex;
align-items: center;
justify-content: center;
width: 76rpx;
height: 76rpx;
margin-left: 18rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #1677ff 0%, #35bfa3 100%);
box-shadow: 0 12rpx 26rpx rgba(22, 119, 255, 0.2);
}
.scan-button.small {
width: 68rpx;
height: 68rpx;
border-radius: 22rpx;
}
.machine-footer {
margin-top: 18rpx;
}
.quantity-control {
display: flex;
align-items: center;
overflow: hidden;
border-radius: 18rpx;
background: #ffffff;
}
.qty-button {
display: flex;
align-items: center;
justify-content: center;
width: 58rpx;
height: 58rpx;
color: #1677ff;
font-size: 28rpx;
font-weight: 700;
}
.qty-input {
width: 80rpx;
height: 58rpx;
color: #17233d;
font-size: 24rpx;
font-weight: 700;
text-align: center;
}
.remove-machine,
.add-machine {
display: flex;
align-items: center;
justify-content: center;
}
.remove-machine {
justify-content: flex-end;
margin-top: 18rpx;
color: #e91f3f;
font-size: 20rpx;
}
.remove-machine text,
.add-machine text {
margin-left: 8rpx;
}
.add-machine {
margin-top: 18rpx;
padding: 22rpx;
border: 1rpx dashed rgba(22, 119, 255, 0.34);
border-radius: 22rpx;
background: #f8fbff;
color: #1677ff;
font-size: 24rpx;
font-weight: 700;
}
.add-machine--active {
background: #eef5ff;
}
.allocation-summary {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #edf1f7;
}
.summary-label {
display: block;
color: #8a94a6;
font-size: 20rpx;
}
.summary-value {
display: block;
margin-top: 6rpx;
color: #17233d;
font-size: 32rpx;
font-weight: 800;
}
.summary-unit {
display: inline;
color: #8a94a6;
font-size: 20rpx;
font-weight: 400;
}
.action-row {
display: flex;
align-items: center;
margin-top: 28rpx;
}
.submit-secondary,
.submit-primary {
display: flex;
align-items: center;
justify-content: center;
height: 86rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 700;
}
.submit-secondary {
width: 176rpx;
background: #f2f4f8;
color: #5f6b7a;
}
.submit-primary {
margin-left: 14rpx;
flex: 1;
background: linear-gradient(135deg, #1677ff 0%, #35bfa3 100%);
color: #ffffff;
box-shadow: 0 14rpx 24rpx rgba(22, 119, 255, 0.18);
}
.submit-primary.is-loading {
opacity: 0.8;
}
.stats-header {
margin-bottom: 22rpx;
}
.total-badge {
font-size: 26rpx;
font-weight: 800;
}
.stats-grid {
grid-template-columns: repeat(3, 1fr);
margin-top: 0;
}
.stat-item {
display: flex;
align-items: center;
justify-content: center;
min-height: 112rpx;
padding: 0;
flex-direction: column;
}
.stat-value {
color: #17233d;
font-size: 32rpx;
font-weight: 800;
}
.stat-value.success { color: #19be6b; }
.stat-value.danger { color: #e91f3f; }
.stat-label {
margin-top: 8rpx;
}
.record-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22rpx 0;
border-bottom: 1rpx solid #edf1f7;
}
.record-item:last-child { border-bottom: 0; }
.record-main { color: #17233d; font-size: 26rpx; font-weight: 700; }
.record-sub { margin-top: 6rpx; color: #8a94a6; font-size: 20rpx; }
.record-status { padding: 8rpx 18rpx; border-radius: 999rpx; background: rgba(25, 190, 107, 0.12); color: #19be6b; font-size: 20rpx; font-weight: 600; }
.empty-record { padding: 30rpx 0 8rpx; color: #a0a7b5; font-size: 22rpx; text-align: center; }
.config-card,
.form-card,
.stats-card,
.record-card {
margin-top: 24rpx;
padding: 28rpx;
border: 1rpx solid rgba(255, 255, 255, 0.72);
border-radius: 28rpx;
background: #ffffff;
box-shadow: 0 16rpx 42rpx rgba(23, 35, 61, 0.06);
}
.card-title,
.card-subtitle,
.field-label,
.quantity-tip,
.record-main,
.record-sub,
.stat-label {
display: block;
}
.card-title {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
}
.card-subtitle,
.quantity-tip {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
}
.chip-row,
.form-title-row,
.quantity-heading,
.quantity-preview,
.stats-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.chip-row { margin-top: 20rpx; }
.select-chip {
width: calc(50% - 8rpx);
padding: 18rpx 20rpx;
box-sizing: border-box;
border-radius: 18rpx;
background: #f5f8fd;
}
.chip-label { color: #8a94a6; font-size: 20rpx; }
.chip-value { display: block; margin-top: 6rpx; color: #17233d; font-size: 24rpx; font-weight: 700; }
.progress-badge, .quantity-unit { color: #1677ff; font-size: 22rpx; font-weight: 700; }
.field-card,
.quantity-card {
margin-top: 22rpx;
padding: 20rpx 22rpx;
border: 1rpx solid rgba(22, 119, 255, 0.18);
border-radius: 22rpx;
}
.primary-field { display: flex; align-items: center; }
.field-main { flex: 1; }
.field-label { color: #5f6b7a; font-size: 22rpx; font-weight: 600; }
.scan-button { display: flex; align-items: center; justify-content: center; width: 72rpx; height: 72rpx; margin-left: 16rpx; border-radius: 18rpx; background: #1677ff; }
.quantity-control { display: flex; align-items: center; margin-top: 20rpx; }
.qty-button { display: flex; align-items: center; justify-content: center; width: 72rpx; height: 72rpx; border-radius: 18rpx; background: #edf4ff; color: #1677ff; font-size: 38rpx; font-weight: 500; }
.qty-input { flex: 1; height: 72rpx; margin: 0 16rpx; border-radius: 18rpx; background: #f7f9fc; color: #17233d; font-size: 32rpx; font-weight: 800; text-align: center; }
.quantity-preview { margin-top: 20rpx; padding: 20rpx; border-radius: 18rpx; background: #f4fbf8; color: #5f6b7a; font-size: 22rpx; }
.quantity-preview-value { color: #19be6b; font-size: 30rpx; font-weight: 800; }
.theme-dark.entry-page {
background: #151923;
}
.theme-dark .config-card,
.theme-dark .form-card,
.theme-dark .stats-card,
.theme-dark .record-card,
.theme-dark .machine-card {
border-color: #364152;
background: #222936;
box-shadow: none;
}
.theme-dark .select-chip,
.theme-dark .field-card,
.theme-dark .quantity-card,
.theme-dark .stat-item,
.theme-dark .add-machine {
border-color: #364152;
background: #1c2431;
}
.theme-dark .quantity-control,
.theme-dark .qty-input,
.theme-dark .part-stepper {
border-color: #364152;
background: #1c2431;
}
.theme-dark .qty-button,
.theme-dark .submit-secondary {
background: #293344;
}
.theme-dark .card-title,
.theme-dark .chip-value,
.theme-dark .field-label,
.theme-dark .machine-name,
.theme-dark .record-main,
.theme-dark .stat-value,
.theme-dark .summary-value,
.theme-dark .qty-input {
color: #f2f5fa;
}
.theme-dark .card-subtitle,
.theme-dark .chip-label,
.theme-dark .quantity-tip,
.theme-dark .machine-hint,
.theme-dark .record-sub,
.theme-dark .stat-label,
.theme-dark .summary-label {
color: #aeb9ca;
}
.theme-dark .record-item,
.theme-dark .allocation-summary {
border-color: #364152;
}
.theme-dark :deep(.u-input__input) {
color: #f2f5fa !important;
}
</style>