Переглянути джерело

完成领包记录页面重构

yuanmingze 4 місяців тому
батько
коміт
ae5318d0b0

+ 1 - 0
components.d.ts

@@ -20,6 +20,7 @@ declare module 'vue' {
     WdNavbar: typeof import('@wot-ui/ui/components/wd-navbar/wd-navbar.vue')['default']
     WdNoticeBar: typeof import('@wot-ui/ui/components/wd-notice-bar/wd-notice-bar.vue')['default']
     WdPopup: typeof import('@wot-ui/ui/components/wd-popup/wd-popup.vue')['default']
+    WdProgress: typeof import('@wot-ui/ui/components/wd-progress/wd-progress.vue')['default']
     WdSearch: typeof import('@wot-ui/ui/components/wd-search/wd-search.vue')['default']
     WdTag: typeof import('@wot-ui/ui/components/wd-tag/wd-tag.vue')['default']
     WdToast: typeof import('@wot-ui/ui/components/wd-toast/wd-toast.vue')['default']

+ 935 - 0
src/pages-task/points-package-record/index.vue

@@ -0,0 +1,935 @@
+<template>
+  <view class="points-package-record">
+    <!-- 顶部筛选栏 -->
+    <view class="record-header">
+      <view class="record-tabs">
+        <view
+          v-for="tab in tabs"
+          :key="tab.value"
+          class="record-tabs__item"
+          :class="{ 'record-tabs__item--active': params.activeTab === tab.value }"
+          @click="handleTabChange(tab.value)"
+        >
+          {{ tab.label }}
+        </view>
+      </view>
+
+      <!-- 月份筛选 -->
+      <view class="record-date">
+        <view class="record-date__divider" />
+
+        <view v-if="!selectedDate" class="record-date__inner" @click="openDatePicker">
+          <text class="record-date__text">筛选</text>
+          <wd-icon name="caret-down" size="28rpx" />
+        </view>
+
+        <view v-else class="record-date__inner" @click="clearDatePicker">
+          <text class="record-date__text">{{ selectedDateText }}</text>
+          <wd-icon name="close-circle" size="28rpx" />
+        </view>
+      </view>
+    </view>
+
+    <view class="record-content">
+      <!-- 顶部提示 -->
+      <view class="record-notice">
+        <wd-notice-bar text="任务包即将完成时,请不要做积分较高的任务,避免浪费积分哦~" />
+      </view>
+
+      <!-- 空状态 -->
+      <view v-if="showEmpty" class="record-empty">
+        <wd-empty icon="no-content" tip="暂时还没有任务记录哦~" />
+      </view>
+
+      <!-- 列表 -->
+      <view v-else class="record-list">
+        <view
+          v-for="item in recordList"
+          :key="item.statusId || item.id"
+          class="record-card"
+          :class="{ 'record-card--disabled': isDisabledPackage(item) }"
+          @click="handleOpenDetail(item)"
+        >
+          <!-- 卡片头部 -->
+          <view class="record-card__header">
+            <view class="record-card__title-wrap">
+              <view class="record-card__title">
+                {{ item.scorePackageName || '-' }}
+
+                <wd-tag
+                  v-if="getPackageTypeTag(item.packageType1, isDisabledPackage(item))"
+                  custom-class="record-card__tag"
+                  plain
+                  :color="getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.color"
+                  :bg-color="getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.bgColor"
+                >
+                  {{ getPackageTypeTag(item.packageType1, isDisabledPackage(item))?.label }}
+                </wd-tag>
+              </view>
+
+              <view v-if="item.packageSn" class="record-card__sn">
+                编号:{{ item.packageSn }}
+              </view>
+            </view>
+
+            <view class="record-card__status">
+              <view class="record-card__dot" />
+              <text>{{ getPackageStatusText(item.packageStatus) }}</text>
+            </view>
+          </view>
+
+          <!-- 卡片主体 -->
+          <view class="record-card__body">
+            <view class="record-card__info">
+              <text>发包方:{{ item.packageEntName || '-' }}</text>
+            </view>
+
+            <view class="record-card__meta">
+              <text>发包时间:{{ formatDate(item.createTime) }}</text>
+              <text>接包时间:{{ formatDate(item.receiveTime) }}</text>
+            </view>
+
+            <!-- 进度信息 -->
+            <view class="record-card__progress">
+              <view class="record-card__progress-main">
+                <view class="record-card__progress-top">
+                  <text class="record-card__progress-label">完成进度</text>
+                  <text
+                    class="record-card__progress-value"
+                    :class="{ 'record-card__progress-value--disabled': isDisabledPackage(item) }"
+                  >
+                    {{ getProgressText(item) }}
+                  </text>
+                </view>
+
+                <wd-progress
+                  :percentage="getProgressPercentage(item)"
+                  :stroke-width="8"
+                  :color="isDisabledPackage(item) ? '#d5d5d5' : '#3fa3f1'"
+                  hide-text
+                />
+              </view>
+            </view>
+
+            <!-- 结算信息 -->
+            <view v-if="isSettledPackage(item)" class="record-card__settle">
+              <view class="record-card__settle-item">结算状态:已结算</view>
+              <view class="record-card__settle-item"
+                >税后收入:{{ formatAmount(item.userRealAmount) }}</view
+              >
+              <view class="record-card__settle-item"
+                >已交税金:{{ formatAmount(item.actualTax) }}</view
+              >
+            </view>
+
+            <!-- 待审批状态下支持撤回 -->
+            <view v-if="showRevokeButton" class="record-card__actions" @click.stop>
+              <view class="record-card__action-tip">
+                <wd-icon name="warning" size="24rpx" />
+                <text>当前申请待审批,可撤回后重新申领</text>
+              </view>
+
+              <wd-button
+                size="small"
+                round
+                custom-class="record-card__revoke"
+                @click.stop="handleRevoke(item)"
+              >
+                撤回申领
+              </wd-button>
+            </view>
+          </view>
+
+          <!-- 过期标识 -->
+          <view v-if="String(item.packageStatus) === '2'" class="record-card__expired">
+            <image src="/static/images/common/expired.png" mode="heightFix" />
+          </view>
+        </view>
+
+        <!-- 底部加载状态 -->
+        <view class="record-load-status">
+          <text v-if="loading">加载中...</text>
+          <text v-else-if="finished">没有更多了</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 月份选择器 -->
+    <wd-datetime-picker
+      v-model:visible="datetimePickerShow"
+      v-model="datetimePickerValue"
+      type="year-month"
+      @confirm="handleDateConfirm"
+    />
+
+    <wd-toast />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue'
+
+import { onLoad, onReachBottom } from '@dcloudio/uni-app'
+import { useToast } from '@wot-ui/ui'
+import dayjs from 'dayjs'
+
+import { getDictTypeApi } from '@/services/modules/common'
+import {
+  getUserScorePackageListApi,
+  pkgWithdrawApi,
+} from '@/services/modules/pointsPackageRecord/index'
+import type { ScorePackageRecordItem } from '@/services/modules/pointsPackageRecord/type'
+
+const toast = useToast()
+
+/**
+ * 页面入口类型
+ * on-the-way:从工作台「进行中」进入
+ * wait-approve:从工作台「待审批」进入
+ */
+type PageEntryType = 'on-the-way' | 'wait-approve' | ''
+
+/**
+ * 积分包记录 Tab
+ * 这里的值需要和后端 activeId 入参保持一致
+ */
+enum RecordTab {
+  All = '1',
+  Processing = '2',
+  Approving = '3',
+  Finished = '4',
+}
+
+type Nullable<T> = T | null | undefined
+
+interface DictItem {
+  label: string
+  value: string | number
+}
+
+/**
+ * 列表分页响应结构
+ */
+interface ListResponse {
+  records?: ScorePackageRecordItem[]
+  total?: number
+}
+
+/**
+ * 页面 Tab 配置
+ */
+const tabs = [
+  { label: '全部', value: RecordTab.All },
+  { label: '进行中', value: RecordTab.Processing },
+  { label: '待审批', value: RecordTab.Approving },
+  { label: '已完成', value: RecordTab.Finished },
+] as const
+
+/**
+ * 工作台入口 type 与页面 Tab 的映射
+ */
+const entryTypeTabMap: Record<Exclude<PageEntryType, ''>, RecordTab> = {
+  'on-the-way': RecordTab.Processing,
+  'wait-approve': RecordTab.Approving,
+}
+
+/**
+ * 查询参数
+ */
+const params = reactive({
+  activeTab: RecordTab.All,
+  current: 1,
+  size: 10,
+  selectedYear: '',
+  selectedMonth: '',
+})
+
+/**
+ * 页面状态
+ */
+const openType = ref<PageEntryType>('')
+const loading = ref(false)
+const finished = ref(false)
+const recordList = ref<ScorePackageRecordItem[]>([])
+const packageStatusMap = ref<DictItem[]>([])
+
+/**
+ * 请求标识,用于规避快速切换筛选时旧请求覆盖新数据
+ */
+let requestId = 0
+
+/**
+ * 当前选中的月份,格式:YYYY-MM
+ */
+const selectedDate = computed(() => {
+  const { selectedYear, selectedMonth } = params
+  return selectedYear && selectedMonth ? `${selectedYear}-${selectedMonth}` : ''
+})
+
+/**
+ * 月份筛选展示文案
+ */
+const selectedDateText = computed(() => {
+  const { selectedYear, selectedMonth } = params
+  return selectedYear && selectedMonth ? `${selectedYear}年${selectedMonth}月` : '筛选'
+})
+
+/**
+ * 是否展示空状态
+ */
+const showEmpty = computed(() => !loading.value && recordList.value.length === 0)
+
+/**
+ * 只有待审批 Tab 展示撤回按钮
+ */
+const showRevokeButton = computed(() => params.activeTab === RecordTab.Approving)
+
+/**
+ * 日期选择器状态
+ */
+const datetimePickerShow = ref(false)
+const datetimePickerValue = ref(dayjs().startOf('month').valueOf())
+
+/**
+ * 重置列表分页
+ */
+const resetList = () => {
+  params.current = 1
+  finished.value = false
+  recordList.value = []
+}
+
+/**
+ * 刷新列表
+ */
+const refreshRecordList = () => {
+  resetList()
+  fetchRecordList()
+}
+
+/**
+ * 获取积分包状态字典
+ */
+const fetchPackageStatus = async () => {
+  try {
+    const res = await getDictTypeApi('package_new_status')
+    packageStatusMap.value = res?.data ?? []
+  } catch (error) {
+    console.error('获取积分包状态字典失败:', error)
+  }
+}
+
+/**
+ * 获取积分包记录列表
+ */
+const fetchRecordList = async () => {
+  if (loading.value || finished.value) return
+
+  loading.value = true
+  const currentRequestId = ++requestId
+
+  try {
+    const res = await getUserScorePackageListApi({
+      current: params.current,
+      size: params.size,
+      selType: 2,
+      selDate: selectedDate.value,
+      packageName: '',
+      activeId: params.activeTab,
+    })
+
+    if (currentRequestId !== requestId) return
+
+    const data = (res?.data ?? {}) as ListResponse
+    const records = data.records ?? []
+    const total = data.total ?? 0
+
+    recordList.value = params.current === 1 ? records : recordList.value.concat(records)
+    finished.value = recordList.value.length >= total || records.length < params.size
+  } catch (error) {
+    console.error('获取积分包记录失败:', error)
+    toast.error('获取列表失败')
+  } finally {
+    if (currentRequestId === requestId) {
+      loading.value = false
+    }
+  }
+}
+
+/**
+ * 加载下一页
+ */
+const loadMoreRecordList = () => {
+  if (loading.value || finished.value) return
+
+  params.current += 1
+  fetchRecordList()
+}
+
+/**
+ * 切换 Tab
+ */
+const handleTabChange = (value: RecordTab) => {
+  if (params.activeTab === value) return
+
+  params.activeTab = value
+  refreshRecordList()
+}
+
+/**
+ * 打开月份筛选
+ */
+const openDatePicker = () => {
+  datetimePickerShow.value = true
+}
+
+/**
+ * 确认月份筛选
+ */
+const handleDateConfirm = (event: { value: string | number | Date }) => {
+  const date = dayjs(event.value)
+
+  params.selectedYear = date.format('YYYY')
+  params.selectedMonth = date.format('MM')
+
+  refreshRecordList()
+}
+
+/**
+ * 清空月份筛选
+ */
+const clearDatePicker = () => {
+  datetimePickerValue.value = dayjs().startOf('month').valueOf()
+  params.selectedYear = ''
+  params.selectedMonth = ''
+
+  refreshRecordList()
+}
+
+/**
+ * 点击撤回申领
+ */
+const handleRevoke = (item: ScorePackageRecordItem) => {
+  uni.showModal({
+    title: '提示',
+    content: '请确认是否撤回积分包申领?撤回后可重新发起申请。',
+    cancelText: '取消',
+    confirmText: '撤回',
+    success: (res) => {
+      if (!res.confirm) return
+      revokeApply(item)
+    },
+  })
+}
+
+/**
+ * 提交撤回申领
+ */
+const revokeApply = async (item: ScorePackageRecordItem) => {
+  if (!item.statusId) {
+    toast.warning('缺少申领记录ID')
+    return
+  }
+
+  try {
+    const res = await pkgWithdrawApi({
+      packageStatusId: item.statusId,
+    })
+    console.log('res', res)
+
+    if (res.code === 0) {
+      toast.success('撤回成功')
+      refreshRecordList()
+    }
+  } catch (error) {
+    console.error('撤回申领失败:', error)
+    toast.error(getErrorMessage(error, '撤回失败'))
+  }
+}
+
+/**
+ * 打开详情页
+ */
+const handleOpenDetail = (item: ScorePackageRecordItem) => {
+  uni.navigateTo({
+    url: `/pages-task/task-package/detail?id=${item.id}`,
+  })
+}
+
+/**
+ * 获取积分包状态文案
+ */
+const getPackageStatusText = (status: Nullable<string | number>) => {
+  const statusItem = packageStatusMap.value.find((item) => String(item.value) === String(status))
+  return statusItem?.label || '-'
+}
+
+/**
+ * 获取积分包类型标签
+ */
+const getPackageTypeTag = (type: Nullable<string | number>, disabled = false) => {
+  const disabledTag = {
+    label: getPackageTypeText(type),
+    color: '#999999',
+    bgColor: '#f5f5f5',
+  }
+
+  const tagMap = {
+    '1': { label: '积分值', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
+    '6': { label: '积分值', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
+    '2': { label: '任务量', color: '#4cd964', bgColor: 'rgba(76, 217, 100, 0.1)' },
+    '3': { label: '通用', color: '#3fa3f1', bgColor: 'rgba(63, 163, 241, 0.1)' },
+    '4': { label: '特定', color: '#ffbe1c', bgColor: 'rgba(255, 190, 28, 0.1)' },
+  } as const
+
+  const key = String(type) as keyof typeof tagMap
+
+  if (!type || !tagMap[key]) return undefined
+  return disabled ? disabledTag : tagMap[key]
+}
+
+/**
+ * 获取积分包类型文案
+ */
+const getPackageTypeText = (type: Nullable<string | number>) => {
+  const textMap = {
+    '1': '积分值',
+    '6': '积分值',
+    '2': '任务量',
+    '3': '通用',
+    '4': '特定',
+  } as const
+
+  return textMap[String(type) as keyof typeof textMap] || ''
+}
+
+/**
+ * 是否为不可继续追加任务的积分包
+ */
+const isDisabledPackage = (item: ScorePackageRecordItem) => {
+  return String(item.taskAddFlag) === '0'
+}
+
+/**
+ * 是否为积分值类型积分包
+ */
+const isScorePackage = (item: ScorePackageRecordItem) => {
+  return ['1', '6'].includes(String(item.packageType1))
+}
+
+/**
+ * 是否已结算
+ */
+const isSettledPackage = (item: ScorePackageRecordItem) => {
+  return String(item.packageStatus) === '5' && String(item.settleNoteStatus) === '1'
+}
+
+/**
+ * 获取进度百分比
+ */
+const getProgressPercentage = (item: ScorePackageRecordItem) => {
+  if (isDisabledPackage(item)) return 100
+
+  const progress = Number(item.speedProgress ?? 0)
+  if (Number.isNaN(progress)) return 0
+
+  return Math.max(0, Math.min(progress, 100))
+}
+
+/**
+ * 获取进度展示文案
+ */
+const getProgressText = (item: ScorePackageRecordItem) => {
+  if (isScorePackage(item)) {
+    return `${formatAmount(item.finishScore)}/${formatAmount(item.score)}`
+  }
+
+  const finishedNum = String(item.taskAddFlag) === '1' ? item.taskFinishNum : item.taskNum2
+  return `${formatAmount(finishedNum)}/${formatAmount(item.taskNum2)}`
+}
+
+/**
+ * 格式化日期
+ */
+const formatDate = (value: Nullable<string>) => {
+  if (!value) return '-'
+
+  const date = dayjs(value)
+  return date.isValid() ? date.format('YYYY-MM-DD') : '-'
+}
+
+/**
+ * 格式化金额 / 数值
+ */
+const formatAmount = (value: Nullable<string | number>) => {
+  return value === null || value === undefined || value === '' ? '-' : value
+}
+
+/**
+ * 获取错误文案
+ */
+const getErrorMessage = (error: unknown, fallback: string) => {
+  if (error instanceof Error) return error.message || fallback
+  return fallback
+}
+
+/**
+ * 根据入口参数设置默认 Tab
+ */
+const setDefaultTabByEntryType = (type: PageEntryType) => {
+  if (!type) return
+
+  const tab = entryTypeTabMap[type as Exclude<PageEntryType, ''>]
+  if (tab) {
+    params.activeTab = tab
+  }
+}
+
+onReachBottom(loadMoreRecordList)
+
+onLoad((query) => {
+  const type = String(query?.type || '') as PageEntryType
+
+  openType.value = type
+  setDefaultTabByEntryType(type)
+
+  /**
+   * 兼容旧入口:如果仍然传 page,则 page 优先级高于 type
+   */
+  if (query?.page) {
+    const page = String(query.page)
+
+    if (Object.values(RecordTab).includes(page as RecordTab)) {
+      params.activeTab = page as RecordTab
+    }
+  }
+
+  fetchRecordList()
+  fetchPackageStatus()
+})
+</script>
+
+<style lang="scss" scoped>
+.points-package-record {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #f3f8ff 0%, #fafafa 360rpx, #fafafa 100%);
+
+  .record-header {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 99;
+    display: flex;
+    align-items: center;
+    height: 96rpx;
+    padding: 0 20rpx;
+    background: rgba(255, 255, 255, 0.96);
+    box-shadow: 0 8rpx 24rpx rgba(31, 91, 150, 0.06);
+    box-sizing: border-box;
+  }
+
+  .record-tabs {
+    display: flex;
+    flex: 1;
+    height: 100%;
+  }
+
+  .record-tabs__item {
+    position: relative;
+    flex: 1;
+    height: 100%;
+    color: #6b7280;
+    font-size: 28rpx;
+    font-weight: 400;
+    line-height: 96rpx;
+    text-align: center;
+  }
+
+  .record-tabs__item--active {
+    color: #1f2937;
+    font-weight: 700;
+
+    &::after {
+      position: absolute;
+      bottom: 10rpx;
+      left: 50%;
+      width: 44rpx;
+      height: 8rpx;
+      background: linear-gradient(90deg, #3fa3f1 0%, #68c7ff 100%);
+      border-radius: 999rpx;
+      transform: translateX(-50%);
+      content: '';
+    }
+  }
+
+  .record-date {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 240rpx;
+    height: 100%;
+    color: #333333;
+  }
+
+  .record-date__divider {
+    width: 2rpx;
+    height: 40rpx;
+    margin-right: 24rpx;
+    background: #ebebeb;
+  }
+
+  .record-date__inner {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-width: 170rpx;
+    height: 100%;
+  }
+
+  .record-date__text {
+    margin-right: 10rpx;
+    color: #333333;
+    font-size: 28rpx;
+    white-space: nowrap;
+  }
+
+  .record-content {
+    padding: 120rpx 28rpx 28rpx;
+  }
+
+  .record-notice {
+    margin-bottom: 20rpx;
+    overflow: hidden;
+    border-radius: 16rpx;
+    box-shadow: 0 8rpx 22rpx rgba(255, 142, 40, 0.08);
+  }
+
+  .record-empty {
+    padding-top: 220rpx;
+  }
+
+  .record-card {
+    position: relative;
+    margin-bottom: 24rpx;
+    overflow: hidden;
+    background: #ffffff;
+    border: 2rpx solid rgba(63, 163, 241, 0.08);
+    border-radius: 24rpx;
+    box-shadow: 0 12rpx 32rpx rgba(31, 91, 150, 0.08);
+  }
+
+  .record-card__header {
+    display: flex;
+    align-items: flex-start;
+    gap: 18rpx;
+    padding: 26rpx 26rpx 24rpx;
+    background: linear-gradient(135deg, #eaf6ff 0%, #f5fbff 100%);
+  }
+
+  .record-card__title-wrap {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .record-card__title {
+    color: #1f2937;
+    font-size: 34rpx;
+    font-weight: 700;
+    line-height: 48rpx;
+    word-break: break-all;
+  }
+
+  .record-card__sn {
+    margin-top: 8rpx;
+    color: #94a3b8;
+    font-size: 22rpx;
+    line-height: 32rpx;
+  }
+
+  :deep(.record-card__tag) {
+    display: inline-flex;
+    height: 34rpx;
+    margin-left: 10rpx;
+    padding: 0 10rpx;
+    font-size: 22rpx;
+    font-weight: 500;
+    line-height: 32rpx;
+    vertical-align: 4rpx;
+    border-radius: 8rpx;
+  }
+
+  .record-card__status {
+    display: flex;
+    flex-shrink: 0;
+    align-items: center;
+    min-height: 48rpx;
+    padding: 0 14rpx;
+    color: #3fa3f1;
+    font-size: 26rpx;
+    font-weight: 500;
+    background: rgba(63, 163, 241, 0.1);
+    border-radius: 999rpx;
+  }
+
+  .record-card__dot {
+    flex-shrink: 0;
+    width: 10rpx;
+    height: 10rpx;
+    margin-right: 8rpx;
+    background: #3fa3f1;
+    border-radius: 50%;
+  }
+
+  .record-card__body {
+    padding: 24rpx 26rpx 26rpx;
+    background: #ffffff;
+  }
+
+  .record-card__info,
+  .record-card__meta {
+    color: #8a94a6;
+    font-size: 24rpx;
+    line-height: 40rpx;
+  }
+
+  .record-card__meta {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 0 28rpx;
+    margin-top: 4rpx;
+  }
+
+  .record-card__progress {
+    margin-top: 20rpx;
+    padding: 20rpx;
+    background: #f8fbff;
+    border-radius: 18rpx;
+  }
+
+  .record-card__progress-main {
+    width: 100%;
+  }
+
+  .record-card__progress-top {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 14rpx;
+  }
+
+  .record-card__progress-label {
+    color: #6b7280;
+    font-size: 24rpx;
+  }
+
+  .record-card__progress-value {
+    color: #1f2937;
+    font-size: 26rpx;
+    font-weight: 700;
+  }
+
+  .record-card__progress-value--disabled {
+    color: #9ca3af;
+  }
+
+  .record-card__settle {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10rpx 16rpx;
+    margin-top: 18rpx;
+    padding: 18rpx;
+    color: #6b7280;
+    font-size: 24rpx;
+    line-height: 36rpx;
+    background: #f9fafb;
+    border-radius: 16rpx;
+  }
+
+  .record-card__settle-item {
+    flex-shrink: 0;
+  }
+
+  .record-card__actions {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 18rpx;
+    margin-top: 20rpx;
+    padding: 18rpx 18rpx 18rpx 20rpx;
+    background: linear-gradient(135deg, #f8fbff 0%, #eef7ff 100%);
+    border: 2rpx solid rgba(63, 163, 241, 0.12);
+    border-radius: 18rpx;
+  }
+
+  .record-card__action-tip {
+    display: flex;
+    flex: 1;
+    align-items: center;
+    min-width: 0;
+    color: #7b8794;
+    font-size: 22rpx;
+    line-height: 32rpx;
+
+    text {
+      margin-left: 6rpx;
+    }
+  }
+
+  :deep(.record-card__revoke) {
+    flex-shrink: 0;
+    min-width: 148rpx;
+    height: 58rpx;
+    padding: 0 26rpx;
+    color: #ffffff;
+    font-size: 26rpx;
+    font-weight: 600;
+    background: linear-gradient(135deg, #3fa3f1 0%, #61bef8 100%);
+    border: 0;
+    box-shadow: 0 8rpx 18rpx rgba(63, 163, 241, 0.24);
+  }
+
+  .record-card__expired {
+    z-index: 2;
+    position: absolute;
+    right: 30rpx;
+    bottom: 25rpx;
+    width: 150rpx;
+    height: 150rpx;
+
+    image {
+      width: 150rpx;
+      height: 150rpx;
+    }
+  }
+
+  .record-card--disabled {
+    border-color: rgba(156, 163, 175, 0.1);
+    box-shadow: 0 10rpx 28rpx rgba(31, 41, 55, 0.05);
+
+    .record-card__header {
+      background: linear-gradient(135deg, #f3f4f6 0%, #fafafa 100%);
+    }
+
+    .record-card__title,
+    .record-card__status {
+      color: #9ca3af;
+    }
+
+    .record-card__status {
+      background: #f3f4f6;
+    }
+
+    .record-card__dot {
+      background: #9ca3af;
+    }
+  }
+
+  .record-load-status {
+    padding: 12rpx 0 32rpx;
+    color: #9ca3af;
+    font-size: 24rpx;
+    text-align: center;
+  }
+}
+</style>

+ 2 - 2
src/pages-task/task-package/list.vue

@@ -51,7 +51,7 @@
         class="task-card"
         @click="handleOpenDetail(item)"
       >
-        <view class="task-card__header">
+        <view class="task-card__header" @click.stop>
           <view class="task-card__title">
             {{ item.scorePackageName }}{{ item.packageSn ? `(${item.packageSn})` : '' }}
             <wd-tag
@@ -70,7 +70,7 @@
             size="small"
             round
             custom-class="task-card__button"
-            @click.stop="handleReceive(item)"
+            @click.stop.self="handleReceive(item)"
           >
             立即领取
           </wd-button>

+ 7 - 0
src/pages.json

@@ -76,6 +76,13 @@
             "navigationBarTitleText": "积分包详情",
             "navigationStyle": "default"
           }
+        },
+        {
+          "path": "points-package-record/index",
+          "style": {
+            "navigationBarTitleText": "我的积分包记录",
+            "navigationStyle": "default"
+          }
         }
       ]
     }

+ 1 - 1
src/pages/index/components/WorkbenchCard.vue

@@ -14,7 +14,7 @@
     <view class="workbench-card-summary">
       <view class="workbench-card-summary-group" @click="handleOpenHistory">
         <view class="workbench-card-summary-value">我的任务包</view>
-        <view class="workbench-card-summary-link">领包记录</view>
+        <view class="workbench-card-summary-link"> {{ '领包记录 > > >' }}</view>
       </view>
       <view class="workbench-card-summary-stat" @click="handleOpenOnTheWay">
         <view

+ 7 - 3
src/pages/index/index.vue

@@ -119,12 +119,16 @@ const goTask = () => {
 }
 const taskHistory = () => {
   if (!ensureCanOperate()) return
-  console.log('领包记录')
+  uni.navigateTo({
+    url: '/pages-task/points-package-record/index',
+  })
 }
 const handleOpenStatus = (type: 'on-the-way' | 'wait-approve') => {
   if (!ensureCanOperate()) return
-  // 领包记录
-  console.log('type', type)
+
+  uni.navigateTo({
+    url: `/pages-task/points-package-record/index?type=${type}`,
+  })
 }
 
 const handleSelectTask = (item: TASK_TYPE) => {

+ 0 - 4
src/services/modules/common/type.d.ts

@@ -8,19 +8,15 @@ export type DictTypeResponse = DictItem[]
 export interface DictItem {
   id: number
   dictId: number
-
   type: string
   value: string
   label: string
-
   description: string
   remarks: string
   externalValue: string
-
   sort: number
   enabled: boolean
   delFlag: string
-
   createTime: string
   updateTime: string
 }

+ 24 - 0
src/services/modules/pointsPackageRecord/index.ts

@@ -0,0 +1,24 @@
+import http from '../../index'
+import type {
+  getUserScorePackageListRequst,
+  getUserScorePackageListResponse,
+  pkgWithdrawRequest,
+} from './type'
+
+export const getUserScorePackageListApi = (params: getUserScorePackageListRequst) => {
+  return http.get<getUserScorePackageListResponse>(`/admin/api/getUserScorePackageList2`, params, {
+    loading: true,
+    loadingText: '加载中...',
+  })
+}
+
+export const pkgWithdrawApi = (data: pkgWithdrawRequest) => {
+  return http.post(
+    `/admin/api/pkg/withdraw?packageStatusId=${data.packageStatusId}`,
+    {},
+    {
+      loading: true,
+      loadingText: '撤回中...',
+    }
+  )
+}

+ 58 - 0
src/services/modules/pointsPackageRecord/type.d.ts

@@ -0,0 +1,58 @@
+export interface getUserScorePackageListRequst {
+  current: number
+  size: number
+  selType: 2
+  selDate: string
+  packageName: string
+  activeId: string
+}
+
+export interface getUserScorePackageListResponse {
+  current: number
+  hitCount: boolean
+  optimizeCountSql: boolean
+  orders: unknown[]
+  pages: number
+  records: ScorePackageRecordItem[]
+  searchCount: boolean
+  size: number
+  total: number
+}
+
+export interface ScorePackageRecordItem {
+  actualTax: number | string | null
+  belongDate: string
+  createTime: string
+  description: string
+  enableFlag: string
+  enableType: string
+  finishScore: number
+  id: string
+  notifyTime: string | null
+  packageEntName: string
+  packageSn: string
+  packageStatus: string
+  packageType1: string
+  quizRelations: unknown[] | null
+  receiveTime: string
+  score: string
+  scorePackageName: string
+  sendPackageDeptId: string
+  settleNoteStatus: string | null
+  speedProgress: number
+  status: string
+  statusId: string
+  taskAddFlag: string
+  taskFinishNum: number
+  taskNum: number | null
+  taskNum2: number
+  userGetNum: number
+  userNum: number
+  userRealAmount: number | string | null
+  waitApprovalScore: number
+  waitApprovalTaskNum: number
+}
+
+export interface pkgWithdrawRequest {
+  packageStatusId: string
+}