index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. <template>
  2. <view class="points-package-record">
  3. <!-- 顶部筛选栏 -->
  4. <view class="record-header">
  5. <view class="record-tabs">
  6. <view
  7. v-for="tab in tabs"
  8. :key="tab.value"
  9. class="record-tabs__item"
  10. :class="{ 'record-tabs__item--active': params.activeTab === tab.value }"
  11. @click="handleTabChange(tab.value)"
  12. >
  13. {{ tab.label }}
  14. </view>
  15. </view>
  16. <!-- 月份筛选 -->
  17. <view class="record-date">
  18. <view class="record-date__divider" />
  19. <view v-if="!selectedDate" class="record-date__inner" @click="openDatePicker">
  20. <text class="record-date__text">筛选</text>
  21. <wd-icon name="caret-down" size="28rpx" />
  22. </view>
  23. <view v-else class="record-date__inner" @click="clearDatePicker">
  24. <text class="record-date__text">{{ selectedDateText }}</text>
  25. <wd-icon name="close-circle" size="28rpx" />
  26. </view>
  27. </view>
  28. </view>
  29. <view class="record-content">
  30. <!-- 顶部提示 -->
  31. <view class="record-notice">
  32. <wd-notice-bar text="任务包即将完成时,请不要做积分较高的任务,避免浪费积分哦~" />
  33. </view>
  34. <!-- 空状态 -->
  35. <view v-if="showEmpty" class="record-empty">
  36. <wd-empty icon="no-content" tip="暂时还没有任务记录哦~" />
  37. </view>
  38. <!-- 列表 -->
  39. <view v-else class="record-list">
  40. <view
  41. v-for="item in recordList"
  42. :key="item.statusId || item.id"
  43. class="record-card"
  44. :class="{ 'record-card--disabled': isDisabledPackage(item) }"
  45. @click="handleOpenDetail(item)"
  46. >
  47. <!-- 卡片头部 -->
  48. <view class="record-card__header">
  49. <view class="record-card__title-wrap">
  50. <view class="record-card__title">
  51. {{ item.scorePackageName || '-' }}
  52. <wd-tag
  53. v-if="getPackageTypeTag(item.packageType1, isDisabledPackage(item))"
  54. custom-class="record-card__tag"
  55. plain
  56. :color="getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.color"
  57. :bg-color="getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.bgColor"
  58. >
  59. {{ getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.label }}
  60. </wd-tag>
  61. </view>
  62. <view v-if="item.packageSn" class="record-card__sn">
  63. 编号:{{ item.packageSn }}
  64. </view>
  65. </view>
  66. <view class="record-card__status">
  67. <view class="record-card__dot" />
  68. <text>{{ getPackageStatusText(item.packageStatus) }}</text>
  69. </view>
  70. </view>
  71. <!-- 卡片主体 -->
  72. <view class="record-card__body">
  73. <view class="record-card__info">
  74. <text>发包方:{{ item.packageEntName || '-' }}</text>
  75. </view>
  76. <view class="record-card__meta">
  77. <text>发包时间:{{ formatDate(item.createTime) }}</text>
  78. <text>接包时间:{{ formatDate(item.receiveTime) }}</text>
  79. </view>
  80. <!-- 进度信息 -->
  81. <view class="record-card__progress">
  82. <view class="record-card__progress-main">
  83. <view class="record-card__progress-top">
  84. <text class="record-card__progress-label">完成进度</text>
  85. <text
  86. class="record-card__progress-value"
  87. :class="{ 'record-card__progress-value--disabled': isDisabledPackage(item) }"
  88. >
  89. {{ getProgressText(item) }}
  90. </text>
  91. </view>
  92. <wd-progress
  93. :percentage="getProgressPercentage(item)"
  94. :stroke-width="8"
  95. :color="isDisabledPackage(item) ? '#d5d5d5' : '#3fa3f1'"
  96. hide-text
  97. />
  98. </view>
  99. </view>
  100. <!-- 结算信息 -->
  101. <view v-if="isSettledPackage(item)" class="record-card__settle">
  102. <view class="record-card__settle-item">结算状态:已结算</view>
  103. <view class="record-card__settle-item"
  104. >税后收入:{{ formatAmount(item.userRealAmount) }}</view
  105. >
  106. <view class="record-card__settle-item"
  107. >已交税金:{{ formatAmount(item.actualTax) }}</view
  108. >
  109. </view>
  110. <!-- 待审批状态下支持撤回 -->
  111. <view v-if="showRevokeButton" class="record-card__actions" @click.stop>
  112. <view class="record-card__action-tip">
  113. <wd-icon name="warning" size="24rpx" />
  114. <text>当前申请待审批,可撤回后重新申领</text>
  115. </view>
  116. <wd-button
  117. size="small"
  118. round
  119. custom-class="record-card__revoke"
  120. @click.stop="handleRevoke(item)"
  121. >
  122. 撤回申领
  123. </wd-button>
  124. </view>
  125. </view>
  126. <!-- 过期标识 -->
  127. <view v-if="String(item.packageStatus) === '2'" class="record-card__expired">
  128. <image src="/static/images/common/expired.png" mode="heightFix" />
  129. </view>
  130. </view>
  131. <!-- 底部加载状态 -->
  132. <view class="record-load-status">
  133. <text v-if="loading">加载中...</text>
  134. <text v-else-if="finished">没有更多了</text>
  135. </view>
  136. </view>
  137. </view>
  138. <!-- 月份选择器 -->
  139. <wd-datetime-picker
  140. v-model:visible="datetimePickerShow"
  141. v-model="datetimePickerValue"
  142. type="year-month"
  143. @confirm="handleDateConfirm"
  144. />
  145. <wd-toast />
  146. </view>
  147. </template>
  148. <script setup lang="ts">
  149. import { computed, reactive, ref } from 'vue'
  150. import { onLoad, onReachBottom } from '@dcloudio/uni-app'
  151. import { useToast } from '@wot-ui/ui'
  152. import dayjs from 'dayjs'
  153. import { getDictTypeApi } from '@/services/modules/common'
  154. import {
  155. getUserScorePackageListApi,
  156. pkgWithdrawApi,
  157. } from '@/services/modules/pointsPackageRecord/index'
  158. import type { ScorePackageRecordItem } from '@/services/modules/pointsPackageRecord/type'
  159. const toast = useToast()
  160. /**
  161. * 页面入口类型
  162. * on-the-way:从工作台「进行中」进入
  163. * wait-approve:从工作台「待审批」进入
  164. */
  165. type PageEntryType = 'on-the-way' | 'wait-approve' | ''
  166. /**
  167. * 积分包记录 Tab
  168. * 这里的值需要和后端 activeId 入参保持一致
  169. */
  170. enum RecordTab {
  171. All = '1',
  172. Processing = '2',
  173. Approving = '3',
  174. Finished = '4',
  175. }
  176. type Nullable<T> = T | null | undefined
  177. interface DictItem {
  178. label: string
  179. value: string | number
  180. }
  181. /**
  182. * 列表分页响应结构
  183. */
  184. interface ListResponse {
  185. records?: ScorePackageRecordItem[]
  186. total?: number
  187. }
  188. /**
  189. * 页面 Tab 配置
  190. */
  191. const tabs = [
  192. { label: '全部', value: RecordTab.All },
  193. { label: '进行中', value: RecordTab.Processing },
  194. { label: '待审批', value: RecordTab.Approving },
  195. { label: '已完成', value: RecordTab.Finished },
  196. ] as const
  197. /**
  198. * 工作台入口 type 与页面 Tab 的映射
  199. */
  200. const entryTypeTabMap: Record<Exclude<PageEntryType, ''>, RecordTab> = {
  201. 'on-the-way': RecordTab.Processing,
  202. 'wait-approve': RecordTab.Approving,
  203. }
  204. /**
  205. * 查询参数
  206. */
  207. const params = reactive({
  208. activeTab: RecordTab.All,
  209. current: 1,
  210. size: 10,
  211. selectedYear: '',
  212. selectedMonth: '',
  213. })
  214. /**
  215. * 页面状态
  216. */
  217. const openType = ref<PageEntryType>('')
  218. const loading = ref(false)
  219. const finished = ref(false)
  220. const recordList = ref<ScorePackageRecordItem[]>([])
  221. const packageStatusMap = ref<DictItem[]>([])
  222. /**
  223. * 请求标识,用于规避快速切换筛选时旧请求覆盖新数据
  224. */
  225. let requestId = 0
  226. /**
  227. * 当前选中的月份,格式:YYYY-MM
  228. */
  229. const selectedDate = computed(() => {
  230. const { selectedYear, selectedMonth } = params
  231. return selectedYear && selectedMonth ? `${selectedYear}-${selectedMonth}` : ''
  232. })
  233. /**
  234. * 月份筛选展示文案
  235. */
  236. const selectedDateText = computed(() => {
  237. const { selectedYear, selectedMonth } = params
  238. return selectedYear && selectedMonth ? `${selectedYear}年${selectedMonth}月` : '筛选'
  239. })
  240. /**
  241. * 是否展示空状态
  242. */
  243. const showEmpty = computed(() => !loading.value && recordList.value.length === 0)
  244. /**
  245. * 只有待审批 Tab 展示撤回按钮
  246. */
  247. const showRevokeButton = computed(() => params.activeTab === RecordTab.Approving)
  248. /**
  249. * 日期选择器状态
  250. */
  251. const datetimePickerShow = ref(false)
  252. const datetimePickerValue = ref(dayjs().startOf('month').valueOf())
  253. /**
  254. * 重置列表分页
  255. */
  256. const resetList = () => {
  257. params.current = 1
  258. finished.value = false
  259. recordList.value = []
  260. }
  261. /**
  262. * 刷新列表
  263. */
  264. const refreshRecordList = () => {
  265. resetList()
  266. fetchRecordList()
  267. }
  268. /**
  269. * 获取积分包状态字典
  270. */
  271. const fetchPackageStatus = async () => {
  272. try {
  273. const res = await getDictTypeApi('package_new_status')
  274. packageStatusMap.value = res?.data ?? []
  275. } catch (error) {
  276. console.error('获取积分包状态字典失败:', error)
  277. }
  278. }
  279. /**
  280. * 获取积分包记录列表
  281. */
  282. const fetchRecordList = async () => {
  283. if (loading.value || finished.value) return
  284. loading.value = true
  285. const currentRequestId = ++requestId
  286. try {
  287. const res = await getUserScorePackageListApi({
  288. current: params.current,
  289. size: params.size,
  290. selType: 2,
  291. selDate: selectedDate.value,
  292. packageName: '',
  293. activeId: params.activeTab,
  294. })
  295. if (currentRequestId !== requestId) return
  296. const data = (res?.data ?? {}) as ListResponse
  297. const records = data.records ?? []
  298. const total = data.total ?? 0
  299. recordList.value = params.current === 1 ? records : recordList.value.concat(records)
  300. finished.value = recordList.value.length >= total || records.length < params.size
  301. } catch (error) {
  302. console.error('获取积分包记录失败:', error)
  303. toast.error('获取列表失败')
  304. } finally {
  305. if (currentRequestId === requestId) {
  306. loading.value = false
  307. }
  308. }
  309. }
  310. /**
  311. * 加载下一页
  312. */
  313. const loadMoreRecordList = () => {
  314. if (loading.value || finished.value) return
  315. params.current += 1
  316. fetchRecordList()
  317. }
  318. /**
  319. * 切换 Tab
  320. */
  321. const handleTabChange = (value: RecordTab) => {
  322. if (params.activeTab === value) return
  323. params.activeTab = value
  324. refreshRecordList()
  325. }
  326. /**
  327. * 打开月份筛选
  328. */
  329. const openDatePicker = () => {
  330. datetimePickerShow.value = true
  331. }
  332. /**
  333. * 确认月份筛选
  334. */
  335. const handleDateConfirm = (event: { value: string | number | Date }) => {
  336. const date = dayjs(event.value)
  337. params.selectedYear = date.format('YYYY')
  338. params.selectedMonth = date.format('MM')
  339. refreshRecordList()
  340. }
  341. /**
  342. * 清空月份筛选
  343. */
  344. const clearDatePicker = () => {
  345. datetimePickerValue.value = dayjs().startOf('month').valueOf()
  346. params.selectedYear = ''
  347. params.selectedMonth = ''
  348. refreshRecordList()
  349. }
  350. /**
  351. * 点击撤回申领
  352. */
  353. const handleRevoke = (item: ScorePackageRecordItem) => {
  354. uni.showModal({
  355. title: '提示',
  356. content: '请确认是否撤回积分包申领?撤回后可重新发起申请。',
  357. cancelText: '取消',
  358. confirmText: '撤回',
  359. success: (res) => {
  360. if (!res.confirm) return
  361. revokeApply(item)
  362. },
  363. })
  364. }
  365. /**
  366. * 提交撤回申领
  367. */
  368. const revokeApply = async (item: ScorePackageRecordItem) => {
  369. if (!item.statusId) {
  370. toast.warning('缺少申领记录ID')
  371. return
  372. }
  373. try {
  374. const res = await pkgWithdrawApi({
  375. packageStatusId: item.statusId,
  376. })
  377. console.log('res', res)
  378. if (res.code === 0) {
  379. toast.success('撤回成功')
  380. refreshRecordList()
  381. }
  382. } catch (error) {
  383. console.error('撤回申领失败:', error)
  384. toast.error(getErrorMessage(error, '撤回失败'))
  385. }
  386. }
  387. /**
  388. * 打开详情页
  389. */
  390. const handleOpenDetail = (item: ScorePackageRecordItem) => {
  391. uni.navigateTo({
  392. url: `/pages-task/task-package/detail?id=${item.id}`,
  393. })
  394. }
  395. /**
  396. * 获取积分包状态文案
  397. */
  398. const getPackageStatusText = (status: Nullable<string | number>) => {
  399. const statusItem = packageStatusMap.value.find((item) => String(item.value) === String(status))
  400. return statusItem?.label || '-'
  401. }
  402. /**
  403. * 获取积分包类型标签
  404. */
  405. const getPackageTypeTag = (type: Nullable<string | number>, disabled = false) => {
  406. const disabledTag = {
  407. label: getPackageTypeText(type),
  408. color: '#999999',
  409. bgColor: '#f5f5f5',
  410. }
  411. const tagMap = {
  412. '1': { label: '积分值', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
  413. '6': { label: '积分值', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
  414. '2': { label: '任务量', color: '#4cd964', bgColor: 'rgba(76, 217, 100, 0.1)' },
  415. '3': { label: '通用', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
  416. '4': { label: '特定', color: '#ffbe1c', bgColor: 'rgba(255, 190, 28, 0.1)' },
  417. } as const
  418. const key = String(type) as keyof typeof tagMap
  419. if (!type || !tagMap[key]) return undefined
  420. return disabled ? disabledTag : tagMap[key]
  421. }
  422. /**
  423. * 获取积分包类型文案
  424. */
  425. const getPackageTypeText = (type: Nullable<string | number>) => {
  426. const textMap = {
  427. '1': '积分值',
  428. '6': '积分值',
  429. '2': '任务量',
  430. '3': '通用',
  431. '4': '特定',
  432. } as const
  433. return textMap[String(type) as keyof typeof textMap] || ''
  434. }
  435. /**
  436. * 是否为不可继续追加任务的积分包
  437. */
  438. const isDisabledPackage = (item: ScorePackageRecordItem) => {
  439. return String(item.taskAddFlag) === '0'
  440. }
  441. /**
  442. * 是否为积分值类型积分包
  443. */
  444. const isScorePackage = (item: ScorePackageRecordItem) => {
  445. return ['1', '6'].includes(String(item.packageType1))
  446. }
  447. /**
  448. * 是否已结算
  449. */
  450. const isSettledPackage = (item: ScorePackageRecordItem) => {
  451. return String(item.packageStatus) === '5' && String(item.settleNoteStatus) === '1'
  452. }
  453. /**
  454. * 获取进度百分比
  455. */
  456. const getProgressPercentage = (item: ScorePackageRecordItem) => {
  457. if (isDisabledPackage(item)) return 100
  458. const progress = Number(item.speedProgress ?? 0)
  459. if (Number.isNaN(progress)) return 0
  460. return Math.max(0, Math.min(progress, 100))
  461. }
  462. /**
  463. * 获取进度展示文案
  464. */
  465. const getProgressText = (item: ScorePackageRecordItem) => {
  466. if (isScorePackage(item)) {
  467. return `${formatAmount(item.finishScore)}/${formatAmount(item.score)}`
  468. }
  469. const finishedNum = String(item.taskAddFlag) === '1' ? item.taskFinishNum : item.taskNum2
  470. return `${formatAmount(finishedNum)}/${formatAmount(item.taskNum2)}`
  471. }
  472. /**
  473. * 格式化日期
  474. */
  475. const formatDate = (value: Nullable<string>) => {
  476. if (!value) return '-'
  477. const date = dayjs(value)
  478. return date.isValid() ? date.format('YYYY-MM-DD') : '-'
  479. }
  480. /**
  481. * 格式化金额 / 数值
  482. */
  483. const formatAmount = (value: Nullable<string | number>) => {
  484. return value === null || value === undefined || value === '' ? '-' : value
  485. }
  486. /**
  487. * 获取错误文案
  488. */
  489. const getErrorMessage = (error: unknown, fallback: string) => {
  490. if (error instanceof Error) return error.message || fallback
  491. return fallback
  492. }
  493. /**
  494. * 根据入口参数设置默认 Tab
  495. */
  496. const setDefaultTabByEntryType = (type: PageEntryType) => {
  497. if (!type) return
  498. const tab = entryTypeTabMap[type as Exclude<PageEntryType, ''>]
  499. if (tab) {
  500. params.activeTab = tab
  501. }
  502. }
  503. onReachBottom(loadMoreRecordList)
  504. onLoad((query) => {
  505. const type = String(query?.type || '') as PageEntryType
  506. openType.value = type
  507. setDefaultTabByEntryType(type)
  508. /**
  509. * 兼容旧入口:如果仍然传 page,则 page 优先级高于 type
  510. */
  511. if (query?.page) {
  512. const page = String(query.page)
  513. if (Object.values(RecordTab).includes(page as RecordTab)) {
  514. params.activeTab = page as RecordTab
  515. }
  516. }
  517. fetchRecordList()
  518. fetchPackageStatus()
  519. })
  520. </script>
  521. <style lang="scss" scoped>
  522. .points-package-record {
  523. min-height: 100vh;
  524. background: linear-gradient(180deg, #f3f8ff 0%, #fafafa 360rpx, #fafafa 100%);
  525. .record-header {
  526. position: fixed;
  527. top: 0;
  528. left: 0;
  529. right: 0;
  530. z-index: 99;
  531. display: flex;
  532. align-items: center;
  533. height: 96rpx;
  534. padding: 0 20rpx;
  535. background: rgba(255, 255, 255, 0.96);
  536. box-shadow: 0 8rpx 24rpx rgba(31, 91, 150, 0.06);
  537. box-sizing: border-box;
  538. }
  539. .record-tabs {
  540. display: flex;
  541. flex: 1;
  542. height: 100%;
  543. }
  544. .record-tabs__item {
  545. position: relative;
  546. flex: 1;
  547. height: 100%;
  548. color: #6b7280;
  549. font-size: 28rpx;
  550. font-weight: 400;
  551. line-height: 96rpx;
  552. text-align: center;
  553. }
  554. .record-tabs__item--active {
  555. color: #1f2937;
  556. font-weight: 700;
  557. &::after {
  558. position: absolute;
  559. bottom: 10rpx;
  560. left: 50%;
  561. width: 44rpx;
  562. height: 8rpx;
  563. background: linear-gradient(90deg, #3fa3f1 0%, #68c7ff 100%);
  564. border-radius: 999rpx;
  565. transform: translateX(-50%);
  566. content: '';
  567. }
  568. }
  569. .record-date {
  570. display: flex;
  571. align-items: center;
  572. justify-content: center;
  573. width: 240rpx;
  574. height: 100%;
  575. color: #333333;
  576. }
  577. .record-date__divider {
  578. width: 2rpx;
  579. height: 40rpx;
  580. margin-right: 24rpx;
  581. background: #ebebeb;
  582. }
  583. .record-date__inner {
  584. display: flex;
  585. align-items: center;
  586. justify-content: center;
  587. min-width: 170rpx;
  588. height: 100%;
  589. }
  590. .record-date__text {
  591. margin-right: 10rpx;
  592. color: #333333;
  593. font-size: 28rpx;
  594. white-space: nowrap;
  595. }
  596. .record-content {
  597. padding: 120rpx 28rpx 28rpx;
  598. }
  599. .record-notice {
  600. margin-bottom: 20rpx;
  601. overflow: hidden;
  602. border-radius: 16rpx;
  603. box-shadow: 0 8rpx 22rpx rgba(255, 142, 40, 0.08);
  604. }
  605. .record-empty {
  606. padding-top: 220rpx;
  607. }
  608. .record-card {
  609. position: relative;
  610. margin-bottom: 24rpx;
  611. overflow: hidden;
  612. background: #ffffff;
  613. border: 2rpx solid rgba(63, 163, 241, 0.08);
  614. border-radius: 24rpx;
  615. box-shadow: 0 12rpx 32rpx rgba(31, 91, 150, 0.08);
  616. }
  617. .record-card__header {
  618. display: flex;
  619. align-items: flex-start;
  620. gap: 18rpx;
  621. padding: 26rpx 26rpx 24rpx;
  622. background: linear-gradient(135deg, #eaf6ff 0%, #f5fbff 100%);
  623. }
  624. .record-card__title-wrap {
  625. flex: 1;
  626. min-width: 0;
  627. }
  628. .record-card__title {
  629. color: #1f2937;
  630. font-size: 34rpx;
  631. font-weight: 700;
  632. line-height: 48rpx;
  633. word-break: break-all;
  634. }
  635. .record-card__sn {
  636. margin-top: 8rpx;
  637. color: #94a3b8;
  638. font-size: 22rpx;
  639. line-height: 32rpx;
  640. }
  641. :deep(.record-card__tag) {
  642. display: inline-flex;
  643. height: 34rpx;
  644. margin-left: 10rpx;
  645. padding: 0 10rpx;
  646. font-size: 22rpx;
  647. font-weight: 500;
  648. line-height: 32rpx;
  649. vertical-align: 4rpx;
  650. border-radius: 8rpx;
  651. }
  652. .record-card__status {
  653. display: flex;
  654. flex-shrink: 0;
  655. align-items: center;
  656. min-height: 48rpx;
  657. padding: 0 14rpx;
  658. color: #3fa3f1;
  659. font-size: 26rpx;
  660. font-weight: 500;
  661. background: rgba(63, 163, 241, 0.1);
  662. border-radius: 999rpx;
  663. }
  664. .record-card__dot {
  665. flex-shrink: 0;
  666. width: 10rpx;
  667. height: 10rpx;
  668. margin-right: 8rpx;
  669. background: #3fa3f1;
  670. border-radius: 50%;
  671. }
  672. .record-card__body {
  673. padding: 24rpx 26rpx 26rpx;
  674. background: #ffffff;
  675. }
  676. .record-card__info,
  677. .record-card__meta {
  678. color: #8a94a6;
  679. font-size: 24rpx;
  680. line-height: 40rpx;
  681. }
  682. .record-card__meta {
  683. display: flex;
  684. flex-wrap: wrap;
  685. gap: 0 28rpx;
  686. margin-top: 4rpx;
  687. }
  688. .record-card__progress {
  689. margin-top: 20rpx;
  690. padding: 20rpx;
  691. background: #f8fbff;
  692. border-radius: 18rpx;
  693. }
  694. .record-card__progress-main {
  695. width: 100%;
  696. }
  697. .record-card__progress-top {
  698. display: flex;
  699. align-items: center;
  700. justify-content: space-between;
  701. margin-bottom: 14rpx;
  702. }
  703. .record-card__progress-label {
  704. color: #6b7280;
  705. font-size: 24rpx;
  706. }
  707. .record-card__progress-value {
  708. color: #1f2937;
  709. font-size: 26rpx;
  710. font-weight: 700;
  711. }
  712. .record-card__progress-value--disabled {
  713. color: #9ca3af;
  714. }
  715. .record-card__settle {
  716. display: flex;
  717. flex-wrap: wrap;
  718. gap: 10rpx 16rpx;
  719. margin-top: 18rpx;
  720. padding: 18rpx;
  721. color: #6b7280;
  722. font-size: 24rpx;
  723. line-height: 36rpx;
  724. background: #f9fafb;
  725. border-radius: 16rpx;
  726. }
  727. .record-card__settle-item {
  728. flex-shrink: 0;
  729. }
  730. .record-card__actions {
  731. display: flex;
  732. align-items: center;
  733. justify-content: space-between;
  734. gap: 18rpx;
  735. margin-top: 20rpx;
  736. padding: 18rpx 18rpx 18rpx 20rpx;
  737. background: linear-gradient(135deg, #f8fbff 0%, #eef7ff 100%);
  738. border: 2rpx solid rgba(63, 163, 241, 0.12);
  739. border-radius: 18rpx;
  740. }
  741. .record-card__action-tip {
  742. display: flex;
  743. flex: 1;
  744. align-items: center;
  745. min-width: 0;
  746. color: #7b8794;
  747. font-size: 22rpx;
  748. line-height: 32rpx;
  749. text {
  750. margin-left: 6rpx;
  751. }
  752. }
  753. :deep(.record-card__revoke) {
  754. flex-shrink: 0;
  755. min-width: 148rpx;
  756. height: 58rpx;
  757. padding: 0 26rpx;
  758. color: #ffffff;
  759. font-size: 26rpx;
  760. font-weight: 600;
  761. background: linear-gradient(135deg, #3fa3f1 0%, #61bef8 100%);
  762. border: 0;
  763. box-shadow: 0 8rpx 18rpx rgba(63, 163, 241, 0.24);
  764. }
  765. .record-card__expired {
  766. z-index: 2;
  767. position: absolute;
  768. right: 30rpx;
  769. bottom: 25rpx;
  770. width: 150rpx;
  771. height: 150rpx;
  772. image {
  773. width: 150rpx;
  774. height: 150rpx;
  775. }
  776. }
  777. .record-card--disabled {
  778. border-color: rgba(156, 163, 175, 0.1);
  779. box-shadow: 0 10rpx 28rpx rgba(31, 41, 55, 0.05);
  780. .record-card__header {
  781. background: linear-gradient(135deg, #f3f4f6 0%, #fafafa 100%);
  782. }
  783. .record-card__title,
  784. .record-card__status {
  785. color: #9ca3af;
  786. }
  787. .record-card__status {
  788. background: #f3f4f6;
  789. }
  790. .record-card__dot {
  791. background: #9ca3af;
  792. }
  793. }
  794. .record-load-status {
  795. padding: 12rpx 0 32rpx;
  796. color: #9ca3af;
  797. font-size: 24rpx;
  798. text-align: center;
  799. }
  800. }
  801. </style>