Browse Source

feat(study-tour): 添加游学活动列表和详情页面

- 新增游学活动列表页面,展示推荐的游学活动
- 实现游学活动详情页面,包括活动信息、报名状态等- 添加注册卡组件,用于展示活动报名信息
- 优化活动列表项组件,增加游学活动相关字段
EvilDragon 4 months ago
parent
commit
ed458d09af

+ 7 - 2
packages/app/src/pages/home/activity/detail/index.vue

@@ -208,9 +208,14 @@ onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {
     }"
   >
     <NavbarEvo transparent dark></NavbarEvo>
-    <div class="aspect-[1.26/1] relative mx--3.5">
+    <div class="aspect-[1.26/1] relative mx--3.5 relative">
       <!-- <wd-img width="100%" height="100%" :src="data.bannerUrl?.at(0)"></wd-img> -->
-      <canvas class="w-full h-full" canvas-id="firstCanvas" id="firstCanvas"></canvas>
+      <canvas
+        class="w-full h-full absolute top--1000"
+        canvas-id="firstCanvas"
+        id="firstCanvas"
+      ></canvas>
+      <wd-img width="100%" height="100%" :src="data?.backgroundUrl"></wd-img>
       <div class="absolute left-3.5 bottom-3" @click="listShow = true">
         <div
           v-if="isStudyTour"

+ 6 - 3
packages/app/src/pages/home/components/register-card.vue

@@ -1,6 +1,6 @@
 <script lang="ts" setup>
 import Card from '@/components/card.vue'
-import { Activity, DictType } from '../../../core/libs/models'
+import { Activity, DictType, StudyTour } from '../../../core/libs/models'
 import dayjs from 'dayjs'
 import TiltedButton from '@/components/tilted-button.vue'
 import { getActivitySignups, getByDictType } from '../../../core/libs/requests'
@@ -8,7 +8,10 @@ import { NetImages } from '../../../core/libs/net-images'
 import { useRouter } from '../../../core/utils/router'
 import { getActivityStatusButtonText, getActivityStatusText } from '../../../core/utils/common'
 
-const props = defineProps<{ customClass?: string; options?: Activity }>()
+const props = defineProps<{
+  customClass?: string
+  options?: Activity & StudyTour
+}>()
 const router = useRouter()
 const { data: signups, run: setSignups } = useRequest(
   () => getActivitySignups({ activityId: props.options!.id.toString() }),
@@ -87,7 +90,7 @@ onMounted(async () => {
               {{
                 options?.ifSingnUp
                   ? '已报名'
-                  : getActivityStatusButtonText(options?.applyStartTime, options?.activityEndTime)
+                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
               }}
             </tilted-button>
           </view>

+ 104 - 0
packages/app/src/pages/home/study-tour/components/register-card.vue

@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import Card from '@/components/card.vue'
+import { Activity, StudyTour } from '../../../../core/libs/models'
+import dayjs from 'dayjs'
+import TiltedButton from '@/components/tilted-button.vue'
+import { getActivitySignups, getStudyTourSignups } from '../../../../core/libs/requests'
+import { NetImages } from '../../../../core/libs/net-images'
+import { useRouter } from '../../../../core/utils/router'
+import { getActivityStatusButtonText, getActivityStatusText } from '../../../../core/utils/common'
+
+const props = defineProps<{
+  customClass?: string
+  options?: StudyTour & { levelsByMemberLevel }
+}>()
+const router = useRouter()
+const { data: signups, run: setSignups } = useRequest(
+  () => getStudyTourSignups({ studyId: props.options!.id.toString() }),
+  { initialData: { list: [], total: 0 } },
+)
+onMounted(async () => {
+  await setSignups()
+})
+</script>
+<template>
+  <div @click="router.push(`/pages/home/activity/detail/index?id=${options?.id}&type=studyTour`)">
+    <Card custom-class="w-full p-0! relative aspect-[0.75/1]">
+      <div class="absolute left-0 top-0 right-0 bottom-0">
+        <wd-img
+          custom-class="vertical-bottom"
+          width="100%"
+          height="100%"
+          :src="options?.bannerUrl"
+        ></wd-img>
+      </div>
+      <div
+        class="w-[63px] h-[29px] bg-black/60 rounded-[20px] backdrop-blur-[15px] absolute top-5 right-3.5 flex items-center justify-center"
+      >
+        <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-relaxed">
+          {{ getActivityStatusText(options?.applyStartTime, options?.applyEndTime) }}
+        </div>
+      </div>
+      <view class="absolute bottom-0 left-0 right-0">
+        <view class="flex items-center mx-4 my-2.5 gap-1">
+          <avatar-group-casual
+            :show-number="3"
+            :urls="signups.list.map((it) => it.avatar || NetImages.DefaultAvatar)"
+          ></avatar-group-casual>
+          <div
+            class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]"
+          >
+            {{ signups.total }}人已报名
+          </div>
+        </view>
+        <div class="bg-[#27130d]/50 rounded-bl-2xl rounded-br-2xl backdrop-blur-[20px] p-3.5">
+          <div
+            class="w-[293px] text-white text-xl font-normal font-['PingFang_SC'] leading-relaxed"
+          >
+            {{ options?.name }}
+          </div>
+          <view class="flex items-center">
+            <div class="text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[34px]">
+              游学时间:
+            </div>
+            <div
+              class="text-white/60 text-base font-normal font-['PingFang_SC'] leading-[34px] flex items-center gap-1"
+            >
+              <div class="text-center">
+                {{ dayjs(options?.studyStartTime || options?.planStudyStartTime).format('MM.DD') }}
+              </div>
+              <wd-icon name="play" size="22px"></wd-icon>
+              <div class="text-center">
+                {{ dayjs(options?.studyEndTime || options?.planStudyEndTime).format('MM.DD') }}
+              </div>
+            </div>
+          </view>
+          <div
+            class="text-justify text-white/60 text-sm font-normal font-['PingFang_SC'] leading-relaxed"
+          >
+            等级限制:{{
+              options?.memberLevel
+                ?.map((it) => options?.levelsByMemberLevel[String(it)]?.memberLevelName)
+                ?.join('、')
+            }}
+          </div>
+          <view class="flex items-center justify-between">
+            <view class="flex items-end">
+              <div class="text-white text-3xl font-bold font-['D-DIN_Exp']">
+                {{ options?.needPointsCount }}
+              </div>
+              <div class="ml-1 text-white/60 text-sm font-normal font-['PingFang_SC']">积分</div>
+            </view>
+            <tilted-button custom-class="" size="large" color="white">
+              {{
+                options?.ifSingnUp
+                  ? '已报名'
+                  : getActivityStatusButtonText(options?.applyStartTime, options?.applyEndTime)
+              }}
+            </tilted-button>
+          </view>
+        </div>
+      </view>
+    </Card>
+  </div>
+</template>

+ 5 - 2
packages/app/src/pages/home/study-tour/components/study-tour-card.vue

@@ -8,7 +8,10 @@ import { NetImages } from '../../../../core/libs/net-images'
 import ActivityCountDown from '../../components/activity-count-down.vue'
 import { getActivityStatusButtonText, getActivityStatusText } from '../../../../core/utils/common'
 
-const props = defineProps<{ customClass?: string; options?: StudyTour & { levelsByMemberLevel } }>()
+const props = defineProps<{
+  customClass?: string
+  options?: StudyTour & { levelsByMemberLevel; index }
+}>()
 
 const router = useRouter()
 const toDetail = () => {
@@ -26,7 +29,7 @@ const toDetail = () => {
       <div class="flex gap-1">
         <wd-img width="23" height="23" :src="map"></wd-img>
         <div class="text-white text-base font-normal font-['PingFang_SC'] leading-relaxed">
-          第
+          第{{ options?.index + 1 }}
         </div>
         <div class="flex-1"></div>
         <div class="bg-[#f3f3f3] rounded-[20px] backdrop-blur-[15px] px-4 py-1.5">

+ 43 - 12
packages/app/src/pages/home/study-tour/index.vue

@@ -12,14 +12,23 @@ import MomentItem from '@/components/moment-item.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import ClassItem from '../components/class-item.vue'
 import TimeLine from './components/time-line.vue'
-import { getBanners, getCircles, getContents } from '../../../core/libs/requests'
+import {
+  getAppMemberLevelConfigs,
+  getBanners,
+  getCircles,
+  getContents,
+  getStudyTours,
+} from '../../../core/libs/requests'
 import { NetImages } from '../../../core/libs/net-images'
 import { BannerMode } from '../../../core/libs/models'
 import { useRouter } from '../../../core/utils/router'
 import PageHelper from '@/components/page-helper.vue'
+import RegisterCard from './components/register-card.vue'
 
 const router = useRouter()
-const { data: studyTours, run: setStudyTours } = useRequest(() => getContents({ contentType: '1' }))
+const { data: studyTours, run: setStudyTours } = useRequest(() =>
+  getStudyTours({ headRecommend: 1 }),
+)
 const { data: classmates, run: setClassmates } = useRequest(
   () => getContents({ contentCategory: '101', pageSize: '2' }),
   { initialData: { list: [] } },
@@ -28,10 +37,17 @@ const { data: banners, run: setBanners } = useRequest(
   () => getBanners({ mode: BannerMode.StudyTour }),
   { initialData: [] },
 )
+const { data: levels, run: setLevels } = useRequest(() => getAppMemberLevelConfigs(), {
+  initialData: [],
+})
+const levelsByMemberLevel = computed(() =>
+  levels.value.reduce((acc, item) => {
+    acc[item.memberLevel] = item
+    return acc
+  }, {}),
+)
 onMounted(async () => {
-  await setStudyTours()
-  await setClassmates()
-  await setBanners()
+  await Promise.all([setLevels(), setStudyTours(), setClassmates(), setBanners()])
 })
 </script>
 <template>
@@ -39,15 +55,30 @@ onMounted(async () => {
     <div class="">
       <!-- <TimeLine></TimeLine> -->
       <template v-if="banners.length">
-        <wd-img
-          width="100%"
-          height="100%"
-          custom-class="aspect-[1.73/1]"
-          :src="banners[0].bannerImgUrl"
-          @click="router.push(`/pages/home/study-tour/list`)"
-        ></wd-img>
+        <div class="aspect-[1.73/1]">
+          <wd-img
+            width="100%"
+            height="100%"
+            custom-class="aspect-[1.73/1]"
+            :src="banners[0].bannerImgUrl"
+            @click="router.push(`/pages/home/study-tour/list`)"
+          ></wd-img>
+        </div>
       </template>
     </div>
+    <!-- <PageHelper :request="getActivities" :query="{ headRecommend: 1 }">
+      <template #default="{ source }"> -->
+    <template v-if="studyTours.list.length">
+      <swiper class="aspect-[0.75/1] rounded-[20px] overflow-hidden">
+        <template v-for="(it, i) in studyTours.list" :key="i">
+          <swiper-item>
+            <RegisterCard :options="{ ...it, levelsByMemberLevel }"></RegisterCard>
+          </swiper-item>
+        </template>
+      </swiper>
+    </template>
+    <!-- </template>
+    </PageHelper> -->
     <!-- <card custom-class="p-0!">
       <view class="relative">
         <wd-img

+ 1 - 1
packages/app/src/pages/home/study-tour/list.vue

@@ -42,7 +42,7 @@ onMounted(async () => {
             <div class="mx--2.5 my--2.5">
               <StudyTourCard
                 custom-class=""
-                :options="{ ...it, levelsByMemberLevel }"
+                :options="{ ...it, levelsByMemberLevel, index }"
               ></StudyTourCard>
             </div>
           </template>

+ 1 - 1
packages/app/src/pages/mine/levels/index.vue

@@ -25,7 +25,7 @@ const handleSwiperChange = ({ detail }) => {
 }
 onMounted(async () => {
   await setLevelConfigs()
-  current.value = levelConfigs.value?.findIndex(
+  current.value = sort(levelConfigs.value, (it) => it.memberLevel)?.findIndex(
     (it) => it.memberLevel === userInfo.value?.level?.level,
   )
 })