Prechádzať zdrojové kódy

修改个人信息展示

shmily1213 1 mesiac pred
rodič
commit
5e71592191

+ 5 - 2
src/api/modules/study.ts

@@ -212,8 +212,11 @@ export function correctQuestion(params: { questionid: number, remark: string })
  * @param params 
  * @returns 
  */
-export function getPracticeHistory() {
-  return flyio.get('/front/student/record/practice', {}) as Promise<ApiResponseList<PracticeHistory>>;
+export function getPracticeHistory({pageNum, pageSize}: {pageNum: number, pageSize: number}) {
+  return flyio.get('/front/student/record/practice', {
+    pageNum,
+    pageSize
+  }) as Promise<ApiResponseList<PracticeHistory>>;
 }
 
 /**

+ 30 - 0
src/common/enum.ts

@@ -261,4 +261,34 @@ export enum EnumReviewMode {
    * 答完一题就评卷
    */
   DURING_ANSWER = 2
+}
+
+export enum EnumUserRole {
+  /**
+   * 普通用户
+   */
+  NORMAL = 'normal',
+  /**
+   * 游客
+   */
+  GUEST = 'guest',
+  /**
+   * 会员
+   */
+  VIP = 'vip',
+  /**
+   * 代理商
+   */
+  AGENT = 'agent',
+  /**
+   * 教师
+   */
+  TEACHER = 'teacher'
+}
+
+export enum EnumEvent {
+  /**
+   * 打开VIP弹窗
+   */
+  OPEN_VIP_POPUP = 'OPEN_VIP_POPUP'
 }

+ 1 - 1
src/components/ie-image/ie-image.vue

@@ -37,7 +37,7 @@ const props = defineProps({
   },
   bgColor: {
     type: String,
-    default: '#F3F5F6'
+    default: ''
   }
 });
 const imageSrc = computed(() => {

+ 18 - 5
src/components/ie-page/ie-page.vue

@@ -17,6 +17,7 @@
 <script lang="ts" setup>
 import { CLOSE_VIP_POPUP, OPEN_VIP_POPUP } from '@/types/injectionSymbols';
 import VipPopup from './components/vip-popup.vue';
+import { EnumEvent } from '@/common/enum';
 defineOptions({
   name: 'ie-page',
   options: {
@@ -66,14 +67,26 @@ defineExpose({
   showVipPopup,
   closeVipPopup
 });
+const addListener = () => {
+  console.log('监听')
+  uni.$on(EnumEvent.OPEN_VIP_POPUP, showVipPopup);
+}
+const removeListener = () => {
+  console.log('取消监听')
+  uni.$off(EnumEvent.OPEN_VIP_POPUP);
+}
 onMounted(() => {
-  // console.log('ie-page mounted, vipPopupRef.value:', vipPopupRef.value);
-  // uni.$on('showVipPopup', showVipPopup);
-  // uni.$on('closeVipPopup', closeVipPopup);
+  addListener();
 });
 onBeforeUnmount(() => {
-  // uni.$off('showVipPopup', showVipPopup);
-  // uni.$off('closeVipPopup', closeVipPopup);
+  removeListener();
+});
+
+onHide(() => {
+  removeListener();
+});
+onUnload(() => {
+  removeListener();
 });
 </script>
 

+ 46 - 0
src/hooks/useAuth.ts

@@ -0,0 +1,46 @@
+import { EnumEvent, EnumUserRole, EnumUserType } from "@/common/enum";
+import { useUserStore } from "@/store/userStore";
+import IePage from "@/components/ie-page/ie-page.vue";
+
+export const useAuth = () => {
+  const userStore = useUserStore();
+  const { userInfo, isLogin, isVip, isStudent } = storeToRefs(userStore);
+  const hasPermission = (roles: EnumUserRole[] = []) => {
+    if (roles.length === 0) {
+      return true;
+    }
+    const hasAuth = roles.some(role => {
+      if (role === EnumUserRole.VIP) {
+        return isVip.value;
+      }
+      if (role === EnumUserRole.NORMAL) {
+        return isStudent.value && !isVip.value;
+      }
+      if (role === EnumUserRole.GUEST) {
+        return !isLogin.value;
+      }
+      if (role === EnumUserRole.TEACHER) {
+        return userInfo.value.userType === EnumUserType.TEACHER;
+      }
+      if (role === EnumUserRole.AGENT) {
+        return userInfo.value.userType === EnumUserType.AGENT;
+      }
+      return true;
+    });
+    if (!hasAuth && roles.includes(EnumUserRole.VIP)) {
+      uni.$emit(EnumEvent.OPEN_VIP_POPUP);
+      return false;
+    }
+    return hasAuth;
+  }
+
+  const checkVipPermission = () => {
+    if (!isVip.value) {
+
+    }
+  }
+
+  return {
+    hasPermission
+  }
+}

+ 46 - 22
src/pagesMain/pages/me/components/me-info.vue

@@ -1,37 +1,44 @@
 <template>
   <view class="mx-30 mt-80">
     <view class="flex items-center justify-between gap-x-20" @click="handleHeaderClick">
-      <ie-image :src="avatar" custom-class="w-128 h-128" :round="999" />
+      <ie-image :src="avatar" custom-class="w-134 h-134" :round="999" />
       <view class="flex-1 min-w-1">
         <view class="text-40 text-fore-title flex items-center gap-x-20">
           <text class="font-bold">{{ nickName }}</text>
-          <ie-image v-if="isVip" src="/static/personal/vip_tag.png" custom-class="w-100 h-36" />
+          <!-- <ie-image v-if="isVip" src="/static/personal/vip_tag.png" custom-class="w-100 h-36" /> -->
+          <view v-if="isLogin" class="bg-[#EBF4FC] pl-6 pr-20 py-6 rounded-20 flex items-center">
+            <ie-image src="/static/personal/icon-role.png" custom-class="w-30 h-30" />
+            <text class="ml-10 text-20 text-primary">{{ roleDesc }}</text>
+          </view>
         </view>
-        <view v-if="phonenumber" class="text-30 text-fore-subcontent">{{ phonenumber }}</view>
+        <view v-if="!isLogin" class="mt-8 text-34 text-[#666666]">注册/登录</view>
+        <view v-else-if="phonenumber" class="mt-8 text-30 text-fore-subcontent">{{ phonenumber }}</view>
       </view>
       <view>
         <ie-image src="/static/personal/setting.png" custom-class="w-48 h-48" @click.stop="handleSettingClick" />
       </view>
     </view>
-    <!-- <view class="my-30 flex items-center text-center">
-      <view class="flex-1">
-        <view class="text-30 text-fore-title font-bold">0</view>
-        <view class="mt-10 text-26 text-fore-subcontent">做题数量</view>
-      </view>
-      <view class="flex-1">
-        <view class="text-30 text-fore-title font-bold">0</view>
-        <view class="mt-10 text-26 text-fore-subcontent">视频观看时长</view>
-      </view>
-      <view class="flex-1">
-        <view class="text-30 text-fore-title font-bold">0</view>
-        <view class="mt-10 text-26 text-fore-subcontent">登录次数</view>
-      </view>
-    </view> -->
-    <view v-if="isVip" class="mt-30 relative">
-      <ie-image src="/static/personal/buy_vip.png" custom-class="w-full h-96" />
-      <view class="absolute left-100 right-20 top-0 h-full flex items-center justify-between">
-        <view class="text-26 text-fore-title">已开通会员,享受权益中</view>
-        <view class="text-26 text-fore-subcontent">{{ vipInfo.outDate }} 到期</view>
+
+    <view v-if="isVip"
+      class="mt-50 h-184 rounded-15 relative bg-gradient-to-r from-[#253045] to-[#141D2F] overflow-hidden">
+      <ie-image src="/static/personal/bg-vip-card.png"
+        custom-class="w-276 h-full absolute left-1/2 top-0 -translate-x-1/2 z-0" mode="heightFix" />
+      <view class="h-full box-border px-45 flex items-center justify-between relative z-1">
+        <view class="flex-1 min-w-1">
+          <view class="flex items-center justify-between">
+            <ie-image src="/static/personal/icon-vip.png" custom-class="w-210 h-46" />
+            <view v-if="!isVip" class="flex items-center justify-center px-20 py-10 rounded-22"
+              style="background: radial-gradient( 0% 0% at 0% 0%, #FEE8BD 0%, #EFCC8D 100%);">
+              <text class="mr-4 text-24 text-[#532F12] font-bold leading-23">开通会员</text>
+              <uv-icon name="arrow-right" size="13" color="#532F12" />
+            </view>
+          </view>
+          <view class="mt-16 text-26 text-[#FEECCB]">{{ isVip ? `使用有效期至 ${vipInfo.outDate}` : '开通VIP会员,享受更多专属权益' }}
+          </view>
+        </view>
+        <view class="shrink-0">
+          <ie-image v-if="isVip" src="/static/personal/icon-vip2.png" custom-class="w-160 h-168" mode="heightFix" />
+        </view>
       </view>
     </view>
   </view>
@@ -45,7 +52,24 @@ const avatar = computed(() => userStore.avatar);
 const nickName = computed(() => userStore.nickName);
 const phonenumber = computed(() => userStore.anonymousPhoneNumber);
 const isVip = computed(() => userStore.isVip);
+const isLogin = computed(() => userStore.isLogin);
 const vipInfo = computed(() => userStore.vipInfo);
+const roleDesc = computed(() => {
+  if (isLogin.value && !isVip && !userStore.isStudent) {
+    return '普通会员';
+  }
+  if (isVip.value) {
+    return 'VIP会员';
+  }
+  if (userStore.isAgent) {
+    return '内部账号';
+  }
+  if (userStore.isTeacher) {
+    return '教师账号';
+  }
+  return '普通会员';
+});
+
 const handleHeaderClick = async () => {
   // 不询问直接跳转登录
   const isLogin = await userStore.checkLogin({ askToLogin: false });

+ 0 - 7
src/pagesMain/pages/me/components/me-menu copy.vue

@@ -1,7 +0,0 @@
-<template>
-  <view></view>
-</template>
-<script lang="ts" setup>
-
-</script>
-<style lang="scss" scoped></style>

+ 23 - 33
src/pagesMain/pages/me/components/me-menu.vue

@@ -9,40 +9,30 @@
         </view>
       </view>
       <view class="mt-40 text-30 text-fore-title font-bold">其他功能</view>
+      <view class="mt-16 shadow-card rounded-8 py-10 bg-white">
+        <uv-cell-group :border="false">
+          <uv-cell isLink :cellStyle="cellStyle"
+            @click="handleNavigate('/pagesSystem/pages/edit-profile/edit-profile', '基本资料')">
+            <template #title>
+              <view class="flex items-center gap-x-10">
+                <ie-image src="/static/personal/icon_jibenziliao@2x.png" custom-class="w-34 h-34" />
+                <text class="text-30 text-fore-subtitle">基本资料</text>
+              </view>
+            </template>
+          </uv-cell>
+          <uv-cell isLink :cellStyle="cellStyle" @click="handleQuestion" :border="false">
+            <template #title>
+              <view class="flex items-center gap-x-10">
+                <uv-icon name="question-circle" size="16" color="#888888" />
+                <text class="text-30 text-fore-subtitle">常见问题</text>
+              </view>
+            </template>
+          </uv-cell>
+        </uv-cell-group>
+      </view>
     </view>
-    <view class="-mt-10 rounded-8 py-20">
-      <uv-cell-group :border="false">
-        <uv-cell isLink :cellStyle="cellStyle"
-          @click="handleNavigate('/pagesSystem/pages/edit-profile/edit-profile', '基本资料')">
-          <template #title>
-            <view class="flex items-center gap-x-10">
-              <ie-image src="/static/personal/icon_jibenziliao@2x.png" custom-class="w-34 h-34" />
-              <text class="text-30 text-fore-subtitle">基本资料</text>
-            </view>
-          </template>
-
-        </uv-cell>
-        <!-- <uv-cell isLink :cellStyle="cellStyle"
-          @click="handleNavigate('/pagesOther/pages/personal-center/change-pwd/change-pwd', '修改密码')">
-          <template #title>
-            <view class="flex items-center gap-x-10">
-              <ie-image src="/static/personal/icon_password@2x.png" custom-class="w-36 h-36" />
-              <text class="text-30 text-fore-subtitle">修改密码</text>
-            </view>
-          </template>
-        </uv-cell> -->
-        <uv-cell isLink :cellStyle="cellStyle" @click="handleQuestion">
-          <template #title>
-            <view class="flex items-center gap-x-10">
-              <uv-icon name="question-circle" size="16" color="#888888" />
-              <text class="text-30 text-fore-subtitle">常见问题</text>
-            </view>
-          </template>
-        </uv-cell>
-      </uv-cell-group>
-    </view>
-    <view v-if="userStore.isLogin" class="mt-80 mb-40 w-400 mx-auto">
-      <uv-button type="error" size="medium" shape="circle" text="退出登陆" plain @click="handleLogout"></uv-button>
+    <view v-if="userStore.isLogin" class="mt-80 mb-40 px-30">
+      <ie-button type="primary" custom-class="w-full" @click="handleLogout">退出登陆</ie-button>
     </view>
   </view>
 </template>

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

@@ -1,6 +1,6 @@
 <template>
   <ie-page>
-    <ie-image :is-oss="true" src="/volunteer/page-bg.png" custom-class="w-full h-auto absolute top-0 left-0 -z-1" />
+    <ie-image :is-oss="true" src="/me/page-bg.png" custom-class="w-full h-auto absolute top-0 left-0 -z-1" />
     <me-info />
     <me-menu />
     <template #tabbar>

+ 3 - 3
src/pagesStudy/components/knowledge-tree-node.vue

@@ -1,13 +1,13 @@
 <template>
   <view class="knowledge-tree-node">
-    <view class="min-h-[68px]" @click.stop="handleClick">
-      <view class="flex items-center border-0 border-b border-solid border-[#E6E6E6] py-26">
+    <view class="" @click.stop="handleClick">
+      <view class="flex items-center border-0 border-b border-solid border-[#E6E6E6] py-20">
         <view class="flex-1 min-w-1 flex items-center">
           <uv-icon v-if="!nodeData.isLeaf" name="arrow-right" size="14" color="#888"
             :custom-class="['mr-16 transition-transform duration-300', nodeData.isExpanded ? 'rotate-90' : '']" />
           <view>
             <view class="block text-28 text-fore-title font-bold ellipsis-1">{{ nodeData.name }}</view>
-            <view class="mt-4 text-24 text-fore-light flex items-center">
+            <view class="mt-8 text-24 text-fore-light flex items-center">
               <progress class="w-100 rounded-full overflow-hidden" :percent="getProgressPercent(nodeData)"
                 :show-text="false" activeColor="#31a0fc" backgroundColor="#efefef" />
               <text class="ml-10 text-primary">{{ nodeData.finishedCount }}</text>

+ 4 - 1
src/pagesStudy/pages/index/index.vue

@@ -68,13 +68,16 @@ import IndexMenu from './compoentns/index-menu.vue';
 import IndexBanner from './compoentns/index-banner.vue';
 import IndexTest from './compoentns/index-test.vue';
 import IndexExamRecord from './compoentns/index-exam-record.vue';
-import { EnumDictName, EnumExamType } from '@/common/enum';
+import { EnumDictName, EnumExamType, EnumUserRole } from '@/common/enum';
 import { useUserStore } from '@/store/userStore';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import IePage from '@/components/ie-page/ie-page.vue';
+import { useAuth } from '@/hooks/useAuth';
 
 const { transferTo } = useTransferPage();
 const userStore = useUserStore();
+const { hasPermission } = useAuth();
+
 const hasTestAndRecord = computed(() => userStore.getExamType !== EnumExamType.VHS);
 // 通过 ref 获取 ie-page 组件实例
 const iePageRef = ref<InstanceType<typeof IePage>>();

+ 33 - 25
src/pagesStudy/pages/knowledge-practice-history/knowledge-practice-history.vue

@@ -1,25 +1,29 @@
 <template>
   <ie-page bg-color="#F6F8FA" :fix-height="true">
-    <ie-navbar title="刷题记录" />
-    <view class="mt-20">
-      <view v-for="(item, index) in historyList" :key="index"
-        class="bg-white px-40 py-30 flex items-center sibling-border-top">
-        <view class="flex-1">
-          <view class="text-28">
-            <text class=" text-fore-light">知识点:</text>
-            <text class="text-fore-title">{{ item.paperName }}</text>
+    <z-paging ref="pagingRef" v-model="historyList" :loading-more-enabled="false" @query="handleQuery">
+      <template #top>
+        <ie-navbar title="刷题记录" />
+      </template>
+      <view class="mt-20">
+        <view v-for="(item, index) in historyList" :key="index"
+          class="bg-white px-40 py-30 flex items-center sibling-border-top">
+          <view class="flex-1">
+            <view class="text-28">
+              <text class=" text-fore-light">知识点:</text>
+              <text class="text-fore-title">{{ item.paperName }}</text>
+            </view>
+            <view class="mt-10 text-28">
+              <text class=" text-fore-light">完成时间:</text>
+              <text class="text-fore-title">{{ item.endTime }}</text>
+            </view>
           </view>
-          <view class="mt-10 text-28">
-            <text class=" text-fore-light">完成时间:</text>
-            <text class="text-fore-title">{{ item.endTime }}</text>
+          <view class="text-24 text-white bg-primary w-fit px-40 py-12 rounded-full text-center"
+            @click="handleViewHistory(item)">
+            查看
           </view>
         </view>
-        <view class="text-24 text-white bg-primary w-fit px-40 py-12 rounded-full text-center"
-          @click="handleViewHistory(item)">
-          查看
-        </view>
       </view>
-    </view>
+    </z-paging>
   </ie-page>
 </template>
 <script lang="ts" setup>
@@ -42,23 +46,27 @@ const handleViewHistory = (value: Study.PracticeHistory) => {
     }
   });
 }
-const loadData = async () => {
-  uni.$ie.showLoading();
-  try {
-    const { rows } = await getPracticeHistory();
-    historyList.value = rows.map(item => {
+
+const pagingRef = ref();
+const handleQuery = (pageNum: number, pageSize: number) => {
+  getPracticeHistory({
+    pageNum,
+    pageSize
+  }).then(res => {
+    historyList.value = res.rows.map(item => {
       return {
         ...item,
         endTime: uni.$uv.timeFormat(item.endTime, 'yyyy-mm-dd hh:MM:ss')
       } as Study.PracticeHistory;
     });
-  } finally {
-    uni.$ie.hideLoading();
-  }
+    pagingRef.value.complete(res.rows)
+  }).catch(() => {
+    pagingRef.value.complete(false);
+  });
 }
 onLoad(() => {
   console.log(prevData.value)
-  loadData();
+  // loadData();
 });
 </script>
 <style lang="scss" scoped></style>

+ 7 - 5
src/pagesStudy/pages/knowledge-practice/knowledge-practice.vue

@@ -21,12 +21,15 @@ import { useTransferPage } from '@/hooks/useTransferPage';
 import { getSubjectList, getKnowledgeList } from '@/api/modules/study';
 import knowledgeTree from '@/pagesStudy/components/knowledge-tree.vue';
 import * as Study from '@/types/study';
-import { EnumPaperType } from '@/common/enum';
+import { EnumPaperType, EnumUserRole } from '@/common/enum';
 import { useUserStore } from '@/store/userStore';
+import { useAuth } from '@/hooks/useAuth';
+
 const { prevData, transferTo } = useTransferPage();
 const currentSubjectIndex = ref<number>(-1);
 const userStore = useUserStore();
 const iePageRef = ref<InstanceType<typeof IePage>>();
+const { hasPermission } = useAuth();
 const pageTitle = computed(() => {
   if (prevData.value.directed) {
     return '定向刷题';
@@ -70,8 +73,9 @@ const loadKnowledgeList = async () => {
 }
 
 const handleStartPractice = async (node: Study.KnowledgeNode) => {
-  const isVip = await userStore.checkVip();
-  if (isVip) {
+  // const isVip = await userStore.checkVip();
+  const hasAuth = hasPermission([EnumUserRole.VIP, EnumUserRole.AGENT, EnumUserRole.TEACHER]);
+  if (hasAuth) {
     transferTo('/pagesStudy/pages/exam-start/exam-start', {
       data: {
         name: '知识点练习-' + node.name,
@@ -83,8 +87,6 @@ const handleStartPractice = async (node: Study.KnowledgeNode) => {
         },
       }
     });
-  } else {
-    iePageRef.value?.showVipPopup();
   }
 }
 

+ 6 - 5
src/pagesStudy/pages/textbooks-practice/textbooks-practice.vue

@@ -20,12 +20,14 @@ import { useTransferPage } from '@/hooks/useTransferPage';
 import { getTextbooksKnowledgeList } from '@/api/modules/study';
 import knowledgeTree from '@/pagesStudy/components/knowledge-tree.vue';
 import * as Study from '@/types/study';
-import { EnumPaperType } from '@/common/enum';
+import { EnumPaperType, EnumUserRole } from '@/common/enum';
 import { useUserStore } from '@/store/userStore';
+import { useAuth } from '@/hooks/useAuth';
 const { prevData, transferTo } = useTransferPage();
 
 const userStore = useUserStore();
 const iePageRef = ref<InstanceType<typeof IePage>>();
+const { hasPermission } = useAuth();
 const pageTitle = computed(() => {
   return '教材同步练习';
 });
@@ -50,8 +52,9 @@ const loadKnowledgeList = async () => {
 }
 
 const handleStartPractice = async (node: Study.KnowledgeNode) => {
-  const isVip = await userStore.checkVip();
-  if (isVip) {
+  // const isVip = await userStore.checkVip();
+  const hasAuth = hasPermission([EnumUserRole.VIP, EnumUserRole.AGENT, EnumUserRole.TEACHER]);
+  if (hasAuth) {
     transferTo('/pagesStudy/pages/exam-start/exam-start', {
       data: {
         name: '教材同步练习-' + node.name,
@@ -62,8 +65,6 @@ const handleStartPractice = async (node: Study.KnowledgeNode) => {
         },
       }
     });
-  } else {
-    iePageRef.value?.showVipPopup();
   }
 }
 

+ 7 - 0
src/pagesSystem/pages/bind-teacher-profile/bind-teacher-profile.vue

@@ -154,6 +154,13 @@ const handleBack = () => {
     transferBack();
     return;
   }
+  const pages = getCurrentPages();
+  const page = pages[pages.length - 2];
+  // 如果是登录页,允许直接返回
+  if (page?.route === 'pagesSystem/pages/login/login') {
+    transferBack();
+    return;
+  }
   uni.$ie.showToast('请先完善信息');
 };
 const loginValidate = () => {

+ 1 - 1
src/pagesSystem/pages/edit-profile/edit-profile.vue

@@ -102,7 +102,7 @@
             </uv-form-item>
           </content-card>
         </template>
-        <content-card v-else title="学校信息">
+        <content-card v-else-if="userStore.isTeacher" title="学校信息">
           <uv-form-item label="学校名称" prop="form.campusName" borderBottom>
             <uv-input v-model="form.campusName" border="none" placeholder="" placeholderClass="text-30"
               font-size="30rpx" :custom-style="customStyle" readonly>

BIN
src/static/personal/avatar_default.png


BIN
src/static/personal/bg-vip-card.png


BIN
src/static/personal/icon-role.png


BIN
src/static/personal/icon-vip.png


BIN
src/static/personal/icon-vip2.png


+ 4 - 0
src/static/style/tailwind.scss

@@ -34,6 +34,10 @@
     box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
   }
 
+  .shadow-card-dark {
+    box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.08);
+  }
+
   .sibling-border-top {
     &+.sibling-border-top {
       border-top: 1px solid #E5E5E5;

+ 1 - 1
src/store/userStore.ts

@@ -46,7 +46,7 @@ export const useUserStore = defineStore('ie-user', {
   }),
   getters: {
     isLogin(state: UserStoreState): boolean {
-      return !!state.accessToken && state.user !== null;
+      return !!state.accessToken && state.user !== null && Object.keys(state.user).length > 0;
     },
     userInfo(state: UserStoreState): UserInfo {
       return state.user || {} as UserInfo;

+ 2 - 0
src/types/user.ts

@@ -171,6 +171,8 @@ export interface VipCardInfo {
   outDate: string; // 到期时间
 }
 
+export type UserRole = 'vip' | 'normal' | 'guest' | 'teacher' | 'agent' | 'auditor'
+
 // export interface BindCardInfo {
 //   cardNo: string;
 //   password: string;