Browse Source

feat: 添加活动日程接口,优化活动逻辑,支持响应式属性,更新组件属性

EvilDragon 3 months ago
parent
commit
35494eafe2

+ 2 - 1
packages/app/src/components/image-evo.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-withDefaults(defineProps<{ src?: string }>(), {})
+withDefaults(defineProps<{ src?: string; mode?: 'aspectFill' }>(), {})
 const emits = defineEmits<{ displayed: [] }>()
 const visible = ref(false)
 const imgClass = ref('blur-lg transition duration-500 hover:blur-none')
@@ -17,6 +17,7 @@ const handleLoad = async () => {
     class="w-full h-full"
     width="100%"
     height="100%"
+    :mode="mode"
     :style="{ visibility: visible ? 'visible' : 'hidden' }"
     :src="src"
     :custom-class="`vertical-bottom ${imgClass}`"

+ 32 - 17
packages/app/src/composables/activity.ts

@@ -8,27 +8,40 @@ import { storeToRefs } from 'pinia'
 /**
  * 游学活动
  */
-export const useActivity = (options: Partial<Activity | StudyTour>) => {
+export const useActivity = (options: globalThis.Ref<Partial<Activity | StudyTour>>) => {
   const userStore = useUserStore()
   const { userInfo } = storeToRefs(userStore)
-  const applyStartAt = options?.applyStartTime || options?.planApplyStartTime
-  const applyEndAt = options?.applyEndTime || options?.planApplyEndTime
-  const startAt = options?.activityStartTime || options?.studyStartTime
-  const endAt = options?.activityEndTime || options?.studyEndTime
-  const listItemButtonText = ref(getActivityStatusButtonText(applyStartAt, applyEndAt))
+  const applyStartAt = computed(
+    () => options.value?.applyStartTime || options.value?.planApplyStartTime,
+  )
+  const applyEndAt = computed(() => options.value?.applyEndTime || options.value?.planApplyEndTime)
+  const startAt = computed(() => options.value?.activityStartTime || options.value?.studyStartTime)
+  const endAt = computed(() => options.value?.activityEndTime || options.value?.studyEndTime)
+  const listItemButtonText = ref(getActivityStatusButtonText(applyStartAt.value, applyEndAt.value))
   const tooltipShow = ref(true)
   const difference = computed(() =>
-    options?.needPointsType === '1' ? options?.needPointsCount - userInfo.value?.level?.point : 0,
+    options.value?.needPointsType === '1'
+      ? options.value?.needPointsCount - userInfo.value?.level?.point
+      : 0,
   )
   const getActivityStatus = () => {
     const now = new Date()
-    if (dayjs(now).isBefore(dayjs(applyStartAt))) {
+    if (dayjs(now).isBefore(dayjs(applyStartAt.value))) {
       return 'waiting'
-    } else if (dayjs(now).isAfter(dayjs(applyStartAt)) && dayjs(now).isBefore(dayjs(applyEndAt))) {
+    } else if (
+      dayjs(now).isAfter(dayjs(applyStartAt.value)) &&
+      dayjs(now).isBefore(dayjs(applyEndAt.value))
+    ) {
       return 'registering'
-    } else if (dayjs(now).isAfter(dayjs(applyEndAt)) && dayjs(now).isBefore(dayjs(startAt))) {
+    } else if (
+      dayjs(now).isAfter(dayjs(applyEndAt.value)) &&
+      dayjs(now).isBefore(dayjs(startAt.value))
+    ) {
       return 'closed'
-    } else if (dayjs(now).isAfter(dayjs(startAt)) && dayjs(now).isBefore(dayjs(endAt))) {
+    } else if (
+      dayjs(now).isAfter(dayjs(startAt.value)) &&
+      dayjs(now).isBefore(dayjs(endAt.value))
+    ) {
       return 'running'
     } else {
       return 'overdue'
@@ -46,16 +59,18 @@ export const useActivity = (options: Partial<Activity | StudyTour>) => {
   const statusText = ref(getActivityStatusText())
   const detailButtonText = ref()
   const refresh = () => {
-    listItemButtonText.value = getActivityStatusButtonText(applyStartAt, applyEndAt)
+    listItemButtonText.value = getActivityStatusButtonText(applyStartAt.value, applyEndAt.value)
     status.value = getActivityStatus()
     statusText.value = getActivityStatusText()
   }
+  watch(
+    () => options.value,
+    () => {
+      console.log(1111)
+      refresh()
+    },
+  )
   return {
-    getActivityStatusButtonText: () =>
-      getActivityStatusButtonText(
-        options?.applyStartTime || options?.planApplyStartTime,
-        options?.applyEndTime || options?.planApplyEndTime,
-      ),
     getActivityStatusText: () => getActivityStatusText(),
     status,
     listItemButtonText,

+ 26 - 15
packages/app/src/core/libs/models.ts

@@ -198,6 +198,27 @@ export interface Category {
   level: number
   children?: Category[]
 }
+/**
+ * 活动日程
+ */
+export interface Schedule {
+  createTime: number
+  updateTime: number
+  creator: string
+  updater: string
+  deleted: boolean
+  id: number
+  studyId: number
+  travelDate: number
+  travelTime: number
+  title: string
+  travelDesc: string
+  clockExplainDesc: string
+  clockExplainUrl: string
+}
+/**
+ * 线下活动
+ */
 export interface Activity {
   id: number
   name: string
@@ -295,6 +316,10 @@ export interface Activity {
    * 已报名
    */
   ifSingnUp: boolean
+  /**
+   * 类型补充字段
+   */
+  studyTravelList?: Schedule[]
 }
 export interface StudyTour {
   id: number
@@ -334,21 +359,7 @@ export interface StudyTour {
   viewCount: any
   createTime: number
   ifSingnUp: boolean
-  studyTravelList: {
-    createTime: number
-    updateTime: number
-    creator: string
-    updater: string
-    deleted: boolean
-    id: number
-    studyId: number
-    travelDate: number
-    travelTime: number
-    title: string
-    travelDesc: string
-    clockExplainDesc: string
-    clockExplainUrl: string
-  }[]
+  studyTravelList: Schedule[]
   /**
    * 补充字段
    */

+ 59 - 37
packages/app/src/pages/home/activity/detail/index.vue

@@ -30,7 +30,7 @@ import { NetImages } from '../../../../core/libs/net-images'
 import signupListDialogBg from '@designer-hub/assets/src/libs/assets/signupListDialogBg'
 import { getActivityStatusText, getCountsArr } from '../../../../core/utils/common'
 import { extractColorsFromImageData } from 'extract-colors/lib/extract-colors.mjs'
-import { sort } from 'radash'
+import { group, mapEntries, sort } from 'radash'
 import { Activity, StudyTour } from '../../../../core/libs/models'
 import mapLocation from '@designer-hub/assets/src/libs/assets/mapLocation'
 import cameraWhite from '@designer-hub/assets/src/libs/assets/cameraWhite'
@@ -143,8 +143,12 @@ const infos = computed(() => [
     visable: true,
   },
 ])
+const schedules = computed(() =>
+  group(data.value?.studyTravelList, (it) => dayjs(it?.travelTime).format('YYYY-MM-DD')),
+)
 
-const { status, statusText, difference, refresh } = useActivity(data.value)
+const activity = useActivity(data)
+const { status, statusText, difference, refresh } = activity
 
 const handleConfirm = async () => {
   const { data, code, msg } = await (isActivity.value ? activitySignup : studyTourSignup)({
@@ -319,51 +323,66 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
         <mpHtml :content="data['activityDesc'] || data['studyDesc']"></mpHtml>
       </div>
 
-      <div v-if="tab === 1 && 'studyTravelList' in data">
-        <template v-for="(it, i) in data.studyTravelList" :key="i">
+      <div v-if="tab === 1 && 'studyTravelList' in data" class="flex flex-col gap-6">
+        <!-- {{ mapEntries(schedules, (key, value) => [key, value]) }} -->
+        <template v-for="([key, items], i) in Object.entries(schedules)" :key="key">
           <div class="flex flex-col gap-6">
             <div class="text-white text-base font-normal font-['PingFang_SC'] leading-normal">
               <!-- 6月26日 第一天 -->
-              {{ dayjs(it?.travelTime).format('MM月DD日') }}
+              {{ dayjs(key).format('MM月DD日') }}
               <span class="ml-1">{{ `第${i + 1}天` }}</span>
             </div>
-            <div class="flex gap-2">
-              <div class="w-7 h-7 bg-white/10 rounded-full flex items-center justify-center">
-                <wd-img width="82%" height="82%" :src="mapLocation"></wd-img>
-              </div>
-              <div class="flex-1 flex flex-col gap-4">
-                <div class="h-7 flex items-center gap-2.5">
-                  <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-normal">
-                    9:00
+            <template v-for="(item, index) in items" :key="index">
+              <div class="flex gap-2">
+                <div class="w-7 h-7 bg-white/10 rounded-full flex items-center justify-center">
+                  <wd-img width="82%" height="82%" :src="mapLocation"></wd-img>
+                </div>
+                <div class="flex-1 flex flex-col gap-4">
+                  <div class="h-7 flex items-center gap-2.5">
+                    <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-normal">
+                      {{ dayjs(item?.travelTime).format('HH:mm') }}
+                    </div>
+                    <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-normal">
+                      <!-- 早稻田大学课程 -->
+                      {{ item.title }}
+                    </div>
                   </div>
-                  <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-normal">
-                    <!-- 早稻田大学课程 -->
-                    {{ it.title }}
+                  <div class="">
+                    <span
+                      class="text-[#c1c1c1] text-sm font-normal font-['PingFang_SC'] leading-[23px]"
+                    >
+                      行程介绍:
+                    </span>
+                    <span
+                      class="text-[#ababab] text-sm font-normal font-['PingFang_SC'] leading-[23px]"
+                    >
+                      <!-- 是位于日本东京都新宿区的一所著名的私立大学。它由早稻田大学的创始人大隈重信于1882年创立,是日本超级国际化大学计划(Top
+                    Global University Project)选定的大学之一,也是日本顶尖的高等教育机构之一。 -->
+                      {{ item.travelDesc }}
+                    </span>
                   </div>
-                </div>
-                <div class="">
-                  <span
-                    class="text-[#c1c1c1] text-sm font-normal font-['PingFang_SC'] leading-[23px]"
-                  >
-                    行程介绍:
-                  </span>
-                  <span
-                    class="text-[#ababab] text-sm font-normal font-['PingFang_SC'] leading-[23px]"
+                  <div class="flex items-center gap-1">
+                    <wd-img width="16" height="16" :src="cameraWhite"></wd-img>
+                    <div class="text-white text-xs font-normal font-['PingFang_SC'] leading-normal">
+                      打卡示例
+                    </div>
+                  </div>
+                  <!-- <img class="w-full rounded-2xl border" :src="it.clockExplainUrl" /> -->
+                  <wd-img
+                    v-if="(item.clockExplainUrl ?? '') !== ''"
+                    width="100%"
+                    custom-class="rounded-2xl overflow-hidden"
+                    :src="item.clockExplainUrl"
+                    mode="widthFix"
+                  ></wd-img>
+                  <div
+                    class="text-white/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
                   >
-                    <!-- 是位于日本东京都新宿区的一所著名的私立大学。它由早稻田大学的创始人大隈重信于1882年创立,是日本超级国际化大学计划(Top
-                    Global University Project)选定的大学之一,也是日本顶尖的高等教育机构之一。 -->
-                    {{ it.travelDesc }}
-                  </span>
-                </div>
-                <div class="flex items-center gap-1">
-                  <wd-img width="16" height="16" :src="cameraWhite"></wd-img>
-                  <div class="text-white text-xs font-normal font-['PingFang_SC'] leading-normal">
-                    打卡示例
+                    {{ item.clockExplainDesc }}
                   </div>
                 </div>
-                <img class="w-full rounded-2xl border" :src="it.clockExplainUrl" />
               </div>
-            </div>
+            </template>
           </div>
         </template>
       </div>
@@ -382,7 +401,10 @@ onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailU
         <div>
           <div class="relative">
             <div class="absolute bottom-3 left-0 right-0 flex flex-col justify-center items-center">
-              <div class="bg-[#3b3c46] rounded-[60px] flex items-center py-1.5 px-4">
+              <div
+                v-if="['waiting', 'registering'].includes(status)"
+                class="bg-[#3b3c46] rounded-[60px] flex items-center py-1.5 px-4"
+              >
                 <ActivityAsOf
                   :start-at="data?.applyStartTime || data?.planApplyStartTime"
                   :end-at="data?.applyEndTime || data?.planApplyEndTime"

+ 91 - 99
packages/app/src/pages/home/schedule/index.vue

@@ -4,8 +4,7 @@ style:
   navigationBarTitleText: 游学日程
 </route>
 <script setup lang="ts">
-import TiltedButton from '@/components/tilted-button.vue'
-import { getMyStudyTours, getSchedule, getUserInfo } from '../../../core/libs/requests'
+import { getMyStudyTours } from '../../../core/libs/requests'
 import { camera, map } from '../../../core/libs/svgs'
 import { useUserStore } from '../../../store'
 import { storeToRefs } from 'pinia'
@@ -15,6 +14,7 @@ import NavbarEvo from '@/components/navbar-evo.vue'
 import BottomAppBar from '@/components/bottom-app-bar.vue'
 import ButtonEvo from '@/components/button-evo.vue'
 import { useRouter } from '../../../core/utils/router'
+import ImageEvo from '@/components/image-evo.vue'
 
 const router = useRouter()
 const userStore = useUserStore()
@@ -27,122 +27,114 @@ const currentStudyTour = computed(() =>
     (it) => dayjs(it.studyStartTime).isBefore(dayjs()) && dayjs(it.studyEndTime).isAfter(dayjs()),
   ),
 )
-const schedules = computed(() =>
-  group(currentStudyTour.value?.studyTravelDOList, (it) =>
-    dayjs(it?.travelTime).format('YYYY-MM-DD'),
-  ),
+const schedules = computed(
+  () =>
+    group(currentStudyTour.value?.studyTravelDOList || [], (it) =>
+      dayjs(it?.travelTime).format('YYYY-MM-DD'),
+    ) || { [dayjs().format('YYYY-MM-DD')]: [] },
 )
 
-onMounted(() => {
-  setStudyTours()
+onMounted(async () => {
+  await setStudyTours()
 })
 </script>
 <template>
-  <view class="">
+  <view class="flex-grow flex flex-col">
     <NavbarEvo transparent dark></NavbarEvo>
-    <view class="bg-black w-full pos-relative aspect-[1.26/1]">
-      <wd-img width="100%" height="100%" mode="aspectFill" :src="currentStudyTour?.backgroundUrl" />
+    <view class="bg-black w-full aspect-[1.26/1]">
+      <ImageEvo :src="currentStudyTour?.backgroundUrl"></ImageEvo>
       <div
-        class="w-[375px] h-[90px] bg-gradient-to-t from-black to-black/0 absolute left-0 bottom-0 w-full flex items-center"
-      >
-        <!-- <view class="mx-7">
-          <wd-button plain custom-class="bg-transparent! border-white! text-white!">
-            02:30
-          </wd-button>
-        </view> -->
-      </div>
+        class="aspect-[4.17/1] bg-gradient-to-t from-black to-black/0 absolute left-0 bottom-0 w-full flex items-center"
+      ></div>
     </view>
-    <view class="bg-white relative bottom-4 rounded-t-2xl p-6.5">
-      <view class="border-b border-black/10 border-b-solid pb-5">
-        <div class="text-black/90 text-2xl font-normal font-['PingFang_SC'] leading-normal">
-          {{ currentStudyTour?.name }}
-        </div>
-        <view class="flex mt-5">
-          <wd-img
-            custom-class="rounded-full overflow-hidden mr-1"
-            width="22"
-            height="22"
-            :src="userInfo?.avatar"
-          />
-          <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-normal">
-            {{ userInfo?.nickname }}
+    <div class="flex-grow bg-white">
+      <view class="flex-grow bg-white relative bottom-4 rounded-t-2xl p-6.5">
+        <view class="border-b border-black/10 border-b-solid pb-5">
+          <div class="text-black/90 text-2xl font-normal font-['PingFang_SC'] leading-normal">
+            {{ currentStudyTour?.name }}
           </div>
-          <div class="flex text-black/40 text-base font-normal font-['PingFang_SC'] leading-normal">
-            <view class="mx-3.5">|</view>
-            <!-- 6月26日 第二天 -->
-            {{ dayjs().format('M月D日') }}
-            第{{
-              Object.keys(schedules).findIndex((it) => it === dayjs().format('YYYY-MM-DD')) + 1
-            }}天
-          </div>
-        </view>
-      </view>
-      <!-- <template
-        v-for="(date, index) in group(currentStudyTour?.studyTravelDOList, (it) =>
-          dayjs(it?.travelTime).format('YYYY-MM-DD'),
-        )"
-        :key="index"
-      > -->
-      <template
-        v-for="(item, itemIndex) in schedules?.[dayjs().format('YYYY-MM-DD')]"
-        :key="itemIndex"
-      >
-        <view class="grid grid-gap-2 mt-8">
-          <view class="col-start-1 row-start-1 flex items-center justify-center">
-            <view
-              class="w-[27px] h-[27px] bg-[#f6f6f6] rounded-full flex justify-center items-center"
-            >
-              <wd-img width="16" height="16" :src="map"></wd-img>
-            </view>
-          </view>
-          <view class="col-start-2 row-start-1 flex">
+          <view class="flex mt-5">
+            <wd-img
+              custom-class="rounded-full overflow-hidden mr-1"
+              width="22"
+              height="22"
+              :src="userInfo?.avatar"
+            />
+            <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-normal">
+              {{ userInfo?.nickname }}
+            </div>
             <div
-              class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal mr-5"
+              class="flex text-black/40 text-base font-normal font-['PingFang_SC'] leading-normal"
             >
-              {{ dayjs(item?.travelTime).format('HH:mm') }}
-            </div>
-            <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal">
-              {{ item.title }}
+              <view class="mx-3.5">|</view>
+              {{ dayjs().format('M月D日') }}
+              第{{
+                Object.keys(schedules || []).findIndex(
+                  (it) => it === dayjs().format('YYYY-MM-DD'),
+                ) + 1
+              }}天
             </div>
           </view>
-          <view
-            class="col-start-2 row-start-2 border-b border-black/10 pb-5"
-            :class="`${itemIndex < (schedules?.[dayjs().format('YYYY-MM-DD')] || []).length - 1 ? 'border-b-solid' : ''}`"
-          >
-            <div class="">
-              <span class="text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[23px]">
-                行程介绍:
-              </span>
-              <span
-                class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[23px]"
-                v-html="item.travelDesc"
-              ></span>
-            </div>
-            <view class="flex items-center my-4">
-              <wd-img width="16" height="16" :src="camera"></wd-img>
+        </view>
+        <template
+          v-for="(item, itemIndex) in schedules?.[dayjs().format('YYYY-MM-DD')]"
+          :key="itemIndex"
+        >
+          <view class="grid grid-gap-2 mt-8">
+            <view class="col-start-1 row-start-1 flex items-center justify-center">
+              <view
+                class="w-[27px] h-[27px] bg-[#f6f6f6] rounded-full flex justify-center items-center"
+              >
+                <wd-img width="16" height="16" :src="map"></wd-img>
+              </view>
+            </view>
+            <view class="col-start-2 row-start-1 flex">
               <div
-                class="ml-1 text-black/90 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal mr-5"
               >
-                打卡示例
+                {{ dayjs(item?.travelTime).format('HH:mm') }}
+              </div>
+              <div class="text-black/90 text-base font-normal font-['PingFang_SC'] leading-normal">
+                {{ item.title }}
               </div>
             </view>
-            <div class="w-[285px]">
-              <img
-                v-if="item.clockExplainUrl"
-                class="w-[285px] h-[157px] rounded-lg"
-                :src="item.clockExplainUrl"
-              />
-            </div>
-            <div
-              class="mt-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
+            <view
+              class="col-start-2 row-start-2 border-b border-black/10 pb-5"
+              :class="`${itemIndex < (schedules?.[dayjs().format('YYYY-MM-DD')] || []).length - 1 ? 'border-b-solid' : ''}`"
             >
-              {{ item.clockExplainDesc }}
-            </div>
+              <div class="">
+                <span class="text-black/60 text-sm font-normal font-['PingFang_SC'] leading-[23px]">
+                  行程介绍:
+                </span>
+                <span class="text-black/40 text-sm font-normal font-['PingFang_SC'] leading-[23px]">
+                  {{ item.travelDesc }}
+                </span>
+              </div>
+              <view class="flex items-center my-4">
+                <wd-img width="16" height="16" :src="camera"></wd-img>
+                <div
+                  class="ml-1 text-black/90 text-xs font-normal font-['PingFang_SC'] leading-normal"
+                >
+                  打卡示例
+                </div>
+              </view>
+              <div class="w-[285px]">
+                <img
+                  v-if="item.clockExplainUrl"
+                  class="w-[285px] h-[157px] rounded-lg"
+                  :src="item.clockExplainUrl"
+                />
+              </div>
+              <div
+                class="mt-2.5 text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal"
+              >
+                {{ item.clockExplainDesc }}
+              </div>
+            </view>
           </view>
-        </view>
-      </template>
-      <!-- </template> -->
-    </view>
+        </template>
+      </view>
+    </div>
     <BottomAppBar fixed placeholder>
       <div
         class="p-3.5 bg-white/90 rounded-2xl backdrop-blur-[20px] bottom-4 left-0 right-0 flex items-center justify-between gap-1"

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

@@ -16,9 +16,8 @@ const props = defineProps<{
 }>()
 
 const router = useRouter()
-const { listItemButtonText, statusText, status, difference, refresh } = useActivity(
-  omit(props.options, ['levelsByMemberLevel', 'index']),
-)
+const activityOptions = ref(omit(props.options, ['levelsByMemberLevel', 'index']))
+const { listItemButtonText, statusText, status, difference, refresh } = useActivity(activityOptions)
 const toDetail = () => {
   router.push(`/pages/home/activity/detail/index?id=${props.options?.id}&type=studyTour`)
 }