| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 | <route lang="json">{  "style": {    "navigationStyle": "custom"  }}</route><script setup lang="ts">import NavbarEvo from '@/components/navbar-evo.vue'import {  activitySignup,  getActivity,  getActivitySignups,  getAppMemberLevelConfigs,  getStudyTour,  getStudyTourSignups,  studyTourSignup,} from '../../../../core/libs/requests'import { bell, map, rightFill } from '@designer-hub/assets/src/assets/svgs'import dayjs from 'dayjs'import BottomAppBar from '@/components/bottom-app-bar.vue'import { useRouter } from '../../../../core/utils/router'import PageHelper from '@/components/page-helper.vue'import { ConfigProviderThemeVars } from 'wot-design-uni'import SectionHeading from '@/components/section-heading.vue'import AvatarGroupCasual from '@/components/avatar-group-casual/avatar-group-casual.vue'import { calendar, clock, funnel, location, user } from '@designer-hub/assets/src/icons'import { signupSuccessDialogBg } from '@designer-hub/assets/src/bgs'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 { 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'import ButtonEvo from '@/components/button-evo.vue'import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html.vue'import { useActivity } from '../../../../composables/activity'import ImageEvo from '@/components/image-evo.vue'import TooltipEvo from '@/components/tooltip-evo.vue'import ActivityAsOf from '../../components/activity-as-of.vue'const themeVars = ref<ConfigProviderThemeVars>({  tableBorderColor: 'white',  tabsNavLineBgColor: 'white',  tabsNavColor: 'white',})const router = useRouter()const id = ref()const type = ref<'activity' | 'studyTour'>()const activityTypes = ref({ activity: '活动', studyTour: '游学' })const tab = ref(0)const request = ref<() => Promise<IResData<Partial<StudyTour> | Partial<Activity>>>>()const { data, run: setData } = useRequest(() => request.value(), { initialData: {} })const { data: signups, run: setSignups } = useRequest(  () => getActivitySignups({ activityId: id.value }),  { initialData: { list: [], total: 0 } },)const { data: levels, run: setLevels } = useRequest(() => getAppMemberLevelConfigs(), {  initialData: [],})const show = ref(false)const successShow = ref(false)const listShow = ref(false)const dominantColor = ref()const isActivity = computed(() => type.value === 'activity')const isStudyTour = computed(() => type.value === 'studyTour')const levelsById = computed(() =>  levels.value.reduce((acc, item) => {    acc[item.id] = item    return acc  }, {}),)const levelsByMemberLevel = computed(() =>  levels.value.reduce((acc, item) => {    acc[item.memberLevel] = item    return acc  }, {}),)const places = computed(() => {  if (isActivity.value && data.value?.activityAllowType === '1') {    return data.value?.activityAllowCount  }  if (isStudyTour.value && data.value?.studyAllowType === '1') {    return data.value?.studyAllowCount  }  return '不限制'})const remainedCount = computed(() => {  if (isActivity.value && data.value?.activityAllowType === '1') {    return data.value?.activityAllowCount - signups.value.total  }  if (isStudyTour.value && data.value?.studyAllowType === '1') {    return data.value?.studyAllowCount - signups.value.total  }  return '不限制'})const infos = computed(() => [  {    icon: clock,    title: '报名时间',    content: [      dayjs(data.value.applyStartTime).format('YYYY.MM.DD HH:mm'),      // dayjs(data.value.applyEndTime).format('YYYY.MM.DD'),    ],    visable: true,  },  {    icon: calendar,    title: `${activityTypes.value[type.value]}时间`,    content: [      dayjs(        data.value.activityStartTime || data.value.studyStartTime || data.value.planStudyStartTime,      ).format('YYYY.MM.DD'),      dayjs(        data.value.activityEndTime || data.value.studyEndTime || data.value.planStudyEndTime,      ).format('YYYY.MM.DD'),    ],    visable: true,  },  {    icon: location,    title: `${activityTypes.value[type.value]}地点`,    content: [data.value.activityAddr || ''],    visable: isActivity.value,  },  {    icon: user,    title: `${activityTypes.value[type.value]}名额`,    content: [      places.value === '不限制' ? `不限制` : `${places.value}人/剩余${remainedCount.value}人`,    ],    visable: true,  },  {    icon: funnel,    title: `等级限制`,    content: [      data.value.memberLevel        ?.map((it) => levelsByMemberLevel.value[String(it)]?.memberLevelName)        .join('、') || '',    ],    visable: true,  },])const { status, statusText, difference, refresh } = useActivity(data.value)const handleConfirm = async () => {  const { data, code, msg } = await (isActivity.value ? activitySignup : studyTourSignup)({    id: id.value,  })  console.log(msg)  if (code === 0) {    // todo: 报名成功弹框    show.value = false    successShow.value = true  }  await setData()}onLoad(async (query: { id: string; type: 'activity' | 'studyTour' }) => {  id.value = query.id  type.value = query.type  if (type.value === 'activity') {    request.value = () => getActivity(id.value)  }  if (type.value === 'studyTour') {    request.value = () => getStudyTour(id.value)  }  await setData()  const { path } = await uni.getImageInfo({ src: data.value.backgroundUrl })  const ctx = uni.createCanvasContext('firstCanvas')  uni    .createSelectorQuery()    .select('#firstCanvas')    .fields({ size: true }, async ({ width, height }: any) => {      // ctx.setFillStyle('#ffffff')      ctx.drawImage(path, 0, 0, width, height)      ctx.draw(true, async () => {        const res1 = await uni.canvasGetImageData({          canvasId: 'firstCanvas',          x: 0,          y: 0,          width: width.toFixed(0),          height: height.toFixed(0),        })        const { data: imageData } = res1        dominantColor.value = `rgb(${getCountsArr(imageData, width, height)})`        console.log(res1)        const a = await extractColorsFromImageData(res1, {          pixels: 1000000,          distance: 0.22,          colorValidator: (red, green, blue, alpha = 255) => alpha > 250,          saturationDistance: 0.2,          lightnessDistance: 0.2,          hueDistance: 0.083333333,        })        console.log(a)        const colors = sort(a, (it: any) => it.intensity, true)        dominantColor.value = a[0].hex      })    })    .exec()  await setSignups()  await setLevels()})onShareAppMessage(() => ({ title: data.value.name, imageUrl: data.value.thumbnailUrl }))onShareTimeline(() => ({ title: data.value.name, imageUrl: data.value.thumbnailUrl }))</script><template>  <div    class="flex-grow bg-white px-3.5 bg-[length:100%_100%]"    :style="{      backgroundColor: `${dominantColor}`,    }"  >    <NavbarEvo transparent dark></NavbarEvo>    <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 absolute top--1000"        canvas-id="firstCanvas"        id="firstCanvas"      ></canvas>      <ImageEvo :src="data?.backgroundUrl"></ImageEvo>      <!-- <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"          class="bg-white/20 rounded-[20px] backdrop-blur-[6px] px-3.5 py-1 flex gap-2.5"        >          <wd-img width="20" height="20" :src="bell"></wd-img>          <div class="text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal">            白金会员王凯峰已报名          </div>          <div class="w-6 bg-black aspect-square rounded-full flex items-center justify-center">            <wd-img width="18" height="18" :src="rightFill"></wd-img>          </div>        </div>        <div v-if="isActivity" class="flex items-center gap-1.25">          <AvatarGroupCasual            :urls="signups.list.map((it) => it.headImgUrl || NetImages.DefaultAvatar)"            :width="40"            :height="40"          ></AvatarGroupCasual>          <div class="text-white/60 text-sm font-normal font-['PingFang_SC'] leading-[10.18px]">            {{ signups.total }}人已报名          </div>        </div>      </div>    </div>    <div class="h-9">      <div v-if="type === 'studyTour'" class="flex items-center h-full mt-9 gap-1.5">        <wd-img width="18" height="18" :src="map"></wd-img>        <div class="text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal">          第一站        </div>      </div>    </div>    <div      class="text-white text-[26px] font-normal font-['PingFang_SC'] leading-[44px] flex items-center gap-4"    >      <!-- 日本研学·东京艺术大学设计游学 -->      <div class="inline-block">{{ data?.name }}</div>      <div class="inline-block py-1.5 px-4 bg-white rounded-[20px] backdrop-blur-[15px]">        <div class="text-[#a60707] text-sm font-normal font-['PingFang_SC'] leading-relaxed">          <!-- {{ getActivityStatusText(data?.applyStartTime, data?.applyEndTime) }} -->          {{ statusText }}        </div>      </div>    </div>    <div      class="px-4 py-6 bg-[#010102]/30 backdrop-blur-[20px] rounded-2xl my-8 flex flex-col gap-3"    >      <!-- {{ levelsById }} -->      <template v-for="(it, i) in infos" :key="i">        <div v-if="it.visable" class="flex items-center gap-1.5">          <wd-img width="16" height="16" :src="it.icon"></wd-img>          <div            class="w-17.5 whitespace-nowrap text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-normal"          >            {{ it.title }}          </div>          <div class="w-3"></div>          <div            class="flex-1 flex break-all items-center text-white text-base font-normal font-['PingFang_SC'] leading-[34px]"          >            <template v-if="it.content.length === 2">              <div class="w-22 text-start">{{ it.content[0] }}</div>              <wd-icon name="play" size="22px"></wd-icon>              <div class="w-22 text-center">{{ it.content[0] }}</div>            </template>            <template v-else>{{ it.content[0] }}</template>          </div>        </div>      </template>    </div>    <div v-if="isStudyTour" class="w-50%">      <wd-config-provider :themeVars="themeVars">        <wd-tabs v-model="tab" class="bg-transparent!" custom-class="bg-transparent!">          <wd-tab title="活动介绍"></wd-tab>          <wd-tab title="行程安排"></wd-tab>        </wd-tabs>      </wd-config-provider>    </div>    <SectionHeading v-if="isActivity" size="lg" title="活动介绍"></SectionHeading>    <div class="mt-5 mx-3.5">      <!-- v-html="data['activityDesc'] || data['studyDesc']" -->      <div        v-if="tab === 0"        class="text-justify text-[#c1c1c1] text-base font-normal font-['PingFang_SC'] leading-relaxed"      >        <!-- <u-parse :content="data['activityDesc'] || data['studyDesc']"></u-parse> -->        <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 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日') }}              <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                  </div>                  <div class="text-white text-sm font-normal font-['PingFang_SC'] leading-normal">                    <!-- 早稻田大学课程 -->                    {{ it.title }}                  </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]"                  >                    <!-- 是位于日本东京都新宿区的一所著名的私立大学。它由早稻田大学的创始人大隈重信于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">                    打卡示例                  </div>                </div>                <img class="w-full rounded-2xl border" :src="it.clockExplainUrl" />              </div>            </div>          </div>        </template>      </div>    </div>    <BottomAppBar fixed placeholder transparent>      <div        class="h-[63px] bg-white/90 rounded-2xl backdrop-blur-[20px] flex items-center gap-1 px-4 box-border"      >        <div class="text-[#ef4343] text-2xl font-normal font-['D-DIN_Exp'] leading-normal">          {{ data.needPointsCount || 0 }}        </div>        <div class="text-black/40 text-base font-normal font-['PingFang_SC'] leading-[34px]">          积分        </div>        <div class="flex-1"></div>        <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">                <ActivityAsOf                  :start-at="data?.applyStartTime || data?.planApplyStartTime"                  :end-at="data?.applyEndTime || data?.planApplyEndTime"                  @end="refresh"                ></ActivityAsOf>              </div>            </div>          </div>          <TooltipEvo            placement="top"            :content="`还差${difference}积分`"            :model-value="status === 'runing' && difference > 0"          >            <div @click="show = true">              <ButtonEvo>{{ data?.ifSingnUp ? '已报名' : '立即报名' }}</ButtonEvo>            </div>          </TooltipEvo>        </div>      </div>    </BottomAppBar>    <wd-action-sheet v-model="show">      <view class="px-3.5 py-10">        <div class="flex gap-5 mb-13.5">          <div class="w-[110px] h-[94px] bg-[#f6f6f6] rounded-2xl">            <wd-img width="100%" height="100%" :src="data.thumbnailUrl"></wd-img>          </div>          <div class="flex flex-col justify-between flex-1">            <div class="text-black text-base font-normal font-['PingFang_SC'] leading-normal">              {{ data.name }}            </div>            <div class="flex items-end gap-1">              <div class="text-[#ef4343] text-[22px] font-normal leading-[22px]">                {{ data.needPointsCount || 0 }}              </div>              <div class="text-black/40 text-sm font-normal font-['PingFang_SC']">积分</div>              <div class="ml-1 text-black/40 text-xs font-normal font-['PingFang_SC']">                剩余:{{ remainedCount || 0 }}              </div>              <div class="flex-1"></div>            </div>          </div>        </div>        <wd-button block :round="false" @click="handleConfirm">确认报名</wd-button>      </view>    </wd-action-sheet>    <wd-overlay :show="listShow" @click="listShow = false">      <view class="flex px-10 h-full items-center justify-center">        <div class="w-full flex flex-col gap-5 aspect-[0.71/1] relative">          <div class="absolute top-0 left-0 right-0 bottom-0 z--1">            <wd-img width="100%" height="100%" :src="signupListDialogBg"></wd-img>          </div>          <div class="h-full box-border py-5 px-7.25 flex flex-col justify-between">            <div class="flex justify-between">              <div class="text-justify text-white text-2xl font-bold font-['Alimama_ShuHeiTi']">                报名详情              </div>            </div>            <div class="flex flex-col justify-center aspect-[0.7/1] gap-5 p-6.5">              <PageHelper                :request="isActivity ? getActivitySignups : getStudyTourSignups"                :query="isActivity ? { activityId: id } : { studyId: id }"                class="flex-grow flex flex-col"              >                <template #default="{ source }">                  <div class="flex flex-col gap-5">                    <template v-for="(it, i) in source.list" :key="i">                      <div                        class="text-black text-sm font-normal font-['PingFang_SC'] leading-normal"                      >                        {{ dayjs(it.createTime).format('YYYY-MM-DD') }} {{ it.name }}已报名                      </div>                    </template>                  </div>                </template>              </PageHelper>            </div>          </div>        </div>      </view>    </wd-overlay>    <wd-overlay :show="successShow" @click="successShow = false">      <view class="flex mx-10 h-full items-center justify-center">        <div class="w-full flex flex-col gap-5 aspect-[1.12/1] relative">          <div class="absolute top-0 left-0 right-0 bottom-0 z--1">            <wd-img width="100%" height="100%" :src="signupSuccessDialogBg"></wd-img>          </div>          <div class="h-full box-border py-5 px-7.25 flex flex-col justify-between">            <div class="flex justify-between">              <div class="text-justify text-white text-2xl font-bold font-['Alimama_ShuHeiTi']">                报名成功              </div>              <wd-icon name="close" color="white" size="22px"></wd-icon>            </div>            <div class="flex flex-col justify-center aspect-[1.46/1] gap-5">              <div class="flex gap-1.5">                <wd-icon name="error-circle" size="22px"></wd-icon>                <div                  class="w-[151px] h-[21px] text-justify text-black text-base font-normal font-['PingFang_SC'] leading-[21px]"                >                  请准时参加活动!                </div>              </div>              <div                class="w-[237px] h-[60px] text-justify text-black/60 text-base font-normal font-['PingFang_SC'] leading-normal"              >                如有问题可咨询官方客服或您的专属经纪人!              </div>            </div>          </div>        </div>      </view>    </wd-overlay>  </div></template><style lang="scss"></style>
 |