| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- <template>
- <view class="task-package-list">
- <view class="task-filter-header">
- <view class="task-filter-header__search">
- <wd-search
- v-model="params.keyword"
- hide-cancel
- placeholder-left
- placeholder="搜索任务包名称/编号"
- @change="onKeywordChange"
- @clear="handleClearSearch"
- />
- </view>
- <view class="task-list-controls">
- <view class="task-list-controls__tabs">
- <view
- v-for="tab in tabs"
- :key="tab.value"
- class="task-list-controls__tab"
- :class="{ 'task-list-controls__tab--active': params.activeTab === tab.value }"
- @click="handleTabChange(tab.value)"
- >
- {{ tab.label }}
- <text v-if="tab.value === TaskPackageTab.Available">({{ params.total }})</text>
- </view>
- </view>
- <view class="task-list-controls__date">
- <view class="task-list-controls__divider" />
- <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>
- <view v-if="showEmpty" class="task-empty">
- <wd-empty icon="no-content" tip="暂无内容" />
- </view>
- <view v-else class="task-list-content">
- <view v-for="item in packageListArr" :key="item.id" class="task-card">
- <view class="task-card__header">
- <view class="task-card__title">
- {{ item.scorePackageName }}{{ item.packageSn ? `(${item.packageSn})` : '' }}
- <wd-tag
- v-if="getPackageTypeTag(item.packageType1)"
- custom-class="task-card__tag"
- plain
- :color="getPackageTypeTag(item.packageType1)?.color"
- :bg-color="getPackageTypeTag(item.packageType1)?.bgColor"
- >
- {{ getPackageTypeTag(item.packageType1)?.label }}
- </wd-tag>
- </view>
- <wd-button
- v-if="params.activeTab === TaskPackageTab.Available"
- size="small"
- round
- custom-class="task-card__button"
- @click.stop="handleReceive(item)"
- >
- 立即领取
- </wd-button>
- <view v-else class="task-card__status">
- <view class="task-card__dot" />
- <text>{{ getPackageStatusText(item.packageStatus) }}</text>
- </view>
- </view>
- <view class="task-card__info">
- <view>发包企业:{{ item.packageEntName || '-' }}</view>
- <view class="task-card__meta">
- <text>需求人数:{{ item.userGetNum }}/{{ item.userNum }}</text>
- <text>预计积分值:{{ item.score }}分</text>
- <text v-if="item.taskNum">任务数:{{ item.taskNum }}个</text>
- </view>
- <view>发布时间:{{ formatDate(item.createTime) }}</view>
- <view>需求描述:{{ item.description || '无' }}</view>
- </view>
- <view v-if="item.packageStatus === '2'" class="task-card__expired">
- <image src="/static/images/common/expired.png" mode="heightFix" />
- </view>
- </view>
- <view class="task-load-status">
- <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>
- <script setup lang="ts">
- 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 { 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',
- }
- const tabs = [
- { label: '可领取', value: TaskPackageTab.Available },
- { label: '已领取', value: TaskPackageTab.Received },
- ] as const
- const params = reactive({
- keyword: '',
- total: 0,
- activeTab: TaskPackageTab.Available,
- current: 1,
- size: 10,
- selectedYear: '',
- selectedMonth: '',
- })
- const loading = ref(false)
- const finished = ref(false)
- const packageListArr = ref<TaskPackageItem[]>([])
- let requestId = 0
- 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 requestParams = computed<getUserScorePackageListRequest>(() => ({
- current: params.current,
- size: params.size,
- selType: params.activeTab,
- selDate: selectedDate.value,
- packageName: params.keyword.trim(),
- }))
- const showEmpty = computed(() => !loading.value && packageListArr.value.length === 0)
- const resetList = () => {
- params.current = 1
- finished.value = false
- packageListArr.value = []
- }
- const refreshTaskPackageList = () => {
- resetList()
- fetchTaskPackageList()
- }
- const fetchTaskPackageList = async () => {
- if (loading.value || finished.value) return
- loading.value = true
- const currentRequestId = ++requestId
- try {
- const res = await getUserScorePackageListApi(requestParams.value)
- if (currentRequestId !== requestId) return
- if (res.code !== 0) return
- const records = res.data?.records ?? []
- const total = res.data?.total ?? 0
- if (params.activeTab === TaskPackageTab.Available) {
- params.total = total
- }
- packageListArr.value = params.current === 1 ? records : packageListArr.value.concat(records)
- finished.value = packageListArr.value.length >= total || records.length < params.size
- } finally {
- if (currentRequestId === requestId) {
- loading.value = false
- }
- }
- }
- const loadMoreTaskPackageList = () => {
- if (loading.value || finished.value) return
- params.current += 1
- fetchTaskPackageList()
- }
- let searchTimer: ReturnType<typeof setTimeout> | null = null
- const onKeywordChange = () => {
- if (searchTimer) {
- clearTimeout(searchTimer)
- }
- searchTimer = setTimeout(() => {
- refreshTaskPackageList()
- }, 1500)
- }
- const handleClearSearch = () => {
- params.keyword = ''
- refreshTaskPackageList()
- }
- const handleTabChange = (value: TaskPackageTab) => {
- if (params.activeTab === value) return
- params.activeTab = value
- refreshTaskPackageList()
- }
- const datetimePickerValue = ref(dayjs().startOf('month').valueOf())
- const datetimePickerShow = ref(false)
- const openDatePicker = () => {
- 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 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) => {
- 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: '#dd524d', bgColor: 'rgba(221, 82, 77, 0.1)' },
- '4': { label: '特定', color: '#dd524d', bgColor: 'rgba(221, 82, 77, 0.1)' },
- } as const
- return type ? tagMap[type as keyof typeof tagMap] : undefined
- }
- 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()
- getPackageStatus()
- })
- </script>
- <style lang="scss" scoped>
- .task-package-list {
- min-height: 100vh;
- background: #fafafa;
- .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;
- }
- .task-list-controls {
- display: flex;
- align-items: center;
- height: 88rpx;
- padding: 0 18rpx;
- .task-list-controls__tabs {
- display: flex;
- flex: 1;
- height: 100%;
- }
- .task-list-controls__tab {
- position: relative;
- flex: 1;
- height: 100%;
- color: #666666;
- font-size: 32rpx;
- font-weight: 400;
- line-height: 88rpx;
- text-align: center;
- }
- .task-list-controls__tab--active {
- color: #333333;
- font-weight: 600;
- &::after {
- position: absolute;
- bottom: 0;
- left: 50%;
- width: 78rpx;
- height: 6rpx;
- background: #3fa3f1;
- border-radius: 999rpx;
- transform: translateX(-50%);
- content: '';
- }
- }
- .task-list-controls__date {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 280rpx;
- height: 100%;
- color: #333333;
- }
- .task-list-controls__divider {
- width: 2rpx;
- height: 42rpx;
- margin-right: 42rpx;
- background: #eeeeee;
- }
- .task-list-controls__date-text {
- margin-right: 14rpx;
- color: #333333;
- font-size: 32rpx;
- }
- }
- }
- .task-list-content {
- padding: 24rpx 32rpx;
- margin-top: 200rpx;
- .task-card {
- margin-bottom: 24rpx;
- position: relative;
- padding: 28rpx;
- background: #ffffff;
- border-radius: 20rpx;
- box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
- .task-card__header {
- display: flex;
- align-items: flex-start;
- gap: 18rpx;
- }
- .task-card__title {
- flex: 1;
- min-width: 0;
- color: #333333;
- font-size: 34rpx;
- font-weight: 600;
- line-height: 48rpx;
- word-break: break-all;
- }
- :deep(.task-card__tag) {
- display: inline-flex;
- height: 32rpx;
- margin-left: 10rpx;
- padding: 0 10rpx;
- font-size: 22rpx;
- line-height: 30rpx;
- vertical-align: 4rpx;
- border-radius: 6rpx;
- }
- :deep(.task-card__button) {
- flex-shrink: 0;
- min-width: 132rpx;
- height: 58rpx;
- padding: 0 18rpx;
- color: #ffffff;
- font-size: 28rpx;
- background: #3fa3f1;
- border-color: #3fa3f1;
- }
- .task-card__status {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- min-height: 48rpx;
- color: #333333;
- font-size: 26rpx;
- }
- .task-card__dot {
- width: 10rpx;
- height: 10rpx;
- margin-right: 8rpx;
- background: #3fa3f1;
- border-radius: 50%;
- }
- .task-card__info {
- margin-top: 12rpx;
- color: #999999;
- font-size: 26rpx;
- line-height: 42rpx;
- }
- .task-card__meta {
- display: flex;
- flex-wrap: wrap;
- gap: 0 18rpx;
- }
- .task-card__expired {
- z-index: 99;
- position: absolute;
- right: 30rpx;
- bottom: 25rpx;
- width: 150rpx;
- height: 150rpx;
- image {
- width: 150rpx;
- height: 150rpx;
- }
- }
- }
- .task-load-status {
- padding: 12rpx 0 32rpx;
- color: #999999;
- font-size: 24rpx;
- text-align: center;
- }
- }
- .task-empty {
- padding-top: 360rpx;
- }
- }
- </style>
|