Files
MrleeAdmin/frontend/app/pages/query/index.vue
T

1382 lines
31 KiB
Vue
Raw Normal View History

2026-08-31 11:33:59 +08:00
<template>
<view class="query-page query-layout" :class="`theme-${themeMode}`">
<view class="query-header">
<view>
<text class="query-title">记录查询</text>
2026-09-03 19:56:19 +08:00
<text class="query-subtitle">快速定位扫码、整机、异常与维修记录</text>
2026-08-31 11:33:59 +08:00
</view>
<u-icon name="search" color="#2f6cf6" size="34" />
</view>
<view class="query-tabs">
2026-09-03 19:56:19 +08:00
<view v-for="item in queryTypes" :key="item.value" class="query-tab" :class="{ 'is-active': activeQueryType === item.value }" @tap="handleRecordTypeChange(item.value)">
2026-08-31 11:33:59 +08:00
<u-icon :name="item.icon" :color="activeQueryType === item.value ? '#ffffff' : '#9aa4b4'" size="28" />
<text>{{ item.label }}</text>
</view>
</view>
<view class="query-search-row">
<view class="query-input-wrap">
<u-input v-model="keyword" :input-mode="pdaMode ? 'none' : 'text'" clearable prefix-icon="search" placeholder="请输入单号、商品编码或操作人" placeholder-style="color: #a0a7b5;" @focus="handleKeywordInputFocus" @confirm="handleSearch" />
</view>
<view class="camera-button" :class="{ 'is-scanning': isScanning }" @tap="scanKeyword">
<u-icon name="scan" color="#ffffff" size="32" />
</view>
<view class="query-button" @tap="handleSearch">查询</view>
</view>
<view class="advanced-trigger" @tap="showAdvanced = !showAdvanced">
<view class="advanced-title">
<u-icon name="list" color="#687386" size="28" />
<text>高级筛选</text>
<text class="advanced-state">{{ showAdvanced ? '收起' : '可选' }}</text>
</view>
<u-icon :name="showAdvanced ? 'arrow-up' : 'arrow-down'" color="#9aa4b4" size="24" />
</view>
<view v-if="showAdvanced" class="advanced-panel">
2026-09-03 19:56:19 +08:00
<view class="filter-select-row" @tap="showDatePicker = true">
<text class="filter-input-label">时间区间</text>
<text class="filter-select-value">{{ dateRangeLabel }}</text>
<u-icon name="arrow-right" color="#a0a7b5" size="24" />
2026-08-31 11:33:59 +08:00
</view>
2026-09-03 19:56:19 +08:00
<view class="filter-select-row" @tap="chooseProcess">
<text class="filter-input-label">工序</text>
<text class="filter-select-value">{{ processLabel }}</text>
<u-icon name="arrow-right" color="#a0a7b5" size="24" />
</view>
<view class="filter-select-row" :class="{ 'is-disabled': !activeProcess }" @tap="chooseStep">
<text class="filter-input-label">步骤</text>
<text class="filter-select-value">{{ stepLabel }}</text>
<u-icon name="arrow-right" color="#a0a7b5" size="24" />
2026-08-31 11:33:59 +08:00
</view>
<view class="filter-actions">
<text @tap="resetFilter">重置条件</text>
<text class="filter-confirm" @tap="handleSearch">确定筛选</text>
</view>
</view>
<view class="result-header">
2026-09-03 19:56:19 +08:00
<text>{{ hasSearched ? `找到 ${recordTotal} 条结果` : '请输入条件后点击查询' }}</text>
2026-08-31 11:33:59 +08:00
<text>{{ currentTypeLabel }}</text>
</view>
2026-09-03 19:56:19 +08:00
<view v-if="hasSearched && recordLoading" class="query-loading">正在查询,请稍候…</view>
2026-08-31 11:33:59 +08:00
2026-09-03 19:56:19 +08:00
<view v-if="hasSearched && filteredRecords.length && !recordLoading" class="record-list">
<view v-for="item in filteredRecords" :key="item.id" class="record-card compact-record" @tap="showRecordDetail(item)" @click="showRecordDetail(item)">
2026-08-31 11:33:59 +08:00
<view class="record-top">
<view class="record-number">
<u-icon name="list" color="#44c653" size="28" />
<text class="record-main">{{ item.mainNo }}</text>
</view>
<view class="record-type-pill">{{ getRecordTypeLabel(item) }}</view>
</view>
<view class="record-detail-grid">
<view class="record-detail"><text>岗位:</text><text>{{ item.position }}</text></view>
2026-09-03 19:56:19 +08:00
<view class="record-detail"><text>步骤:</text><text>{{ item.process }} / {{ item.step }}</text></view>
2026-08-31 11:33:59 +08:00
<view class="record-detail"><text>状态:</text><text class="record-status" :class="item.statusClass">{{ item.statusLabel }}</text></view>
<view class="record-detail"><text>操作员:</text><text>{{ item.operator }}</text></view>
</view>
<view class="record-footer">
<text>{{ item.scanTime }}</text>
<u-icon name="arrow-right" color="#c1c7d0" size="24" />
</view>
</view>
</view>
2026-09-03 19:56:19 +08:00
<u-empty v-else-if="hasSearched && !recordLoading" mode="data" text="暂无查询结果" />
2026-08-31 11:33:59 +08:00
<view v-if="detailVisible && selectedRecord" class="detail-mask" @tap="closeRecordDetail">
<view class="detail-sheet" @tap.stop>
<view class="sheet-handle"></view>
<view class="detail-sheet-header">
<text>录入数据明细</text>
<u-icon name="close" color="#7d8798" size="30" @tap="closeRecordDetail" />
</view>
<text class="sheet-main-no">{{ selectedRecord.mainNo }}</text>
<view class="sheet-detail-grid">
<view class="sheet-detail-item"><text>记录类型</text><text>{{ getRecordTypeLabel(selectedRecord) }}</text></view>
<view class="sheet-detail-item"><text>录入模式</text><text>{{ selectedRecord.modeLabel }}</text></view>
<view class="sheet-detail-item"><text>岗位</text><text>{{ selectedRecord.position }}</text></view>
<view class="sheet-detail-item"><text>工序 / 步骤</text><text>{{ selectedRecord.process }} / {{ selectedRecord.step }}</text></view>
<view class="sheet-detail-item"><text>商品编码</text><text>{{ selectedRecord.productCode || '-' }}</text></view>
<view class="sheet-detail-item"><text>数量</text><text>{{ selectedRecord.quantity || '-' }}</text></view>
<view class="sheet-detail-item"><text>操作人</text><text>{{ selectedRecord.operator }}</text></view>
<view class="sheet-detail-item"><text>扫码时间</text><text>{{ selectedRecord.scanTime }}</text></view>
</view>
<view class="sheet-remark"><text>备注</text><text>{{ selectedRecord.remark }}</text></view>
</view>
</view>
2026-09-03 19:56:19 +08:00
<u-calendar v-model="showDatePicker" mode="range" @change="handleDateChange" />
2026-08-31 11:33:59 +08:00
</view>
</template>
<script>
// UTS 原生扫码插件仅在 App-Plus 真机/自定义基座中解析,Web 环境不加载。
// #ifdef APP-PLUS
import { openCodeScan } from '@/uni_modules/mas-code-scan'
// #endif
import { getScanRegionSetting } from '@/utils/scan-settings.js'
2026-09-03 19:56:19 +08:00
import { queryProduceRecords } from '@/api/produce/query.js'
import { getProcessOptions, getStepOptions } from '@/api/produce/data-entry.js'
2026-08-31 11:33:59 +08:00
export default {
data() {
return {
keyword: '',
2026-09-03 19:56:19 +08:00
orderNo: '',
userCode: '',
startDate: '',
endDate: '',
showDatePicker: false,
2026-08-31 11:33:59 +08:00
pdaMode: false,
activeQueryType: 'all',
showAdvanced: false,
isScanning: false,
2026-09-03 19:56:19 +08:00
hasSearched: false,
recordLoading: false,
recordTotal: 0,
recordPage: 1,
recordLimit: 20,
activeProcess: '',
activeStep: '',
2026-08-31 11:33:59 +08:00
detailVisible: false,
selectedRecord: null,
queryTypes: [
{ label: '全部', value: 'all', icon: 'search' },
2026-09-03 19:56:19 +08:00
{ label: '订单', value: 'scan', icon: 'scan' },
{ label: '整机', value: 'product', icon: 'grid-fill' },
{ label: '异常', value: 'exception', icon: 'warning-fill' },
{ label: '维修', value: 'repair', icon: 'wrench' }
2026-08-31 11:33:59 +08:00
],
2026-09-03 19:56:19 +08:00
processOptions: [],
stepOptions: [],
records: []
2026-08-31 11:33:59 +08:00
}
},
computed: {
filteredRecords() {
return this.records.filter((item) => {
const matchType = this.activeQueryType === 'all' || item.queryType === this.activeQueryType
2026-09-03 19:56:19 +08:00
const matchProcess = !this.activeProcess || item.processId === String(this.activeProcess) || item.process === this.processLabel
const matchStep = !this.activeStep || item.stepId === String(this.activeStep) || item.step === this.stepLabel
return matchType && matchProcess && matchStep
2026-08-31 11:33:59 +08:00
})
},
currentTypeLabel() {
const current = this.queryTypes.find((item) => item.value === this.activeQueryType)
return current ? current.label : '全部类型'
},
2026-09-03 19:56:19 +08:00
processLabel() {
return this.processOptions.find((item) => String(item.value) === String(this.activeProcess))?.label || '全部工序'
},
stepLabel() {
return this.stepOptions.find((item) => String(item.value) === String(this.activeStep))?.label || '全部步骤'
},
dateRangeLabel() {
return this.startDate && this.endDate ? `${this.startDate} 至 ${this.endDate}` : '不限时间'
},
2026-08-31 11:33:59 +08:00
successCount() {
return this.filteredRecords.filter((item) => item.status === 'success').length
},
failedCount() {
return this.filteredRecords.filter((item) => item.status === 'failed').length
}
},
watch: {
filteredRecords: {
immediate: true,
2026-09-03 19:56:19 +08:00
handler(list) {
if (!list.length) {
this.selectedRecord = null
this.detailVisible = false
return
}
if (!this.selectedRecord || !list.some((item) => item.id === this.selectedRecord.id)) {
this.detailVisible = false
this.selectedRecord = list[0]
}
2026-08-31 11:33:59 +08:00
}
}
},
methods: {
2026-09-03 19:56:19 +08:00
isLoggedIn() {
return Boolean(uni.getStorageSync('scanapp_token'))
},
requireLogin() {
if (this.isLoggedIn()) return true
uni.showToast({ title: '请先登录后再查询', icon: 'none' })
return false
},
mapRecord(item) {
const type = item.record_type || 'scan'
const resultName = String(item.result_name || '').trim()
const statusName = String(item.status_name || '').trim()
const status = resultName === '成功' ? 'success' : resultName === '失败' ? 'failed' : this.mapStatus(statusName)
return {
id: `${type}-${item.id}`,
queryType: type,
typeLabel: item.record_type_name || '记录',
mainNo: item.order_no || item.repair_no || '-',
position: item.position || '-',
process: item.process_name || '-',
step: item.step_name || '-',
processId: item.process_id || '',
stepId: item.step_id || '',
operator: item.operator || item.user_code || '未记录',
userCode: item.user_code || '',
status,
statusLabel: resultName || statusName || '已记录',
statusClass: status === 'failed' ? 'is-failed' : status === 'success' ? 'is-success' : '',
scanTime: item.event_date || this.formatEventDate(item.event_at),
remark: item.remark || '',
modeLabel: item.record_type_name || '记录',
productCode: item.product_code || '',
quantity: item.quantity ?? '',
exceptionReason: item.exception_reason || '',
exceptionContent: item.exception_content || '',
materialCategory: item.material_category || '',
faultDescription: item.fault_description || ''
}
},
formatEventDate(timestamp) {
const value = Number(timestamp || 0)
if (!value) return '-'
const date = new Date(value * 1000)
if (Number.isNaN(date.getTime())) return '-'
const pad = (part) => String(part).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
},
mapStatus(statusName) {
const statusMap = { 待处理: 'pending', 处理中: 'processing', 已完成: 'done', 已维修: 'repaired' }
return statusMap[statusName] || statusName || 'recorded'
},
async loadRecords() {
if (this.recordLoading) return
this.detailVisible = false
this.selectedRecord = null
this.recordLoading = true
const types = this.activeQueryType === 'all' ? ['scan', 'product', 'exception', 'repair'] : [this.activeQueryType]
const params = {
keyword: this.keyword.trim(),
order_no: this.orderNo.trim(),
user_code: this.userCode.trim(),
start_date: this.startDate,
end_date: this.endDate,
process_id: this.activeProcess,
step_id: this.activeStep,
page: this.recordPage,
limit: this.recordLimit
}
try {
const responses = await Promise.all(types.map((recordType) => queryProduceRecords({ ...params, record_type: recordType })))
const items = responses.flatMap((data) => Array.isArray(data?.items) ? data.items.map((item) => this.mapRecord(item)) : [])
items.sort((a, b) => String(b.scanTime).localeCompare(String(a.scanTime)))
this.records = items
this.recordTotal = responses.reduce((total, data) => total + Number(data?.total || 0), 0)
} catch (error) {
this.records = []
this.recordTotal = 0
uni.showToast({ title: '查询失败,请稍后重试', icon: 'none' })
} finally {
this.recordLoading = false
}
},
async loadProcessOptions() {
try {
const options = await getProcessOptions()
this.processOptions = Array.isArray(options) ? options : []
} catch (error) {
this.processOptions = []
}
},
chooseProcess() {
const labels = ['全部工序', ...this.processOptions.map((item) => item.label)]
uni.showActionSheet({ itemList: labels, success: async (result) => {
this.activeProcess = result.tapIndex === 0 ? '' : this.processOptions[result.tapIndex - 1]?.value || ''
this.activeStep = ''
this.stepOptions = []
if (this.activeProcess) {
try {
const options = await getStepOptions(this.activeProcess)
this.stepOptions = Array.isArray(options) ? options : []
} catch (error) {
this.stepOptions = []
}
}
} })
},
chooseStep() {
if (!this.activeProcess) {
uni.showToast({ title: '请先选择工序', icon: 'none' })
return
}
const labels = ['全部步骤', ...this.stepOptions.map((item) => item.label)]
uni.showActionSheet({ itemList: labels, success: (result) => {
this.activeStep = result.tapIndex === 0 ? '' : this.stepOptions[result.tapIndex - 1]?.value || ''
} })
},
handleDateChange(result) {
this.startDate = result.startDate || ''
this.endDate = result.endDate || ''
this.showDatePicker = false
},
handleRecordTypeChange(type) {
if (this.activeQueryType === type) return
this.activeQueryType = type
this.recordPage = 1
if (this.hasSearched) this.loadRecords()
},
2026-08-31 11:33:59 +08:00
getCurrentUserCode() {
const user = uni.getStorageSync('scanapp_user') || {}
return user.username || user.nickname || 'U001'
},
loadPdaMode() {
const settings = uni.getStorageSync(`scanapp_settings_${this.getCurrentUserCode()}`)
this.pdaMode = settings?.pdaMode === true
},
handleKeywordInputFocus() {
// #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
},
handleSearch() {
2026-09-03 19:56:19 +08:00
const hasInput = this.keyword.trim() || this.orderNo.trim() || this.userCode.trim()
if (!hasInput) {
this.hasSearched = false
this.records = []
this.recordTotal = 0
2026-08-31 11:33:59 +08:00
this.selectedRecord = null
this.detailVisible = false
2026-09-03 19:56:19 +08:00
uni.showToast({ title: '请输入查询内容', icon: 'none' })
2026-08-31 11:33:59 +08:00
return
}
2026-09-03 19:56:19 +08:00
this.recordPage = 1
this.hasSearched = true
this.loadRecords()
2026-08-31 11:33:59 +08:00
},
resetFilter() {
this.keyword = ''
2026-09-03 19:56:19 +08:00
this.orderNo = ''
this.userCode = ''
this.startDate = ''
this.endDate = ''
2026-08-31 11:33:59 +08:00
this.activeQueryType = 'all'
2026-09-03 19:56:19 +08:00
this.activeProcess = ''
this.activeStep = ''
this.stepOptions = []
2026-08-31 11:33:59 +08:00
this.showAdvanced = false
this.detailVisible = false
2026-09-03 19:56:19 +08:00
this.recordPage = 1
this.hasSearched = false
this.records = []
this.recordTotal = 0
2026-08-31 11:33:59 +08:00
},
scanKeyword() {
if (this.isScanning) return
// #ifdef APP-PLUS
this.isScanning = true
openCodeScan({
smartZoom: true,
vibrate: true,
scanRegion: getScanRegionSetting(),
success: (result) => {
const value = String(result?.content || '').trim()
if (!value) return
this.keyword = value
this.handleSearch()
},
fail: (error) => {
const code = Number(error?.errCode)
if (code === 9021003) return
const messageMap = {
9021001: '当前环境不支持扫码,请在 Android 自定义调试基座或正式安装包中使用',
9021002: '相机权限被拒绝,请到系统设置中开启相机权限',
9021004: '未获取到有效扫码结果,请重试'
}
uni.showToast({ title: messageMap[code] || String(error?.errMsg || '扫码插件调用异常'), icon: 'none' })
},
complete: () => {
this.isScanning = false
}
})
// #endif
// #ifndef APP-PLUS
uni.showToast({ title: '请在 Android App 中使用扫码功能', icon: 'none' })
// #endif
},
showRecordDetail(item) {
2026-09-03 19:56:19 +08:00
if (!item) return
2026-08-31 11:33:59 +08:00
this.selectedRecord = item
this.detailVisible = true
},
closeRecordDetail() {
this.detailVisible = false
},
getRecordTypeLabel(item) {
return item.typeLabel || '记录'
}
},
onLoad() {
2026-09-03 19:56:19 +08:00
if (!this.requireLogin()) return
2026-08-31 11:33:59 +08:00
this.loadPdaMode()
2026-09-03 19:56:19 +08:00
this.loadProcessOptions()
2026-08-31 11:33:59 +08:00
},
onShow() {
2026-09-03 19:56:19 +08:00
if (!this.isLoggedIn()) return
2026-08-31 11:33:59 +08:00
this.loadPdaMode()
2026-09-03 19:56:19 +08:00
},
onHide() {
this.detailVisible = false
this.selectedRecord = null
2026-08-31 11:33:59 +08:00
}
}
</script>
<style lang="scss" scoped>
.query-page {
min-height: 100vh;
padding: 28rpx 24rpx 48rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eef6ff 0%, #f4f6fb 38%, #f4f6fb 100%);
}
.hero-card,
.filter-card,
.summary-card,
.detail-card,
.list-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);
}
.hero-card {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0;
padding: 34rpx 30rpx;
background: linear-gradient(135deg, #1677ff 0%, #35bfa3 100%);
}
.hero-title,
.hero-subtitle,
.card-title,
.filter-label,
.summary-value,
.summary-label,
.detail-main,
.detail-sub,
.detail-key,
.detail-value,
.record-main,
.record-sub,
.record-key,
.record-value {
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.82);
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);
}
.card-title {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.search-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 20rpx;
}
.search-input-wrap {
flex: 1;
min-width: 0;
}
.filter-group {
margin-top: 20rpx;
}
.filter-label {
margin-bottom: 14rpx;
color: #5f6b7a;
font-size: 22rpx;
font-weight: 600;
line-height: 1.35;
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 14rpx;
}
.chip {
padding: 14rpx 20rpx;
border-radius: 999rpx;
background: #f3f6fb;
color: #5f6b7a;
font-size: 24rpx;
line-height: 1.35;
}
.chip.is-active {
background: rgba(22, 119, 255, 0.12);
color: #1677ff;
}
.action-row {
display: flex;
justify-content: space-between;
gap: 16rpx;
margin-top: 24rpx;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.summary-item {
padding: 22rpx 18rpx;
border-radius: 22rpx;
background: #f8fbff;
}
.summary-value {
color: #17233d;
font-size: 32rpx;
font-weight: 800;
line-height: 1.2;
}
.summary-value.success {
color: #19be6b;
}
.summary-value.danger {
color: #e91f3f;
}
.summary-label {
margin-top: 8rpx;
color: #8a94a6;
font-size: 20rpx;
line-height: 1.35;
}
.detail-card,
.list-card {
margin-top: 24rpx;
}
.detail-header,
.record-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16rpx;
}
.detail-main,
.record-main {
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.detail-sub,
.record-sub {
margin-top: 6rpx;
color: #8a94a6;
font-size: 20rpx;
line-height: 1.35;
}
.status-pill {
padding: 10rpx 18rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-weight: 600;
line-height: 1.35;
}
.status-pill.is-success {
background: rgba(25, 190, 107, 0.12);
color: #19be6b;
}
.status-pill.is-failed {
background: rgba(233, 31, 63, 0.12);
color: #e91f3f;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
margin-top: 22rpx;
}
.detail-item {
padding: 18rpx 18rpx 20rpx;
border-radius: 20rpx;
background: #f8fbff;
}
.detail-key,
.record-key {
color: #8a94a6;
font-size: 20rpx;
line-height: 1.35;
}
.detail-value,
.record-value {
margin-top: 8rpx;
color: #17233d;
font-size: 22rpx;
font-weight: 600;
line-height: 1.35;
}
.record-card {
padding: 22rpx 24rpx;
border-radius: 24rpx;
background: #f8fbff;
margin-top: 18rpx;
}
.record-card.is-selected {
border: 1rpx solid rgba(22, 119, 255, 0.22);
background: #f5f9ff;
}
.record-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
margin-top: 18rpx;
}
.record-info {
padding: 16rpx 16rpx 18rpx;
border-radius: 18rpx;
background: #ffffff;
}
.query-page.query-layout {
padding: 24rpx 20rpx 40rpx;
background: #f5f7fb;
}
.query-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4rpx 4rpx 20rpx;
}
.query-title,
.query-subtitle {
display: block;
}
.query-title {
color: #17233d;
font-size: 32rpx;
font-weight: 800;
line-height: 1.3;
}
.query-subtitle {
margin-top: 8rpx;
color: #8a94a6;
font-size: 18rpx;
line-height: 1.35;
}
.query-tabs {
display: grid;
2026-09-03 19:56:19 +08:00
grid-template-columns: repeat(5, 1fr);
2026-08-31 11:33:59 +08:00
gap: 10rpx;
margin-top: 8rpx;
}
.query-tab {
display: flex;
align-items: center;
justify-content: center;
gap: 6rpx;
height: 68rpx;
border: 1rpx solid transparent;
border-radius: 14rpx;
background: #edf0f5;
color: #7d8798;
font-size: 20rpx;
font-weight: 600;
line-height: 1;
}
.query-tab.is-active {
background: #2f6cf6;
color: #ffffff;
box-shadow: 0 10rpx 24rpx rgba(47, 108, 246, 0.22);
}
.query-search-row {
display: flex;
align-items: center;
gap: 0;
margin-top: 16rpx;
padding: 12rpx;
border: 1rpx solid #e9edf4;
border-radius: 18rpx;
background: #ffffff;
box-shadow: 0 8rpx 20rpx rgba(23, 35, 61, 0.04);
}
.query-input-wrap {
flex: 1;
min-width: 0;
height: 64rpx;
padding: 0 16rpx;
border: 1rpx solid #e3e7ef;
border-radius: 12rpx;
background: #f6f8fb;
box-sizing: border-box;
}
.camera-button,
.query-button {
display: flex;
align-items: center;
justify-content: center;
height: 64rpx;
border-radius: 12rpx;
background: #2f6cf6;
color: #ffffff;
font-size: 22rpx;
font-weight: 600;
line-height: 1;
box-shadow: 0 8rpx 18rpx rgba(47, 108, 246, 0.18);
}
.camera-button {
width: 64rpx;
margin-left: 16rpx;
flex: none;
transition: opacity 0.2s ease;
}
.camera-button.is-scanning {
opacity: 0.58;
}
.query-button {
width: 104rpx;
margin-left: 16rpx;
flex: none;
}
.advanced-trigger {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 14rpx;
padding: 18rpx 16rpx;
border: 1rpx solid #e9edf4;
border-radius: 14rpx;
background: #ffffff;
}
.advanced-title {
display: flex;
align-items: center;
gap: 8rpx;
color: #2c313a;
font-size: 22rpx;
font-weight: 600;
line-height: 1;
}
.advanced-state {
margin-left: 4rpx;
padding: 2rpx 8rpx;
border-radius: 8rpx;
background: #eef5ff;
color: #3a82ff;
font-size: 18rpx;
font-weight: 500;
}
.advanced-panel {
margin-top: 10rpx;
padding: 20rpx 18rpx 16rpx;
border: 1rpx solid #e9edf4;
border-radius: 16rpx;
background: #ffffff;
box-shadow: 0 8rpx 20rpx rgba(23, 35, 61, 0.04);
}
2026-09-03 19:56:19 +08:00
.filter-input-row {
display: flex;
align-items: center;
min-height: 64rpx;
margin-bottom: 12rpx;
padding: 0 14rpx;
border: 1rpx solid #e3e7ef;
border-radius: 12rpx;
background: #f6f8fb;
box-sizing: border-box;
}
.filter-input-label {
width: 112rpx;
flex: none;
color: #5f6b7a;
font-size: 22rpx;
}
.filter-input-row :deep(.u-input) {
flex: 1;
min-width: 0;
}
.filter-select-row {
display: flex;
align-items: center;
min-height: 64rpx;
margin-bottom: 12rpx;
padding: 0 14rpx;
border: 1rpx solid #e3e7ef;
border-radius: 12rpx;
background: #f6f8fb;
box-sizing: border-box;
}
.filter-select-row.is-disabled {
opacity: 0.55;
}
.filter-select-value {
flex: 1;
min-width: 0;
color: #2b313b;
font-size: 22rpx;
}
2026-08-31 11:33:59 +08:00
.status-filter-label {
margin-top: 10rpx;
}
.compact-chip-row {
gap: 10rpx;
}
.chip {
padding: 12rpx 18rpx;
border-radius: 999rpx;
background: #f1f4f8;
color: #6b778c;
font-size: 20rpx;
line-height: 1.35;
}
.chip.is-active {
background: #2f6cf6;
color: #ffffff;
}
.filter-actions {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12rpx;
color: #6b778c;
font-size: 20rpx;
}
.filter-confirm {
color: #2f6cf6;
font-weight: 600;
}
.result-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
padding: 0 6rpx 8rpx;
color: #7d8798;
font-size: 18rpx;
line-height: 1.35;
}
2026-09-03 19:56:19 +08:00
.query-loading {
padding: 28rpx 0;
color: #7d8798;
font-size: 20rpx;
text-align: center;
}
2026-08-31 11:33:59 +08:00
.record-list {
margin-top: 2rpx;
}
.record-card.compact-record {
margin-top: 10rpx;
padding: 20rpx 18rpx 16rpx;
border: 1rpx solid #edf0f5;
border-radius: 16rpx;
background: #ffffff;
box-shadow: 0 8rpx 18rpx rgba(23, 35, 61, 0.04);
}
.record-top {
align-items: center;
}
.record-number {
display: flex;
align-items: center;
gap: 8rpx;
min-width: 0;
}
.record-main {
max-width: 430rpx;
overflow: hidden;
color: #17233d;
font-size: 26rpx;
font-weight: 800;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-type-pill {
padding: 6rpx 12rpx;
border: 1rpx solid #b7ec8f;
border-radius: 999rpx;
background: #f8fff0;
color: #7ad63a;
font-size: 18rpx;
line-height: 1.35;
flex: none;
}
.record-detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8rpx 18rpx;
margin-top: 16rpx;
padding-top: 14rpx;
border-top: 1rpx solid #f0f2f6;
}
.record-detail {
display: flex;
align-items: baseline;
font-size: 20rpx;
line-height: 1.55;
color: #7d8798;
}
.record-detail text:last-child {
color: #2b313b;
margin-left: 4rpx;
}
.record-status.is-success {
color: #7ad63a;
}
.record-status.is-failed {
color: #ff8a5b;
}
.record-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 14rpx;
padding-top: 10rpx;
border-top: 1rpx solid #f5f6f8;
color: #9aa4b4;
font-size: 18rpx;
line-height: 1.35;
}
.detail-mask {
position: fixed;
2026-09-03 19:56:19 +08:00
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
2026-08-31 11:33:59 +08:00
z-index: 999;
display: flex;
2026-09-03 19:56:19 +08:00
flex-direction: column;
align-items: stretch;
justify-content: flex-end;
2026-08-31 11:33:59 +08:00
background: rgba(0, 0, 0, 0.32);
}
.sheet-handle {
width: 64rpx;
height: 8rpx;
margin: 0 auto 18rpx;
border-radius: 999rpx;
background: #dfe4ec;
}
.detail-sheet {
width: 100%;
max-height: 72vh;
overflow: auto;
padding: 22rpx 22rpx 30rpx;
border-radius: 24rpx 24rpx 0 0;
background: #ffffff;
box-shadow: 0 -16rpx 40rpx rgba(0, 0, 0, 0.08);
box-sizing: border-box;
}
.detail-sheet-header {
display: flex;
align-items: center;
justify-content: space-between;
color: #17233d;
font-size: 28rpx;
font-weight: 700;
line-height: 1.35;
}
.sheet-main-no {
display: block;
margin-top: 12rpx;
color: #17233d;
font-size: 32rpx;
font-weight: 800;
line-height: 1.35;
}
.sheet-detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12rpx;
margin-top: 16rpx;
}
.sheet-detail-item {
padding: 14rpx;
border-radius: 14rpx;
background: #f8fbff;
}
.sheet-detail-item text:first-child {
display: block;
color: #7d8798;
font-size: 18rpx;
line-height: 1.35;
}
.sheet-detail-item text:last-child {
display: block;
margin-top: 4rpx;
color: #17233d;
font-size: 22rpx;
font-weight: 600;
line-height: 1.35;
}
.sheet-remark {
margin-top: 14rpx;
padding: 14rpx;
border-radius: 14rpx;
background: #f8fbff;
color: #7d8798;
font-size: 18rpx;
line-height: 1.45;
}
.sheet-remark text:last-child {
display: block;
margin-top: 4rpx;
color: #17233d;
font-size: 22rpx;
line-height: 1.45;
}
.theme-dark.query-page {
background: #151923;
}
.theme-dark .query-header,
.theme-dark .query-search-row,
.theme-dark .advanced-trigger,
.theme-dark .advanced-panel,
.theme-dark .record-card,
.theme-dark .detail-sheet {
border-color: #364152;
background: #222936;
box-shadow: none;
}
.theme-dark .query-tab:not(.is-active),
.theme-dark .query-input-wrap,
2026-09-03 19:56:19 +08:00
.theme-dark .filter-input-row,
.theme-dark .filter-select-row,
2026-08-31 11:33:59 +08:00
.theme-dark .chip,
.theme-dark .record-info,
.theme-dark .sheet-detail-item,
.theme-dark .sheet-remark {
border-color: #364152;
background: #1c2431;
}
.theme-dark .query-title,
.theme-dark .advanced-title,
2026-09-03 19:56:19 +08:00
.theme-dark .filter-select-value,
2026-08-31 11:33:59 +08:00
.theme-dark .record-main,
.theme-dark .record-detail text:last-child,
.theme-dark .detail-sheet-header,
.theme-dark .sheet-main-no,
.theme-dark .sheet-detail-item text:last-child,
.theme-dark .sheet-remark text:last-child {
color: #f2f5fa;
}
.theme-dark .query-subtitle,
.theme-dark .result-header,
.theme-dark .filter-actions,
.theme-dark .record-detail,
.theme-dark .record-footer,
.theme-dark .sheet-detail-item text:first-child,
.theme-dark .sheet-remark {
color: #aeb9ca;
}
.theme-dark .record-detail-grid,
.theme-dark .record-footer {
border-color: #364152;
}
.theme-dark :deep(.u-input__input) {
color: #f2f5fa !important;
}
2026-09-03 19:56:19 +08:00
/* 查询页统一视觉层级:保持移动端紧凑、清晰的生产工具风格。 */
.query-page.query-layout {
padding: 24rpx 24rpx 56rpx;
background: linear-gradient(180deg, #edf5ff 0%, #f5f7fb 30%, #f5f7fb 100%);
}
.query-header {
padding: 24rpx 22rpx;
border: 1rpx solid #e5ebf4;
border-radius: 22rpx;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 12rpx 30rpx rgba(39, 74, 125, 0.08);
}
.query-title {
letter-spacing: 1rpx;
}
.query-tabs {
display: flex;
gap: 10rpx;
margin-top: 18rpx;
padding: 8rpx;
border: 1rpx solid #e5ebf4;
border-radius: 18rpx;
background: #ffffff;
box-shadow: 0 8rpx 22rpx rgba(39, 74, 125, 0.05);
overflow-x: auto;
}
.query-tab {
min-width: 120rpx;
flex: 1;
flex-shrink: 0;
border-radius: 12rpx;
transition: background 0.2s ease, color 0.2s ease;
}
.query-search-row {
margin-top: 18rpx;
padding: 10rpx;
border-radius: 18rpx;
box-shadow: 0 10rpx 26rpx rgba(39, 74, 125, 0.07);
}
.query-input-wrap {
background: #f7f9fc;
}
.camera-button,
.query-button {
box-shadow: 0 8rpx 18rpx rgba(47, 108, 246, 0.2);
}
.advanced-trigger,
.advanced-panel {
box-shadow: 0 8rpx 22rpx rgba(39, 74, 125, 0.05);
}
.advanced-trigger {
margin-top: 16rpx;
padding: 20rpx 18rpx;
}
.advanced-panel {
padding: 20rpx 18rpx 18rpx;
}
.filter-select-row {
margin-bottom: 14rpx;
background: #f8faff;
}
.filter-input-label {
font-weight: 600;
}
.result-header {
margin-top: 24rpx;
padding: 0 8rpx 10rpx;
color: #738199;
}
.record-card.compact-record {
margin-top: 12rpx;
padding: 22rpx 20rpx 18rpx;
border-radius: 18rpx;
box-shadow: 0 10rpx 24rpx rgba(39, 74, 125, 0.06);
}
.record-detail-grid {
gap: 10rpx 16rpx;
margin-top: 18rpx;
padding-top: 16rpx;
}
.record-detail text:last-child {
min-width: 0;
word-break: break-all;
}
.record-footer {
margin-top: 16rpx;
}
.theme-dark.query-page.query-layout {
background: #151923;
}
.theme-dark .query-header,
.theme-dark .query-tabs {
border-color: #364152;
background: #222936;
}
.theme-dark .query-search-row,
.theme-dark .advanced-trigger,
.theme-dark .advanced-panel,
.theme-dark .record-card.compact-record {
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.16);
}
/* 防止记录类型标签换行,并让输入文字在控件内垂直居中。 */
.query-tab {
min-width: 0;
padding: 0 4rpx;
flex: 1 1 0;
margin-right: 10rpx;
white-space: nowrap;
}
.query-tabs {
gap: 0;
}
.query-tab:last-child {
margin-right: 0;
}
.query-tab text {
white-space: nowrap;
}
.query-input-wrap {
display: flex;
align-items: center;
height: 80rpx;
}
.camera-button,
.query-button {
height: 80rpx;
}
.camera-button {
width: 80rpx;
}
.query-button {
width: 128rpx;
}
.query-input-wrap :deep(.u-input) {
height: 100%;
display: flex;
align-items: center;
padding: 0 !important;
}
.query-input-wrap :deep(.u-input__input) {
height: 100%;
line-height: 80rpx;
}
2026-08-31 11:33:59 +08:00
</style>