Pārlūkot izejas kodu

小程序适配查专业

shmily1213 3 nedēļas atpakaļ
vecāks
revīzija
954993e52c

+ 39 - 0
src/api/modules/major.ts

@@ -0,0 +1,39 @@
+import flyio from "../flyio";
+import { ApiResponse, ApiResponseList } from "@/types";
+import { Major } from "@/types";
+
+/**
+ * 获取专业树
+ * @param params 
+ * @returns 
+ */
+export function getMajorTree(params: Major.MajorTreeQueryDTO) {
+  return flyio.get('/front/major/getAllMajor', params) as Promise<ApiResponse<Major.MajorItem[]>>;
+}
+
+/**
+ * 根据专业名称获取专业列表
+ * @param params 
+ * @returns 
+ */
+export function getMajorByName(params: Major.MajorTreeQueryDTO) {
+  return flyio.get('/front/major/getMajorByName', params) as Promise<ApiResponse<Major.MajorItem[]>>;
+}
+
+/**
+ * 根据专业代码获取专业概览
+ * @param code 专业代码
+ * @returns 
+ */
+export function getMajorOverviewByCode(code: string) {
+  return flyio.get(`/front/major/getMajorOverviewByCode?code=${code}`) as Promise<ApiResponse<Major.MajorOverview>>;
+}
+
+/**
+ * 根据专业代码获取开设院校
+ * @param params 参数
+ * @returns 
+ */
+export function getUniversityByMajorCode(params: Major.UniversityQueryDTO) {
+  return flyio.get('/front/major/getUniversityByCode', params) as Promise<ApiResponseList<Major.University>>;
+}

+ 16 - 0
src/common/routes.ts

@@ -7,6 +7,22 @@ export const routes = {
    * 新闻列表
    */
   newsGroup: '/pagesOther/pages/news/group/group',
+  /**
+   * 专业详情
+   */
+  majorDetail: '/pagesOther/pages/major/detail/detail',
+  /**
+   * 二级专业
+   */
+  majorLevelTwo: '/pagesOther/pages/major/level-two/level-two',
+  /**
+   * 专业列表
+   */
+  majorIndex: '/pagesOther/pages/major/index/index',
+  /**
+   * 大学详情
+   */
+  universityDetail: '/pagesOther/pages/university/detail/detail',
 } as const;
 
 export type Routes = keyof typeof routes;

+ 0 - 2
src/components/ie-page/ie-page.vue

@@ -75,11 +75,9 @@ defineExpose({
   closeVipPopup
 });
 const addListener = () => {
-  console.log('监听')
   uni.$on(EnumEvent.OPEN_VIP_POPUP, showVipPopup);
 }
 const removeListener = () => {
-  console.log('取消监听')
   uni.$off(EnumEvent.OPEN_VIP_POPUP);
 }
 onMounted(() => {

+ 28 - 0
src/components/ie-search/ie-search.vue

@@ -0,0 +1,28 @@
+<template>
+  <view class="ie-search px-20 py-20 border-0 border-b border-solid border-border-light">
+    <uv-search v-model="modelValue" shape="square" :showAction="false" :placeholder="placeholder" @input="handleInput" @search="handleSearch">
+      <!-- <template #suffix>
+        <view class="text-24 text-fore-title bg-primary text-white px-24 py-10 rounded-full translate-x-10"
+          @click="handleSearch">搜索
+        </view>
+      </template> -->
+    </uv-search>
+  </view>
+</template>
+<script lang="ts" setup>
+const props = defineProps<{
+  placeholder?: string;
+}>();
+const modelValue = defineModel<string>('modelValue', {
+  default: ''
+});
+
+const emit = defineEmits(['search']);
+const handleSearch = (value: string) => {
+  emit('search', value);
+};
+const handleInput = (value: string) => {
+  modelValue.value = value;
+};
+</script>
+<style lang="scss" scoped></style>

+ 15 - 11
src/components/ie-tabs-swiper/ie-tabs-swiper.vue

@@ -1,32 +1,36 @@
 <template>
   <view class="w-full h-full flex flex-col relative">
-    <uv-tabs :list="list" :scrollable="scrollable" @change="handleChange" :current="modelValue" :key-name="keyName"></uv-tabs>
+    <view :style="{ backgroundColor: bgColor, border: border ? '1px solid #e5e6e9' : 'none' }">
+      <uv-tabs :list="list" :scrollable="scrollable" @change="handleChange" :current="modelValue"
+        :key-name="keyName"></uv-tabs>
+    </view>
     <ie-auto-resizer>
       <slot></slot>
     </ie-auto-resizer>
   </view>
 </template>
 <script lang="ts" setup>
-  defineOptions({
+import { SwiperTabItem } from '@/types';
+
+defineOptions({
   name: 'ie-tabs-swiper',
   options: {
     virtualHost: true
   }
 });
-type TabItem = {
-  name: string;
-  keyName: string;
-  slot: string;
-}
 type Props = {
-  list: TabItem[];
-  scrollable: boolean
-  keyName: string
+  list: SwiperTabItem[];
+  scrollable: boolean;
+  keyName: string;
+  bgColor: string;
+  border: boolean;
 }
 const props = withDefaults(defineProps<Props>(), {
   list: () => [],
   modelValue: 0,
-  keyName: 'name'
+  keyName: 'name',
+  bgColor: '#FFFFFF',
+  border: true
 });
 const modelValue = defineModel<number>('modelValue', {
   type: Number,

+ 28 - 4
src/pages.json

@@ -40,25 +40,49 @@
       "root": "pagesOther",
       "pages": [
         {
-          "path": "pages/university/index/index",
+          "path": "pages/news/group/group",
           "style": {
             "navigationBarTitleText": ""
           }
         },
         {
-          "path": "pages/news/group/group",
+          "path": "pages/news/detail/detail",
           "style": {
             "navigationBarTitleText": ""
           }
         },
         {
-          "path": "pages/news/detail/detail",
+          "path": "pages/news/index/index",
           "style": {
             "navigationBarTitleText": ""
           }
         },
         {
-          "path": "pages/news/index/index",
+          "path": "pages/major/index/index",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/major/level-two/level-two",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/major/detail/detail",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/university/index/index",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/university/detail/detail",
           "style": {
             "navigationBarTitleText": ""
           }

+ 1 - 1
src/pagesMain/pages/index/components/index-banner.vue

@@ -64,7 +64,7 @@ const validMenus = computed(() => {
     {
       name: '查专业',
       icon: '/menu/menu-major.png',
-      pageUrl: '/pagesOther/pages/major-library/index/index',
+      pageUrl: '/pagesOther/pages/major/index/index',
       noLogin: true
     },
     {

+ 1 - 1
src/pagesMain/pages/me/components/me-menu.vue

@@ -9,7 +9,7 @@
         </view>
       </view>
       <view class="mt-40 text-30 text-fore-title font-bold">其他功能</view>
-      <view class="mt-16 shadow-card rounded-8 bg-white">
+      <view class="mt-16 shadow-card rounded-8 bg-white overflow-hidden">
         <uv-cell-group :border="false">
           <uv-cell isLink :cellStyle="cellStyle"
             @click="handleNavigate('/pagesSystem/pages/edit-profile/edit-profile', '基本资料')">

+ 73 - 0
src/pagesOther/pages/major/detail/components/major-overview.vue

@@ -0,0 +1,73 @@
+<template>
+  <view class="h-full overflow-auto">
+    <view class="flex items-center justify-between bg-white p-30 text-26 text-fore-light">
+      <view>
+        <view>{{ data.bigName }}</view>
+        <view>➣ {{ data.middleName }}</view>
+        <view>➣➣ {{ data.name }}</view>
+      </view>
+      <view class="flex flex-col items-center justify-center">
+        <uv-icon name="heart" size="22" color="var(--primary-color)"></uv-icon>
+        <text class="text-primary text-24">收藏</text>
+      </view>
+    </view>
+    <view class="mt-20 p-30 bg-white flex flex-col gap-30">
+      <view v-for="card in cards" :key="card.prop">
+        <view class="title">{{ card.title }}</view>
+        <view class="px-30 py-20 text-28 text-fore-content bg-back rounded-12 mt-10 whitespace-pre-line">
+          {{ data[card.prop as keyof Major.MajorOverview] }}
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts" setup>
+import { Major } from '@/types';
+
+const props = defineProps<{
+  data: Major.MajorOverview;
+}>();
+const cards = ref([
+  {
+    title: '核心课程',
+    prop: 'mainCourse'
+  },
+  {
+    title: '就业方向',
+    prop: 'jobDirection'
+  },
+  {
+    title: '职业能力',
+    prop: 'loreAndAbility'
+  },
+  {
+    title: '资格证书',
+    prop: 'qualification'
+  },
+  {
+    title: '衔接中职专业',
+    prop: 'zhongzhiMajors'
+  },
+  {
+    title: '接续本科专业',
+    prop: 'benMajors'
+  },
+]);
+</script>
+<style lang="scss" scoped>
+.title {
+  @apply text-30 text-fore-title font-bold relative pl-20;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 4px;
+    height: 70%;
+    border-radius: 2px;
+    background-color: var(--primary-color);
+  }
+}
+</style>

+ 35 - 0
src/pagesOther/pages/major/detail/components/related-university.vue

@@ -0,0 +1,35 @@
+<template>
+  <z-paging ref="paging" v-model="list" :safe-area-inset-bottom="true" @query="handleQuery">
+    <view v-for="item in list" :key="item.id">
+      <university-item :data="item" @click="handleClick(item)" />
+    </view>
+  </z-paging>
+</template>
+<script lang="ts" setup>
+import { getUniversityByMajorCode } from '@/api/modules/major';
+import UniversityItem from './university-item.vue';
+import { Major } from '@/types';
+import { useTransferPage } from '@/hooks/useTransferPage';
+const { transferTo, routes } = useTransferPage();
+
+const props = defineProps<{
+  data: Major.MajorOverview;
+}>();
+const list = ref<Major.University[]>([]);
+const paging = ref<ZPagingInstance>();
+
+const handleQuery = (page: number, size: number) => {
+  getUniversityByMajorCode({ code: props.data.code, pageNum: page, pageSize: size }).then(res => {
+    paging.value?.completeByTotal(res.rows, res.total);
+  }).catch(() => paging.value?.complete(false));
+}
+
+const handleClick = (item: Major.University) => {
+  transferTo(routes.universityDetail, {
+    data: {
+      id: item.id,
+    }
+  });
+}
+</script>
+<style lang="scss" scoped></style>

+ 38 - 0
src/pagesOther/pages/major/detail/components/university-item.vue

@@ -0,0 +1,38 @@
+<template>
+  <view class="px-30 py-20 flex items-center gap-20" @click="handleClick">
+    <ie-image :src="data.logo" custom-class="w-110 h-110" mode="aspectFill" />
+    <view class="flex-1 flex flex-col gap-8">
+      <view class="text-30 text-fore-title font-bold flex items-center gap-20">
+        <text class="flex-1 min-w-1 ellipsis-1">{{ data.name }}</text>
+        <uv-tags v-if="data.star" type="warning" size="tiny" plain :text="data.star"></uv-tags>
+      </view>
+      <view class="flex items-center gap-10 flex-wrap">
+        <view v-for="value in tags" :key="value">
+          <uv-tags :text="value" type="info" size="tiny" plain />
+        </view>
+      </view>
+      <view class="text-24 text-fore-light flex items-center gap-6">
+        <uv-icon name="map" size="16" color="var(--fore-light)" />
+        <text class="text-24 text-fore-light">{{ data.address }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts" setup>
+import { Major } from '@/types';
+
+const props = defineProps<{
+  data: Major.University;
+}>();
+const tags = computed(() => {
+  return props.data.bxLevel?.split(',') || [];
+});
+const emit = defineEmits<{
+  (e: 'click', data: Major.University): void;
+}>();
+
+const handleClick = () => {
+  emit('click', props.data);
+};
+</script>
+<style lang="scss" scoped></style>

+ 56 - 0
src/pagesOther/pages/major/detail/detail.vue

@@ -0,0 +1,56 @@
+<template>
+  <ie-page :fix-height="true" bg-color="#F6F8FA">
+    <ie-navbar title="专业详情" />
+    <ie-auto-resizer>
+      <ie-tabs-swiper v-model="current" :list="tabs" :scrollable="false">
+        <swiper class="swiper h-full" :current="current" @change="handleChangeSwiper">
+          <swiper-item v-for="(item, index) in tabs" :key="index" class="h-full">
+            <view class="h-full" v-if="Math.abs(index - current) <= 2">
+              <major-overview v-if="item.slot === 'major-overview' && overviewData" :data="overviewData" />
+              <related-university v-if="item.slot === 'open-college' && overviewData" :data="overviewData" />
+            </view>
+          </swiper-item>
+        </swiper>
+      </ie-tabs-swiper>
+    </ie-auto-resizer>
+  </ie-page>
+</template>
+<script lang="ts" setup>
+import { getMajorOverviewByCode } from '@/api/modules/major';
+import { Major, SwiperTabItem } from '@/types';
+import { useTransferPage } from '@/hooks/useTransferPage';
+import MajorOverview from './components/major-overview.vue';
+import RelatedUniversity from './components/related-university.vue';
+const current = ref(0);
+const tabs = ref<SwiperTabItem[]>([
+  {
+    name: '专业概况',
+    slot: 'major-overview'
+  },
+  {
+    name: '开设院校',
+    slot: 'open-college'
+  },
+]);
+const { prevData } = useTransferPage();
+const majorData = ref<Major.MajorOverview>({} as Major.MajorOverview);
+const overviewData = ref<Major.MajorOverview>({} as Major.MajorOverview);
+const loadData = async () => {
+  uni.$ie.showLoading();
+  try {
+    const res = await getMajorOverviewByCode(prevData.value.code);
+    overviewData.value = res.data;
+    
+  } finally {
+    uni.$ie.hideLoading();
+  }
+}
+const handleChangeSwiper = (e: any) => {
+  current.value = e.detail.current;
+}
+onLoad(() => {
+  majorData.value = prevData.value;
+  loadData();
+});
+</script>
+<style lang="scss" scoped></style>

+ 60 - 0
src/pagesOther/pages/major/index/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <id-page>
+    <z-paging ref="paging" v-model="list" :safe-area-inset-bottom="true" @query="handleQuery">
+      <template #top>
+        <ie-navbar title="专业库" />
+        <ie-search v-model="keyword" placeholder="输入专业名称" @search="handleSearch" />
+      </template>
+      <view v-for="item in list" :key="item.id">
+        <view class="text-30 text-fore-title py-20 px-30 font-bold mt-20">{{ item.name }}</view>
+        <uv-cell-group>
+          <uv-cell v-for="child in item.children" :key="child.id" icon="" :title="child.name" :isLink="true"
+            :value="`${child.children?.length}个专业`" arrow-direction="right" @click="handleClick(child)"></uv-cell>
+        </uv-cell-group>
+      </view>
+    </z-paging>
+  </id-page>
+</template>
+<script lang="ts" setup>
+import { getMajorTree, getMajorByName } from '@/api/modules/major';
+import { Major } from '@/types';
+import { useTransferPage } from '@/hooks/useTransferPage';
+const { transferTo, routes } = useTransferPage();
+
+const keyword = ref('');
+const list = ref<Major.MajorItem[]>([]);
+const paging = ref<ZPagingInstance>();
+
+const handleQuery = (page: number, size: number) => {
+  const params: Major.MajorTreeQueryDTO = {};
+  uni.$ie.showLoading();
+  if (keyword.value.trim()) {
+    params.name = keyword.value.trim();
+    params.level = 1;
+    getMajorByName(params).then(res => {
+      paging.value?.completeByNoMore(res.data, true);
+    }).catch(() => paging.value?.complete(false))
+      .finally(() => uni.$ie.hideLoading());
+  } else {
+    getMajorTree(params).then(res => {
+      paging.value?.completeByNoMore(res.data, true);
+    }).catch(() => paging.value?.complete(false))
+      .finally(() => uni.$ie.hideLoading());
+  }
+}
+const handleSearch = () => {
+  paging.value?.reload();
+}
+const handleClick = (item: Major.MajorItem) => {
+  if (item.childCount > 0) {
+    transferTo(routes.majorLevelTwo, {
+      data: item
+    });
+  } else {
+    transferTo(routes.majorDetail, {
+      data: item
+    });
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 39 - 0
src/pagesOther/pages/major/level-two/level-two.vue

@@ -0,0 +1,39 @@
+<template>
+  <ie-page bg-color="#F6F8FA">
+    <ie-navbar :title="pageTitle" />
+    <view class="">
+      <view class="px-30 py-20 text-28 text-fore-light">
+        包含<span class="text-primary mx-10 font-bold">{{ childCount }}</span>个专业
+      </view>
+    </view>
+    <view class="bg-white">
+      <uv-cell-group :border="false">
+        <uv-cell v-for="child in majorData.children" :key="child.id" icon="" :title="child.name" :isLink="true"
+          arrow-direction="right" @click="handleClick(child)"></uv-cell>
+      </uv-cell-group>
+    </view>
+  </ie-page>
+</template>
+<script lang="ts" setup>
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { Major } from '@/types';
+
+const { prevData, transferTo, routes } = useTransferPage();
+const majorData = ref<Major.MajorItem>({} as Major.MajorItem);
+
+const pageTitle = computed(() => {
+  return majorData.value?.name || '';
+});
+const childCount = computed(() => {
+  return majorData.value?.children?.length || 0;
+});
+onLoad(() => {
+  majorData.value = prevData.value;
+});
+const handleClick = (item: Major.MajorItem) => {
+  transferTo(routes.majorDetail, {
+    data: item
+  });
+}
+</script>
+<style lang="scss" scoped></style>

+ 1 - 1
src/pagesMain/pages/index/components/index-popup copy.vue → src/pagesOther/pages/university/detail/detail.vue

@@ -1,5 +1,5 @@
 <template>
-  <view></view>
+
 </template>
 <script lang="ts" setup>
 

+ 9 - 1
src/types/index.ts

@@ -3,6 +3,7 @@ import * as User from "./user";
 import * as News from "./news";
 import * as Transfer from "./transfer";
 import * as System from './system';
+import * as Major from "./major";
 import { VipCardInfo } from "./user";
 import { EnumExamMode, EnumExamType, EnumReviewMode } from "@/common/enum";
 
@@ -125,6 +126,13 @@ export interface ConfigItem {
   configId: number;
 }
 
+export interface SwiperTabItem {
+  name: string;
+  value?: string;
+  slot?: string;
+  params?: any;
+}
+
 
 
-export { Study, User, News, Transfer, System };
+export { Study, User, News, Transfer, System, Major };

+ 151 - 0
src/types/major.ts

@@ -0,0 +1,151 @@
+/**
+ * 专业项接口
+ */
+export interface MajorItem {
+  id: number;
+  name: string;
+  type: string;
+  level: number;
+  code: string;
+  childCount: number;
+  children: MajorItem[];
+  learnYearArab: string;
+}
+
+/**
+ * 专业树查询参数
+ */
+export interface MajorTreeQueryDTO {
+  name?: string;
+  level?: number;
+}
+
+/**
+ * 专业详情接口
+ */
+export interface MajorOverview {
+  /** 接续高职本科专业举例 */
+  benMajors: string;
+  /** 大类名称 */
+  bigName: string;
+  /** 子级数量 */
+  childCount: number;
+  /** 专业代码 */
+  code: string;
+  /** 代码列表 */
+  codes: string[] | null;
+  /** 创建人 */
+  createBy: string | null;
+  /** 创建时间 */
+  createTime: string | null;
+  /** 学位 */
+  degree: string;
+  /** 教育层次:zhuan-专科,ben-本科 */
+  eduLevel: string;
+  /** 培养目标 */
+  eduObjective: string;
+  /** 培养要求 */
+  eduRequirement: string;
+  /** 就业热度 */
+  employmentHeat: number;
+  /** 知名学者 */
+  famousScholar: string;
+  /** 女性比例 */
+  femaleRatio: number;
+  /** 女性比例文本 */
+  femaleRatioText: string;
+  /** 点击量 */
+  hits: number;
+  /** 专业ID */
+  id: number;
+  /** 实习描述 */
+  internshipDesc: string;
+  /** 专业介绍 */
+  introduction: string;
+  /** 是否收藏 */
+  isCollect: boolean;
+  /** 就业方向 */
+  jobDirection: string;
+  /** 就业文本 */
+  jobText: string | null;
+  /** 学制(数字) */
+  learnYear: string;
+  /** 学制(阿拉伯数字文本) */
+  learnYearArab: string;
+  /** 学制(中文文本) */
+  learnYearZh: string;
+  /** 层级 */
+  level: number;
+  /** 理科比例 */
+  lkRatio: number;
+  /** 理科比例文本 */
+  lkRatiotext: string;
+  /** 知识与能力 */
+  loreAndAbility: string;
+  /** 主要课程 */
+  mainCourse: string;
+  /** 男性比例 */
+  maleRatio: number;
+  /** 男性比例文本 */
+  maleRatioText: string;
+  /** 专业ID(另一个字段) */
+  marjorId: number;
+  /** 中类名称 */
+  middleName: string;
+  /** 专业名称 */
+  name: string;
+  /** 开设院校数量 */
+  openCollegeCount: number;
+  /** 资格证书 */
+  qualification: string;
+  /** 相关专业 */
+  relationMajors: string;
+  /** 备注 */
+  remark: string | null;
+  /** 薪资 */
+  salary: number | null;
+  /** 学习方向 */
+  studyDirection: string | null;
+  /** 选科要求 */
+  subjectRequirement: string;
+  /** 摘要 */
+  summary: string | null;
+  /** 更新人 */
+  updateBy: string | null;
+  /** 更新时间 */
+  updateTime: string | null;
+  /** 文科比例 */
+  wkRatio: number;
+  /** 文科比例文本 */
+  wkRatioText: string;
+  /** 接续中职专业 */
+  zhongzhiMajors: string;
+  /** 专升本方向 */
+  zhuanToBenOrient: string;
+}
+
+export interface UniversityQueryDTO {
+  code: string;
+  pageNum: number;
+  pageSize: number;
+}
+export interface University {
+  address: string;
+  area: number;
+  bxLevel: string;
+  cityName: string;
+  code: string;
+  collect: boolean;
+  comScore: string;
+  enrollLocation: string;
+  features: string;
+  hits: number;
+  id: number;
+  location: string;
+  logo: string;
+  name: string;
+  natureTypeCN: string;
+  star: string;
+  type: string;
+  webSite: string;
+}

+ 1 - 1
src/uni_modules/uv-search/components/uv-search/uv-search.vue

@@ -49,7 +49,7 @@
             />
             <view
                 class="uv-search__content__icon uv-search__content__close"
-                v-if="keyword && clearabled && focused"
+                v-if="keyword && clearabled"
                 @tap="clear"
             >
                 <uv-icon

+ 1 - 0
tailwind.config.js

@@ -101,6 +101,7 @@ module.exports = {
         },
         border: {
           DEFAULT: "var(--border)",
+          light: "var(--border-light)",
         },
         danger: {
           DEFAULT: "var(--danger)",