Selaa lähdekoodia

完善绑卡登录

shmily1213 1 kuukausi sitten
vanhempi
commit
0be6662375
33 muutettua tiedostoa jossa 1170 lisäystä ja 160 poistoa
  1. 12 9
      src/App.vue
  2. 11 2
      src/api/modules/login.ts
  3. 0 11
      src/api/modules/plan.ts
  4. 50 0
      src/api/modules/study.ts
  5. 10 0
      src/api/modules/system.ts
  6. 35 0
      src/api/modules/user.ts
  7. 22 2
      src/components/ie-button/ie-button.vue
  8. 2 2
      src/components/ie-image/ie-image.vue
  9. 16 3
      src/components/ie-picker/ie-picker.vue
  10. 5 3
      src/components/ie-sms/ie-sms.vue
  11. 12 12
      src/hooks/useSms.ts
  12. 3 0
      src/hooks/useUserStore.js
  13. 24 0
      src/pages.json
  14. 5 5
      src/pagesMain/pages/me/components/me-info.vue
  15. 6 1
      src/pagesMain/pages/me/components/me-menu.vue
  16. 3 2
      src/pagesStudy/pages/index/compoentns/index-banner.vue
  17. 61 0
      src/pagesSystem/pages/bind-vip/bind-vip.vue
  18. 53 0
      src/pagesSystem/pages/card-verify/card-verify.vue
  19. 54 15
      src/pagesSystem/pages/login/login.vue
  20. 72 0
      src/pagesSystem/pages/phone-verify/phone-verify.vue
  21. 55 0
      src/pagesSystem/pages/school-select/school-select.vue
  22. 10 0
      src/pagesSystem/pages/user-profile/components/exam-info copy 2.vue
  23. 10 0
      src/pagesSystem/pages/user-profile/components/exam-info copy 3.vue
  24. 10 0
      src/pagesSystem/pages/user-profile/components/exam-info copy.vue
  25. 10 0
      src/pagesSystem/pages/user-profile/components/exam-info.vue
  26. 262 0
      src/pagesSystem/pages/user-profile/user-profile copy.vue
  27. 230 82
      src/pagesSystem/pages/user-profile/user-profile.vue
  28. 5 0
      src/store/appStore.ts
  29. 14 3
      src/store/userStore.ts
  30. 3 0
      src/types/index.ts
  31. 7 1
      src/types/injectionSymbols.ts
  32. 97 6
      src/types/user.ts
  33. 1 1
      src/uni_modules/uv-input/components/uv-input/uv-input.vue

+ 12 - 9
src/App.vue

@@ -1,14 +1,17 @@
 <script>
+import { useAppStore } from '@/store/appStore';
 export default {
-    onLaunch: function () {
-        console.log('App Launch')
-    },
-    onShow: function () {
-        console.log('App Show')
-    },
-    onHide: function () {
-        console.log('App Hide')
-    }
+  onLaunch: function () {
+    console.log('App Launch')
+    const appStore = useAppStore();
+    appStore.init();
+  },
+  onShow: function () {
+    console.log('App Show')
+  },
+  onHide: function () {
+    console.log('App Hide')
+  }
 }
 </script>
 

+ 11 - 2
src/api/modules/login.ts

@@ -1,4 +1,4 @@
-import { LoginInfo, MobileLoginRequestDTO, MobileLoginResponseDTO, RegisterInfo, UserInfo } from "@/types/user";
+import { BindCardInfo, LoginInfo, MobileLoginRequestDTO, MobileLoginResponseDTO, RegisterInfo, UserInfo } from "@/types/user";
 import { ApiResponse } from "@/types";
 import flyio from "../flyio";
 
@@ -17,10 +17,19 @@ export function mobileLogin(params: MobileLoginRequestDTO) {
  * @param params 注册参数
  * @returns 注册结果
  */
-export function registry(params: RegisterInfo) {
+export function registry(params: BindCardInfo) {
   return flyio.post('/registry', params) as Promise<ApiResponse<any>>;
 }
 
+/**
+ * 完善信息
+ * @param params 注册参数
+ * @returns 注册结果
+ */
+export function improve(params: BindCardInfo) {
+  return flyio.post('/improve', params) as Promise<ApiResponse<any>>;
+}
+
 /**
  * 获取用户信息
  * @returns 用户信息

+ 0 - 11
src/api/modules/plan.ts

@@ -1,11 +0,0 @@
-import { ApiResponse } from "@/types";
-import flyio from "../flyio";
-
-/**
- * 获取学习计划
- * @param params 
- * @returns 
- */
-export function getStudyPlan() {
-  return flyio.get('/front/student/plan') as Promise<ApiResponse<any>>;
-}

+ 50 - 0
src/api/modules/study.ts

@@ -0,0 +1,50 @@
+import { ApiResponse } from "@/types";
+import flyio from "../flyio";
+import { StudyPlan } from "@/types/study";
+
+/**
+ * 获取学习计划
+ * @param params 
+ * @returns 
+ */
+export function getStudyPlan() {
+  return flyio.get('/front/student/plan') as Promise<ApiResponse<any>>;
+}
+
+/**
+ * 保存学习计划
+ * @param params 
+ * @returns 
+ */
+export function saveStudyPlan(params: StudyPlan) {
+  return flyio.post('/front/student/plan', params) as Promise<ApiResponse<any>>;
+}
+
+/**
+ * 获取学习计划统计
+ * @param params 
+ * @returns 
+ */
+export function getStudyPlanStats() {
+  return flyio.get('/front/student/plan/stats') as Promise<ApiResponse<any>>;
+}
+
+
+/**
+ * 获取定向学校列表
+ * @param params 
+ * @returns 
+ */
+export function getDirectedSchool() {
+  return flyio.get('/front/student/directed/school') as Promise<ApiResponse<any>>;
+}
+
+
+/**
+ * 保存定向学校
+ * @param params 
+ * @returns 
+ */
+export function saveDirectedSchool(params: any) {
+  return flyio.post('/front/student/directed/school', params) as Promise<ApiResponse<any>>;
+}

+ 10 - 0
src/api/modules/system.ts

@@ -86,3 +86,13 @@ export function getGraduateYears(location: string, examType: string) {
 export function getCaptchaImage() {
   return flyio.get('/captchaImage') as Promise<ApiCaptchaResponse>;
 }
+
+
+/**
+ * 验证短信验证码
+ * @param params 包含手机号等参数的对象
+ * @returns 
+ */
+export function validateSms(params: SmsRequestDTO) {
+  return flyio.post('/front/comm/validateSms', null, { params }) as Promise<ApiResponse<any>>;
+}

+ 35 - 0
src/api/modules/user.ts

@@ -0,0 +1,35 @@
+import flyio from "../flyio";
+import { ApiCaptchaResponse, ApiResponse, DictItem, ConfigItem, ApiResponseList } from "@/types";
+import { ClassItem, ClassListQueryDTO, SchoolItem, SchoolListQueryDTO, SmsRequestDTO, CardInfo } from "@/types/user";
+
+/**
+ * 验证会员卡
+ * @param cardNo 
+ * @param password 
+ * @returns 
+ */
+export function verifyCard(cardNo: string, password: string) {
+  return flyio.post('/front/user/verifyCard', { cardNo, password }, {
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    }
+  }) as Promise<ApiResponse<CardInfo>>;
+}
+
+/**
+ * 获取学校列表
+ * @param keyword 
+ * @returns 
+ */
+export function getSchoolList(params: SchoolListQueryDTO) {
+  return flyio.get('/front/user/getSchoolList', params) as Promise<ApiResponseList<SchoolItem>>;
+}
+
+/**
+ * 获取班级列表
+ * @param params 
+ * @returns 
+ */
+export function getClassList(params: ClassListQueryDTO) {
+  return flyio.get('/front/user/getClassList', params) as Promise<ApiResponse<ClassItem[]>>;
+}

+ 22 - 2
src/components/ie-button/ie-button.vue

@@ -1,5 +1,6 @@
 <template>
-  <button class="ie-button" :class="['ie-button', `ie-button-${type}`, customClass, { 'is-disabled': disabled }]"
+  <button class="ie-button"
+    :class="['ie-button', `ie-button-${type}`, `ie-button-${size}`, customClass, { 'is-disabled': disabled }]"
     :disabled="disabled" hover-class="button-hover" @click="handleClick">
     <slot></slot>
   </button>
@@ -16,6 +17,10 @@ const props = defineProps({
     type: String as PropType<'primary' | 'secondary'>,
     default: 'primary'
   },
+  size: {
+    type: String as PropType<'large' | 'normal' | 'small' | 'mini'>,
+    default: 'large'
+  },
   customClass: {
     type: String as PropType<string>,
     default: ''
@@ -34,9 +39,24 @@ const handleClick = () => {
   line-height: 1;
   font-weight: 800;
   box-shadow: 0 5px 8px 0px rgba(49, 160, 252, 0.6);
-  @apply relative rounded-full bg-primary text-white text-center py-36 text-30;
+  @apply relative rounded-full bg-primary text-white text-center;
+}
+
+.ie-button-large {
+  @apply py-36 text-30;
+}
+
+.ie-button-normal {
+  @apply py-32 text-28;
+}
+
+.ie-button-small {
+  @apply py-26 text-26;
 }
 
+.ie-button-mini {
+  @apply py-20 text-24;
+}
 
 .ie-button::after {
   border: none;

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

@@ -44,8 +44,8 @@ const imageSrc = computed(() => {
   return props.src ? (props.isOss ? config.ossUrl + props.src : resolvePath(props.src)) : '';
 });
 const emit = defineEmits(['click']);
-const handleClick = () => {
-  emit('click');
+const handleClick = (e: any) => {
+  emit('click', e);
 }
 
 const loaded = ref(false);

+ 16 - 3
src/components/ie-picker/ie-picker.vue

@@ -1,14 +1,14 @@
 <template>
   <view class="w-full" @click="handleClick">
     <view class="flex items-center gap-x-10" :style="customStyle">
-      <view v-if="matchValue" class="flex-1 text-fore-title" :style="getValueStyle"
+      <view v-if="matchValue || customLabel" class="flex-1 text-fore-title" :style="getValueStyle"
         :class="{ 'text-[#c0c4cc]': disabled || readonly }">
         <slot :label="label">
           {{ label }}
         </slot>
       </view>
       <view v-else class="flex-1 text-[#c0c4cc]" :style="getPlaceholderStyle">{{ placeholder }}</view>
-      <view v-if="!readonly" class="transition-all duration-300">
+      <view v-if="!readonly && showArrow" class="transition-all duration-300">
         <uv-icon :name="icon" size="15" color="#B3B3B3" />
       </view>
     </view>
@@ -36,6 +36,10 @@ const props = defineProps({
     type: String,
     default: '请选择',
   },
+  customLabel: {
+    type: String,
+    default: '',
+  },
   title: {
     type: String,
     default: '',
@@ -75,7 +79,11 @@ const props = defineProps({
   fontSize: {
     type: Number,
     default: 30,
-  }
+  },
+  showArrow: {
+    type: Boolean,
+    default: true,
+  },
 });
 const defaultIndex = ref([0]);
 const label = ref('');
@@ -96,6 +104,11 @@ const getValueStyle = computed(() => {
   }
 });
 const init = () => {
+  if (props.customLabel) {
+    label.value = props.customLabel;
+    matchValue.value = true;
+    return;
+  }
   if (modelValue.value !== null && modelValue.value !== undefined && modelValue.value !== '') {
     const index = props.list.findIndex(item => item[props.keyValue] == modelValue.value);
     if (index !== -1) {

+ 5 - 3
src/components/ie-sms/ie-sms.vue

@@ -54,16 +54,18 @@ const handleSubmit = () => {
     uuid: uuid.value,
     code: code.value
   };
-  sms.sendSms(props.smsApiType, params).then(success => {
+  sms.sendSms(props.smsApiType, params).then(res => {
     uni.$ie.hideLoading();
-    if (success) {
+    if (res.success) {
       captchaRef.value.close();
       uni.$ie.showToast('短信发送成功');
       emit('send', props.phone, code.value, uuid.value);
+    } else if (res.error.msg === '手机号已存在') {
+      captchaRef.value.close();
     } else {
       captchaRef.value.changeImage();
     }
-  }).catch(() => {
+  }).catch((err) => {
     uni.$ie.hideLoading();
   });
 }

+ 12 - 12
src/hooks/useSms.ts

@@ -81,16 +81,16 @@ export const useSms = () => {
   };
 
   // 发送短信
-  const sendSms = async (type: SmsApiType, params: SmsParams): Promise<boolean> => {
+  const sendSms = async (type: SmsApiType, params: SmsParams): Promise<{ success: boolean, error: any }> => {
     // 检查是否正在发送或倒计时中
     if (smsIsSending.value || isCountingDown.value) {
-      return false;
+      return Promise.resolve({ success: false, error: null });
     }
     const { mobile, smsType, uuid, code } = params;
     // 验证手机号
     if (!validatePhone(mobile)) {
       console.warn('手机号格式不正确');
-      return false;
+      return Promise.resolve({ success: false, error: null });
     }
     const smsParams = {
       mobile: mobile,
@@ -100,44 +100,44 @@ export const useSms = () => {
     };
     try {
       smsIsSending.value = true;
-      return new Promise((resolve) => {
+      return new Promise<{ success: boolean, error: any }>((resolve) => {
         switch (type) {
           case EnumSmsApiType.NO_VALIDATION_NO_TOKEN:
             sendSmsNoValidationNoToken(smsParams).then(res => {
               startCountdown();
-              resolve(true);
+              resolve({ success: true, error: null });
             }).catch(error => {
               console.error('发送短信请求失败:', error);
-              resolve(false);
+              resolve({ success: false, error: error });
             });
             break;
           case EnumSmsApiType.NO_TOKEN:
             sendSmsNoToken(smsParams).then(res => {
               startCountdown();
-              resolve(true);
+              resolve({ success: true, error: null });
             }).catch(error => {
               console.error('发送短信请求失败:', error);
-              resolve(false);
+              resolve({ success: false, error: error });
             });
             break;
           case EnumSmsApiType.NORMAL:
             sendSmsApi(smsParams).then(res => {
               startCountdown();
-              resolve(true);
+              resolve({ success: true, error: null });
             }).catch(error => {
               console.error('发送短信请求失败:', error);
-              resolve(false);
+              resolve({ success: false, error: error });
             });
             break;
           default:
             console.error('未知的短信发送类型:', type);
-            resolve(false);
+            resolve({ success: false, error: null });
             break;
         }
       });
     } catch (error) {
       console.error('发送短信失败:', error);
-      return false;
+      return Promise.resolve({ success: false, error: error });
     } finally {
       smsIsSending.value = false;
     }

+ 3 - 0
src/hooks/useUserStore.js

@@ -38,6 +38,9 @@ export const useUserStore = createGlobalState(() => {
     // methods
     function mergeAppConfig() {
         const overrideConfig = appConfig.value;
+        if (!uni.$uv) {
+          return;
+        }
         const mergedConfig = uni.$uv.deepMerge(mxConfig, overrideConfig);
         Object.assign(mxConfig, mergedConfig);
         // console.log("merge app config: to mx config", overrideConfig, mxConfig);

+ 24 - 0
src/pages.json

@@ -517,6 +517,30 @@
           "style": {
             "navigationBarTitleText": ""
           }
+        },
+        {
+          "path": "pages/bind-vip/bind-vip",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/card-verify/card-verify",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/phone-verify/phone-verify",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/school-select/school-select",
+          "style": {
+            "navigationBarTitleText": ""
+          }
         }
       ]
     },

+ 5 - 5
src/pagesMain/pages/me/components/me-info.vue

@@ -10,7 +10,7 @@
         <view v-if="phonenumber" class="text-30 text-fore-subcontent">{{ phonenumber }}</view>
       </view>
       <view>
-        <ie-image src="/static/personal/setting.png" custom-class="w-48 h-48" @click="handleSettingClick" />
+        <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">
@@ -27,11 +27,11 @@
         <view class="mt-10 text-26 text-fore-subcontent">登录次数</view>
       </view>
     </view>
-    <view class="relative">
+    <view v-if="isVip" class="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">2025-01-01 到期</view>
+        <view class="text-26 text-fore-subcontent">{{ vipInfo.outDate }} 到期</view>
       </view>
     </view>
   </view>
@@ -45,13 +45,13 @@ const avatar = computed(() => userStore.avatar);
 const nickName = computed(() => userStore.nickName);
 const phonenumber = computed(() => userStore.anonymousPhoneNumber);
 const isVip = computed(() => userStore.isVip);
-
+const vipInfo = computed(() => userStore.vipInfo);
 const handleHeaderClick = async () => {
   // 不询问直接跳转登录
   const isLogin = await userStore.checkLogin({ askToLogin: false });
 }
 const handleSettingClick = async () => {
-  const isLogin = await userStore.checkLogin();
+  const isLogin = await userStore.checkLogin({ askToLogin: true });
   if (isLogin) {
     transferTo('/pagesOther/pages/personal-center/setting/setting');
   }

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

@@ -77,7 +77,8 @@ const menus = [
   {
     name: '绑定会员卡',
     icon: '/static/personal/bind_card.png',
-    pagePath: '/pagesOther/pages/personal-center/bind-card/bind-card',
+    // pagePath: '/pagesOther/pages/personal-center/bind-card/bind-card',
+    pagePath: '/pagesSystem/pages/card-verify/card-verify',
   }
 ];
 const cellStyle = {
@@ -86,6 +87,10 @@ const cellStyle = {
 const handleClick = async (item: MenuItem) => {
   const isLogin = await userStore.checkLogin();
   if (isLogin) {
+    if (item.name === '绑定会员卡' && userStore.isVip) {
+      uni.$ie.showToast('您已是会员');
+      return;
+    }
     transferTo(item.pagePath);
   }
 }

+ 3 - 2
src/pagesStudy/pages/index/compoentns/index-banner.vue

@@ -30,7 +30,7 @@
 </template>
 <script lang="ts" setup>
 import { useTransferPage } from '@/hooks/useTransferPage';
-import { getStudyPlan } from '@/api/modules/plan';
+import { getStudyPlan, getDirectedSchool } from '@/api/modules/study';
 const { transferTo } = useTransferPage();
 const navigateTo = (pageUrl: string) => {
   transferTo(pageUrl);
@@ -38,7 +38,8 @@ const navigateTo = (pageUrl: string) => {
 
 const handleOpenPlan = async () => {
   const data = await getStudyPlan()
-  console.log(data)
+  const directedSchool = await getDirectedSchool()
+  console.log(data, directedSchool)
   // transferTo('/pagesStudy/pages/study-plan-edit/study-plan-edit');
 }
 </script>

+ 61 - 0
src/pagesSystem/pages/bind-vip/bind-vip.vue

@@ -0,0 +1,61 @@
+<template>
+  <ie-page bg-color="#f8fcff">
+    <ie-navbar title="绑定会员卡" />
+    <view class="px-10 py-30">
+      <uv-steps current="0" dot>
+        <uv-steps-item title="检验会员卡"></uv-steps-item>
+        <uv-steps-item title="确认信息"></uv-steps-item>
+        <uv-steps-item title="绑卡完成"></uv-steps-item>
+      </uv-steps>
+    </view>
+    <view class="mx-40">
+      <view class="mt-10 px-20 py-50 bg-white">
+        <uv-form labelPosition="left" :model="form" ref="formRef" :labelWidth="110">
+          <uv-form-item label="会员卡号" prop="form.cardNo" borderBottom>
+            <uv-input v-model="form.cardNo" type="number" inputAlign="right" border="none" maxlength="11"
+              placeholder="请输入会员卡号"></uv-input>
+          </uv-form-item>
+          <uv-form-item label="会员卡密码" prop="form.password" borderBottom>
+            <uv-input v-model="form.password" type="text" inputAlign="right" border="none"
+              placeholder="请输入会员卡密码"></uv-input>
+          </uv-form-item>
+        </uv-form>
+      </view>
+      <view class="mt-100">
+        <ie-button type="primary" @click="handleCheckCard">校验会员卡</ie-button>
+        <view class="mt-40 text-center text-primary underline">还没有卡?去购卡</view>
+      </view>
+    </view>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { verifyCard } from '@/api/modules/user';
+const form = ref({
+  cardNo: '20000001',
+  password: '669132'
+});
+const formRef = ref();
+const handleCheckCard = async () => {
+  console.log('handleCheckCard');
+  const { cardNo, password } = form.value;
+  if (!cardNo || cardNo.trim() === '') {
+    uni.$ie.showToast('请输入会员卡号');
+    return;
+  }
+  if (!password || password.trim() === '') {
+    uni.$ie.showToast('请输入会员卡密码');
+    return;
+  }
+  uni.$ie.showLoading();
+  const data = await verifyCard(cardNo, password);
+  uni.$ie.hideLoading();
+  console.log(data);
+};
+</script>
+
+<style lang="scss" scoped>
+::v-deep .uv-steps-item__wrapper {
+  border-radius: 50%;
+}
+</style>

+ 53 - 0
src/pagesSystem/pages/card-verify/card-verify.vue

@@ -0,0 +1,53 @@
+<template>
+  <ie-page bg-color="#F6F8FA">
+    <ie-navbar title="绑定会员卡" />
+    <view class="h-20"></view>
+    <view class="px-46 py-50 bg-white">
+      <ie-input v-model="form.cardNo" type="number" :maxlength="8" placeholder="请输入会员卡号" />
+      <ie-input custom-class="mt-28" type="text" :maxlength="6" v-model="form.password" placeholder="请输入会员卡密码" />
+    </view>
+    <ie-safe-toolbar :height="84" :shadow="false">
+      <view class="px-46 pt-24">
+        <ie-button type="primary" @click="handleSubmit">开始校验</ie-button>
+      </view>
+    </ie-safe-toolbar>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { verifyCard } from '@/api/modules/user';
+import { useTransferPage } from '@/hooks/useTransferPage';
+const { transferTo } = useTransferPage();
+const form = ref({
+  cardNo: '20000001',
+  password: '669132'
+});
+const handleSubmit = async () => {
+  const { cardNo, password } = form.value;
+  if (!cardNo || cardNo.trim() === '') {
+    uni.$ie.showToast('请输入会员卡号');
+    return;
+  }
+  if (!password || password.trim() === '') {
+    uni.$ie.showToast('请输入会员卡密码');
+    return;
+  }
+  uni.$ie.showLoading();
+  verifyCard(cardNo, password).then(() => {
+    uni.$ie.hideLoading();
+    transferTo('/pagesSystem/pages/user-profile/user-profile', {
+      data: {
+        cardNo,
+        password,
+        type: 'card',
+        scene: 'phone_improve'
+      }
+    });
+  }).catch(() => {
+    uni.$ie.hideLoading();
+  });
+
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 54 - 15
src/pagesSystem/pages/login/login.vue

@@ -22,12 +22,12 @@
         </view>
         <view v-show="loginType === 'card'">
           <view class="mt-28 relative flex items-center px-42 bg-back rounded-40 h-104 box-border">
-            <input class="flex-1 h-full text-30 text-fore-subcontent" v-model="phone" type="number" placeholder="请输入卡号"
-              placeholder-class="text-fore-light" />
+            <input class="flex-1 h-full text-30 text-fore-subcontent" v-model="cardNo" type="number" maxlength="8"
+              placeholder="请输入卡号" placeholder-class="text-fore-light" />
           </view>
           <view class="mt-28 relative flex items-center px-42 bg-back rounded-40 h-104 box-border">
-            <input class="flex-1 h-full text-30 text-fore-subcontent" v-model="password" type="text"
-              :password="!showPassword" placeholder="请输入密码" placeholder-class="text-fore-light" />
+            <input class="flex-1 h-full text-30 text-fore-subcontent" v-model="cardPassword" type="text"
+              :password="!showPassword" placeholder="请输入密码" maxlength="6" placeholder-class="text-fore-light" />
             <cover-view class="w-60 h-60 flex items-center justify-center" @click="toggleShowPassword">
               <cover-image v-show="!showPassword" src="@/pagesSystem/static/image/icon/icon-eye.png" mode="widthFix"
                 class="w-44 h-44" />
@@ -55,15 +55,16 @@
         </uv-checkbox-group>
       </view>
     </view>
+    <ie-captcha ref="captchaRef" v-model:code="code" v-model:uuid="uuid" @valid="handleValid" />
   </ie-page>
 </template>
 
 <script lang="ts" setup>
+import ieCaptcha from '@/components/ie-sms/ie-captcha.vue';
 import { useUserStore } from '@/store/userStore';
-import config from '@/config';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useValidation } from '@/hooks/useValidation';
-// import {useDictStore} from '@/hooks/useDictStore';
+import { verifyCard } from '@/api/modules/user';
 import { EnumSmsApiType } from '@/common/enum';
 import { mobileLogin } from '@/api/modules/login';
 import { MobileLoginRequestDTO, MobileLoginResponseDTO } from '@/types/user';
@@ -74,6 +75,8 @@ const { validatePhone, validateTelephone } = useValidation();
 const loginType = ref('phone');
 const phone = ref('13222222222');
 const password = ref('');
+const cardNo = ref('20000002');
+const cardPassword = ref('823749');
 const code = ref('');
 const uuid = ref('');
 const showPassword = ref(false);
@@ -114,11 +117,11 @@ const loginValidate = () => {
       return false;
     }
   } else if (loginType.value === 'card') {
-    if (!phone.value) {
+    if (!cardNo.value || !cardNo.value.trim()) {
       uni.$ie.showToast('请输入卡号');
       return false;
     }
-    if (!code.value) {
+    if (!cardPassword.value || !cardPassword.value.trim()) {
       uni.$ie.showToast('请输入密码');
       return false;
     }
@@ -130,21 +133,36 @@ const loginValidate = () => {
   return true;
 }
 
+const captchaRef = ref();
 const handleLogin = async () => {
   if (!loginValidate()) {
     return;
   }
+  if (loginType.value === 'phone') {
+    submitLogin();
+  } else if (loginType.value === 'card') {
+    // captchaRef.value.open();
+    submitLogin();
+  }
+}
+
+const submitLogin = async () => {
   const params: MobileLoginRequestDTO = {
-    mobile: phone.value,
-    password: password.value,
     code: code.value,
     uuid: uuid.value
   };
-  // if (loginType.value === 'phone') {
-  //   params.code = code.value;
-  // } else if (loginType.value === 'card') {
-  //   params.password = code.value;
-  // }
+  if (loginType.value === 'phone') {
+    params.mobile = phone.value;
+    params.password = password.value;
+    handleMobileLogin(params);
+  } else if (loginType.value === 'card') {
+    params.username = cardNo.value;
+    params.password = cardPassword.value;
+    handleCardLogin(params);
+  }
+}
+
+const handleMobileLogin = async (params: MobileLoginRequestDTO) => {
   uni.$ie.showLoading();
   mobileLogin(params).then((res) => {
     uni.$ie.hideLoading();
@@ -171,7 +189,28 @@ const handleLogin = async () => {
     console.log('登录失败', err)
   })
 }
+const handleCardLogin = (params: MobileLoginRequestDTO) => {
+  verifyCard(cardNo.value, cardPassword.value).then(res => {
+    console.log(res)
+    if (res.data) {
+      transferTo('/pagesSystem/pages/phone-verify/phone-verify', {
+        data: {
+          card: res.data,
+          cardNo: res.data.cardNo,
+          password: res.data.password
+        }
+      });
+    }
+  }).catch(err => {
+    console.log('验证会员卡失败', err);
+  })
+}
 
+const handleValid = (data: { code: string; uuid: string }) => {
+  console.log(code.value, uuid.value);
+  captchaRef.value.close();
+  submitLogin();
+}
 
 
 onLoad(() => { });

+ 72 - 0
src/pagesSystem/pages/phone-verify/phone-verify.vue

@@ -0,0 +1,72 @@
+<template>
+  <ie-page bg-color="#F6F8FA">
+    <ie-navbar title="手机号验证" />
+    <view class="h-20"></view>
+    <view class="px-46 py-50 bg-white">
+      <ie-input v-model="form.phone" type="number" :maxlength="11" placeholder="请输入手机号" />
+      <ie-input custom-class="mt-28" type="number" :maxlength="6" v-model="form.code" placeholder="请输入验证码">
+        <ie-sms :phone="form.phone" :sms-api-type="EnumSmsApiType.NO_TOKEN" @send="handleSendSuccess" />
+      </ie-input>
+    </view>
+    <ie-safe-toolbar :height="84" :shadow="false">
+      <view class="px-46 pt-24">
+        <ie-button type="primary" @click="handleSubmit">下一步</ie-button>
+      </view>
+    </ie-safe-toolbar>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { verifyCard } from '@/api/modules/user';
+import { useTransferPage } from '@/hooks/useTransferPage';
+import { EnumSmsApiType } from '@/common/enum';
+import { validateSms } from '@/api/modules/system';
+const { prevData, transferTo } = useTransferPage();
+const form = ref({
+  phone: '17363958509',
+  code: '',
+  uuid: '',
+});
+const handleSendSuccess = (_phone: string, _code: string, _uuid: string) => {
+  console.log('短信发送成功', _phone, _code, _uuid);
+  // form.value.code = _code;
+  form.value.uuid = _uuid;
+}
+const handleSubmit = async () => {
+  const { phone, code, uuid } = form.value;
+  if (!phone || phone.trim() === '') {
+    uni.$ie.showToast('请输入手机号');
+    return;
+  }
+  if (!uuid && code) {
+    uni.$ie.showToast('请输入正确的验证码');
+    return;
+  }
+  if (!uuid || !code || code.trim() === '') {
+    uni.$ie.showToast('请输入验证码');
+    return;
+  }
+  uni.$ie.showLoading();
+  validateSms({ mobile: phone, code }).then(() => {
+    uni.$ie.hideLoading();
+    const params = {
+      phone,
+      code,
+      uuid,
+      type: 'phone',
+      scene: 'card_improve',
+      card: prevData.value.card,
+      cardNo: prevData.value.cardNo,
+      password: prevData.value.password
+    };
+    console.log(params)
+    transferTo('/pagesSystem/pages/user-profile/user-profile', {
+      data: params
+    });
+  }).catch(() => {
+    uni.$ie.hideLoading();
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 55 - 0
src/pagesSystem/pages/school-select/school-select.vue

@@ -0,0 +1,55 @@
+<template>
+  <ie-page bg-color="#F6F8FA">
+    <z-paging ref="paging" v-model="list" :auto="false" :default-page-size="30" @query="handleQuery">
+      <template #top>
+        <view>
+          <ie-navbar title="选择学校" />
+          <view class="bg-white px-30 py-20">
+            <uv-input placeholder="请输入关键字" border="surround" shape="circle" prefixIcon="search"
+              prefixIconStyle="font-size: 22px;color: #909399" v-model="keyword" @confirm="handleConfirm"></uv-input>
+          </view>
+        </view>
+      </template>
+      <view class="mt-16">
+        <view class="p-20 bg-white" v-for="item in list" :key="item.id" @click="handleItemClick(item)">
+          {{ item.name }}
+        </view>
+      </view>
+    </z-paging>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { getSchoolList } from '@/api/modules/user';
+import { SchoolItem } from '@/types/user';
+import { useTransferPage } from '@/hooks/useTransferPage';
+const { transferTo, transferBack } = useTransferPage();
+const keyword = ref<string>('');
+const list = ref<SchoolItem[]>([]);
+const paging = ref();
+const handleQuery = (pageNum: number, pageSize: number) => {
+  uni.$ie.showLoading();
+  getSchoolList({
+    keyword: keyword.value,
+    pageNum,
+    pageSize
+  }).then(res => {
+    paging.value.complete(res.rows, res.total);
+  }).catch(e => {
+    paging.value.complete(false);
+  }).finally(() => {
+    uni.$ie.hideLoading();
+  });
+};
+const handleItemClick = (item: SchoolItem) => {
+  transferBack(item);
+};
+const handleConfirm = () => {
+  paging.value.reload();
+};
+onMounted(() => {
+  paging.value.reload();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 10 - 0
src/pagesSystem/pages/user-profile/components/exam-info copy 2.vue

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

+ 10 - 0
src/pagesSystem/pages/user-profile/components/exam-info copy 3.vue

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

+ 10 - 0
src/pagesSystem/pages/user-profile/components/exam-info copy.vue

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

+ 10 - 0
src/pagesSystem/pages/user-profile/components/exam-info.vue

@@ -0,0 +1,10 @@
+<template>
+
+</template>
+<script lang="ts" setup>
+import { STUDENT_BIND_INFO } from '@/types/injectionSymbols';
+const bindInfo = inject(STUDENT_BIND_INFO);
+// console.log(bindInfo?.value.);
+</script>
+
+<style lang="scss" scoped></style>

+ 262 - 0
src/pagesSystem/pages/user-profile/user-profile copy.vue

@@ -0,0 +1,262 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :safeAreaInsetBottom="false">
+    <ie-navbar title="完善信息"></ie-navbar>
+    <uv-form labelPosition="left" :model="form" labelWidth="70px" ref="formRef">
+      <content-card title="考生信息">
+        <uv-form-item label="学生姓名" prop="name" borderBottom required>
+          <uv-input v-model="form.nickName" border="none" placeholder="请输入姓名" placeholderClass="text-30"
+            font-size="30rpx" :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="所在省份" prop="location" borderBottom required>
+          <ie-picker ref="pickerRef" v-model="form.location" :list="appStore.provinceList" placeholder="选择省份"
+            :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"
+            @change="handleProvinceChange"></ie-picker>
+        </uv-form-item>
+        <uv-form-item label="考生类别" prop="examType" borderBottom required>
+          <ie-picker ref="pickerRef" v-model="form.examType" :list="examTypeList" :disabled="!form.location"
+            placeholder="选择考生类别" :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"
+            @change="handleExamTypeChange"></ie-picker>
+        </uv-form-item>
+        <uv-form-item v-if="form.examType === 'VHS'" label="专业类别" prop="majorType" borderBottom required>
+          <ie-picker ref="pickerRef" v-model="form.majorType" :list="majorTypes" :disabled="!form.examType"
+            placeholder="选择专业类别" :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"
+            @change="handleMajorChange"></ie-picker>
+        </uv-form-item>
+        <uv-form-item label="毕业年份" prop="year" required>
+          <ie-picker ref="pickerRef" v-model="form.endYear" :list="endYearList" :disabled="!form.examType"
+            placeholder="选择毕业年份" :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"></ie-picker>
+        </uv-form-item>
+
+      </content-card>
+      <content-card title="邀请信息">
+        <uv-form-item label="邀请码" prop="form.inviteCode">
+          <uv-input v-model="form.inviteCode" border="none" placeholder="请输入邀请码(非必填)" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+      </content-card>
+      <content-card title="文化素质">
+        <uv-form-item label="语文" prop="form.scores.chinese" borderBottom>
+          <uv-input v-model="scores.chinese" border="none" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="数学" prop="form.score.mathematics" borderBottom>
+          <uv-input v-model="scores.mathematics" border="none" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="外语" prop="form.scores.foreign" borderBottom>
+          <uv-input v-model="scores.foreign" border="none" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="物理" prop="form.scores.physics" borderBottom>
+          <uv-input v-model="scores.physics" border="none" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="政治" prop="form.scores.political">
+          <uv-input v-model="scores.political" border="none" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+      </content-card>
+      <!-- <content-card title="学校信息">
+        <uv-form-item label="学校名称" prop="form.name">
+          <uv-input v-model="form.name" border="none" placeholder="请输入学校名称" :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+        <uv-form-item label="所在班级" prop="form.name">
+          <uv-input v-model="form.name" border="none" placeholder="请输入所在班级" :custom-style="customStyle">
+          </uv-input>
+        </uv-form-item>
+      </content-card> -->
+    </uv-form>
+    <ie-safe-toolbar :height="84" :shadow="false">
+      <view class="px-18 py-16">
+        <ie-button @click="handleSubmit">确认提交</ie-button>
+      </view>
+    </ie-safe-toolbar>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import ContentCard from './components/content-card.vue';
+import { useUserStore } from '@/store/userStore';
+import { } from '@/api/modules/login';
+import { getExamTypes, getExamMajors, getGraduateYears } from '@/api/modules/system';
+import { registry } from '@/api/modules/login';
+import { useTransferPage } from '@/hooks/useTransferPage';
+
+import { useAppStore } from '@/store/appStore';
+import { useDictStore } from '@/store/dictStore';
+import config from "@/config";
+import { RegisterInfo, Scores } from '@/types/user';
+import { DictItem } from '@/types';
+const userStore = useUserStore();
+const appStore = useAppStore();
+const { prevData, transferTo } = useTransferPage();
+
+const form = ref<Partial<RegisterInfo>>({});
+const scores = ref<Scores>({})
+const formRef = ref();
+const rules = ref({
+  name: [{ type: 'string', required: true, message: '请输入姓名', trigger: ['blur', 'change'] }],
+  location: [{ type: 'string', required: true, message: '请选择省份', trigger: ['blur', 'change'] }],
+  examType: [{ type: 'string', required: true, message: '请选择考生类别', trigger: ['blur', 'change'] }],
+  majorType: [{ type: 'string', required: true, message: '请选择专业类别', trigger: ['blur', 'change'] }],
+  endYear: [{ type: 'string', required: true, message: '请选择毕业年份', trigger: ['blur', 'change'] }],
+});
+const customStyle = {
+  paddingLeft: '26px'
+};
+const examTypeList = ref<DictItem[]>([]);
+const endYearList = ref<DictItem[]>([]);
+const majorTypes = ref<DictItem[]>([]);
+
+const handleProvinceChange = (val: string) => {
+  form.value.examType = '';
+  form.value.majorType = '';
+  loadExamTypes();
+}
+
+const handleExamTypeChange = (val: string) => {
+  form.value.majorType = '';
+  loadMajorTypes();
+  loadGraduateYears();
+}
+
+const handleShowExamTypePicker = () => {
+  if (!form.value.location) {
+    uni.$ie.showToast('请先选择省份');
+    return;
+  }
+}
+
+const loadExamTypes = async () => {
+  if (!form.value.location) {
+    return;
+  }
+  getExamTypes(form.value.location).then(res => {
+    examTypeList.value = res.data;
+    // if (examTypeList.value.length > 0) {
+    //   // 只有一个考生类别,自动选择
+    //   form.value.examType = examTypeList.value[0].dictValue as string;
+    // }
+  }).catch(err => {
+    console.log('获取考生类别失败', err)
+  });
+};
+
+const loadMajorTypes = async () => {
+  if (!form.value.location || !form.value.examType) {
+    return;
+  }
+  getExamMajors(form.value.location, form.value.examType).then(res => {
+    majorTypes.value = res.data;
+  }).catch(err => {
+    console.log('获取专业类别失败', err)
+  });
+}
+
+const loadGraduateYears = async () => {
+  if (!form.value.location || !form.value.examType) {
+    return;
+  }
+  getGraduateYears(form.value.location, form.value.examType).then(res => {
+    endYearList.value = res.data;
+    if (endYearList.value.length > 0) {
+      // 只有一个毕业年份,自动选择
+      // form.value.endYear = endYearList.value[0].dictValue as string;
+    }
+  }).catch(err => {
+    console.log('获取毕业年份失败', err)
+  });
+}
+
+const handleMajorChange = (val: string) => {
+
+}
+
+const loginValidate = () => {
+  const { nickName, location, examType, endYear } = form.value;
+  if (!nickName || nickName.trim() === '') {
+    uni.$ie.showToast('请输入姓名');
+    return false;
+  }
+  if (!location || location.trim() === '') {
+    uni.$ie.showToast('请选择省份');
+    return false;
+  }
+  if (!examType || examType.trim() === '') {
+    uni.$ie.showToast('请选择考生类别');
+    return false;
+  }
+  if (examType === 'VHS') {
+    if (!form.value.majorType) {
+      uni.$ie.showToast('请选择专业类别');
+      return false;
+    }
+  }
+  if (!endYear) {
+    uni.$ie.showToast('请选择毕业年份');
+    return false;
+  }
+  return true;
+}
+const handleSubmit = async () => {
+  console.log(form.value)
+  // const valid = await formRef.value.validate();
+  // const valid = await loginValidate();
+  const valid = loginValidate();
+  if (valid) {
+    const { mobile, password, code, uuid } = prevData.value;
+    const params = {
+      ...form.value,
+      mobile,
+      password,
+      code,
+      uuid,
+      scores: scores.value,
+    };
+    registry(params as RegisterInfo).then(res => {
+      const token = res.token;
+      if (token) {
+
+      }
+    });
+  }
+}
+
+const gatherInfo = () => {
+  const { nickName, location, examType, endYear, scores } = userStore.userInfo;
+  console.log(nickName, location, examType, endYear, scores)
+  form.value = {
+    nickName,
+    location,
+    examType,
+    endYear,
+    scores
+  };
+  if (location) {
+    loadExamTypes();
+  }
+  if (examType) {
+    loadMajorTypes();
+  }
+  if (endYear) {
+    loadGraduateYears();
+  }
+  console.log(form.value)
+}
+
+onLoad(() => {
+  console.log(prevData.value)
+  console.log(userStore.userInfo)
+  gatherInfo();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 230 - 82
src/pagesSystem/pages/user-profile/user-profile.vue

@@ -1,21 +1,22 @@
 <template>
-  <ie-page :safeAreaInsetBottom="false">
+  <ie-page bg-color="#F6F8FA" :safeAreaInsetBottom="false">
     <ie-navbar title="完善信息"></ie-navbar>
     <uv-form labelPosition="left" :model="form" labelWidth="70px" ref="formRef">
       <content-card title="考生信息">
         <uv-form-item label="学生姓名" prop="name" borderBottom required>
-          <uv-input v-model="form.nickName" border="none" placeholder="请输入姓名" :custom-style="customStyle">
+          <uv-input v-model="form.nickName" border="none" placeholder="请输入姓名" placeholderClass="text-30"
+            font-size="30rpx" :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
         <uv-form-item label="所在省份" prop="location" borderBottom required>
           <ie-picker ref="pickerRef" v-model="form.location" :list="appStore.provinceList" placeholder="选择省份"
-            :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"
-            @change="handleProvinceChange"></ie-picker>
+            :custom-style="customStyle" key-label="dictLabel" key-value="dictValue" :disabled="isProvinceDisabled"
+            :show-arrow="!isProvinceDisabled" @change="handleProvinceChange"></ie-picker>
         </uv-form-item>
         <uv-form-item label="考生类别" prop="examType" borderBottom required>
-          <ie-picker ref="pickerRef" v-model="form.examType" :list="examTypeList" :disabled="!form.location"
+          <ie-picker ref="pickerRef" v-model="form.examType" :list="examTypeList" :disabled="isExamTypeDisabled"
             placeholder="选择考生类别" :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"
-            @change="handleExamTypeChange"></ie-picker>
+            :show-arrow="!isExamTypeDisabled" @change="handleExamTypeChange"></ie-picker>
         </uv-form-item>
         <uv-form-item v-if="form.examType === 'VHS'" label="专业类别" prop="majorType" borderBottom required>
           <ie-picker ref="pickerRef" v-model="form.majorType" :list="majorTypes" :disabled="!form.examType"
@@ -23,51 +24,58 @@
             @change="handleMajorChange"></ie-picker>
         </uv-form-item>
         <uv-form-item label="毕业年份" prop="year" required>
-          <ie-picker ref="pickerRef" v-model="form.endYear" :list="endYearList" placeholder="选择毕业年份"
-            :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"></ie-picker>
+          <ie-picker ref="pickerRef" v-model="form.endYear" :list="endYearList" :disabled="!form.examType"
+            placeholder="选择毕业年份" :custom-style="customStyle" key-label="dictLabel" key-value="dictValue"></ie-picker>
         </uv-form-item>
 
       </content-card>
       <content-card title="邀请信息">
         <uv-form-item label="邀请码" prop="form.inviteCode">
-          <uv-input v-model="form.inviteCode" border="none" placeholder="请输入邀请码(非必填)" :custom-style="customStyle">
+          <uv-input v-model="form.inviteCode" border="none" placeholder="请输入邀请码(非必填)" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
       </content-card>
       <content-card title="文化素质">
-        <uv-form-item label="语文" prop="form.scores.chinese" borderBottom>
-          <uv-input v-model="scores.chinese" border="none" placeholder="满分100分" :custom-style="customStyle">
+        <uv-form-item label="语文" prop="form.scores.chinese" borderBottom :required="isImproveMode">
+          <uv-input v-model="scoresForm.chinese" border="none" type="number" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
-        <uv-form-item label="数学" prop="form.score.mathematics" borderBottom>
-          <uv-input v-model="scores.mathematics" border="none" placeholder="满分100分" :custom-style="customStyle">
+        <uv-form-item label="数学" prop="form.score.mathematics" borderBottom :required="isImproveMode">
+          <uv-input v-model="scoresForm.mathematics" border="none" type="number" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
-        <uv-form-item label="外语" prop="form.scores.foreign" borderBottom>
-          <uv-input v-model="scores.foreign" border="none" placeholder="满分100分" :custom-style="customStyle">
+        <uv-form-item label="外语" prop="form.scores.foreign" borderBottom :required="isImproveMode">
+          <uv-input v-model="scoresForm.foreign" border="none" type="number" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
-        <uv-form-item label="物理" prop="form.scores.physics" borderBottom>
-          <uv-input v-model="scores.physics" border="none" placeholder="满分100分" :custom-style="customStyle">
+        <uv-form-item label="物理" prop="form.scores.physics" borderBottom :required="isImproveMode">
+          <uv-input v-model="scoresForm.physics" border="none" type="number" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
-        <uv-form-item label="政治" prop="form.scores.political">
-          <uv-input v-model="scores.political" border="none" placeholder="满分100分" :custom-style="customStyle">
+        <uv-form-item label="政治" prop="form.scores.political" :required="isImproveMode">
+          <uv-input v-model="scoresForm.political" border="none" type="number" placeholder="满分100分" font-size="30rpx"
+            :custom-style="customStyle">
           </uv-input>
         </uv-form-item>
       </content-card>
-      <!-- <content-card title="学校信息">
-        <uv-form-item label="学校名称" prop="form.name">
-          <uv-input v-model="form.name" border="none" placeholder="请输入学校名称" :custom-style="customStyle">
-          </uv-input>
+      <content-card v-if="isImproveMode" title="学校信息">
+        <uv-form-item label="学校名称" prop="form.name" borderBottom :required="isImproveMode">
+          <ie-picker ref="pickerRef" v-model="form.schoolName" :disabled="isSchoolDisabled" placeholder="请选择就读学校"
+            :custom-style="customStyle" :custom-label="form.schoolName" @click="handleSchoolSelect"
+            :show-arrow="!isSchoolDisabled"></ie-picker>
         </uv-form-item>
-        <uv-form-item label="所在班级" prop="form.name">
-          <uv-input v-model="form.name" border="none" placeholder="请输入所在班级" :custom-style="customStyle">
-          </uv-input>
+        <uv-form-item label="所在班级" prop="form.name" :required="isImproveMode">
+          <ie-picker ref="pickerRef" v-model="form.classId" :list="classList" :disabled="!form.schoolId" title="选择班级"
+            placeholder="请选择所在班级" :custom-style="customStyle" key-label="name" key-value="classId"></ie-picker>
         </uv-form-item>
-      </content-card> -->
+      </content-card>
     </uv-form>
-    <ie-safe-toolbar :height="84">
+    <ie-safe-toolbar :height="84" :shadow="false">
       <view class="px-18 py-16">
         <ie-button @click="handleSubmit">确认提交</ie-button>
       </view>
@@ -80,69 +88,58 @@ import ContentCard from './components/content-card.vue';
 import { useUserStore } from '@/store/userStore';
 import { } from '@/api/modules/login';
 import { getExamTypes, getExamMajors, getGraduateYears } from '@/api/modules/system';
-import { registry } from '@/api/modules/login';
+import { registry, improve } from '@/api/modules/login';
 import { useTransferPage } from '@/hooks/useTransferPage';
 
 import { useAppStore } from '@/store/appStore';
-import { useDictStore } from '@/store/dictStore';
-import config from "@/config";
-import { RegisterInfo, Scores } from '@/types/user';
+import { BindCardInfo, ClassItem, RegisterInfo, SchoolItem, Scores, StudentBindCardInfo } from '@/types/user';
 import { DictItem } from '@/types';
+import { STUDENT_BIND_INFO } from '@/types/injectionSymbols';
+import { getClassList } from '@/api/modules/user';
 const userStore = useUserStore();
 const appStore = useAppStore();
-const dictStore = useDictStore();
-const { prevData, transferTo } = useTransferPage();
-// const endYearList = computed(() => {
-//   return dictStore.getDictValues('end_year');
-// });
-
+const { prevData, transferTo, transferBack } = useTransferPage();
 
-const form = ref<Partial<RegisterInfo>>({});
-const scores = ref<Scores>({})
+// const form2 = ref<Partial<StudentBindCardInfo>>({});
+// provide(STUDENT_BIND_INFO, form2);
+const form = ref<Partial<BindCardInfo>>({});
+const scoresForm = ref<Scores>({})
 const formRef = ref();
-const rules = ref({
-  name: [{ type: 'string', required: true, message: '请输入姓名', trigger: ['blur', 'change'] }],
-  location: [{ type: 'string', required: true, message: '请选择省份', trigger: ['blur', 'change'] }],
-  examType: [{ type: 'string', required: true, message: '请选择考生类别', trigger: ['blur', 'change'] }],
-  majorType: [{ type: 'string', required: true, message: '请选择专业类别', trigger: ['blur', 'change'] }],
-  endYear: [{ type: 'string', required: true, message: '请选择毕业年份', trigger: ['blur', 'change'] }],
-});
 const customStyle = {
   paddingLeft: '26px'
 };
 const examTypeList = ref<DictItem[]>([]);
 const endYearList = ref<DictItem[]>([]);
 const majorTypes = ref<DictItem[]>([]);
-
+const isImproveMode = computed(() => prevData.value.scene === 'phone_improve' || prevData.value.scene === 'card_improve');
+const isSchoolDisabled = computed(() => prevData.value.scene === 'card_improve' && prevData.value.card.assignSchoolId);
+const isProvinceDisabled = computed(() => prevData.value.scene === 'card_improve' && prevData.value.card.assignLocation);
+const isExamTypeDisabled = computed(() => (prevData.value.scene === 'card_improve' && prevData.value.card.assignExamType) || !form.value.location);
 const handleProvinceChange = (val: string) => {
+  if (isProvinceDisabled.value) {
+    return;
+  }
   form.value.examType = '';
   form.value.majorType = '';
   loadExamTypes();
 }
 
 const handleExamTypeChange = (val: string) => {
+  if (isExamTypeDisabled.value) {
+    return;
+  }
   form.value.majorType = '';
   loadMajorTypes();
   loadGraduateYears();
 }
 
-const handleShowExamTypePicker = () => {
-  if (!form.value.location) {
-    uni.$ie.showToast('请先选择省份');
-    return;
-  }
-}
-
 const loadExamTypes = async () => {
+  console.log(111, form.value.location)
   if (!form.value.location) {
     return;
   }
   getExamTypes(form.value.location).then(res => {
     examTypeList.value = res.data;
-    // if (examTypeList.value.length > 0) {
-    //   // 只有一个考生类别,自动选择
-    //   form.value.examType = examTypeList.value[0].dictValue as string;
-    // }
   }).catch(err => {
     console.log('获取考生类别失败', err)
   });
@@ -165,10 +162,6 @@ const loadGraduateYears = async () => {
   }
   getGraduateYears(form.value.location, form.value.examType).then(res => {
     endYearList.value = res.data;
-    if (endYearList.value.length > 0) {
-      // 只有一个毕业年份,自动选择
-      // form.value.endYear = endYearList.value[0].dictValue as string;
-    }
   }).catch(err => {
     console.log('获取毕业年份失败', err)
   });
@@ -178,17 +171,45 @@ const handleMajorChange = (val: string) => {
 
 }
 
+const classList = ref<ClassItem[]>([]);
+const handleSchoolSelect = () => {
+  if (isSchoolDisabled.value) {
+    return;
+  }
+  transferTo('/pagesSystem/pages/school-select/school-select', {
+    data: form.value
+  }).then(res => {
+    const school = res as SchoolItem;
+    form.value.schoolId = school.id;
+    form.value.schoolName = school.name;
+    console.log(form.value)
+    form.value.classId = undefined;
+    classList.value = [];
+    handleGetClassList();
+  });
+}
+const handleGetClassList = () => {
+  if (!form.value.schoolId) {
+    return;
+  }
+  getClassList({ schoolId: form.value.schoolId }).then(res => {
+    classList.value = res.data;
+    console.log(classList.value)
+  });
+}
+
+
 const loginValidate = () => {
   const { nickName, location, examType, endYear } = form.value;
-  if (!nickName) {
+  if (!nickName || nickName.trim() === '') {
     uni.$ie.showToast('请输入姓名');
     return false;
   }
-  if (!location) {
+  if (!location || location.trim() === '') {
     uni.$ie.showToast('请选择省份');
     return false;
   }
-  if (!examType) {
+  if (!examType || examType.trim() === '') {
     uni.$ie.showToast('请选择考生类别');
     return false;
   }
@@ -202,34 +223,161 @@ const loginValidate = () => {
     uni.$ie.showToast('请选择毕业年份');
     return false;
   }
+  if (isImproveMode.value) {
+    if (!scoresForm.value.chinese || scoresForm.value.chinese < 0 || scoresForm.value.chinese > 100) {
+      uni.$ie.showToast('请输入正确的语文成绩');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!scoresForm.value.mathematics || scoresForm.value.mathematics < 0 || scoresForm.value.mathematics > 100) {
+      uni.$ie.showToast('请输入正确的数学成绩');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!scoresForm.value.foreign || scoresForm.value.foreign < 0 || scoresForm.value.foreign > 100) {
+      uni.$ie.showToast('请输入正确的外语成绩');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!scoresForm.value.physics || scoresForm.value.physics < 0 || scoresForm.value.physics > 100) {
+      uni.$ie.showToast('请输入正确的物理成绩');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!scoresForm.value.political || scoresForm.value.political < 0 || scoresForm.value.political > 100) {
+      uni.$ie.showToast('请输入正确的政治成绩');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!form.value.schoolId) {
+      uni.$ie.showToast('请选择学校');
+      return false;
+    }
+  }
+  if (isImproveMode.value) {
+    if (!form.value.classId) {
+      uni.$ie.showToast('请选择班级');
+      return false;
+    }
+  }
   return true;
 }
 const handleSubmit = async () => {
-  console.log(form.value)
-  // const valid = await formRef.value.validate();
-  // const valid = await loginValidate();
   const valid = loginValidate();
   if (valid) {
-    const { mobile, password, code, uuid } = prevData.value;
-    const params = {
+    let params = {
       ...form.value,
-      mobile,
-      password,
-      code,
-      uuid,
-      scores: scores.value,
+      scores: scoresForm.value,
     };
-    registry(params as RegisterInfo).then(res => {
-      const token = res.token;
-      if (token) {
-
+    try {
+      if (isImproveMode.value) {
+        const { cardNo, password } = prevData.value;
+        params = {
+          ...params,
+          username: cardNo,
+          password,
+        }
+        console.log('params', params)
+        if (prevData.value.scene === 'card_improve') {
+          uni.$ie.showLoading();
+          const { token } = await registry(params as BindCardInfo);
+          if (token) {
+            await userStore.login(token);
+          }
+          uni.$ie.hideLoading();
+          uni.$ie.showSuccess('登录成功');
+          setTimeout(() => {
+            transferTo('/pagesMain/pages/index/index', {
+              type: 'reLaunch'
+            });
+          }, 800);
+        } else {
+          uni.$ie.showLoading();
+          await improve(params as BindCardInfo);
+          uni.$ie.hideLoading();
+          uni.$ie.showSuccess('绑定成功');
+          setTimeout(() => {
+            transferTo('/pagesMain/pages/index/index', {
+              type: 'reLaunch'
+            });
+          }, 800);
+        }
+      } else {
+        const { mobile, password, code, uuid } = prevData.value;
+        params = {
+          ...params,
+          mobile,
+          password,
+          code,
+          uuid,
+        }
+        uni.$ie.showLoading();
+        const { token } = await registry(params as RegisterInfo);
+        if (token) {
+          const isLogin = await userStore.login(token);
+          uni.$ie.hideLoading();
+          uni.$ie.showSuccess('登录成功');
+          if (isLogin) {
+            setTimeout(() => {
+              transferTo('/pagesMain/pages/index/index', {
+                type: 'reLaunch'
+              });
+            }, 88);
+          }
+        }
       }
-    });
+    } catch (error) {
+      console.error(error)
+    }
+  }
+}
+
+const gatherInfo = () => {
+  const { scene, card, phone, code, uuid } = prevData.value;
+  console.log('prevData.value', prevData.value)
+  if (scene === 'card_improve') {
+    form.value = {
+      location: card.assignLocation,
+      examType: card.assignExamType,
+      endYear: card.endYear,
+      majorType: card.majorType,
+      schoolId: card.assignSchoolId,
+      schoolName: card.assignSchoolName,
+      classId: card.classId,
+      mobile: phone
+      // code
+    };
+    loadExamTypes();
+    handleGetClassList();
+  } else if (scene === 'phone_improve') {
+    const { nickName, location, examType, endYear, scores } = userStore.userInfo;
+    form.value = {
+      nickName,
+      location,
+      examType,
+      endYear,
+      scores
+    };
+    scoresForm.value = scores;
+    if (location) {
+      loadExamTypes();
+    }
+    if (examType) {
+      loadMajorTypes();
+    }
+    if (endYear) {
+      loadGraduateYears();
+    }
   }
 }
 
 onLoad(() => {
-  console.log(prevData.value)
+  gatherInfo();
 });
 </script>
 

+ 5 - 0
src/store/appStore.ts

@@ -3,6 +3,7 @@ import { AppStoreState, ConfigItem, DictItem, PickerItem } from '@/types';
 import { getConfig, getProvinces } from '@/api/modules/system';
 import { useDictStore } from '@/store/dictStore';
 import { EnumDictName } from '@/common/enum';
+import { useUserStore } from '@/store/userStore';
 const preloadDicts: string[] = [
   EnumDictName.EXAM_TYPE
 ];
@@ -32,6 +33,10 @@ export const useAppStore = defineStore('ie-app', {
         const dictStore = useDictStore();
         // 预加载字典数据
         dictStore.loadDicts(preloadDicts).then(finish => { });
+        const userStore = useUserStore();
+        if (userStore.isLogin) {
+          await userStore.getUserInfo();
+        }
         this.isInitialized = true;
         this.sysInfo = uni.getSystemInfoSync();
         await this.loadPreloadData();

+ 14 - 3
src/store/userStore.ts

@@ -3,7 +3,8 @@ import { useTransferPage } from '@/hooks/useTransferPage';
 import { ref, computed } from 'vue';
 import { getUserInfo } from '@/api/modules/login';
 import avatarDefault from '@/static/image/avatar.png';
-import { LoginInfo, UserInfo, UserStoreState } from '@/types';
+import { UserStoreState } from '@/types';
+import { UserInfo, VipCardInfo } from '@/types/user';
 import tools from '@/utils/uni-tool';
 import defaultAvatar from '@/static/personal/avatar_default.png'
 
@@ -19,6 +20,7 @@ export const useUserStore = defineStore('ie-user', {
   state: (): UserStoreState => ({
     accessToken: null,
     user: null,
+    card: null,
     isDefaultModifyPwd: false,
     isPasswordExpired: false,
   }),
@@ -43,7 +45,13 @@ export const useUserStore = defineStore('ie-user', {
     },
     isVip(state: UserStoreState): boolean {
       return !!this.userInfo.cardId;
-    }
+    },
+    vipInfo(state: UserStoreState): VipCardInfo {
+      if (state.card) {
+        return state.card;
+      }
+      return {} as VipCardInfo;
+    },
   },
   actions: {
     async login(token: string) {
@@ -87,9 +95,12 @@ export const useUserStore = defineStore('ie-user', {
     },
     async getUserInfo() {
       const res = await getUserInfo();
-      const { data, isDefaultModifyPwd, isPasswordExpired } = res;
+      const { data, isDefaultModifyPwd, isPasswordExpired, card } = res;
       if (data) {
         this.user = data;
+        if (card) {
+          this.card = card;
+        }
         this.isDefaultModifyPwd = isDefaultModifyPwd;
         this.isPasswordExpired = isPasswordExpired;
       }

+ 3 - 0
src/types/index.ts

@@ -1,12 +1,14 @@
 import * as Study from "./study";
 import * as User from "./user";
 import * as News from "./news";
+import { VipCardInfo } from "./user";
 
 /// 接口响应
 export interface ApiResponse<T> {
   code: number;
   msg: string;
   data: T;
+  card?: VipCardInfo;
   // 以下是注册登录才有的
   token?: string;
   user?: User.UserInfo;
@@ -75,6 +77,7 @@ export interface UserStoreState {
   user: User.UserInfo | null;
   isDefaultModifyPwd?: boolean;
   isPasswordExpired?: boolean;
+  card: VipCardInfo | null;
 }
 
 /**

+ 7 - 1
src/types/injectionSymbols.ts

@@ -1,4 +1,5 @@
 import type { InjectionKey } from 'vue'
+import { StudentBindCardInfo } from './user';
 
 /**
  * 打开知识点记录详情
@@ -13,4 +14,9 @@ export const OPEN_PRACTICE_DETAIL = Symbol('OPEN_PRACTICE_DETAIL') as InjectionK
 /**
  * 打开视频记录详情
  */
-export const OPEN_VIDEO_DETAIL = Symbol('OPEN_VIDEO_DETAIL') as InjectionKey<(id: number, name: string) => void>;
+export const OPEN_VIDEO_DETAIL = Symbol('OPEN_VIDEO_DETAIL') as InjectionKey<(id: number, name: string) => void>;
+
+/**
+ * 学生绑定卡信息
+ */
+export const STUDENT_BIND_INFO = Symbol('STUDENT_BIND_CARD_INFO') as InjectionKey<Ref<Partial<StudentBindCardInfo>>>;

+ 97 - 6
src/types/user.ts

@@ -7,25 +7,102 @@ export interface LoginInfo {
   openid: string;
 }
 
+export interface StudentExamInfo {
+  nickName: string;
+  location: string;
+  endYear: number;
+  examType: string;
+  majorType?: string;
+}
 
-export interface RegisterInfo {
-  // classId: number;
+export interface InviteInfo {
+  inviteCode?: string;
+}
+
+export interface CultureScores {
+  biology?: number;
+  chemistry?: number;
+  physics?: number;
+  chinese?: number;
+  english?: number;
+  foreign?: number;
+  geography?: number;
+  history?: number;
+  mathematics?: number;
+  political?: number;
+}
+
+export interface SchoolInfo {
+  schoolName?: string;
+  schoolId?: number;
+  className?: string;
+  classId?: number;
+}
+
+export interface StudentBindCardInfo extends StudentExamInfo, InviteInfo, SchoolInfo {
+  scores: Scores;
+}
+
+
+export interface SchoolListQueryDTO {
+  keyword?: string;
+  pageNum: number;
+  pageSize: number;
+}
+export interface SchoolItem {
+  id: number;
+  name: string;
+  location: string;
+}
+
+export interface ClassListQueryDTO {
+  schoolId: number;
+}
+
+export interface ClassItem {
+  id: number;
+  name: string;
+  schoolId: number;
+}
+
+export interface CardInfo {
+  agentId?: number;
+  agentName?: string;
+  assignExamType?: string;
+  assignLocation?: string;
+  assignSchoolId?: number;
+  assignSchoolName?: string;
+  cardNo: string;
+  cardId: number;
+  classId?: number;
+  className?: string;
+  endYear?: number;
+  password: string;
+
+}
 
+export interface RegisterInfo {
+  classId?: number;
   endYear: number;
   examType: string;
   inviteCode?: string;
   location: string;
   majorType?: string;
   nickName: string;
-  // schoolId: number;
+  schoolId?: number;
+  schoolName?: string;
   scores: Scores;
-  // username: string;
+  username?: string;
   mobile: string;
   password: string;
   code: string;
   uuid: string;
 }
 
+export interface BindCardInfo extends RegisterInfo {
+  cardNo?: string;
+}
+
 export interface Scores {
   biology?: number;
   chemistry?: number;
@@ -53,7 +130,7 @@ export interface CaptchaImage {
 
 export interface MobileLoginRequestDTO {
   code?: string;
-  mobile: string;
+  mobile?: string;
   password?: string;
   username?: string;
   uuid?: string;
@@ -80,4 +157,18 @@ export interface UserInfo {
   userId: number;
   userName: string;
   scores: Scores;
-}
+}
+
+export interface VipCardInfo {
+  campusId: number; // 校区ID
+  classId: number; // 班级ID
+  schoolId: number; // 学校ID
+  year: number; // 入学年份
+  endYear: number; // 毕业年份
+  outDate: string; // 到期时间
+}
+
+// export interface BindCardInfo {
+//   cardNo: string;
+//   password: string;
+// }

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

@@ -1,7 +1,7 @@
 <template>
     <view class="uv-input" :class="inputClass" :style="[wrapperStyle]">
       <view class="uv-input__content">
-        <view class="uv-input__content__prefix-icon">
+        <view v-if="$slots.prefix || prefixIcon" class="uv-input__content__prefix-icon">
           <slot name="prefix">
             <uv-icon
 							v-if="prefixIcon"