|
|
|
|
@ -0,0 +1,739 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="page-container">
|
|
|
|
|
<!-- 顶部导航栏 -->
|
|
|
|
|
<view class="custom-nav">
|
|
|
|
|
<view class="nav-back" @click="goBack">
|
|
|
|
|
<text class="back-icon"><</text>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="nav-title">{{ pageType ? (pageType === 'up' ? t('moldOperate.tabUp') : t('moldOperate.tabDown')) + t('moldOperate.historySuffix') : t('moldOperate.historyTitle') }}</text>
|
|
|
|
|
<view class="nav-placeholder"></view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- Tab 切换(路由指定 type 时隐藏) -->
|
|
|
|
|
<view v-if="!pageType" class="tab-bar">
|
|
|
|
|
<view
|
|
|
|
|
class="tab-item"
|
|
|
|
|
:class="{ active: activeTab === 'up' }"
|
|
|
|
|
@click="switchTab('up')"
|
|
|
|
|
>
|
|
|
|
|
<text class="tab-text">{{ t('moldOperate.tabUp') }}</text>
|
|
|
|
|
<view v-if="activeTab === 'up'" class="tab-line"></view>
|
|
|
|
|
</view>
|
|
|
|
|
<view
|
|
|
|
|
class="tab-item"
|
|
|
|
|
:class="{ active: activeTab === 'down' }"
|
|
|
|
|
@click="switchTab('down')"
|
|
|
|
|
>
|
|
|
|
|
<text class="tab-text">{{ t('moldOperate.tabDown') }}</text>
|
|
|
|
|
<view v-if="activeTab === 'down'" class="tab-line"></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索区域 -->
|
|
|
|
|
<view class="search-bar">
|
|
|
|
|
<view class="search-input-wrap">
|
|
|
|
|
<input
|
|
|
|
|
v-model="searchKeyword"
|
|
|
|
|
class="search-input"
|
|
|
|
|
:placeholder="t('moldOperate.searchPlaceholder')"
|
|
|
|
|
placeholder-class="search-placeholder"
|
|
|
|
|
confirm-type="search"
|
|
|
|
|
@confirm="handleSearch"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="search-select" @click="toggleFilter">
|
|
|
|
|
<text class="select-text">{{ filterLabel }}</text>
|
|
|
|
|
<text class="select-arrow">▾</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="search-btn reset" @click="handleReset">
|
|
|
|
|
<text>{{ t('functionCommon.reset') }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="search-btn filter" @click="toggleFilter">
|
|
|
|
|
<text class="filter-icon">☰</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 筛选条件弹层 -->
|
|
|
|
|
<view v-if="showFilter" class="filter-dropdown">
|
|
|
|
|
<view
|
|
|
|
|
v-for="opt in filterOptions"
|
|
|
|
|
:key="opt.key"
|
|
|
|
|
class="filter-option"
|
|
|
|
|
:class="{ active: filterType === opt.key }"
|
|
|
|
|
@click="selectFilter(opt.key)"
|
|
|
|
|
>
|
|
|
|
|
<text>{{ opt.label }}</text>
|
|
|
|
|
<text v-if="filterType === opt.key" class="check-mark">✓</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 列表内容 -->
|
|
|
|
|
<scroll-view scroll-y class="list-scroll" :scroll-top="scrollTop" :style="{ height: pageType ? 'calc(100vh - 88rpx - 98rpx)' : 'calc(100vh - 88rpx - 80rpx - 98rpx)' }">
|
|
|
|
|
<template v-if="displayList.length > 0">
|
|
|
|
|
<view
|
|
|
|
|
v-for="(item, index) in displayList"
|
|
|
|
|
:key="item.id || index"
|
|
|
|
|
class="history-card"
|
|
|
|
|
>
|
|
|
|
|
<!-- 顶部:模具名称 + 标签 -->
|
|
|
|
|
<view class="card-top">
|
|
|
|
|
<text class="card-title">{{ item.moldName || '-' }}</text>
|
|
|
|
|
<view class="type-tag" :class="(item.operateType === '1' || item.operateType === 1) ? 'type-up' : 'type-down'">
|
|
|
|
|
<text>{{ (item.operateType === '1' || item.operateType === 1) ? t('moldOperate.tabUp') : t('moldOperate.tabDown') }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 信息行(与 Web 端表格列对齐) -->
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">{{ t('moldOperate.deviceName') }}</text>
|
|
|
|
|
<text class="info-value">{{ item.deviceName || getDeviceName(item.deviceId) || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">{{ t('moldOperate.productionLine') }}</text>
|
|
|
|
|
<text class="info-value">{{ item.lineName || getTopLineName(item.lineId) || getLineNameByDeviceId(item.deviceId) || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">{{ t('moldOperate.operator') }}</text>
|
|
|
|
|
<text class="info-value">{{ item.creatorName || item.operator || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">{{ t('moldOperate.remark') }}</text>
|
|
|
|
|
<text class="info-value">{{ item.remark || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">{{ t('moldOperate.operateTime') }}</text>
|
|
|
|
|
<text class="info-value">{{ formatDateTime(item.operateTime || item.createTime) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 右下角删除 -->
|
|
|
|
|
<view class="card-footer">
|
|
|
|
|
<view class="delete-wrap" @click="handleDelete(item)">
|
|
|
|
|
<text class="delete-icon">🗑</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
<view v-else class="empty-wrap">
|
|
|
|
|
<text class="empty-text">{{ loading ? t('functionCommon.loading') : t('moldOperate.historyEmpty') }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<view v-if="displayList.length > 0" class="pagination-bar">
|
|
|
|
|
<text class="pagination-total">{{ t('moldOperate.totalPrefix') }}{{ total }}{{ t('moldOperate.totalSuffix') }}</text>
|
|
|
|
|
<view class="pagination-ctrl">
|
|
|
|
|
<text
|
|
|
|
|
class="page-btn"
|
|
|
|
|
:class="{ disabled: pageNo <= 1 }"
|
|
|
|
|
@click="changePage(pageNo - 1)"
|
|
|
|
|
><</text>
|
|
|
|
|
<text class="page-current">{{ pageNo }}</text>
|
|
|
|
|
<text
|
|
|
|
|
class="page-btn"
|
|
|
|
|
:class="{ disabled: pageNo >= totalPages }"
|
|
|
|
|
@click="changePage(pageNo + 1)"
|
|
|
|
|
>></text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed, watch } from 'vue'
|
|
|
|
|
import { onShow, onLoad } from '@dcloudio/uni-app'
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
import { getMoldOperatePage, deleteMoldOperate, getDeviceLedgerList } from '@/api/mes/moldoperate'
|
|
|
|
|
import { getDeviceLineTree } from '@/api/mes/deviceLine'
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
const pageType = ref('')
|
|
|
|
|
const activeTab = ref('up')
|
|
|
|
|
const searchKeyword = ref('')
|
|
|
|
|
const filterType = ref('all')
|
|
|
|
|
const showFilter = ref(false)
|
|
|
|
|
|
|
|
|
|
const allList = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const pageNo = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const scrollTop = ref(0)
|
|
|
|
|
|
|
|
|
|
const lineInfoMap = ref(new Map())
|
|
|
|
|
const deviceMap = ref(new Map())
|
|
|
|
|
|
|
|
|
|
const total = computed(() => filteredList.value.length)
|
|
|
|
|
const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1)
|
|
|
|
|
|
|
|
|
|
const filterLabel = computed(() => {
|
|
|
|
|
if (filterType.value === 'all') return t('moldOperate.filterAll')
|
|
|
|
|
if (filterType.value === 'today') return t('moldOperate.filterToday')
|
|
|
|
|
if (filterType.value === 'week') return t('moldOperate.filterWeek')
|
|
|
|
|
return t('moldOperate.filterAll')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const filterOptions = [
|
|
|
|
|
{ key: 'all', label: t('moldOperate.filterAll') },
|
|
|
|
|
{ key: 'today', label: t('moldOperate.filterToday') },
|
|
|
|
|
{ key: 'week', label: t('moldOperate.filterWeek') }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const filteredList = computed(() => {
|
|
|
|
|
let list = allList.value
|
|
|
|
|
if (filterType.value === 'today') {
|
|
|
|
|
const today = new Date().toDateString()
|
|
|
|
|
list = list.filter(item => {
|
|
|
|
|
const d = item.operateTime || item.createTime
|
|
|
|
|
return d && new Date(d).toDateString() === today
|
|
|
|
|
})
|
|
|
|
|
} else if (filterType.value === 'week') {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const weekAgo = new Date(now.getTime() - 7 * 24 * 3600 * 1000)
|
|
|
|
|
list = list.filter(item => {
|
|
|
|
|
const d = item.operateTime || item.createTime
|
|
|
|
|
return d && new Date(d) >= weekAgo
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if (searchKeyword.value.trim()) {
|
|
|
|
|
const kw = searchKeyword.value.trim().toLowerCase()
|
|
|
|
|
list = list.filter(item => {
|
|
|
|
|
return (item.deviceName || '').toLowerCase().includes(kw) ||
|
|
|
|
|
(item.moldCode || '').toLowerCase().includes(kw) ||
|
|
|
|
|
(item.moldName || '').toLowerCase().includes(kw) ||
|
|
|
|
|
(item.operator || '').toLowerCase().includes(kw) ||
|
|
|
|
|
(item.creatorName || '').toLowerCase().includes(kw)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return list
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const displayList = computed(() => {
|
|
|
|
|
const start = (pageNo.value - 1) * pageSize.value
|
|
|
|
|
return filteredList.value.slice(start, start + pageSize.value)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function getDeviceName(deviceId) {
|
|
|
|
|
if (!deviceId) return '-'
|
|
|
|
|
const d = deviceMap.value.get(String(deviceId))
|
|
|
|
|
return d?.deviceName || '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getLineNameByDeviceId(deviceId) {
|
|
|
|
|
if (!deviceId) return null
|
|
|
|
|
const d = deviceMap.value.get(String(deviceId))
|
|
|
|
|
if (!d) return null
|
|
|
|
|
if (d.deviceLine != null) {
|
|
|
|
|
return getTopLineName(d.deviceLine)
|
|
|
|
|
}
|
|
|
|
|
return d.workshopName || null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flattenLineTree(nodes, parentId) {
|
|
|
|
|
if (!Array.isArray(nodes)) return
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (node.id != null && node.name != null) {
|
|
|
|
|
lineInfoMap.value.set(String(node.id), {
|
|
|
|
|
id: node.id,
|
|
|
|
|
name: node.name,
|
|
|
|
|
parentId: node.parentId != null ? node.parentId : (parentId || null),
|
|
|
|
|
parentChain: node.parentChain || ''
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(node.children)) {
|
|
|
|
|
flattenLineTree(node.children, node.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getTopLineName(lineId) {
|
|
|
|
|
if (lineId == null) return '-'
|
|
|
|
|
const node = lineInfoMap.value.get(String(lineId))
|
|
|
|
|
if (!node) return '-'
|
|
|
|
|
if (node.parentChain) {
|
|
|
|
|
const firstId = node.parentChain.split(',')[0]?.trim()
|
|
|
|
|
if (firstId) {
|
|
|
|
|
const topNode = lineInfoMap.value.get(firstId)
|
|
|
|
|
if (topNode) return topNode.name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let current = node
|
|
|
|
|
const visited = new Set()
|
|
|
|
|
while (current.parentId != null && current.parentId > 0 && !visited.has(current.id)) {
|
|
|
|
|
visited.add(current.id)
|
|
|
|
|
const parent = lineInfoMap.value.get(String(current.parentId))
|
|
|
|
|
if (!parent) break
|
|
|
|
|
current = parent
|
|
|
|
|
}
|
|
|
|
|
return current.name || String(lineId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadLineTree() {
|
|
|
|
|
if (lineInfoMap.value.size > 0) return
|
|
|
|
|
try {
|
|
|
|
|
const res = await getDeviceLineTree()
|
|
|
|
|
const tree = (res && res.data !== undefined) ? res.data : res
|
|
|
|
|
const nodes = Array.isArray(tree) ? tree : (tree?.list || tree?.children || [])
|
|
|
|
|
flattenLineTree(nodes)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('load line tree error', e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadDevices() {
|
|
|
|
|
if (deviceMap.value.size > 0) return
|
|
|
|
|
try {
|
|
|
|
|
const res = await getDeviceLedgerList({ pageNo: 1, pageSize: 99 })
|
|
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
|
|
const data = Array.isArray(root)
|
|
|
|
|
? root
|
|
|
|
|
: Array.isArray(root?.list) ? root.list
|
|
|
|
|
: Array.isArray(root?.records) ? root.records
|
|
|
|
|
: []
|
|
|
|
|
data.forEach(d => {
|
|
|
|
|
if (d.id != null) deviceMap.value.set(String(d.id), d)
|
|
|
|
|
})
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('load devices error', e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchAll() {
|
|
|
|
|
if (loading.value) return
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const operateType = activeTab.value === 'up' ? '1' : '2'
|
|
|
|
|
let page = 1
|
|
|
|
|
const batchSize = 99
|
|
|
|
|
const all = []
|
|
|
|
|
while (true) {
|
|
|
|
|
const res = await getMoldOperatePage({ pageNo: page, pageSize: batchSize, operateType })
|
|
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
|
|
const rows = Array.isArray(root)
|
|
|
|
|
? root
|
|
|
|
|
: Array.isArray(root?.list) ? root.list
|
|
|
|
|
: Array.isArray(root?.rows) ? root.rows
|
|
|
|
|
: Array.isArray(root?.records) ? root.records
|
|
|
|
|
: []
|
|
|
|
|
if (rows.length === 0) break
|
|
|
|
|
all.push(...rows)
|
|
|
|
|
if (rows.length < batchSize) break
|
|
|
|
|
page++
|
|
|
|
|
if (page > 10) break
|
|
|
|
|
}
|
|
|
|
|
allList.value = all
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('fetch history error', e)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function switchTab(tab) {
|
|
|
|
|
activeTab.value = tab
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
searchKeyword.value = ''
|
|
|
|
|
filterType.value = 'all'
|
|
|
|
|
fetchAll()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleFilter() {
|
|
|
|
|
showFilter.value = !showFilter.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectFilter(key) {
|
|
|
|
|
filterType.value = key
|
|
|
|
|
showFilter.value = false
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSearch() {
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleReset() {
|
|
|
|
|
searchKeyword.value = ''
|
|
|
|
|
filterType.value = 'all'
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function changePage(p) {
|
|
|
|
|
if (p < 1 || p > totalPages.value) return
|
|
|
|
|
pageNo.value = p
|
|
|
|
|
scrollTop.value = scrollTop.value === 0 ? 1 : 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDelete(item) {
|
|
|
|
|
if (!item.id) return
|
|
|
|
|
uni.showModal({
|
|
|
|
|
title: t('functionCommon.confirmTitle'),
|
|
|
|
|
content: t('moldOperate.confirmDeleteHistory'),
|
|
|
|
|
confirmColor: '#dc2626',
|
|
|
|
|
success: async (res) => {
|
|
|
|
|
if (res.confirm) {
|
|
|
|
|
try {
|
|
|
|
|
await deleteMoldOperate(item.id)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.deleteSuccess'), icon: 'success' })
|
|
|
|
|
allList.value = allList.value.filter(i => i.id !== item.id)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('delete history error', e)
|
|
|
|
|
uni.showToast({ title: t('functionCommon.deleteFailed'), icon: 'none' })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function goBack() {
|
|
|
|
|
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatDateTime(value) {
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
if (Array.isArray(value) && value.length >= 3) {
|
|
|
|
|
const [year, month, day, hour = 0, minute = 0, second = 0] = value
|
|
|
|
|
const pad = (num) => String(num).padStart(2, '0')
|
|
|
|
|
return `${year}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
|
|
|
|
|
}
|
|
|
|
|
const date = new Date(value)
|
|
|
|
|
if (Number.isNaN(date.getTime())) return String(value)
|
|
|
|
|
const pad = (num) => String(num).padStart(2, '0')
|
|
|
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch(searchKeyword, () => {
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onLoad((options) => {
|
|
|
|
|
if (options && options.type) {
|
|
|
|
|
pageType.value = options.type
|
|
|
|
|
activeTab.value = options.type
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onShow(async () => {
|
|
|
|
|
await Promise.allSettled([fetchAll(), loadDevices(), loadLineTree()])
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.page-container {
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
background: #f5f6f8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== 自定义导航栏 ====== */
|
|
|
|
|
.custom-nav {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
height: 88rpx;
|
|
|
|
|
padding: 0 24rpx;
|
|
|
|
|
background: #1a365d;
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
|
|
.nav-back {
|
|
|
|
|
width: 60rpx;
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-icon {
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-title {
|
|
|
|
|
font-size: 34rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
max-width: 60%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.nav-placeholder {
|
|
|
|
|
width: 60rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== Tab 切换 ====== */
|
|
|
|
|
.tab-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-bottom: 1rpx solid #eee;
|
|
|
|
|
|
|
|
|
|
.tab-item {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
.tab-text {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.active .tab-text {
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tab-line {
|
|
|
|
|
position: absolute;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
width: 48rpx;
|
|
|
|
|
height: 4rpx;
|
|
|
|
|
background: #2563eb;
|
|
|
|
|
border-radius: 2rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== 搜索区域 ====== */
|
|
|
|
|
.search-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 12rpx 16rpx;
|
|
|
|
|
background: #fff;
|
|
|
|
|
gap: 8rpx;
|
|
|
|
|
|
|
|
|
|
.search-input-wrap {
|
|
|
|
|
flex: 1;
|
|
|
|
|
height: 64rpx;
|
|
|
|
|
background: #f5f6f8;
|
|
|
|
|
border-radius: 32rpx;
|
|
|
|
|
padding: 0 24rpx;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
|
flex: 1;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-placeholder {
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-select {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 0 16rpx;
|
|
|
|
|
height: 64rpx;
|
|
|
|
|
border-radius: 32rpx;
|
|
|
|
|
background: #f5f6f8;
|
|
|
|
|
gap: 4rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select-text {
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select-arrow {
|
|
|
|
|
font-size: 18rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-btn {
|
|
|
|
|
height: 64rpx;
|
|
|
|
|
line-height: 64rpx;
|
|
|
|
|
padding: 0 20rpx;
|
|
|
|
|
border-radius: 32rpx;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
|
|
|
|
&.reset {
|
|
|
|
|
background: #fff;
|
|
|
|
|
color: #666;
|
|
|
|
|
border: 1rpx solid #ddd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.filter {
|
|
|
|
|
background: #2563eb;
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
|
|
.filter-icon {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== 筛选下拉 ====== */
|
|
|
|
|
.filter-dropdown {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 176rpx;
|
|
|
|
|
right: 16rpx;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.12);
|
|
|
|
|
z-index: 99;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
.filter-option {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 20rpx 32rpx;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
min-width: 160rpx;
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
background: #f0f5ff;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.check-mark {
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== 列表 ====== */
|
|
|
|
|
.list-scroll {
|
|
|
|
|
padding: 16rpx 16rpx 32rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.history-card {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
padding: 16rpx 24rpx;
|
|
|
|
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
|
|
|
|
|
|
|
|
.card-top {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
|
|
|
|
.card-title {
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #1a365d;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.type-tag {
|
|
|
|
|
padding: 2rpx 10rpx;
|
|
|
|
|
border-radius: 6rpx;
|
|
|
|
|
font-size: 20rpx;
|
|
|
|
|
color: #fff;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
|
|
|
|
&.type-up {
|
|
|
|
|
background: #f97316;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
&.type-down {
|
|
|
|
|
background: #f97316;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 4rpx 0;
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-value {
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
text-align: right;
|
|
|
|
|
flex: 1;
|
|
|
|
|
margin-left: 16rpx;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
margin-top: 6rpx;
|
|
|
|
|
|
|
|
|
|
.delete-wrap {
|
|
|
|
|
padding: 6rpx;
|
|
|
|
|
|
|
|
|
|
.delete-icon {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-wrap {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 120rpx 0;
|
|
|
|
|
|
|
|
|
|
.empty-text {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ====== 分页 ====== */
|
|
|
|
|
.pagination-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 20rpx 0;
|
|
|
|
|
|
|
|
|
|
.pagination-total {
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-ctrl {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12rpx;
|
|
|
|
|
|
|
|
|
|
.page-btn {
|
|
|
|
|
width: 56rpx;
|
|
|
|
|
height: 56rpx;
|
|
|
|
|
line-height: 56rpx;
|
|
|
|
|
text-align: center;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
border: 1rpx solid #ddd;
|
|
|
|
|
|
|
|
|
|
&.disabled {
|
|
|
|
|
color: #ccc;
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-current {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #333;
|
|
|
|
|
min-width: 40rpx;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|