832 lines
20 KiB
Vue
832 lines
20 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="page-head" :style="{ paddingTop: statusBarPx + 'px' }">
|
||
<view class="nav__inner">
|
||
<view class="nav__back" @click="goBack">
|
||
<view class="nav__back-ico" aria-hidden="true" />
|
||
</view>
|
||
<text class="nav__title">草稿箱</text>
|
||
</view>
|
||
<view class="toolbar">
|
||
<view class="toolbar-row">
|
||
<view class="search-box" :class="{ 'is-focus': searchFocused }">
|
||
<view class="search-ico" aria-hidden="true" />
|
||
<input
|
||
class="search-input"
|
||
:value="keyword"
|
||
confirm-type="search"
|
||
placeholder="搜索流程名称"
|
||
placeholder-class="search-ph"
|
||
@input="onSearchInput"
|
||
@confirm="onSearchConfirm"
|
||
@focus="searchFocused = true"
|
||
@blur="searchFocused = false"
|
||
/>
|
||
<view v-if="keyword" class="search-clear" @click="clearKeyword">
|
||
<text class="search-clear__x">×</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="manage-ico-btn"
|
||
:class="{ 'is-done': manageMode }"
|
||
@click="toggleManage"
|
||
>
|
||
<view
|
||
v-if="!manageMode"
|
||
class="manage-ico manage-ico--more"
|
||
aria-hidden="true"
|
||
>
|
||
<view class="manage-ico__dot" />
|
||
<view class="manage-ico__dot" />
|
||
<view class="manage-ico__dot" />
|
||
</view>
|
||
<view
|
||
v-else
|
||
class="manage-ico manage-ico--check"
|
||
aria-hidden="true"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="manageMode && selectedIds.length" class="batch-bar">
|
||
<text class="batch-bar__hint">已选 {{ selectedIds.length }} 项</text>
|
||
<view
|
||
class="batch-bar__btn oa-btn-loading"
|
||
:class="{ 'is-loading': batchDeleting }"
|
||
@click="batchDelete"
|
||
>
|
||
<text class="oa-btn-loading__label">批量删除</text>
|
||
<view
|
||
v-if="batchDeleting"
|
||
class="oa-btn-loading__spin"
|
||
aria-hidden="true"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading && !list.length" class="state state-loading">
|
||
<view class="pulse-dots" aria-hidden="true">
|
||
<view class="pulse-dot" />
|
||
<view class="pulse-dot" />
|
||
<view class="pulse-dot" />
|
||
</view>
|
||
<text class="state-loading__text">加载草稿…</text>
|
||
</view>
|
||
|
||
<view v-else-if="!list.length" class="oa-empty-wrap">
|
||
<VanEmpty
|
||
description="暂无草稿"
|
||
tip="保存过的审批草稿会出现在这里"
|
||
/>
|
||
</view>
|
||
|
||
<view v-else class="draft-list">
|
||
<view
|
||
v-for="(row, idx) in list"
|
||
:key="row.id"
|
||
class="draft-row"
|
||
:class="{ 'is-manage': manageMode, 'is-selected': isSelected(row.id) }"
|
||
@click="onRowClick(row)"
|
||
>
|
||
<view v-if="manageMode" class="draft-check" @click.stop="toggleSelect(row.id)">
|
||
<view class="draft-check__box" :class="{ on: isSelected(row.id) }">
|
||
<text v-if="isSelected(row.id)" class="draft-check__mark">✓</text>
|
||
</view>
|
||
</view>
|
||
<view class="dd-card">
|
||
<view class="dd-card__corner" aria-hidden="true">
|
||
<text class="dd-card__corner-num">{{ cornerIndex(idx) }}</text>
|
||
</view>
|
||
<view class="dd-card__head">
|
||
<text class="dd-card__title">{{ cardTitle(row) }}</text>
|
||
<text class="dd-card__time">{{ formatRelativeTime(row.updateTime) }}</text>
|
||
</view>
|
||
<view v-if="metaLines(row).length" class="dd-card__meta">
|
||
<text
|
||
v-for="(line, idx) in metaLines(row)"
|
||
:key="idx"
|
||
class="dd-card__meta-line"
|
||
>
|
||
{{ line }}
|
||
</text>
|
||
</view>
|
||
<view class="dd-card__foot">
|
||
<view class="dd-card__user">
|
||
<text class="dd-card__user-text">待提交草稿</text>
|
||
</view>
|
||
<text class="dd-card__status">草稿</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="loadingMore" class="oa-task-list__end">加载更多…</view>
|
||
<view v-else-if="!hasMore" class="oa-task-list__end">已经到底了</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
fetchBatchDeleteOaInstanceDrafts,
|
||
fetchOaInstanceDraftCount,
|
||
fetchOaInstanceDraftList
|
||
} from '../../../utils/oa-api.js'
|
||
import { forceRelogin, getToken } from '../../../utils/auth.js'
|
||
import { stashOaStartTitle } from '../../../utils/oa-h5.js'
|
||
import { formatCornerIndex } from '../../../utils/oa-corner-index.js'
|
||
import VanEmpty from '../../../components/van-empty/van-empty.vue'
|
||
|
||
const PAGE_SIZE = 10
|
||
|
||
function pad2(n) {
|
||
return n < 10 ? '0' + n : String(n)
|
||
}
|
||
|
||
function parseDateTime(str) {
|
||
if (!str) return null
|
||
const s = String(str).trim().replace(/-/g, '/')
|
||
const d = new Date(s)
|
||
return Number.isNaN(d.getTime()) ? null : d
|
||
}
|
||
|
||
export default {
|
||
components: { VanEmpty },
|
||
data() {
|
||
return {
|
||
statusBarPx: 0,
|
||
keyword: '',
|
||
searchFocused: false,
|
||
list: [],
|
||
page: 1,
|
||
total: 0,
|
||
loading: false,
|
||
loadingMore: false,
|
||
hasMore: true,
|
||
manageMode: false,
|
||
selectedIds: [],
|
||
batchDeleting: false,
|
||
_searchTimer: null
|
||
}
|
||
},
|
||
onLoad() {
|
||
try {
|
||
const sys = uni.getSystemInfoSync() || {}
|
||
this.statusBarPx = Number(sys.statusBarHeight) || 0
|
||
} catch (_) {
|
||
this.statusBarPx = 0
|
||
}
|
||
},
|
||
onShow() {
|
||
if (!getToken()) {
|
||
forceRelogin('请先登录')
|
||
return
|
||
}
|
||
this.loadList(true)
|
||
this.refreshDraftCount()
|
||
},
|
||
onPullDownRefresh() {
|
||
this.loadList(true)
|
||
.catch(() => {})
|
||
.finally(() => {
|
||
uni.stopPullDownRefresh()
|
||
})
|
||
},
|
||
onReachBottom() {
|
||
if (!this.hasMore || this.loading || this.loadingMore) return
|
||
this.loadList(false)
|
||
},
|
||
onUnload() {
|
||
if (this._searchTimer) {
|
||
clearTimeout(this._searchTimer)
|
||
this._searchTimer = null
|
||
}
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
uni.navigateBack({
|
||
fail: () => {
|
||
uni.reLaunch({ url: '/pages/index/index' })
|
||
}
|
||
})
|
||
},
|
||
cornerIndex(idx) {
|
||
return formatCornerIndex(Number(idx) + 1)
|
||
},
|
||
cardTitle(row) {
|
||
return (row && (row.appName || row.title)) || '-'
|
||
},
|
||
metaLines(row) {
|
||
const lines = []
|
||
const fields = (row && row.previewFields) || []
|
||
for (let i = 0; i < fields.length && i < 3; i++) {
|
||
const f = fields[i] || {}
|
||
const label = String(f.label || '').trim() || '字段'
|
||
const value =
|
||
f.value != null && String(f.value).trim() !== ''
|
||
? String(f.value)
|
||
: '-'
|
||
lines.push(label + ':' + value)
|
||
}
|
||
if (row && row.updateTime) {
|
||
lines.push('最近保存:' + row.updateTime)
|
||
}
|
||
return lines
|
||
},
|
||
formatRelativeTime(str) {
|
||
const d = parseDateTime(str)
|
||
if (!d) return str || '-'
|
||
const now = new Date()
|
||
const startToday = new Date(
|
||
now.getFullYear(),
|
||
now.getMonth(),
|
||
now.getDate()
|
||
)
|
||
const startThat = new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
||
const dayDiff = Math.round(
|
||
(startToday.getTime() - startThat.getTime()) / 86400000
|
||
)
|
||
if (dayDiff === 0) {
|
||
return pad2(d.getHours()) + ':' + pad2(d.getMinutes())
|
||
}
|
||
if (dayDiff === 1) return '昨天'
|
||
if (dayDiff === 2) return '前天'
|
||
if (dayDiff > 2 && dayDiff < 7) return dayDiff + '天前'
|
||
return (
|
||
d.getFullYear() +
|
||
'.' +
|
||
pad2(d.getMonth() + 1) +
|
||
'.' +
|
||
pad2(d.getDate())
|
||
)
|
||
},
|
||
isSelected(id) {
|
||
return this.selectedIds.indexOf(id) >= 0
|
||
},
|
||
onSearchInput(e) {
|
||
const v =
|
||
e && e.detail && e.detail.value != null
|
||
? String(e.detail.value)
|
||
: e && e.target
|
||
? String(e.target.value || '')
|
||
: ''
|
||
this.keyword = v
|
||
if (this._searchTimer) clearTimeout(this._searchTimer)
|
||
this._searchTimer = setTimeout(() => {
|
||
this.loadList(true)
|
||
}, 400)
|
||
},
|
||
onSearchConfirm() {
|
||
if (this._searchTimer) {
|
||
clearTimeout(this._searchTimer)
|
||
this._searchTimer = null
|
||
}
|
||
this.loadList(true)
|
||
},
|
||
clearKeyword() {
|
||
this.keyword = ''
|
||
if (this._searchTimer) {
|
||
clearTimeout(this._searchTimer)
|
||
this._searchTimer = null
|
||
}
|
||
this.loadList(true)
|
||
},
|
||
toggleManage() {
|
||
this.manageMode = !this.manageMode
|
||
if (!this.manageMode) this.selectedIds = []
|
||
},
|
||
toggleSelect(id) {
|
||
if (!id) return
|
||
const i = this.selectedIds.indexOf(id)
|
||
if (i >= 0) {
|
||
this.selectedIds = this.selectedIds.filter((x) => x !== id)
|
||
} else {
|
||
this.selectedIds = this.selectedIds.concat([id])
|
||
}
|
||
},
|
||
onRowClick(row) {
|
||
if (this.manageMode) {
|
||
this.toggleSelect(row && row.id)
|
||
return
|
||
}
|
||
this.openEdit(row)
|
||
},
|
||
openEdit(row) {
|
||
if (!row || !row.id) return
|
||
stashOaStartTitle(row.appName || row.title)
|
||
uni.navigateTo({
|
||
url:
|
||
'/pages/oa/webview/index?draftId=' +
|
||
encodeURIComponent(row.id) +
|
||
'&appId=' +
|
||
encodeURIComponent(row.appId || '')
|
||
})
|
||
},
|
||
confirm(title, content, confirmText) {
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: title || '提示',
|
||
content: content || '',
|
||
confirmText: confirmText || '确定',
|
||
cancelText: '取消',
|
||
success: (res) => resolve(!!(res && res.confirm)),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
},
|
||
async loadList(reset) {
|
||
if (!getToken()) {
|
||
forceRelogin('请先登录')
|
||
return
|
||
}
|
||
const isReset = !!reset
|
||
if (isReset) {
|
||
this.page = 1
|
||
this.hasMore = true
|
||
this.loading = true
|
||
this.selectedIds = []
|
||
} else {
|
||
this.loadingMore = true
|
||
}
|
||
try {
|
||
const res = await fetchOaInstanceDraftList({
|
||
keyword: (this.keyword || '').trim() || undefined,
|
||
page: this.page,
|
||
pageSize: PAGE_SIZE
|
||
})
|
||
const rows = (res && res.list) || []
|
||
const total = (res && res.total) != null ? Number(res.total) : 0
|
||
this.total = total
|
||
if (isReset) this.list = rows
|
||
else this.list = this.list.concat(rows)
|
||
const loaded = this.list.length
|
||
this.hasMore = loaded < total && rows.length > 0
|
||
if (rows.length > 0) this.page += 1
|
||
} catch (e) {
|
||
if (isReset) this.list = []
|
||
uni.showToast({
|
||
title: (e && e.message) || '加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
this.loadingMore = false
|
||
}
|
||
},
|
||
async refreshDraftCount() {
|
||
try {
|
||
await fetchOaInstanceDraftCount()
|
||
} catch (_) {
|
||
/* 角标预热 */
|
||
}
|
||
},
|
||
async batchDelete() {
|
||
const ids = (this.selectedIds || []).filter(Boolean)
|
||
if (!ids.length || this.batchDeleting) return
|
||
const ok = await this.confirm(
|
||
'批量删除',
|
||
'确认删除选中的 ' + ids.length + ' 条草稿吗?',
|
||
'删除'
|
||
)
|
||
if (!ok) return
|
||
this.batchDeleting = true
|
||
try {
|
||
await fetchBatchDeleteOaInstanceDrafts(ids)
|
||
uni.showToast({ title: '已删除', icon: 'success' })
|
||
this.selectedIds = []
|
||
this.manageMode = false
|
||
await this.loadList(true)
|
||
this.refreshDraftCount()
|
||
} catch (e) {
|
||
uni.showToast({
|
||
title: (e && e.message) || '批量删除失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.batchDeleting = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page {
|
||
min-height: 100vh;
|
||
padding-bottom: 48rpx;
|
||
background: #f5f6f8;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.page-head {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 20;
|
||
background: #ecfdf5;
|
||
border-bottom: 1rpx solid #e8eeeb;
|
||
}
|
||
|
||
.nav__inner {
|
||
height: 88rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 16rpx 0 8rpx;
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
background: #ecfdf5;
|
||
}
|
||
|
||
.nav__back {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.nav__back-ico {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
border-left: 4rpx solid #0f291e;
|
||
border-bottom: 4rpx solid #0f291e;
|
||
transform: rotate(45deg);
|
||
margin-left: 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.nav__title {
|
||
position: absolute;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
font-size: 32rpx;
|
||
font-weight: 650;
|
||
color: #0f291e;
|
||
}
|
||
|
||
.toolbar {
|
||
background: #fff;
|
||
padding: 16rpx 24rpx;
|
||
}
|
||
|
||
.toolbar-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.search-box {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
height: 72rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid #e8eaed;
|
||
background: #fafafa;
|
||
box-sizing: border-box;
|
||
transition:
|
||
border-color 0.15s,
|
||
background 0.15s,
|
||
box-shadow 0.15s;
|
||
}
|
||
|
||
.manage-ico-btn {
|
||
flex-shrink: 0;
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid #c6f0dc;
|
||
background: #ecfdf5;
|
||
color: #166534;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.manage-ico-btn.is-done {
|
||
border-color: #e2e8f0;
|
||
background: #f8fafc;
|
||
color: #64748b;
|
||
}
|
||
|
||
.manage-ico-btn:active {
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.manage-ico--more {
|
||
width: 34rpx;
|
||
height: 10rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.manage-ico__dot {
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
border-radius: 50%;
|
||
background: currentColor;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.manage-ico--check {
|
||
width: 20rpx;
|
||
height: 12rpx;
|
||
border-left: 4rpx solid currentColor;
|
||
border-bottom: 4rpx solid currentColor;
|
||
transform: rotate(-45deg);
|
||
margin-top: -4rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.search-box.is-focus {
|
||
border-color: #cbd5e1;
|
||
background: #fff;
|
||
box-shadow: 0 0 0 6rpx rgba(148, 163, 184, 0.18);
|
||
}
|
||
|
||
.search-ico {
|
||
position: relative;
|
||
width: 26rpx;
|
||
height: 26rpx;
|
||
border: 3rpx solid #94a3b8;
|
||
border-radius: 50%;
|
||
box-sizing: border-box;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-ico::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 12rpx;
|
||
height: 3rpx;
|
||
right: -8rpx;
|
||
bottom: -5rpx;
|
||
background: #94a3b8;
|
||
border-radius: 3rpx;
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
font-size: 28rpx;
|
||
color: #1f2937;
|
||
min-width: 0;
|
||
}
|
||
|
||
.search-ph {
|
||
color: #b0b7c3;
|
||
}
|
||
|
||
.search-clear {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
background: #e5e7eb;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-clear__x {
|
||
font-size: 28rpx;
|
||
line-height: 1;
|
||
color: #6b7280;
|
||
margin-top: -2rpx;
|
||
}
|
||
|
||
.batch-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
margin: 16rpx 24rpx 0;
|
||
padding: 20rpx 24rpx;
|
||
border-radius: 16rpx;
|
||
background: #fff;
|
||
border: 1rpx solid #d8f3e5;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.batch-bar__hint {
|
||
font-size: 26rpx;
|
||
color: #5f7a6d;
|
||
}
|
||
|
||
.batch-bar__btn {
|
||
min-height: 64rpx;
|
||
padding: 0 28rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 14rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #ef4444;
|
||
background: rgba(239, 68, 68, 0.06);
|
||
border: 1rpx solid rgba(239, 68, 68, 0.35);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.batch-bar__btn:active {
|
||
opacity: 0.78;
|
||
}
|
||
|
||
.draft-list {
|
||
padding: 20rpx 24rpx 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.draft-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.draft-check {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.draft-check__box {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
border: 2rpx solid #cbd5e1;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.draft-check__box.on {
|
||
border-color: #22c55e;
|
||
background: #22c55e;
|
||
}
|
||
|
||
.draft-check__mark {
|
||
font-size: 22rpx;
|
||
line-height: 1;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.dd-card {
|
||
flex: 1;
|
||
min-width: 0;
|
||
width: 100%;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 28rpx 28rpx 24rpx 84rpx;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 4rpx 20rpx rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.draft-row.is-selected .dd-card {
|
||
box-shadow: 0 8rpx 28rpx rgba(22, 163, 74, 0.12);
|
||
outline: 2rpx solid rgba(34, 197, 94, 0.4);
|
||
}
|
||
|
||
.dd-card__head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.dd-card__title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
line-height: 1.4;
|
||
color: #1f2329;
|
||
}
|
||
|
||
.dd-card__time {
|
||
flex-shrink: 0;
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
color: #8f959e;
|
||
padding-top: 4rpx;
|
||
}
|
||
|
||
.dd-card__meta {
|
||
margin-top: 16rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.dd-card__meta-line {
|
||
font-size: 26rpx;
|
||
line-height: 1.45;
|
||
color: #8f959e;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.dd-card__foot {
|
||
margin-top: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.dd-card__user {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.dd-card__user-text {
|
||
font-size: 24rpx;
|
||
color: #646a73;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.dd-card__status {
|
||
flex-shrink: 0;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #d97706;
|
||
}
|
||
|
||
.state {
|
||
padding: 80rpx 24rpx;
|
||
text-align: center;
|
||
color: #999;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.state-loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 28rpx;
|
||
min-height: 50vh;
|
||
padding: 96rpx 32rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.pulse-dots {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.pulse-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 50%;
|
||
background: var(--brand-green, #22c55e);
|
||
animation: oa-pulse-bounce 1.2s ease-in-out infinite;
|
||
}
|
||
|
||
.pulse-dot:nth-child(2) {
|
||
animation-delay: 0.15s;
|
||
opacity: 0.75;
|
||
}
|
||
|
||
.pulse-dot:nth-child(3) {
|
||
animation-delay: 0.3s;
|
||
opacity: 0.55;
|
||
}
|
||
|
||
.state-loading__text {
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
color: var(--text-muted, #5f7a6d);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
@keyframes oa-pulse-bounce {
|
||
0%,
|
||
80%,
|
||
100% {
|
||
transform: translateY(0);
|
||
}
|
||
40% {
|
||
transform: translateY(-12rpx);
|
||
}
|
||
}
|
||
</style>
|