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

1382 lines
31 KiB
Vue
Raw 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="query-page query-layout" :class="`theme-${themeMode}`">
<view class="query-header">
<view>
<text class="query-title">记录查询</text>
<text class="query-subtitle">快速定位扫码、整机、异常与维修记录</text>
</view>
<u-icon name="search" color="#2f6cf6" size="34" />
</view>
<view class="query-tabs">
<view v-for="item in queryTypes" :key="item.value" class="query-tab" :class="{ 'is-active': activeQueryType === item.value }" @tap="handleRecordTypeChange(item.value)">
<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">
<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" />
</view>
<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" />
</view>
<view class="filter-actions">
<text @tap="resetFilter">重置条件</text>
<text class="filter-confirm" @tap="handleSearch">确定筛选</text>
</view>
</view>
<view class="result-header">
<text>{{ hasSearched ? `找到 ${recordTotal} 条结果` : '请输入条件后点击查询' }}</text>
<text>{{ currentTypeLabel }}</text>
</view>
<view v-if="hasSearched && recordLoading" class="query-loading">正在查询,请稍候…</view>
<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)">
<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>
<view class="record-detail"><text>步骤:</text><text>{{ item.process }} / {{ item.step }}</text></view>
<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>
<u-empty v-else-if="hasSearched && !recordLoading" mode="data" text="暂无查询结果" />
<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>
<u-calendar v-model="showDatePicker" mode="range" @change="handleDateChange" />
</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'
import { queryProduceRecords } from '@/api/produce/query.js'
import { getProcessOptions, getStepOptions } from '@/api/produce/data-entry.js'
export default {
data() {
return {
keyword: '',
orderNo: '',
userCode: '',
startDate: '',
endDate: '',
showDatePicker: false,
pdaMode: false,
activeQueryType: 'all',
showAdvanced: false,
isScanning: false,
hasSearched: false,
recordLoading: false,
recordTotal: 0,
recordPage: 1,
recordLimit: 20,
activeProcess: '',
activeStep: '',
detailVisible: false,
selectedRecord: null,
queryTypes: [
{ label: '全部', value: 'all', icon: 'search' },
{ label: '订单', value: 'scan', icon: 'scan' },
{ label: '整机', value: 'product', icon: 'grid-fill' },
{ label: '异常', value: 'exception', icon: 'warning-fill' },
{ label: '维修', value: 'repair', icon: 'wrench' }
],
processOptions: [],
stepOptions: [],
records: []
}
},
computed: {
filteredRecords() {
return this.records.filter((item) => {
const matchType = this.activeQueryType === 'all' || item.queryType === this.activeQueryType
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
})
},
currentTypeLabel() {
const current = this.queryTypes.find((item) => item.value === this.activeQueryType)
return current ? current.label : '全部类型'
},
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}` : '不限时间'
},
successCount() {
return this.filteredRecords.filter((item) => item.status === 'success').length
},
failedCount() {
return this.filteredRecords.filter((item) => item.status === 'failed').length
}
},
watch: {
filteredRecords: {
immediate: true,
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]
}
}
}
},
methods: {
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()
},
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() {
const hasInput = this.keyword.trim() || this.orderNo.trim() || this.userCode.trim()
if (!hasInput) {
this.hasSearched = false
this.records = []
this.recordTotal = 0
this.selectedRecord = null
this.detailVisible = false
uni.showToast({ title: '请输入查询内容', icon: 'none' })
return
}
this.recordPage = 1
this.hasSearched = true
this.loadRecords()
},
resetFilter() {
this.keyword = ''
this.orderNo = ''
this.userCode = ''
this.startDate = ''
this.endDate = ''
this.activeQueryType = 'all'
this.activeProcess = ''
this.activeStep = ''
this.stepOptions = []
this.showAdvanced = false
this.detailVisible = false
this.recordPage = 1
this.hasSearched = false
this.records = []
this.recordTotal = 0
},
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) {
if (!item) return
this.selectedRecord = item
this.detailVisible = true
},
closeRecordDetail() {
this.detailVisible = false
},
getRecordTypeLabel(item) {
return item.typeLabel || '记录'
}
},
onLoad() {
if (!this.requireLogin()) return
this.loadPdaMode()
this.loadProcessOptions()
},
onShow() {
if (!this.isLoggedIn()) return
this.loadPdaMode()
},
onHide() {
this.detailVisible = false
this.selectedRecord = null
}
}
</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;
grid-template-columns: repeat(5, 1fr);
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);
}
.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;
}
.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;
}
.query-loading {
padding: 28rpx 0;
color: #7d8798;
font-size: 20rpx;
text-align: center;
}
.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;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
z-index: 999;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-end;
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,
.theme-dark .filter-input-row,
.theme-dark .filter-select-row,
.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,
.theme-dark .filter-select-value,
.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;
}
/* 查询页统一视觉层级:保持移动端紧凑、清晰的生产工具风格。 */
.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;
}
</style>