浏览代码

完成领包广场全部内容

Co-authored-by: Copilot <copilot@github.com>
yuanmingze 4 月之前
父节点
当前提交
f64f7efe81

+ 3 - 0
components.d.ts

@@ -12,6 +12,8 @@ declare module 'vue' {
     WdButton: typeof import('@wot-ui/ui/components/wd-button/wd-button.vue')['default']
     WdCheckbox: typeof import('@wot-ui/ui/components/wd-checkbox/wd-checkbox.vue')['default']
     WdConfigProvider: typeof import('@wot-ui/ui/components/wd-config-provider/wd-config-provider.vue')['default']
+    WdDatetimePicker: typeof import('@wot-ui/ui/components/wd-datetime-picker/wd-datetime-picker.vue')['default']
+    WdDialog: typeof import('@wot-ui/ui/components/wd-dialog/wd-dialog.vue')['default']
     WdEmpty: typeof import('@wot-ui/ui/components/wd-empty/wd-empty.vue')['default']
     WdIcon: typeof import('@wot-ui/ui/components/wd-icon/wd-icon.vue')['default']
     WdLoading: typeof import('@wot-ui/ui/components/wd-loading/wd-loading.vue')['default']
@@ -20,5 +22,6 @@ declare module 'vue' {
     WdPopup: typeof import('@wot-ui/ui/components/wd-popup/wd-popup.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']
   }
 }

+ 171 - 16
src/pages-task/task-package/list.vue

@@ -26,10 +26,16 @@
           </view>
         </view>
 
-        <view class="task-list-controls__date" @click="openDatePicker">
+        <view class="task-list-controls__date">
           <view class="task-list-controls__divider" />
-          <text class="task-list-controls__date-text">{{ selectedDateText }}</text>
-          <wd-icon name="arrow-down" size="28rpx" />
+          <view v-if="!selectedDate" @click="openDatePicker">
+            <text class="task-list-controls__date-text">{{ selectedDateText }}</text>
+            <wd-icon name="caret-down" size="28rpx" />
+          </view>
+          <view v-else @click="clearDatePicker">
+            <text class="task-list-controls__date-text">{{ selectedDateText }}</text>
+            <wd-icon name="close-circle" size="28rpx" />
+          </view>
         </view>
       </view>
     </view>
@@ -92,6 +98,17 @@
         <text v-if="finished">没有更多了</text>
       </view>
     </view>
+
+    <!-- 时间选择器 -->
+    <wd-datetime-picker
+      v-model:visible="datetimePickerShow"
+      v-model="datetimePickerValue"
+      type="year-month"
+      @confirm="datetimePickerHandleConfirm"
+    />
+    <!-- 领取积分包测试提示 -->
+    <wd-dialog />
+    <wd-toast />
   </view>
 </template>
 
@@ -99,13 +116,20 @@
 import { computed, onBeforeMount, reactive, ref } from 'vue'
 
 import { onReachBottom } from '@dcloudio/uni-app'
+import { useDialog, useToast } from '@wot-ui/ui'
+import dayjs from 'dayjs'
 
-import { getUserScorePackageListApi } from '@/services/modules/task/list'
+import { getDictTypeApi } from '@/services/modules/common'
+import type { DictItem } from '@/services/modules/common/type'
+import { getUserScorePackageListApi, pickupPackageApi } from '@/services/modules/task/list'
 import type {
   getUserScorePackageListRequest,
   TaskPackageItem,
 } from '@/services/modules/task/list/type'
 
+const dialog = useDialog()
+const toast = useToast()
+
 enum TaskPackageTab {
   Available = '1',
   Received = '2',
@@ -158,6 +182,10 @@ const resetList = () => {
   packageListArr.value = []
 }
 
+const refreshTaskPackageList = () => {
+  resetList()
+  fetchTaskPackageList()
+}
 const fetchTaskPackageList = async () => {
   if (loading.value || finished.value) return
 
@@ -186,11 +214,6 @@ const fetchTaskPackageList = async () => {
   }
 }
 
-const refreshTaskPackageList = () => {
-  resetList()
-  fetchTaskPackageList()
-}
-
 const loadMoreTaskPackageList = () => {
   if (loading.value || finished.value) return
   params.current += 1
@@ -218,12 +241,123 @@ const handleTabChange = (value: TaskPackageTab) => {
   refreshTaskPackageList()
 }
 
+const datetimePickerValue = ref(dayjs().startOf('month').valueOf())
+const datetimePickerShow = ref(false)
+
 const openDatePicker = () => {
-  console.log('打开日期选择器')
+  datetimePickerShow.value = true
+}
+
+const datetimePickerHandleConfirm = (e: { value: string }) => {
+  const d = dayjs(e.value)
+  params.selectedYear = d.format('YYYY')
+  params.selectedMonth = d.format('MM')
+  refreshTaskPackageList()
+}
+
+const clearDatePicker = () => {
+  datetimePickerValue.value = dayjs().startOf('month').valueOf()
+  params.selectedYear = ''
+  params.selectedMonth = ''
+  refreshTaskPackageList()
+}
+
+const GET_STATUS = {
+  UN_GET: {
+    value: '0',
+    desc: '未领取',
+  },
+  ING: {
+    value: '1',
+    desc: '审核中',
+  },
+  OK: {
+    value: '2',
+    desc: '审核通过',
+  },
+  NO: {
+    value: '3',
+    desc: '审核不通过',
+  },
+  OVER: {
+    value: '4',
+    desc: '已终止',
+  },
+} as const
+
+const currentPackage = ref<TaskPackageItem | null>(null)
+
+const hasRequiredQuiz = (item: TaskPackageItem) => {
+  return Array.isArray(item.quizRelations) && item.quizRelations.length > 0
+}
+
+const isUnreceived = (item: TaskPackageItem) => {
+  return item.status === GET_STATUS.UN_GET.value
 }
 
-const handleReceive = (item: TaskPackageItem) => {
-  console.log('立即领取:', item)
+const openTestValidationDialog = () => {
+  dialog
+    .confirm({
+      title: '提示',
+      msg: '该积分包需要通过测试才能领取,是否进行测试?',
+      confirmButtonText: '去测试',
+      cancelButtonText: '取消',
+    })
+    .then(() => {
+      handleGoTest()
+    })
+    .catch(() => {
+      // 点击取消 / 关闭,不处理
+    })
+}
+
+/**
+ * 点击立即领取
+ */
+const handleReceive = async (item: TaskPackageItem) => {
+  currentPackage.value = { ...item }
+
+  // 有测试要求,且当前状态不允许直接领取:弹出测试校验提示
+  if (hasRequiredQuiz(item) && !isUnreceived(item)) {
+    openTestValidationDialog()
+    return
+  }
+
+  await submitReceiveAfterTestPassed(item)
+}
+const submitReceiveAfterTestPassed = async (item: TaskPackageItem) => {
+  const res = await pickupPackageApi({
+    pkgId: item.id,
+  })
+  if (res.code === 0) {
+    toast.success('领取成功')
+    removePackageFromList(item.id)
+    return
+  }
+  if (res.code === 1) {
+    uni.showModal({
+      content: res.msg,
+      confirmText: '去认证',
+      success: (modalRes) => {
+        if (modalRes.confirm) {
+          console.log('认证页面')
+        }
+      },
+    })
+  }
+}
+const removePackageFromList = (pkgId: string | number) => {
+  packageListArr.value = packageListArr.value.filter((item) => item.id !== pkgId)
+
+  if (params.activeTab === TaskPackageTab.Available && params.total > 0) {
+    params.total -= 1
+  }
+}
+const handleGoTest = () => {
+  if (!currentPackage.value) return
+  // uni.navigateTo({
+  //   url: `/pages/xxx/test/index?id=${currentPackage.value.id}`,
+  // })
 }
 
 const getPackageTypeTag = (type: string | null) => {
@@ -238,12 +372,25 @@ const getPackageTypeTag = (type: string | null) => {
   return type ? tagMap[type as keyof typeof tagMap] : undefined
 }
 
-const getPackageStatusText = (status: string | null) => status || '-'
+const packageStatusMap = ref<DictItem[]>([])
+const getPackageStatus = async () => {
+  const res = await getDictTypeApi('package_new_status')
+  if (res.code === 0) {
+    packageStatusMap.value = res.data ?? []
+  }
+}
+const getPackageStatusText = (status: string | null) => {
+  const statusItem = packageStatusMap.value.find((item) => item.value === status)
+  return statusItem ? statusItem.label : '-'
+}
 
 const formatDate = (value: string | null) => value?.slice(0, 10) || '-'
 
 onReachBottom(loadMoreTaskPackageList)
-onBeforeMount(fetchTaskPackageList)
+onBeforeMount(() => {
+  fetchTaskPackageList()
+  getPackageStatus()
+})
 </script>
 
 <style lang="scss" scoped>
@@ -253,6 +400,12 @@ onBeforeMount(fetchTaskPackageList)
 
   .task-filter-header {
     background: #ffffff;
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 99;
+    padding-bottom: 16rpx;
 
     .task-filter-header__search {
       margin-bottom: 6rpx;
@@ -262,6 +415,7 @@ onBeforeMount(fetchTaskPackageList)
       display: flex;
       align-items: center;
       height: 88rpx;
+      padding: 0 18rpx;
 
       .task-list-controls__tabs {
         display: flex;
@@ -301,7 +455,7 @@ onBeforeMount(fetchTaskPackageList)
         display: flex;
         align-items: center;
         justify-content: center;
-        width: 220rpx;
+        width: 280rpx;
         height: 100%;
         color: #333333;
       }
@@ -323,6 +477,7 @@ onBeforeMount(fetchTaskPackageList)
 
   .task-list-content {
     padding: 24rpx 32rpx;
+    margin-top: 200rpx;
 
     .task-card {
       margin-bottom: 24rpx;
@@ -423,7 +578,7 @@ onBeforeMount(fetchTaskPackageList)
   }
 
   .task-empty {
-    padding-top: 160rpx;
+    padding-top: 360rpx;
   }
 }
 </style>

+ 4 - 12
src/pages/index/components/WorkbenchCard.vue

@@ -19,9 +19,9 @@
       <view class="workbench-card-summary-stat" @click="handleOpenOnTheWay">
         <view
           class="workbench-card-summary-value"
-          :class="{ 'workbench-card-summary-value-active': isLogin }"
+          :class="{ 'workbench-card-summary-value-active': props.isLogin }"
         >
-          {{ isLogin ? onTheWay : '-' }}
+          {{ props.isLogin ? props.onTheWay : '-' }}
         </view>
         <view class="workbench-card-summary-label">进行中</view>
       </view>
@@ -29,9 +29,9 @@
       <view class="workbench-card-summary-stat" @click="handleOpenWaitApprove">
         <view
           class="workbench-card-summary-value"
-          :class="{ 'workbench-card-summary-value-active': isLogin }"
+          :class="{ 'workbench-card-summary-value-active': props.isLogin }"
         >
-          {{ isLogin ? waitApprove : '-' }}
+          {{ props.isLogin ? props.waitApprove : '-' }}
         </view>
         <view class="workbench-card-summary-label">待审批</view>
       </view>
@@ -40,12 +40,6 @@
 </template>
 
 <script setup lang="ts">
-/**
- * WorkbenchCard
- * - 只负责 UI 展示与抛出语义事件
- * - 不负责路由、不负责业务判断
- */
-
 type StatusType = 'on-the-way' | 'wait-approve'
 
 const props = defineProps<{
@@ -81,8 +75,6 @@ function handleOpenOnTheWay(): void {
 function handleOpenWaitApprove(): void {
   emit('open-status', 'wait-approve')
 }
-
-const { isLogin, onTheWay, waitApprove } = props
 </script>
 
 <style lang="scss" scoped>

+ 5 - 2
src/pages/index/index.vue

@@ -52,14 +52,17 @@ const userStore = useUserStore()
 
 const isLoggedIn = computed(() => userStore.isLoggedIn)
 
-const waitApprove = computed(() => userStore.currentUserInfo?.waitApprove ?? 0)
-const onTheWay = computed(() => userStore.currentUserInfo?.onTheWay ?? 0)
+const waitApprove = ref('0')
+const onTheWay = ref('0')
 
 onShow(async () => {
   // 每次进入页面时刷新用户状态
   if (!userStore.isLoggedIn) return
   const userInfo = await userStore.getUserInfoByCode()
+
   if (!userInfo) return
+  waitApprove.value = userInfo.waitApprove ?? '0'
+  onTheWay.value = userInfo.onTheWay ?? '0'
   // 未签署廉洁协议
   if (!userInfo.signedAgreement?.includes('HONEST_AGREEMENT_V2')) {
     uni.hideTabBar()

+ 4 - 3
src/services/modules/common/index.ts

@@ -1,5 +1,5 @@
 import http from '../../index'
-import type { SignAgreementRequest } from './type'
+import type { DictTypeResponse, SignAgreementRequest } from './type'
 
 export const signAgreementApi = (data: SignAgreementRequest) => {
   return http.post<boolean>('/admin/api/sign-agreement', data, {
@@ -8,5 +8,6 @@ export const signAgreementApi = (data: SignAgreementRequest) => {
   })
 }
 
-
-
+export const getDictTypeApi = (type: string) => {
+  return http.get<DictTypeResponse>(`/admin/dict/type/${type}`)
+}

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

@@ -2,3 +2,25 @@ export interface SignAgreementRequest {
   agreementType: string
   signatureUrl: string
 }
+
+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
+}

+ 22 - 2
src/services/modules/task/list/index.ts

@@ -1,10 +1,30 @@
 import http from '../../../index'
-import type { getUserScorePackageListRequest, getUserScorePackageListResponse } from './type'
+import type {
+  getUserScorePackageListRequest,
+  getUserScorePackageListResponse,
+  pickupPackageRequest,
+} from './type'
 
-// 获取当前企业配置的任务类型
 export const getUserScorePackageListApi = (params: getUserScorePackageListRequest) => {
   return http.get<getUserScorePackageListResponse>(`/admin/api/getUserScorePackageList2`, params, {
     loading: true,
     loadingText: '加载中...',
   })
 }
+
+export const pickupPackageApi = (data: pickupPackageRequest) => {
+  return http.post(`/admin/api/pkg/pickup`, data, {
+    loading: true,
+    loadingText: '提交中...',
+  })
+}
+
+
+
+  // getPackageStatusNewApi() {
+  //   return getHttp(`/admin/dict/type/package_new_status`);
+  // },
+
+// receivePackageWithQuiz: data => {
+//   return postHttp('/admin/api/pkg/pickup', data);
+// },

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

@@ -51,3 +51,7 @@ export interface TaskPackageItem {
   waitApprovalScore: number | null
   waitApprovalTaskNum: number | null
 }
+
+export interface pickupPackageRequest {
+  pkgId: string
+}