Pārlūkot izejas kodu

Merge branch 'mp' of http://49.234.186.218:9000/root/ieplus-app into mp

abpcoder 9 stundas atpakaļ
vecāks
revīzija
84d24a94fb

+ 6 - 0
src/App.vue

@@ -50,4 +50,10 @@ image {
 .uni-modal__btn {
   font-size: 16px;
 }
+
+#u-a-p,
+#u-a-t {
+  z-index: 3000;
+  position: relative;
+}
 </style>

+ 75 - 79
src/pagesMain/pages/index/index.vue

@@ -1,36 +1,32 @@
 <template>
-    <ie-page bg-color="white">
-        <ie-navbar transparent bg-color="#FFFFFF" :placeholder="false" custom-back :click-hover="false">
-            <template #headerLeft>
-                <view class="flex items-center gap-7 text-fore-title">
-                    <view class="text-38 font-bold">{{ orgName }}</view>
-                    <text>·</text>
-                    <view class="text-30 font-medium">升学备考好帮手</view>
-                </view>
-            </template>
-            <template #headerRight="{ isTransparent }">
-                <view v-if="userStore.getLocation" class="ml-10 flex items-center gap-x-4"
-                      @click="handleChangeLocation">
-                    <uv-icon name="map" size="16" :color="isTransparent ? '#333' : 'var(--primary-color)'"/>
-                    <text class="text-30 font-medium"
-                          :class="[isTransparent ? 'text-fore-title' : 'text-primary']">
-                        {{ userStore.getLocation }}
-                    </text>
-                </view>
-            </template>
-        </ie-navbar>
-        <view class="absolute top-0 left-0 right-0 z-0 h-[489rpx] bg-gradient-to-b from-[#6ACAFF] to-white"></view>
-        <view class="relative z-2" :style="{ paddingTop: baseStickyTop + 10 + 'px' }">
-            <index-banner/>
-            <index-map/>
-            <index-guide v-if="false" @detail="handleDetail"/>
-            <index-news @detail="handleDetail"/>
+  <ie-page bg-color="white">
+    <ie-navbar transparent bg-color="#FFFFFF" :placeholder="false" custom-back :click-hover="false">
+      <template #headerLeft>
+        <view class="flex items-center gap-7 text-fore-title">
+          <view class="text-38 font-bold">{{ orgName }}</view>
         </view>
-        <template #tabbar>
-            <ie-tabbar :active="0"/>
-            <index-popup ref="popupRef"/>
-        </template>
-    </ie-page>
+      </template>
+      <template #headerRight="{ isTransparent }">
+        <view v-if="userStore.getLocation" class="ml-10 flex items-center gap-x-4" @click="handleChangeLocation">
+          <uv-icon name="map" size="16" :color="isTransparent ? '#333' : 'var(--primary-color)'" />
+          <text class="text-30 font-medium" :class="[isTransparent ? 'text-fore-title' : 'text-primary']">
+            {{ userStore.getLocation }}
+          </text>
+        </view>
+      </template>
+    </ie-navbar>
+    <view class="absolute top-0 left-0 right-0 z-0 h-[489rpx] bg-gradient-to-b from-[#6ACAFF] to-white"></view>
+    <view class="relative z-2" :style="{ paddingTop: baseStickyTop + 10 + 'px' }">
+      <index-banner />
+      <index-map />
+      <index-guide v-if="false" @detail="handleDetail" />
+      <index-news @detail="handleDetail" />
+    </view>
+    <template #tabbar>
+      <ie-tabbar :active="0" />
+      <index-popup ref="popupRef" />
+    </template>
+  </ie-page>
 </template>
 
 <script lang="ts" setup>
@@ -40,78 +36,78 @@ import IndexNews from './components/index-news.vue';
 import IndexMap from "./components/index-map.vue";
 import indexPopup from './components/index-popup.vue';
 
-import {useUserStore} from '@/store/userStore';
+import { useUserStore } from '@/store/userStore';
 // @ts-ignore
-import {useTransferPage} from '@/hooks/useTransferPage';
-import {useNavbar} from '@/hooks/useNavbar';
-import {onPageShow} from '@dcloudio/uni-app';
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { useNavbar } from '@/hooks/useNavbar';
+import { onPageShow } from '@dcloudio/uni-app';
 
-const {routes, transferTo} = useTransferPage();
-const {baseStickyTop} = useNavbar();
+const { routes, transferTo } = useTransferPage();
+const { baseStickyTop } = useNavbar();
 const scrollTop = ref(0);
 const isHide = ref(false);
 const userStore = useUserStore();
 const orgName = computed(() => userStore.orgInfo.orgName);
 
 const handleDetail = async (id: number | string, title?: string) => {
-    if (('' + id).includes(',')) {
-        transferTo(routes.newsGroup, {
-            data: {
-                ids: id,
-                title: title
-            }
-        });
-    } else {
-        transferTo(routes.newsDetail, {
-            data: {
-                id: id,
-                title: title
-            }
-        })
-    }
+  if (('' + id).includes(',')) {
+    transferTo(routes.newsGroup, {
+      data: {
+        ids: id,
+        title: title
+      }
+    });
+  } else {
+    transferTo(routes.newsDetail, {
+      data: {
+        id: id,
+        title: title
+      }
+    })
+  }
 }
 
 const popupRef = ref();
 const checkProvinceInfo = () => {
-    if (!userStore.isLogin && !userStore.tempInfo?.location) {
-        popupRef.value.open();
-    }
+  if (!userStore.isLogin && !userStore.tempInfo?.location) {
+    popupRef.value.open();
+  }
 }
 const checkTeacherInfo = async () => {
-    await userStore.checkInfoComplete();
+  await userStore.checkInfoComplete();
 }
 const reloadUserInfo = async () => {
-    if (userStore.isLogin) {
-        await userStore.getUserInfo();
-    }
+  if (userStore.isLogin) {
+    await userStore.getUserInfo();
+  }
 }
 const handleChangeLocation = () => {
-    if (userStore.isLogin) {
-        return;
-    }
-    popupRef.value.open();
+  if (userStore.isLogin) {
+    return;
+  }
+  popupRef.value.open();
 }
 onHide(() => {
-    isHide.value = true;
+  isHide.value = true;
 })
 onPageScroll((e) => {
-    if (!isHide.value) {
-        scrollTop.value = e.scrollTop;
-    }
+  if (!isHide.value) {
+    scrollTop.value = e.scrollTop;
+  }
 });
 onShow(() => {
-    setTimeout(() => {
-        uni.pageScrollTo({
-            scrollTop: scrollTop.value,
-            duration: 0
-        });
-    }, 0);
-    setTimeout(() => {
-        checkProvinceInfo();
-        checkTeacherInfo();
-        reloadUserInfo();
-    }, 500);
-    isHide.value = false;
+  setTimeout(() => {
+    uni.pageScrollTo({
+      scrollTop: scrollTop.value,
+      duration: 0
+    });
+  }, 0);
+  setTimeout(() => {
+    checkProvinceInfo();
+    checkTeacherInfo();
+    reloadUserInfo();
+  }, 500);
+  isHide.value = false;
 });
 </script>
 

+ 91 - 77
src/pagesOther/pages/university/detail/components/college-info.vue

@@ -1,120 +1,134 @@
 <template>
-    <uv-skeleton v-if="loading" avatar rows="2"/>
-    <view v-else class="-mt-50 flex justify-between gap-20">
-        <view class="flex flex-col items-center gap-10">
-            <ie-image :src="info.logo" custom-class="w-140 h-140" :round="9999"/>
-            <uv-button v-if="!info.collected" type="primary" plain size="mini" shape="circle" icon="plus" text="关注"
-                       icon-size="12" icon-color="primary" @click="handleCollect"/>
-            <uv-button v-else type="primary" size="mini" shape="circle" text="已关注" icon-size="12"
-                       icon-color="primary" @click="handleCollectRemove"/>
-        </view>
-        <view class="mt-50 flex-1">
-            <view class="text-36 font-bold text-fore-title">{{ info.name }}</view>
-            <view v-if="bxTags.length" class="mt-8 flex flex-wrap gap-8">
-                <uv-tags v-for="t in bxTags" :text="t" v-bind="getHighlightBindings(t)" @click="handleTagClick(t)"/>
-            </view>
-            <uv-text type="tips" prefix-icon="empty-address" :icon-style="{color: '#999999'}"
-                     size="12" :text="info.address" margin="8px 0 0 0"/>
-        </view>
+  <uv-skeletons v-if="loading" :skeleton="skeleton" />
+  <view v-else class=" -mt-50 flex justify-between gap-20">
+    <view class="flex flex-col items-center gap-10">
+      <ie-image :src="info.logo" custom-class="w-140 h-140" :round="9999" />
+      <uv-button v-if="!info.collected" type="primary" plain size="mini" shape="circle" icon="plus" text="关注"
+        icon-size="12" icon-color="primary" @click="handleCollect" />
+      <uv-button v-else type="primary" size="mini" shape="circle" text="已关注" icon-size="12" icon-color="primary"
+        @click="handleCollectRemove" />
+    </view>
+    <view class="mt-50 flex-1">
+      <view class="text-36 font-bold text-fore-title">{{ info.name }}</view>
+      <view v-if="bxTags.length" class="mt-8 flex flex-wrap gap-8">
+        <uv-tags v-for="t in bxTags" :key="t" :text="t" v-bind="getHighlightBindings(t)" @click="handleTagClick(t)" />
+      </view>
+      <uv-text type="tips" prefix-icon="empty-address" :icon-style="{ color: '#999999' }" size="12" :text="info.address"
+        margin="8px 0 0 0" />
     </view>
-    <scroll-view scroll-x class="mt-30">
-        <view class="inline-flex items-center gap-20 text-fore-title">
-            <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
-                <view>
-                    <text class="text-30 font-bold">{{ info.createdYear || '-' }}</text>
-                    <text class="ml-5 text-20">年</text>
-                </view>
-                <view class="text-24">建校时间</view>
-            </view>
-            <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
-                <view class="text-30 font-bold">{{ info.star || '-'}}</view>
-                <view class="text-24">竞争力</view>
-            </view>
-            <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
-                <view class="text-30 font-bold">{{ info.comScore || '-'}}</view>
-                <view class="text-24">综合评分</view>
-            </view>
-            <view v-if="userStore.isHN" class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
-                <view class="text-30 font-bold">{{ info.tierName || '-'}}</view>
-                <view class="text-24">院校实力</view>
-            </view>
-            <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
-                <view class="text-30 font-bold">{{ info.collect || '-'}}</view>
-                <view class="text-24">收藏量</view>
-            </view>
+  </view>
+  <scroll-view scroll-x class="mt-30">
+    <view class="inline-flex items-center gap-20 text-fore-title">
+      <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
+        <view>
+          <text class="text-30 font-bold">{{ info.createdYear || '-' }}</text>
+          <text class="ml-5 text-20">年</text>
         </view>
-    </scroll-view>
+        <view class="text-24">建校时间</view>
+      </view>
+      <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
+        <view class="text-30 font-bold">{{ info.star || '-' }}</view>
+        <view class="text-24">竞争力</view>
+      </view>
+      <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
+        <view class="text-30 font-bold">{{ info.comScore || '-' }}</view>
+        <view class="text-24">综合评分</view>
+      </view>
+      <view v-if="userStore.isHN" class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
+        <view class="text-30 font-bold">{{ info.tierName || '-' }}</view>
+        <view class="text-24">院校实力</view>
+      </view>
+      <view class="flex-none min-w-120 p-28 flex flex-col items-center bg-back-light rounded-xl">
+        <view class="text-30 font-bold">{{ info.collect || '-' }}</view>
+        <view class="text-24">收藏量</view>
+      </view>
+    </view>
+  </scroll-view>
 </template>
 
 <script setup lang="ts">
-import {University} from "@/types/university";
+import { University } from "@/types/university";
 import _ from "lodash";
-import {concernUniversity, removeConcernedUniversity} from "@/api/modules/university";
-import {useUserStore} from "@/store/userStore";
+import { concernUniversity, removeConcernedUniversity } from "@/api/modules/university";
+import { useUserStore } from "@/store/userStore";
 
 const props = defineProps<{
-    info: University
-    loading: boolean
+  info: University
+  loading: boolean
 }>()
 const emits = defineEmits(['tag'])
-
+const skeleton = [
+  {
+    type: 'flex',
+    children: [
+      {
+        type: 'avatar',
+        num: 1,
+        style: 'width: 140rpx;height: 140rpx;marginRight: 20rpx;borderRadius: 100%;'
+      },
+      {
+        type: 'line',
+        num: 3,
+        gap: '20rpx'
+      }
+    ]
+  }
+]
 const userStore = useUserStore()
 const isCultural = ref(false) //useUserStore() // 这是早先兼容河南文化类填报时的字段
 const highlights = ['双高']
 const tagAttrs = {
-    type: 'info',
-    plain: true,
-    size: 'tiny',
-    'class': 'pointer-events-none'
+  type: 'info',
+  plain: true,
+  size: 'tiny',
+  'class': 'pointer-events-none'
 }
 const starBinding = {
-    ...tagAttrs,
-    type: 'warning'
+  ...tagAttrs,
+  type: 'warning'
 }
 const tagHighlight = {
-    ...tagAttrs,
-    type: 'primary',
-    plainFill: true
+  ...tagAttrs,
+  type: 'primary',
+  plainFill: true
 }
 
 const bxTags = computed(() => {
-    const {bxLevel, bxType} = props.info
-    const tags = bxLevel ? bxLevel.split(',') : []
-    if (bxType) {
-        _.pull(tags, '双高')
-        tags.push(bxType)
-    }
-    return tags
+  const { bxLevel, bxType } = props.info
+  const tags = bxLevel ? bxLevel.split(',') : []
+  if (bxType) {
+    _.pull(tags, '双高')
+    tags.push(bxType)
+  }
+  return tags
 })
 
 const isSpecialTag = (tag: string) => {
-    return !isCultural.value && tag == props.info.bxType
+  return !isCultural.value && tag == props.info.bxType
 }
 
 const isHighlight = (tag: string) => {
-    return highlights.includes(tag) || isSpecialTag(tag)
+  return highlights.includes(tag) || isSpecialTag(tag)
 }
 
 const getHighlightBindings = (tag: string) => {
-    const attrs = isHighlight(tag) ? tagHighlight : tagAttrs
-    return isSpecialTag(tag) ? {...attrs, icon: 'question-circle', reverse: true, 'class': ''} : attrs
+  const attrs = isHighlight(tag) ? tagHighlight : tagAttrs
+  return isSpecialTag(tag) ? { ...attrs, icon: 'question-circle', reverse: true, 'class': '' } : attrs
 }
 
 const handleTagClick = (tag: string) => {
-    if (isSpecialTag(tag)) emits('tag')
+  if (isSpecialTag(tag)) emits('tag')
 }
 
 const handleCollect = async () => {
-    await concernUniversity({universityId: props.info.id})
-    props.info.collected = true
+  await concernUniversity({ universityId: props.info.id })
+  props.info.collected = true
 }
 
 const handleCollectRemove = async () => {
-    await removeConcernedUniversity({universityId: props.info.id})
-    props.info.collected = false
+  await removeConcernedUniversity({ universityId: props.info.id })
+  props.info.collected = false
 }
 </script>
 
-<style scoped>
-
-</style>
+<style scoped></style>

+ 184 - 202
src/pagesOther/pages/university/detail/components/college-profile.vue

@@ -1,103 +1,103 @@
 <template>
-    <view class="p-30">
-        <uv-read-more ref="more" show-height="120" close-text="展开全部" toggle>
-            <uv-parse :content="baseInfo.introduction" content-style="color:#1A1A1A; font-size: 28rpx"
-                      container-style="padding:30rpx; border-radius: 24rpx; background-color: var(--back-light)"/>
-        </uv-read-more>
-        <view class="mt-30 flex justify-between items-center text-28 gap-20">
-            <view class="bg-secondary-light text-secondary" :class="buttonClass" @click="handleWebsite">
-                <uv-icon name="share-fill" color="var(--secondary)"/>
-                <text class="text-secondary">招生官网</text>
-            </view>
-            <view class="bg-primary-100 text-primary" :class="buttonClass" @click="handlePhone">
-                <uv-icon name="phone-fill" color="primary"/>
-                <text class="text-primary">招生电话</text>
-            </view>
-        </view>
-        <view v-if="baseInfo.images?.length" class="mt-30">
-            <view class="flex justify-between items-center">
-                <view class="text-32 font-bold text-fore-title">院校风采</view>
-                <view v-if="baseInfo.images.length>4" class="flex items-center text-24 text-fore-tip"
-                      @click="$refs.imagesPopup.open()">
-                    查看更多
-                    <uv-icon name="arrow-right" color="info"/>
-                </view>
-            </view>
-            <view class="mt-28 grid grid-cols-2 gap-28">
-                <ie-image v-for="(item,i) in baseInfo.images.slice(0,4)" :key="i" :src="item.url" mode="aspectFill"
-                          :round="6" custom-class="w-335 h-200" @click="handlePreview(i)"/>
-            </view>
+  <view class="p-30">
+    <uv-read-more ref="more" show-height="120" close-text="展开全部" toggle>
+      <uv-parse :content="baseInfo.introduction" content-style="color:#1A1A1A; font-size: 28rpx"
+        container-style="padding:30rpx; border-radius: 24rpx; background-color: var(--back-light)" />
+    </uv-read-more>
+    <view class="mt-30 flex justify-between items-center text-28 gap-20">
+      <view class="bg-secondary-light text-secondary" :class="buttonClass" @click="handleWebsite">
+        <uv-icon name="share-fill" color="var(--secondary)" />
+        <text class="text-secondary">招生官网</text>
+      </view>
+      <view class="bg-primary-100 text-primary" :class="buttonClass" @click="handlePhone">
+        <uv-icon name="phone-fill" color="primary" />
+        <text class="text-primary">招生电话</text>
+      </view>
+    </view>
+    <view v-if="baseInfo.images?.length" class="mt-30">
+      <view class="flex justify-between items-center">
+        <view class="text-32 font-bold text-fore-title">院校风采</view>
+        <view v-if="baseInfo.images.length > 4" class="flex items-center text-24 text-fore-tip"
+          @click="handleMore">
+          查看更多
+          <uv-icon name="arrow-right" color="info" />
         </view>
-        <view class="mt-30">
-            <view class="text-32 font-bold text-fore-title">开设专业</view>
-            <uv-gap v-if="loading" height="15"/>
-            <uv-skeleton v-if="loading" :title="false" rows="3" rows-height="30" :rows-width="['100%','100%','100%']"/>
-            <view v-for="(g,i) in grouped" :key="i"
-                  class="mt-28 p-28 flex justify-between items-center bg-back-light rounded-lg">
-                <view class="text-28 font-bold text-fore-title truncate">{{ g.root.name }}</view>
-                <view class="text-24 text-fore-title flex items-center" @click="handleProfessionGroup(g)">
-                    <text>{{ g.count }}个专业</text>
-                    <uv-icon name="arrow-right"/>
-                </view>
-            </view>
+      </view>
+      <view class="mt-28 grid grid-cols-2 gap-28">
+        <ie-image v-for="(item, i) in baseInfo.images.slice(0, 4)" :key="i" :src="item.url" mode="aspectFill" :round="6"
+          custom-class="w-335 h-200" @click="handlePreview(i)" />
+      </view>
+    </view>
+    <view class="mt-30">
+      <view class="text-32 font-bold text-fore-title">开设专业</view>
+      <uv-gap v-if="loading" height="15" />
+      <uv-skeleton v-if="loading" :title="false" rows="3" rows-height="30" :rows-width="['100%', '100%', '100%']" />
+      <view v-for="(g, i) in grouped" :key="i"
+        class="mt-28 p-28 flex justify-between items-center bg-back-light rounded-lg">
+        <view class="text-28 font-bold text-fore-title truncate">{{ g.root.name }}</view>
+        <view class="text-24 text-fore-title flex items-center" @click="handleProfessionGroup(g)">
+          <text>{{ g.count }}个专业</text>
+          <uv-icon name="arrow-right" />
         </view>
-        <!--        <uv-action-sheet ref="actionSheet" :title="baseInfo.tel" :actions="actions" safe-area-inset-bottom-->
-        <!--                         close-on-click-overlay cancel-text="取消" @select="handleActionSelect"/>-->
-        <ie-popup ref="popup" :show-toolbar="false">
-            <template v-if="popupGroup">
-                <view class="h-90 flex justify-center items-center text-32 font-bold">{{ popupGroup.root.name }}</view>
-                <scroll-view scroll-y :style="{maxHeight: '50vh', backgroundColor: 'var(--back-light)'}">
-                    <uv-cell-group v-for="(g,i) in popupGroup.subGroups" :key="i" title="1">
-                        <template #title>
-                            <text class="text-24 text-fore-tip">{{ g.parent.name }}</text>
-                        </template>
-                        <uv-cell v-for="p in g.list" :title="p.name" custom-class="bg-white"/>
-                    </uv-cell-group>
-                </scroll-view>
-            </template>
-        </ie-popup>
-        <ie-popup ref="imagesPopup" :show-toolbar="false">
-            <view class="h-90 flex justify-center items-center text-32 font-bold">院校风采</view>
-            <scroll-view scroll-y :style="{maxHeight: '50vh', backgroundColor: 'var(--back-light)'}">
-                <view class="p-30 grid grid-cols-2 gap-28">
-                    <ie-image v-for="(item,i) in baseInfo.images" :key="i" :src="item.url" mode="aspectFill"
-                              :round="6" custom-class="w-335 h-200" @click="handlePreview(i)"/>
-                </view>
-            </scroll-view>
-        </ie-popup>
+      </view>
     </view>
+    <!--        <uv-action-sheet ref="actionSheet" :title="baseInfo.tel" :actions="actions" safe-area-inset-bottom-->
+    <!--                         close-on-click-overlay cancel-text="取消" @select="handleActionSelect"/>-->
+    <ie-popup ref="popup" :show-toolbar="false">
+      <template v-if="popupGroup">
+        <view class="h-90 flex justify-center items-center text-32 font-bold">{{ popupGroup.root.name }}</view>
+        <scroll-view scroll-y :style="{ maxHeight: '50vh', backgroundColor: 'var(--back-light)' }">
+          <uv-cell-group v-for="(g, i) in popupGroup.subGroups" :key="i" title="1">
+            <template #title>
+              <text class="text-24 text-fore-tip">{{ g.parent.name }}</text>
+            </template>
+            <uv-cell v-for="p in g.list" :title="p.name" custom-class="bg-white" />
+          </uv-cell-group>
+        </scroll-view>
+      </template>
+    </ie-popup>
+    <ie-popup ref="imagesPopup" :show-toolbar="false">
+      <view class="h-90 flex justify-center items-center text-32 font-bold">院校风采</view>
+      <scroll-view scroll-y :style="{ maxHeight: '50vh', backgroundColor: 'var(--back-light)' }">
+        <view class="p-30 grid grid-cols-2 gap-28">
+          <ie-image v-for="(item, i) in baseInfo.images" :key="i" :src="item.url" mode="aspectFill" :round="6"
+            custom-class="w-335 h-200" @click="handlePreview(i)" />
+        </view>
+      </scroll-view>
+    </ie-popup>
+  </view>
 </template>
 
 <script setup lang="ts">
-import {MAJOR_TREE, UNIVERSITY_DETAIL} from "@/types/injectionSymbols";
-import {University, UniversityDetail, UniversityProfession} from "@/types/university";
-import {MajorItem} from "@/types/major";
+import { MAJOR_TREE, UNIVERSITY_DETAIL } from "@/types/injectionSymbols";
+import { University, UniversityDetail, UniversityProfession } from "@/types/university";
+import { MajorItem } from "@/types/major";
 import UvReadMore from "@/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue";
 import UvActionSheet from "@/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue";
 import IePopup from "@/components/ie-popup/ie-popup.vue";
 
 interface ActionItem {
-    id: string;
-    name: string;
-    icon?: string;
-    color?: string;
-    iconColor?: string;
-    disabled?: boolean;
+  id: string;
+  name: string;
+  icon?: string;
+  color?: string;
+  iconColor?: string;
+  disabled?: boolean;
 }
 
 interface ProfessionSubGroup {
-    parent: MajorItem;
-    list: UniversityProfession[];
+  parent: MajorItem;
+  list: UniversityProfession[];
 }
 
 interface ProfessionGroup {
-    root: MajorItem;
-    subGroups: ProfessionSubGroup[];
-    count: number;
+  root: MajorItem;
+  subGroups: ProfessionSubGroup[];
+  count: number;
 }
 
 defineProps({
-    loading: Boolean
+  loading: Boolean
 })
 
 const imagesPopup = ref<InstanceType<typeof IePopup>>()
@@ -109,159 +109,141 @@ const more = ref<InstanceType<typeof UvReadMore>>()
 const actionSheet = ref<InstanceType<typeof UvActionSheet>>()
 const buttonClass = "flex-1 py-16 rounded-lg flex justify-center items-center gap-8"
 
-const actions = computed(() => [{
-    id: 'call',
-    name: '打电话'
-}, {
-    id: 'copy',
-    name: '复制'
-}])
-
 const popup = ref<InstanceType<typeof IePopup>>()
 const popupGroup = ref<ProfessionGroup>()
 const grouped = computed<ProfessionGroup[]>(() => {
-    if (!majorTree.value || !professions.value) return [];
-
-    // 预先构建三级节点到其路径的映射
-    const nodePathCache = new Map<string, { root: MajorItem; parent: MajorItem }>();
-
-    const buildPathCache = (node: MajorItem, root?: MajorItem, parent?: MajorItem) => {
-        if (node.children?.length) {
-            // 非叶子节点
-            const newRoot = root || node;
-            const isRoot = !root;
-            const newParent = isRoot ? undefined : (parent || node);
+  if (!majorTree.value || !professions.value) return [];
 
-            node.children.forEach(child => buildPathCache(child, newRoot, newParent));
-        } else {
-            // 叶子节点(三级节点)
-            if (root && parent) {
-                nodePathCache.set(node.code, {root, parent});
-            }
-        }
-    };
+  // 预先构建三级节点到其路径的映射
+  const nodePathCache = new Map<string, { root: MajorItem; parent: MajorItem }>();
 
-    majorTree.value.forEach(node => buildPathCache(node));
+  const buildPathCache = (node: MajorItem, root?: MajorItem, parent?: MajorItem) => {
+    if (node.children?.length) {
+      // 非叶子节点
+      const newRoot = root || node;
+      const isRoot = !root;
+      const newParent = isRoot ? undefined : (parent || node);
 
-    // 分组逻辑
-    const rootMap = new Map<string, {
-        root: MajorItem;
-        parentMap: Map<string, {
-            parent: MajorItem;
-            list: UniversityProfession[];
-        }>;
-        count: number;
-    }>();
-
-    professions.value.forEach(profession => {
-        const path = nodePathCache.get(profession.code);
-        if (!path) return;
-
-        const {root, parent} = path;
+      node.children.forEach(child => buildPathCache(child, newRoot, newParent));
+    } else {
+      // 叶子节点(三级节点)
+      if (root && parent) {
+        nodePathCache.set(node.code, { root, parent });
+      }
+    }
+  };
 
-        let rootEntry = rootMap.get(root.code);
-        if (!rootEntry) {
-            rootEntry = {
-                root,
-                parentMap: new Map(),
-                count: 0
-            };
-            rootMap.set(root.code, rootEntry);
-        }
+  majorTree.value.forEach(node => buildPathCache(node));
 
-        let parentEntry = rootEntry.parentMap.get(parent.code);
-        if (!parentEntry) {
-            parentEntry = {
-                parent,
-                list: []
-            };
-            rootEntry.parentMap.set(parent.code, parentEntry);
-        }
+  // 分组逻辑
+  const rootMap = new Map<string, {
+    root: MajorItem;
+    parentMap: Map<string, {
+      parent: MajorItem;
+      list: UniversityProfession[];
+    }>;
+    count: number;
+  }>();
+
+  professions.value.forEach(profession => {
+    const path = nodePathCache.get(profession.code);
+    if (!path) return;
+
+    const { root, parent } = path;
+
+    let rootEntry = rootMap.get(root.code);
+    if (!rootEntry) {
+      rootEntry = {
+        root,
+        parentMap: new Map(),
+        count: 0
+      };
+      rootMap.set(root.code, rootEntry);
+    }
 
-        parentEntry.list.push(profession);
-        rootEntry.count++;
-    });
+    let parentEntry = rootEntry.parentMap.get(parent.code);
+    if (!parentEntry) {
+      parentEntry = {
+        parent,
+        list: []
+      };
+      rootEntry.parentMap.set(parent.code, parentEntry);
+    }
 
-    // 转换为最终格式
-    return Array.from(rootMap.values())
-        .map(({root, parentMap, count}) => ({
-            root,
-            subGroups: Array.from(parentMap.values()),
-            count
-        }))
+    parentEntry.list.push(profession);
+    rootEntry.count++;
+  });
+
+  // 转换为最终格式
+  return Array.from(rootMap.values())
+    .map(({ root, parentMap, count }) => ({
+      root,
+      subGroups: Array.from(parentMap.values()),
+      count
+    }))
 });
 
 const handleWebsite = () => {
-    if (baseInfo.value.webSite) {
-        uni.$ie.openBrowser(baseInfo.value.webSite)
-    } else {
-        uni.$ie.showToast('未提供网址')
-    }
+  if (baseInfo.value.webSite) {
+    uni.$ie.openBrowser(baseInfo.value.webSite)
+  } else {
+    uni.$ie.showToast('未提供网址')
+  }
+}
+
+const handleMore = () => {
+  imagesPopup.value?.open()
 }
 
 const handlePhone = () => {
-    if (baseInfo.value.tel) {
-        //UvActionSheet 在这里打开有异常,所以先使用粘贴板
-        // actionSheet.value?.open()
-        uni.setClipboardData({
-            data: baseInfo.value.tel,
-            showToast: false,
-            success: () => {
-                uni.showModal({
-                    title: '提示',
-                    content: '号码已复制',
-                    showCancel: false
-                })
-            }
-        })
+  const { zhaoBanDH } = baseInfo.value;
+  try {
+    if (zhaoBanDH) {
+      const parsedDH = JSON.parse(zhaoBanDH);
+      if (Array.isArray(parsedDH) && parsedDH.length)
+        uni.showActionSheet({
+          title: '招生电话',
+          itemList: parsedDH,
+          success: (res) => {
+            const index = res.tapIndex;
+            const tel = parsedDH[index];
+            uni.makePhoneCall({ phoneNumber: tel })
+          }
+        });
     } else {
-        uni.$ie.showToast('未提供电话')
+      throw new Error('未提供电话')
     }
+  } catch (error) {
+    uni.$ie.showToast('未提供电话')
+  }
 }
 const handlePreview = (index: number) => {
-    if (!baseInfo.value.images?.length) return
-    // TODO: #1 uni.previewImage 与 ie-popup 在H5端有遮挡问题 #2 previewImage有些图片在微信端里加载不出来?见民政
-    uni.previewImage({
-        urls: baseInfo.value.images.map(i => i.url),
-        current: index,
-        indicator: "number"
-    })
-}
-const handleActionSelect = (e: ActionItem) => {
-    if (e.id == 'call') {
-        uni.makePhoneCall({phoneNumber: baseInfo.value.tel})
-    } else if (e.id == 'copy') {
-        uni.setClipboardData({
-            data: baseInfo.value.tel,
-            success: () => {
-                uni.showModal({
-                    title: '提示',
-                    content: '号码已复制',
-                    showCancel: false
-                })
-            }
-        })
-    }
+  if (!baseInfo.value.images?.length) return
+  uni.previewImage({
+    urls: baseInfo.value.images.map(i => i.url),
+    current: index,
+    indicator: "number"
+  })
 }
+
 const handleProfessionGroup = (g: ProfessionGroup) => {
-    popupGroup.value = g
-    popup.value?.open()
+  popupGroup.value = g
+  popup.value?.open()
 }
 
 watch(() => baseInfo.value.introduction, async (val) => {
-    if (val) {
-        await nextTick()
-        more.value?.init()
-    }
+  if (val) {
+    await nextTick()
+    more.value?.init()
+  }
 })
 </script>
 
 <style scoped lang="scss">
 ::v-deep .uv-cell-group__title__text {
-    font-size: 12px;
-    color: #999999;
+  font-size: 12px;
+  color: #999999;
 }
 </style>
 
-<style lang="scss">
-</style>
+<style lang="scss"></style>

+ 112 - 113
src/pagesOther/pages/university/detail/components/plan-enroll-list.vue

@@ -1,117 +1,111 @@
 <template>
-    <view class="h-full">
-        <z-paging ref="paging" v-model="results" bg-color="white" :auto="false" :refresher-enabled="false"
-                  @query="handleQuery">
-            <template #top>
-                <view class="flex items-center gap-20 px-30 py-20">
-                    <ie-picker v-model="queryParams.year" :list="years" width="w-fit" icon="arrow-down"
-                               placeholder="年份"
-                               custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleYearChange"/>
-                    <ie-picker v-model="queryParams.level" :list="levels" width="w-fit" icon="arrow-down"
-                               placeholder="级别"
-                               custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleLevelChange"/>
-                    <ie-picker v-model="queryParams.group" :list="groups" width="w-fit" icon="arrow-down"
-                               placeholder="专业组"
-                               custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleGroupChange"/>
-                </view>
-            </template>
-            <view v-for="([label, values], idx) in groupedResults" :key="label" class="px-30">
-                <uv-divider v-if="idx>0"/>
-                <view class="flex flex-col gap-20">
-                    <view v-for="(item,i) in values" :key="i" class="bg-back-light p-24 rounded-xl">
-                        <view class="text-fore-title">
-                            <text class="font-bold text-30">{{ item.majorName }}</text>
-                            <text v-if="item.specialProject" class="text-24">({{ item.specialProject }})</text>
-                        </view>
-                        <view v-if="item.marjorDirection" class="text-primary text-24">
-                            {{ item.marjorDirection }}
-                        </view>
-                        <view v-if="mode=='plan'" class="mt-12 text-22 flex gap-30">
-                            <view v-if="item.majorGroup" class="font-bold">{{ item.majorGroup }}</view>
-                            <view>
-                                学费
-                                <text class="font-bold">{{ item.fee || '-' }}</text>
-                                /年
-                            </view>
-                            <view>
-                                学制
-                                <text class="font-bold">{{ item.xueZhi || '-' }}</text>
-                                年
-                            </view>
-                        </view>
-                        <view v-if="mode=='enroll'&&item.majorGroup" class="mt-12 text-22 flex gap-30">
-                            <view v-if="item.majorGroup" class="font-bold">{{ item.majorGroup }}</view>
-                        </view>
-                        <view class="mt-20 grid gap-20" :class="`grid-cols-`+descriptors.length">
-                            <plan-enroll-descriptor v-for="d in descriptors" :key="d.title" :title="d.title"
-                                                    :value="item[d.prop]" :title-only="d.titleOnly"
-                                                    @click="handleRuleClick(d, item)"/>
-                        </view>
-                        <view v-if="mode=='plan'&&isLatestYear" class="mt-20 flex justify-between items-center gap-30">
-                            <view class="h-80 flex-1 border border-solid border-primary rounded-full text-primary
+  <view>
+    <z-paging ref="paging" v-model="results" bg-color="white" :fixed="false" :use-page-scroll="true" :auto="false"
+      :refresher-enabled="false" @query="handleQuery">
+      <view class="flex items-center gap-20 px-30 py-20">
+        <ie-picker v-model="queryParams.year" :list="years" width="w-fit" icon="arrow-down" placeholder="年份"
+          custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleYearChange" />
+        <ie-picker v-model="queryParams.level" :list="levels" width="w-fit" icon="arrow-down" placeholder="级别"
+          custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleLevelChange" />
+        <ie-picker v-model="queryParams.group" :list="groups" width="w-fit" icon="arrow-down" placeholder="专业组"
+          custom-class="px-30 py-10 bg-primary-100 rounded" @change="handleGroupChange" />
+      </view>
+      <view v-for="([label, values], idx) in groupedResults" :key="label" class="px-30">
+        <uv-divider v-if="idx > 0" />
+        <view class="flex flex-col gap-20">
+          <view v-for="(item, i) in values" :key="i" class="bg-back-light p-24 rounded-xl">
+            <view class="text-fore-title">
+              <text class="font-bold text-30">{{ item.majorName }}</text>
+              <text v-if="item.specialProject" class="text-24">({{ item.specialProject }})</text>
+            </view>
+            <view v-if="item.marjorDirection" class="text-primary text-24">
+              {{ item.marjorDirection }}
+            </view>
+            <view v-if="mode == 'plan'" class="mt-12 text-22 flex gap-30">
+              <view v-if="item.majorGroup" class="font-bold">{{ item.majorGroup }}</view>
+              <view>
+                学费
+                <text class="font-bold">{{ item.fee || '-' }}</text>
+                /年
+              </view>
+              <view>
+                学制
+                <text class="font-bold">{{ item.xueZhi || '-' }}</text>
+                年
+              </view>
+            </view>
+            <view v-if="mode == 'enroll' && item.majorGroup" class="mt-12 text-22 flex gap-30">
+              <view v-if="item.majorGroup" class="font-bold">{{ item.majorGroup }}</view>
+            </view>
+            <view class="mt-20 grid gap-20" :class="`grid-cols-` + descriptors.length">
+              <plan-enroll-descriptor v-for="d in descriptors" :key="d.title" :title="d.title" :value="item[d.prop as keyof typeof item]"
+                :title-only="d.titleOnly" @click="handleRuleClick(d, item)" />
+            </view>
+            <view v-if="mode == 'plan' && isLatestYear" class="mt-20 flex justify-between items-center gap-30">
+              <view class="h-80 flex-1 border border-solid border-primary rounded-full text-primary
                             text-30 flex justify-center items-center" @click="handleAddVoluntary(item)">
-                                加入志愿表
-                            </view>
-                            <view class="h-80 flex-1 bg-gradient-to-r from-primary-500 to-primary rounded-full text-white
+                加入志愿表
+              </view>
+              <view class="h-80 flex-1 bg-gradient-to-r from-primary-500 to-primary rounded-full text-white
                             text-30 flex justify-center items-center" @click="handleRateVoluntary(item)">
-                                测录取概率
-                            </view>
-                        </view>
-                    </view>
-                </view>
+                测录取概率
+              </view>
             </view>
-        </z-paging>
-    </view>
+          </view>
+        </view>
+      </view>
+    </z-paging>
+  </view>
 </template>
 
 <script setup lang="ts">
 import _ from 'lodash';
-import {HistoryMode, IPlanEnrollDescriptor, IPlanEnrollHistory, University, UniversityDetail} from "@/types/university";
+import { HistoryMode, IPlanEnrollDescriptor, IPlanEnrollHistory, University, UniversityDetail } from "@/types/university";
 import PlanEnrollDescriptor from "@/pagesOther/pages/university/detail/components/plus/plan-enroll-descriptor.vue";
-import {UNIVERSITY_DETAIL} from "@/types/injectionSymbols";
-import {addVoluntary} from "@/api/modules/voluntary";
-import {SelectedUniversityMajor} from "@/types/voluntary";
-import {useTransferPage} from "@/hooks/useTransferPage";
-import {routes} from "@/common/routes";
+import { UNIVERSITY_DETAIL } from "@/types/injectionSymbols";
+import { addVoluntary } from "@/api/modules/voluntary";
+import { SelectedUniversityMajor } from "@/types/voluntary";
+import { useTransferPage } from "@/hooks/useTransferPage";
+import { routes } from "@/common/routes";
 
 
 const props = withDefaults(defineProps<{
-    mode: HistoryMode;
-    list: IPlanEnrollHistory[];
+  mode: HistoryMode;
+  list: IPlanEnrollHistory[];
 }>(), {
-    mode: 'plan',
-    list: () => []
+  mode: 'plan',
+  list: () => []
 })
 
-const {transferTo} = useTransferPage()
+const { transferTo } = useTransferPage()
 const detail = inject(UNIVERSITY_DETAIL, ref({} as UniversityDetail))
 const baseInfo = computed<University>(() => detail.value.baseInfo)
 const paging = ref<ZPagingInstance>()
 const isCultural = ref(false) // 河南文化类遗留结构
 const tag = computed(() => isCultural.value && '') // examMajorName 河南文化类遗留结构
 const descriptors = computed<IPlanEnrollDescriptor[]>(() => {
-    const enrollShared = [{title: '录取', prop: 'realNum'}, {title: '最低分', prop: 'minScore'}]
-    const cols: IPlanEnrollDescriptor[] = props.mode == 'plan'
-        ? [{title: '计划', prop: 'planNum'}, {title: '招录比', prop: 'acceptanceRate'}]
-        : isCultural.value
-            ? [...enrollShared, {title: '最低位', prop: 'minSeat'}]
-            : [...enrollShared]
-    if (!isCultural.value) cols.push({title: '录取规则', prop: '', titleOnly: true})
-    return cols
+  const enrollShared = [{ title: '录取', prop: 'realNum' }, { title: '最低分', prop: 'minScore' }]
+  const cols: IPlanEnrollDescriptor[] = props.mode == 'plan'
+    ? [{ title: '计划', prop: 'planNum' }, { title: '招录比', prop: 'acceptanceRate' }]
+    : isCultural.value
+      ? [...enrollShared, { title: '最低位', prop: 'minSeat' }]
+      : [...enrollShared]
+  if (!isCultural.value) cols.push({ title: '录取规则', prop: '', titleOnly: true })
+  return cols
 })
 
 const results = ref<IPlanEnrollHistory[]>([])
 const groupedResults = computed(() => Object.entries(_.groupBy(results.value, i => i.majorGroup)))
-const queryParams = ref({year: '', level: '', group: ''})
+const queryParams = ref({ year: '', level: '', group: '' })
 const years = computed(() => _.orderBy(_.uniq(_.map(props.list, i => i.year)), [], ['desc'])
-    .map(i => ({label: i, value: i})))
+  .map(i => ({ label: i, value: i })))
 const levels = computed(() => _.uniq(_.map(props.list.filter(i => i.year == queryParams.value.year), i => i.level))
-    .map(i => ({label: i, value: i})))
+  .map(i => ({ label: i, value: i })))
 const groups = computed(() => [{
-    label: '不限',
-    value: ''
+  label: '不限',
+  value: ''
 }, ..._.uniq(_.map(props.list.filter(i => i.year == queryParams.value.year && i.level == queryParams.value.level), i => i.majorGroup))
-    .map(g => ({label: g, value: g}))])
+  .map(g => ({ label: g, value: g }))])
 const isLatestYear = computed(() => queryParams.value.year == years.value[0]?.value)
 
 const listOfYear = computed(() => queryParams.value.year ? props.list.filter(i => i.year == queryParams.value.year) : [])
@@ -119,22 +113,22 @@ const listOfLevel = computed(() => queryParams.value.level ? listOfYear.value.fi
 const listOfGroup = computed(() => !queryParams.value.group ? listOfLevel.value : listOfLevel.value.filter(i => i.majorGroup == queryParams.value.group))
 
 const handleYearChange = () => {
-    paging.value?.reload()
+  paging.value?.reload()
 }
 
 const handleLevelChange = () => {
-    paging.value?.reload()
+  paging.value?.reload()
 }
 
 const handleGroupChange = () => {
-    paging.value?.reload()
+  paging.value?.reload()
 }
 
 const handleQuery = () => {
-    // console.log('plan enroll list: query', props.mode, queryParams.value)
-    // 到这里时listOfLevel应该已经动态计算完毕了
-    // onSearch, results结构都是多余的,这里只是为了实验z-paging的虚拟列表
-    paging.value?.completeByNoMore(listOfGroup.value, true)
+  // console.log('plan enroll list: query', props.mode, queryParams.value)
+  // 到这里时listOfLevel应该已经动态计算完毕了
+  // onSearch, results结构都是多余的,这里只是为了实验z-paging的虚拟列表
+  paging.value?.completeByNoMore(listOfGroup.value, true)
 }
 
 const handleRuleClick = (descriptor: IPlanEnrollDescriptor, history: IPlanEnrollHistory) => {
@@ -148,38 +142,43 @@ const handleRuleClick = (descriptor: IPlanEnrollDescriptor, history: IPlanEnroll
 }
 
 const handleAddVoluntary = async (item: IPlanEnrollHistory) => {
-    const {code} = baseInfo.value
-    await addVoluntary({universityId: code, majorId: item.id + ''})
-    uni.$ie.showSuccess('保存成功')
+  const { code } = baseInfo.value
+  await addVoluntary({ universityId: code, majorId: item.id + '' })
+  uni.$ie.showSuccess('保存成功')
 }
 
 const handleRateVoluntary = (item: IPlanEnrollHistory) => {
-    const selected: SelectedUniversityMajor = {
-        universityId: baseInfo.value.code,
-        universityLogo: baseInfo.value.logo,
-        universityName: baseInfo.value.name,
-        majorId: item.id + '',
-        majorName: item.majorName,
-        majorGroup: item.majorGroup,
-        majorAncestors: '', // 测算部分这个字段不重要
-        info: baseInfo.value
-    }
-    transferTo(routes.voluntaryIndex, {bigData: {selected}})
+  const selected: SelectedUniversityMajor = {
+    universityId: baseInfo.value.code,
+    universityLogo: baseInfo.value.logo,
+    universityName: baseInfo.value.name,
+    majorId: item.id + '',
+    majorName: item.majorName,
+    majorGroup: item.majorGroup,
+    majorAncestors: '', // 测算部分这个字段不重要
+    info: baseInfo.value
+  }
+  transferTo(routes.voluntaryIndex, { bigData: { selected } })
 }
 
-watch(() => props.list, async (list) => {
+onMounted(() => {
+  watch(() => props.list, async (list) => {
     // console.log('plan enroll list: watch', props.mode, list)
     if (list.length) {
-        queryParams.value.year = years.value[0].value
-        queryParams.value.level = levels.value[0].value
+      queryParams.value.year = years.value[0].value
+      queryParams.value.level = levels.value[0].value
     }
-    // paging还没有初始化好. NOTE: nextTick在小程序端不好使
-    setTimeout(() => paging.value?.reload(), 500)
-}, {immediate: true})
+    paging.value?.reload();
+  }, { immediate: true })
+})
+
+onPageScroll((e: any) => {
+  paging.value?.updatePageScrollTop(e.scrollTop)
+});
 </script>
 
 <style scoped lang="scss">
 ::v-deep .uv-button-wrapper {
-    flex: 1
+  flex: 1
 }
 </style>

+ 1 - 1
src/pagesOther/pages/university/detail/components/plus/plan-enroll-descriptor.vue

@@ -14,7 +14,7 @@
 <script setup lang="ts">
 import {IPlanEnrollDescriptor} from "@/types/university";
 
-defineProps<IPlanEnrollDescriptor>()
+defineProps<Omit<IPlanEnrollDescriptor, 'prop'>>()
 defineEmits(['click'])
 </script>
 

+ 63 - 69
src/pagesOther/pages/university/detail/detail.vue

@@ -1,53 +1,41 @@
 <template>
-    <ie-page>
-        <ie-navbar :title="prevData.name" transparent bg-color="#FFFFFF" title-color="black" keep-title-color/>
-        <ie-image :src="baseInfo.bannerUrl || baseInfo.logo" custom-class="w-full h-360"/>
-        <view class="-mt-60 z-1 rounded-t-3xl p-30 bg-white">
-            <college-info :info="baseInfo" :loading="loading"/>
-        </view>
-        <uv-gap height="10" bg-color="#F6F8FA"/>
-        <uv-sticky :offset-top="appStore.isH5 ? 0 : baseStickyTop">
-            <view :style="{ height: tabHeight + 'px' }">
-                <ie-tabs-swiper v-model="current" :cache-count="1" :list="tabs" :scrollable="false">
-                    <template #profile>
-                        <college-profile :loading="loading"/>
-                    </template>
-                    <template #brochure>
-                        <college-brochure />
-                    </template>
-                    <template #plan>
-                        <plan-enroll-list mode="plan" :list="planList"/>
-                    </template>
-                    <template #enroll>
-                        <plan-enroll-list mode="enroll" :list="enrollList"/>
-                    </template>
-                    <template #exam>
-                        <college-exam/>
-                    </template>
-                </ie-tabs-swiper>
-            </view>
-        </uv-sticky>
-    </ie-page>
+  <ie-page>
+    <ie-navbar :title="prevData.name" transparent bg-color="#FFFFFF" title-color="black" keep-title-color />
+    <uv-skeletons v-if="loading" :skeleton="skeleton" />
+    <ie-image v-else :src="baseInfo.bannerUrl || baseInfo.logo" custom-class="w-full h-[240px]" mode="aspectFill" />
+    <view class="-mt-60 z-1 rounded-t-3xl p-30 bg-white">
+      <college-info :info="baseInfo" :loading="loading" />
+    </view>
+    <uv-gap height="10" bg-color="#F6F8FA" />
+    <uv-sticky :offset-top="appStore.isH5 ? 0 : baseStickyTop">
+      <ie-tabs-swiper v-model="current" :cache-count="1" :list="tabs" :scrollable="false" />
+    </uv-sticky>
+    <college-profile v-show="current === 0" :loading="loading" />
+    <college-brochure v-show="current === 1" />
+    <plan-enroll-list v-show="current === 2" mode="plan" :list="planList" />
+    <plan-enroll-list v-show="current === 3" mode="enroll" :list="enrollList" />
+    <college-exam v-show="current === 4" />
+  </ie-page>
 </template>
 <script lang="ts" setup>
 
-import {useTransferPage} from "@/hooks/useTransferPage";
-import {UniversityDetail, University} from "@/types/university";
-import {MajorItem} from "@/types/major";
-import {getUniversitiesStyle, universityDetail} from "@/api/modules/university";
-import {getMajorTree} from "@/api/modules/major";
+import { useTransferPage } from "@/hooks/useTransferPage";
+import { UniversityDetail, University } from "@/types/university";
+import { MajorItem } from "@/types/major";
+import { getUniversitiesStyle, universityDetail } from "@/api/modules/university";
+import { getMajorTree } from "@/api/modules/major";
 import CollegeInfo from "@/pagesOther/pages/university/detail/components/college-info.vue";
-import {SwiperTabItem} from "@/types";
-import {useNavbar} from "@/hooks/useNavbar";
-import {useAppStore} from "@/store/appStore";
-import {MAJOR_TREE, UNIVERSITY_DETAIL} from "@/types/injectionSymbols";
+import { SwiperTabItem } from "@/types";
+import { useNavbar } from "@/hooks/useNavbar";
+import { useAppStore } from "@/store/appStore";
+import { MAJOR_TREE, UNIVERSITY_DETAIL } from "@/types/injectionSymbols";
 import CollegeProfile from "@/pagesOther/pages/university/detail/components/college-profile.vue";
 import CollegeBrochure from "@/pagesOther/pages/university/detail/components/college-brochure.vue";
 import CollegeExam from "@/pagesOther/pages/university/detail/components/college-exam.vue";
 import PlanEnrollList from "@/pagesOther/pages/university/detail/components/plan-enroll-list.vue";
 
-const {prevData} = useTransferPage()
-const {baseStickyTop} = useNavbar()
+const { prevData } = useTransferPage()
+const { baseStickyTop } = useNavbar()
 const detail = ref<UniversityDetail>({} as UniversityDetail)
 const baseInfo = computed<University>(() => detail.value.baseInfo || {} as University)
 const planList = computed(() => detail.value.planHistories || [])
@@ -59,43 +47,49 @@ const appStore = useAppStore()
 const current = ref(0)
 const tabHeight = computed(() => appStore.sysInfo.screenHeight - baseStickyTop.value)
 const tabs = ref<SwiperTabItem[]>([{
-    name: '概况',
-    slot: 'profile'
+  name: '概况',
+  slot: 'profile'
 }, {
-    name: '简章',
-    slot: 'brochure'
+  name: '简章',
+  slot: 'brochure'
 }, {
-    name: '计划',
-    slot: 'plan'
+  name: '计划',
+  slot: 'plan'
 }, {
-    name: '录取',
-    slot: 'enroll'
+  name: '录取',
+  slot: 'enroll'
 }, {
-    name: '考试大纲',
-    slot: 'exam'
+  name: '考试大纲',
+  slot: 'exam'
 }])
+const skeleton = [
+  {
+    type: 'line',
+    style: 'height: 240px;'
+  }
+]
 
 provide(UNIVERSITY_DETAIL, detail)
 provide(MAJOR_TREE, majorTree)
 onMounted(() => {
-    uni.$ie.showLoading()
-    loading.value = true
-    universityDetail({code: prevData.value.code})
-        .then(res => {
-            detail.value = res.data
-            return getUniversitiesStyle({code: prevData.value.code})
-        })
-        .then(res => {
-            baseInfo.value.images = res.data
-            return getMajorTree({})
-        })
-        .then(res => {
-            majorTree.value = res.data
-        })
-        .finally(() => {
-            uni.$ie.hideLoading()
-            loading.value = false
-        })
+  uni.$ie.showLoading()
+  loading.value = true
+  universityDetail({ code: prevData.value.code })
+    .then(res => {
+      detail.value = res.data
+      return getUniversitiesStyle({ code: prevData.value.code })
+    })
+    .then(res => {
+      baseInfo.value.images = res.data
+      return getMajorTree({})
+    })
+    .then(res => {
+      majorTree.value = res.data
+    })
+    .finally(() => {
+      uni.$ie.hideLoading()
+      loading.value = false
+    })
 })
 // 必须手动触发才能保证 navbar.transparent 正常工作
 onPageScroll(() => {
@@ -104,6 +98,6 @@ onPageScroll(() => {
 
 <style lang="scss" scoped>
 ::v-deep .uv-tabs__wrapper__nav__item__text {
-    white-space: nowrap;
+  white-space: nowrap;
 }
 </style>

+ 1 - 7
src/pagesStudy/pages/exam-start/components/question-item.vue

@@ -22,9 +22,6 @@
           <question-item v-show="subQuestion.subIndex === question.activeSubIndex" :question="subQuestion" />
         </view>
       </view>
-      <!-- <view v-if="question.subQuestions[question.activeSubIndex]" class="mt-20">
-        <question-item :question="question.subQuestions[question.activeSubIndex]" />
-      </view> -->
     </view>
   </view>
 </template>
@@ -48,9 +45,6 @@ const examData = inject(EXAM_DATA) || {} as ReturnType<typeof useExam>;
 const { subQuestionIndex, setSubQuestionIndex, currentIndex } = examData;
 const examAutoSubmit = inject(EXAM_AUTO_SUBMIT);
 
-const scrollIntoView = ref('')
-const scrollLeft = ref(0);
-const autoScroll = ref(false);
 const tabRef = ref();
 const tabs = computed(() => {
   return props.question.subQuestions.map((item, index) => {
@@ -65,7 +59,7 @@ const tabItemStyle = {
 };
 
 watch(() => currentIndex.value, (val) => {
-  if (currentIndex.value === props.question.index) {
+  if (currentIndex.value === props.question.index && props.question.hasSubQuestions) {
     nextTick(() => {
       tabRef.value?.init();
     });

+ 7 - 3
src/types/university.ts

@@ -25,8 +25,10 @@ export interface University {
     tier: string;
     tierName: string;
     introduction: string;
-    tel: string;
-    
+
+    createdYear: string;
+    zhaoBanDH?: string; // 招生电话
+    zhaoBanWZ?: string; // 招生官网
     images?: UniversityStyle[]; 
 }
 
@@ -87,6 +89,8 @@ export interface IPlanEnrollHistory {
     "majorName": string;
     "enrollCode": string;
     "totalRule": string;
+    fee: string;
+    xueZhi: string
 }
 
 export interface UniversityPlanHistory extends IPlanEnrollHistory {
@@ -103,7 +107,7 @@ export interface UniversityEnrollHistory extends IPlanEnrollHistory {
 }
 
 export interface IPlanEnrollDescriptor {
-    prop?: string;
+    prop: string;
     title?: string;
     value?: string | number;
     titleOnly?: boolean;

+ 11 - 34
src/utils/uni-tool.ts

@@ -196,40 +196,17 @@ const tool: IeTool = {
 
     // #ifdef MP-WEIXIN
     // 微信小程序
-    if (wx.openBrowser) {
-      wx.openBrowser({
-        url: url,
-        success: () => {
-          console.log('打开浏览器成功')
-        },
-        fail: (err: any) => {
-          console.error('打开浏览器失败', err)
-          // 降级处理:复制链接,提示用户
-          uni.setClipboardData({
-            data: url,
-            success: () => {
-              uni.showModal({
-                title: '提示',
-                content: '链接已复制,请在浏览器中打开',
-                showCancel: false
-              })
-            }
-          })
-        }
-      })
-    } else {
-      // 如果不支持,使用降级方案
-      uni.setClipboardData({
-        data: url,
-        success: () => {
-          uni.showModal({
-            title: '提示',
-            content: '链接已复制,请在浏览器中打开',
-            showCancel: false
-          })
-        }
-      })
-    }
+    uni.setClipboardData({
+      data: url,
+      showToast: false,
+      success: () => {
+        uni.showModal({
+          title: '提示',
+          content: '链接已复制,请在浏览器中打开',
+          showCancel: false
+        })
+      }
+    })
     // #endif
 
     // #ifdef MP-ALIPAY