Prechádzať zdrojové kódy

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

abpcoder 1 mesiac pred
rodič
commit
6d7656d614

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

@@ -1,5 +1,5 @@
-import { BindCardInfo, LoginInfo, LoginRequestDTO, MobileLoginResponseDTO, RegisterInfo, UserInfo } from "@/types/user";
-import { ApiResponse } from "@/types";
+import type { BindCardInfo, LoginInfo, LoginRequestDTO, MobileLoginResponseDTO, RegisterInfo, UserInfo, WxLoginRequestDTO } from "@/types/user";
+import type { ApiResponse } from "@/types";
 import flyio from "../flyio";
 
 
@@ -12,6 +12,14 @@ export function login(params: LoginRequestDTO) {
   return flyio.post('/front/user/userLogin', params) as Promise<ApiResponse<MobileLoginResponseDTO>>;
 }
 
+export function wxLogin(params: WxLoginRequestDTO) {
+  return flyio.post('/front/loginByCode', params) as Promise<ApiResponse<any>>;
+}
+
+export function bindOpenId(code: string) {
+  return flyio.post('/front/loginByCode', { loginCode: code }) as Promise<ApiResponse<any>>;
+}
+
 /**
  * 用户注册
  * @param params 注册参数

+ 11 - 11
src/api/modules/test-center.ts

@@ -1,20 +1,20 @@
 import flyio from "../flyio";
 import {ApiResponse, ApiResponseList} from "@/types";
-import {HollandEntity, HollandRecord, MbtiEntity, MbtiRecommendMajorCategory, MbtiRecord} from "@/types/test-center";
+import {HollandEntity, HollandRecord, MbtiEntity, MbtiRecommendMajorCategory, MbtiRecord, HollandTestQuestion, TestStep, MbtiTestQuestion} from "@/types/test-center";
 
 //  职业兴趣测评 01 测试步骤
-export function hollSteps(params: any) {
-    return flyio.get('/front/syzy/holland/steps', params)
+export function hollSteps() {
+    return flyio.get('/front/syzy/holland/steps', {}) as Promise<ApiResponse<TestStep[]>>
 }
 
 //  职业兴趣测评 02 步骤题目
-export function hollStepsQuestions(params: any) {
-    return flyio.get('/front/syzy/holland/questions', params)
+export function hollStepsQuestions(stepId: number) {
+    return flyio.get('/front/syzy/holland/questions', { stepId }) as Promise<ApiResponseList<HollandTestQuestion>>
 }
 
 //  职业兴趣测评 03 保存测试
 export function hollSaveHolland(data: any) {
-    return flyio.post('/front/syzy/holland/save', data)
+    return flyio.post('/front/syzy/holland/save', data) as Promise<ApiResponse<string>>
 }
 
 //  职业兴趣测评 04 测评记录
@@ -28,18 +28,18 @@ export function hollDetail(params: any) {
 }
 
 //  职业性格测评 01 测试步骤
-export function mbtiSteps(params: any) {
-    return flyio.get('/front/syzy/mbti/steps', params)
+export function mbtiSteps() {
+    return flyio.get('/front/syzy/mbti/steps', {}) as Promise<ApiResponse<TestStep[]>>
 }
 
 //  职业性格测评 02 步骤题目
-export function mbtiStepsQuestions(params: any) {
-    return flyio.get('/front/syzy/mbti/questions', params)
+export function mbtiStepsQuestions(stepId: number) {
+    return flyio.get('/front/syzy/mbti/questions', { stepId }) as Promise<ApiResponseList<MbtiTestQuestion>>
 }
 
 //  职业性格测评 03 保存测试
 export function mbtiSave(data: any) {
-    return flyio.post('/front/syzy/mbti/save', data)
+    return flyio.post('/front/syzy/mbti/save', data) as Promise<ApiResponse<string>>
 }
 
 //  职业性格测评 04 测评记录

+ 3 - 0
src/main.ts

@@ -110,6 +110,9 @@ export function createApp() {
         customClass: {
           default: ''
         }
+      },
+      button: {
+        hoverEffect: { default: true }
       }
     }
   })

+ 1 - 3
src/pages.json

@@ -461,9 +461,7 @@
     "pagesMain/pages/index/index": {
       "network": "all",
       "packages": [
-        "pagesOther",
-        "pagesStudy",
-        "pagesSystem"
+        "pagesStudy"
       ]
     }
   },

+ 137 - 0
src/pagesOther/pages/test-center/components/test-entry.vue

@@ -0,0 +1,137 @@
+<template>
+  <ie-page :fix-height="true">
+    <ie-navbar :title="title" />
+    <template v-if="isReady">
+      <test-step />
+      <test-title />
+      <test-tabs />
+      <uv-line margin="6px 0" />
+      <ie-auto-resizer>
+        <swiper class="h-full" :current="questionIndex" :duration="swiperDuration" @change="handleChangeTab">
+          <swiper-item class="h-full" v-for="(qs, index) in questionList" :key="qs.id">
+            <template v-if="Math.abs(index - questionIndex) <= 4">
+              <scroll-view class="h-full" scroll-y>
+                <view v-if="qs.title" class="text-28 px-30 py-20">
+                  {{ qs.no }}、{{ qs.title }}
+                </view>
+                <view class="px-30">
+                  <uv-checkbox-group v-if="qs.multiple" v-model="qs.answer" shape="square" placement="column">
+                    <uv-checkbox :customStyle="{ padding: '8px 0' }" v-for="(item, index) in qs.options" :key="index"
+                      :label="item.title" :name="item.answer" labelColor="#888"></uv-checkbox>
+                  </uv-checkbox-group>
+                  <uv-radio-group v-else v-model="qs.answer" placement="column" @change="handleRadioChange">
+                    <uv-radio :customStyle="{ padding: '8px 0' }" v-for="(item, index) in qs.options" :key="index"
+                      :label="type === 'holland' ? item.title : item.no + '、' + item.title" :name="item.answer">
+                    </uv-radio>
+                  </uv-radio-group>
+                </view>
+              </scroll-view>
+            </template>
+          </swiper-item>
+        </swiper>
+      </ie-auto-resizer>
+      <view class="px-40 py-30 flex items-center gap-20">
+        <view class="flex-1">
+          <uv-button type="primary" plain shape="circle" :disabled="!canPrev" @click="handlePrevious">上一题</uv-button>
+        </view>
+        <view class="flex-1">
+          <uv-button v-if="canNext" type="primary" shape="circle" :disabled="!canNext"
+            @click="handleNext">下一题</uv-button>
+          <uv-button v-else type="primary" shape="circle" @click="handleGenerateReport">生成报告</uv-button>
+        </view>
+      </view>
+    </template>
+    <test-popup ref="testPopupRef" />
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import TestStep from './test-step.vue';
+import TestTitle from './test-title.vue';
+import TestTabs from './test-tabs.vue';
+import TestPopup from './test-popup.vue';
+import { useTest, SYMBOL_TEST_DATA } from './useTest';
+import { hollSaveHolland, mbtiSave } from '@/api/modules/test-center';
+import { useTransferPage } from '@/hooks/useTransferPage';
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  type: {
+    type: String as PropType<'holland' | 'mbti'>,
+    default: 'holland'
+  }
+});
+
+const { transferTo, routes } = useTransferPage();
+const testData = useTest(props.type);
+provide(SYMBOL_TEST_DATA, testData);
+
+
+// 解构出需要的方法
+const { isReady, questionList, unAnseredQuestionList, questionIndex, setQuestionIndex, prevQuestion, nextQuestion, swiperDuration, canPrev, canNext, getResult } = testData;
+const handleChangeTab = (e: any) => {
+  setQuestionIndex(e.detail.current);
+}
+const handlePrevious = () => {
+  prevQuestion();
+}
+
+const handleRadioChange = () => {
+  setTimeout(() => {
+    nextQuestion();
+  }, 300);
+}
+
+const testPopupRef = ref();
+const handleNext = () => {
+  if (questionIndex.value === questionList.value.length - 1) {
+    // 每个步骤的最后一题检查是否还有未做完的
+    if (unAnseredQuestionList.value.length > 0) {
+      testPopupRef.value?.open();
+      return;
+    }
+    nextQuestion();
+  } else {
+    nextQuestion();
+  }
+}
+const handleGenerateReport = () => {
+  uni.$ie.showLoading();
+  const result = getResult();
+  if (props.type === 'holland') {
+    hollSaveHolland({
+      details: result
+    }).then(res => {
+      uni.$ie.hideLoading();
+      transferTo(routes.pageHollandReport, {
+        data: {
+          code: res.data
+        },
+        type: 'redirectTo'
+      });
+    }).catch(err => {
+      uni.$ie.hideLoading();
+    });
+  } else {
+    mbtiSave({
+      details: result
+    }).then(res => {
+      uni.$ie.hideLoading();
+      transferTo(routes.pageMbtiReport, {
+        data: {
+          code: res.data
+        },
+        type: 'redirectTo'
+      });
+    }).catch(err => {
+      uni.$ie.hideLoading();
+    });
+  }
+
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 48 - 0
src/pagesOther/pages/test-center/components/test-popup.vue

@@ -0,0 +1,48 @@
+<template>
+  <ie-popup ref="popupRef" mode="center">
+    <view class="w-[88vw]">
+      <view class="text-center my-40 font-medium">您还有未完成的题</view>
+      <view class="grid grid-cols-4 gap-20 m-40">
+        <view v-for="item in unAnseredQuestionList" :key="item.id" class="rounded-4 bg-back text-26 px-20 py-14"
+          @click="handleClick(item)">
+          <view class="text-center">第{{ item.no }}题</view>
+        </view>
+      </view>
+      <view class="m-40">
+        <uv-button type="primary" shape="circle" @click="close">去做题</uv-button>
+      </view>
+    </view>
+  </ie-popup>
+</template>
+<script lang="ts" setup>
+defineOptions({
+  name: 'TestPopup',
+  options: {
+    virtualHost: true,
+  }
+});
+import { TestCenter } from '@/types';
+import { SYMBOL_TEST_DATA, useTest } from '../components/useTest';
+
+const testData = inject(SYMBOL_TEST_DATA);
+
+const { questionList, unAnseredQuestionList, setQuestionIndex } = testData as ReturnType<typeof useTest>;
+
+const popupRef = ref();
+const open = () => {
+  popupRef.value?.open();
+}
+const close = () => {
+  popupRef.value?.close();
+}
+
+const handleClick = (item: TestCenter.TestQuestionVO) => {
+  const index = questionList?.value.findIndex(qs => qs.id === item.id);
+  if (index !== -1) {
+    setQuestionIndex?.(index, false);
+  }
+}
+
+defineExpose({ open, close });
+</script>
+<style lang="scss" scoped></style>

+ 26 - 0
src/pagesOther/pages/test-center/components/test-step.vue

@@ -0,0 +1,26 @@
+<template>
+  <view>
+    <uv-steps :current="stepIndex" dot>
+      <uv-steps-item v-for="(item, index) in steps" :key="index" :title="getTitle(index)"></uv-steps-item>
+    </uv-steps>
+  </view>
+</template>
+<script lang="ts" setup>
+defineOptions({
+  name: 'TestStep',
+  options: {
+    virtualHost: true,
+  }
+});
+import { SYMBOL_TEST_DATA } from '../components/useTest';
+
+const testData = inject(SYMBOL_TEST_DATA);
+const { steps, stepIndex } = testData || {};
+
+const order = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
+
+const getTitle = (index: number) => {
+  return order[index];
+}
+</script>
+<style lang="scss" scoped></style>

+ 38 - 0
src/pagesOther/pages/test-center/components/test-tabs.vue

@@ -0,0 +1,38 @@
+<template>
+  <view>
+    <uv-tabs ref="tabRef" :current="questionIndex" keyName="label" :animationEnabled="true" :itemStyle="tabItemStyle"
+      :list="questionList" :scrollable="true" lineHeight="0" @change="handleTabChange">
+      <template #default="{ data: { item, index } }">
+        <view class="px-20 py-12 text-24 relative"
+          :class="[index === questionIndex ? 'bg-primary-light text-primary' : 'bg-back']">
+          第{{ index + 1 }}题
+          <view v-if="item.answer !== undefined" class="absolute top-0 right-0 w-12 h-12 bg-success rounded-full">
+          </view>
+        </view>
+      </template>
+    </uv-tabs>
+  </view>
+</template>
+<script lang="ts" setup>
+defineOptions({
+  name: 'TestTabs',
+  options: {
+    virtualHost: true,
+  }
+});
+import { SYMBOL_TEST_DATA, useTest } from '../components/useTest';
+
+const tabItemStyle = {
+  padding: '0 5px',
+};
+const testData = inject(SYMBOL_TEST_DATA);
+const { questionIndex, currentStep, setQuestionIndex } = testData as ReturnType<typeof useTest>;
+
+const questionList = computed(() => {
+  return currentStep.value.questions || []
+});
+const handleTabChange = (e: any) => {
+  setQuestionIndex?.(e.index);
+}
+</script>
+<style lang="scss" scoped></style>

+ 16 - 0
src/pagesOther/pages/test-center/components/test-title.vue

@@ -0,0 +1,16 @@
+<template>
+  <view class="p-30 text-28 text-fore-subcontent">{{ currentStep?.title }}</view>
+</template>
+<script lang="ts" setup>
+defineOptions({
+  name: 'TestTitle',
+  options: {
+    virtualHost: true,
+  }
+});
+import { SYMBOL_TEST_DATA } from '../components/useTest';
+
+const testData = inject(SYMBOL_TEST_DATA);
+const { currentStep } = testData || {};
+</script>
+<style lang="scss" scoped></style>

+ 271 - 0
src/pagesOther/pages/test-center/components/useTest.ts

@@ -0,0 +1,271 @@
+import type { TestCenter } from "@/types";
+import type { InjectionKey } from "vue";
+import { reactive, computed, toRefs } from "vue";
+import { hollStepsQuestions, hollSteps, mbtiSteps, mbtiStepsQuestions } from "@/api/modules/test-center";
+
+export const useTest = (type: 'holland' | 'mbti') => {
+  // 使用 reactive 创建响应式状态对象
+  const steps = ref<TestCenter.TestStepVO[]>([])
+  const stepIndex = ref(0)
+  const questionIndex = ref(0)
+  const isReady = ref(false)
+  const swiperDuration = ref(300)
+
+  // 计算属性
+  const currentStep = computed(() => {
+    return steps.value?.[stepIndex.value];
+  });
+
+  const questionList = computed(() => {
+    return currentStep.value?.questions || [];
+  });
+
+  const currentQuestion = computed(() => {
+    return steps.value?.[stepIndex.value]?.questions?.[questionIndex.value];
+  });
+
+  const canPrev = computed(() => {
+    return stepIndex.value > 0 || (stepIndex.value === 0 && questionIndex.value > 0);
+  });
+
+  const canNext = computed(() => {
+    return questionList.value.length > 0 && (stepIndex.value < steps.value.length - 1 || (stepIndex.value === steps.value.length - 1 && questionIndex.value < questionList.value.length - 1));
+  });
+
+  const unAnseredQuestionList = computed(() => {
+    return questionList.value.filter(item => {
+      return item.answer === undefined;
+    });
+  });
+
+  // 方法
+  const prevQuestionQuickly = () => {
+    if (!canPrev.value) {
+      return;
+    }
+    swiperDuration.value = 0;
+    setTimeout(() => {
+      prevQuestion();
+      setTimeout(() => {
+        swiperDuration.value = 300;
+      }, 0);
+    }, 0);
+  }
+  const prevQuestion = () => {
+    if (!canPrev.value) {
+      return;
+    }
+    if (questionIndex.value > 0) {
+      questionIndex.value--;
+    } else {
+      const index = stepIndex.value - 1;
+      stepIndex.value = index;
+      questionIndex.value = steps.value[index].questions.length - 1;
+    }
+  }
+
+  const nextQuestionQuickly = () => {
+    if (!canNext.value) {
+      return;
+    }
+    swiperDuration.value = 0;
+    setTimeout(() => {
+      nextQuestion();
+      setTimeout(() => {
+        swiperDuration.value = 300;
+      }, 0);
+    }, 0);
+  }
+
+  const nextQuestion = () => {
+    if (!canNext.value) {
+      return;
+    }
+    if (questionList.value.length > 0 && questionIndex.value < questionList.value.length - 1) {
+      questionIndex.value++;
+    } else {
+      stepIndex.value++;
+      questionIndex.value = 0;
+    }
+  }
+
+  const getSteps = async () => {
+    let data: TestCenter.TestStepVO[] = [];
+    if (type === 'holland') {
+      const res = await hollSteps();
+      data = res.data.map(item => {
+        return {
+          id: item.id,
+          no: item.no,
+          title: item.title,
+          questions: [] // 初始化为空数组
+        }
+      });
+    } else {
+      const res = await mbtiSteps();
+      data = res.data.map(item => {
+        return {
+          id: item.id,
+          no: item.no,
+          title: item.title,
+          questions: [] // 初始化为空数组
+        }
+      });
+    }
+    steps.value = data;
+    return data;
+  }
+  const genScoreList = () => {
+    return new Array(7).fill(null).map((item, index) => {
+      return {
+        no: (index + 1).toString(),
+        title: `${index + 1}分`,
+        answer: index + 1
+      }
+    });
+  }
+  const getQuestions = async (step: TestCenter.TestStepVO, index: number) => {
+    if (step.id) {
+      const stepId = step.id;
+      if (type === 'holland') {
+        const res = await hollStepsQuestions(stepId);
+        const questions = res.rows.map(item => {
+          if (index === 3) {
+            return {
+              id: item.id,
+              no: item.no.toString(),
+              title: item.title,
+              options: genScoreList(),
+              answer: undefined,
+              multiple: false
+            }
+          } else {
+            return {
+              id: item.id,
+              no: item.no.toString(),
+              title: item.title,
+              options: item.options.map(option => {
+                return {
+                  no: option.no.toString(),
+                  title: option.label,
+                  answer: option.id
+                }
+              }),
+              answer: undefined,
+              multiple: true
+            }
+          }
+        });
+        // 关键修复:找到 steps 数组中对应的 step,然后替换整个对象
+        const stepIndexInArray = steps.value.findIndex(s => s.id === step.id);
+        if (stepIndexInArray !== -1) {
+          steps.value[stepIndexInArray] = {
+            ...steps.value[stepIndexInArray],
+            questions
+          };
+        }
+      } else {
+        const res = await mbtiStepsQuestions(stepId);
+        const questions = res.rows.map(item => {
+          return {
+            id: item.id,
+            no: item.no.toString(),
+            title: item.title,
+            options: [
+              {
+                no: 'A',
+                title: item.optionA,
+                answer: item.valueA
+              },
+              {
+                no: 'B',
+                title: item.optionB,
+                answer: item.valueB
+              }
+            ],
+            answer: undefined,
+            multiple: false
+          }
+        });
+        // 关键修复:找到 steps 数组中对应的 step,然后替换整个对象
+        const stepIndexInArray = steps.value.findIndex(s => s.id === step.id);
+        if (stepIndexInArray !== -1) {
+          steps.value[stepIndexInArray] = {
+            ...steps.value[stepIndexInArray],
+            questions
+          };
+        }
+      }
+    }
+  }
+
+  const setQuestionIndex = (index: number, widthAnimation = true) => {
+    if (widthAnimation) {
+      swiperDuration.value = 300;
+      setTimeout(() => {
+        questionIndex.value = index;
+      }, 0);
+    } else {
+      swiperDuration.value = 0;
+      setTimeout(() => {
+        questionIndex.value = index;
+      }, 0);
+    }
+  }
+
+  const getResult = () => {
+    if (type === 'holland') {
+      return steps.value.map(item => item.questions.map(qs => {
+        return {
+          questionId: qs.id,
+          answer: qs.multiple ? JSON.stringify(qs.answer) : JSON.stringify([qs.answer])
+        }
+      })).flat();
+    } else {
+      return steps.value.map(item => item.questions.map(qs => {
+        return {
+          questionId: qs.id,
+          answer: qs.answer
+        }
+      })).flat();
+    }
+  }
+
+  const init = async () => {
+    uni.$ie.showLoading();
+    try {
+      const data = await getSteps();
+      const promiseList = data.map((item, index) => {
+        return getQuestions(item, index);
+      });
+      await Promise.all(promiseList);
+      isReady.value = true;
+    } catch (error) { } finally {
+      uni.$ie.hideLoading();
+    }
+  }
+
+  init();
+
+  return {
+    steps,
+    stepIndex,
+    questionIndex,
+    isReady,
+    swiperDuration,
+    currentStep,
+    questionList,
+    currentQuestion,
+    prevQuestion,
+    prevQuestionQuickly,
+    nextQuestion,
+    nextQuestionQuickly,
+    setQuestionIndex,
+    canPrev,
+    canNext,
+    unAnseredQuestionList,
+    getResult
+  };
+};
+
+export const SYMBOL_TEST_DATA = Symbol('TEST_DATA') as InjectionKey<ReturnType<typeof useTest>>;

+ 4 - 10
src/pagesOther/pages/test-center/holland/test.vue

@@ -1,13 +1,7 @@
 <template>
-  <view>
-    
-  </view>
+  <test-entry title="霍兰德职业兴趣测评" type="holland" />
 </template>
-
-<script setup>
-  
+<script lang="ts" setup>
+import TestEntry from '../components/test-entry.vue';
 </script>
-
-<style lang="scss">
-
-</style>
+<style lang="scss" scoped></style>

+ 4 - 10
src/pagesOther/pages/test-center/mbti/test.vue

@@ -1,13 +1,7 @@
 <template>
-  <view>
-    
-  </view>
+  <test-entry title="MBTI职业性格测评" type="mbti" />
 </template>
-
-<script setup>
-  
+<script lang="ts" setup>
+import TestEntry from '../components/test-entry.vue';
 </script>
-
-<style lang="scss">
-
-</style>
+<style lang="scss" scoped></style>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
src/pagesOther/static/echarts.min.js


+ 1 - 1
src/pagesStudy/pages/exam-start/components/exam-navbar.vue

@@ -44,7 +44,7 @@ const isPractice = computed(() => {
 });
 const pageTitle = computed(() => {
   if (examPageOptions) {
-    const { name, readonly, paperType } = examPageOptions;
+    const { readonly, paperType } = examPageOptions;
     if (readonly) {
       return readonlyTitleMap[paperType as keyof typeof readonlyTitleMap];
     }

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
src/pagesStudy/static/echarts.min.js


+ 83 - 6
src/pagesSystem/pages/login/login.vue

@@ -3,7 +3,7 @@
     <ie-navbar title="" :hide-back="hideBack" :placeholder="false" bgColor="transparent" />
     <ie-image :is-oss="true" src="/login-bg.png" custom-class="w-full min-h-350 absolute top-0 left-0 z-1" />
     <ie-image :is-oss="true" src="/login-title.png" custom-class="w-full h-96 mx-auto mt-240" mode="heightFix" />
-    <view class="relative z-2 mx-46 mt-178">
+    <view class="relative z-2 mx-46 mt-150">
       <view class="ml-18 flex items-center">
         <view class="text-32" :class="{ 'is-active': loginType === 'card' }" @click="changeLoginType('card')">
           会员卡登录
@@ -40,6 +40,25 @@
       </view>
       <view class="mt-84">
         <ie-button @click="handleLogin">登录</ie-button>
+        <!-- #ifdef MP-WEIXIN -->
+        <view class="flex items-center justify-center">
+          <uv-button v-if="!isAgree" :custom-style="customBtnStyle" :hover-effect="false" type="primary" @click="handleCheckAgree">
+            <view class="mt-40 flex items-center justify-center gap-10">
+              <uv-icon name="weixin-circle-fill" color="#66dc79" size="26" />
+              <text class="text-28 text-primary">一键登录</text>
+            </view>
+          </uv-button>
+          <uv-button v-else :custom-style="customBtnStyle" :hover-effect="false" :disabled="!isAgree" type="primary" text="手机号快捷登录"
+            icon-color="white" open-type="getPhoneNumber|agreePrivacyAuthorization"
+            @getphonenumber="handleGetPhoneNumber" @agreeprivacyauthorization="handleAgreePrivacyAuthorization"
+            @click="handleWxLogin">
+            <view class="mt-40 flex items-center justify-center gap-10">
+              <uv-icon name="weixin-circle-fill" color="#66dc79" size="26" />
+              <text class="text-28 text-primary">一键登录</text>
+            </view>
+          </uv-button>
+        </view>
+        <!-- #endif -->
       </view>
       <view class="mt-42 ml-26">
         <uv-checkbox-group v-model="agreePrivacy">
@@ -63,8 +82,8 @@ import { validatePhone } from '@/hooks/useValidation';
 import { useAppConfig } from '@/hooks/useAppConfig';
 import { verifyCard } from '@/api/modules/user';
 import { EnumBindScene, EnumSmsApiType, EnumUserType } from '@/common/enum';
-import { login } from '@/api/modules/login';
-import { LoginRequestDTO, MobileLoginResponseDTO, UserInfo } from '@/types/user';
+import { login, wxLogin } from '@/api/modules/login';
+import { LoginRequestDTO, UserInfo } from '@/types/user';
 
 const { transferBack, transferTo, routes } = useTransferPage();
 const userStore = useUserStore();
@@ -80,7 +99,17 @@ const uuid = ref('');
 const showPassword = ref(false);
 const rememberPassword = ref([false]);
 const agreePrivacy = ref([false]);
+const loading = ref(false);
+const isAgree = computed(() => {
+  return agreePrivacy.value[0];
+});
 
+const customBtnStyle = {
+  background: 'none !important',
+  border: 'none !important',
+  padding: '0 !important',
+  width: 'fit-content'
+}
 
 const changeLoginType = (type: string) => {
   loginType.value = type;
@@ -124,8 +153,7 @@ const loginValidate = () => {
       return false;
     }
   }
-  if (!agreePrivacy.value[0]) {
-    uni.$ie.showToast('请先阅读并同意用户协议和隐私政策');
+  if (!handleCheckAgree()) {
     return false;
   }
   return true;
@@ -165,6 +193,50 @@ const submitLogin = async () => {
   }
 }
 
+const handleCheckAgree = () => {
+  console.log(isAgree.value, 111)
+  if (!isAgree.value) {
+    uni.$ie.showToast('请先阅读并同意用户协议和隐私政策');
+    return false;
+  }
+  return true;
+}
+
+async function getWxCode() {
+  const { code, errMsg } = await uni.login();
+  if (errMsg === 'login:ok' && code) {
+    return code;
+  }
+  return null;
+}
+
+const handleWxLogin = () => {
+  loading.value = true;
+  uni.$ie.showLoading();
+}
+const handleGetPhoneNumber = async (e: any) => {
+  loading.value = false;
+  uni.$ie.hideLoading();
+  if (e.errMsg === 'getPhoneNumber:ok') {
+    loading.value = true;
+    uni.$ie.showLoading();
+    const code = await getWxCode();
+    if (code) {
+      wxLogin({
+        phoneCode: e.code,
+        loginCode: code,
+      }).then(res => {
+        console.log('微信登录结果:', res)
+      }).finally(() => {
+        loading.value = false;
+        uni.$ie.hideLoading();
+      });
+    }
+  }
+}
+
+const handleAgreePrivacyAuthorization = (e: any) => { }
+
 const handleMobileLogin = async (params: LoginRequestDTO) => {
   uni.$ie.showLoading();
   login(params).then((res) => {
@@ -203,6 +275,11 @@ const handleMobileLogin = async (params: LoginRequestDTO) => {
     console.log('登录失败', err)
   })
 }
+
+const handleLoginResponse = () => {
+
+}
+
 const handleCardLogin = (params: LoginRequestDTO) => {
   uni.$ie.showLoading();
   login(params).then(res => {
@@ -266,7 +343,7 @@ onShow(() => {
 onLoad(() => {
   rememberPassword.value[0] = userStore.rememberPwd;
   if (import.meta.env.DEV) {
-    agreePrivacy.value = [true];
+    // agreePrivacy.value = [true];
     phone.value = '17363958504';
     password.value = '1234';
   }

+ 80 - 0
src/types/test-center.ts

@@ -141,4 +141,84 @@ export interface MbtiRecommendMajorCategory {
     "category": string;
     "major": string | null;
     "majors": MbtiRecommendMajor[];
+}
+
+export interface TestStep {
+  createBy: string | null;
+  createTime: string | null;
+  updateBy: string | null;
+  updateTime: string | null;
+  remark: string | null;
+  id: number;
+  no: number;
+  title: string;
+  status: number;
+}
+
+export interface MbtiTestQuestion {
+  createBy: string | null;
+  createTime: string | null;
+  id: number;
+  no: number;
+  optionA: string;
+  optionB: string;
+  percent: number;
+  remark: string | null;
+  status: number;
+  stepId: number;
+  title: string;
+  updateBy: string | null;
+  updateTime: string | null;
+  valueA: string;
+  valueB: string;
+}
+
+export interface HollandTestQuestion {
+  createBy: string | null;
+  createTime: string | null;
+  id: number;
+  no: number;
+  options: HollandTestQuestionOption[];
+  percent: number;
+  remark: string | null;
+  ruleCode: string;
+  status: number;
+  stepId: number;
+  title: string;
+  updateBy: string | null;
+  updateTime: string | null;
+}
+
+export interface HollandTestQuestionOption {
+  createBy: string | null;
+  createTime: string | null;
+  id: number;
+  label: string;
+  no: number;
+  questionId: number;
+  remark: string | null;
+  status: number;
+  updateBy: string | null;
+  updateTime: string | null;
+}
+
+export interface TestStepVO {
+  id: number;
+  no: number;
+  title: string;
+  questions: TestQuestionVO[];
+}
+export interface TestQuestionVO {
+  id: number;
+  no: string;
+  title: string;
+  options: TestOptionVO[];
+  answer?: string | number;
+  multiple: boolean
+}
+
+export interface TestOptionVO {
+  no: string;
+  title: string;
+  answer: string | number;
 }

+ 9 - 1
src/types/user.ts

@@ -86,6 +86,7 @@ export interface RegisterInfo {
   password: string;
   code: string;
   uuid: string;
+  openId?: string;
 }
 
 export interface BindCardInfo extends RegisterInfo {
@@ -131,6 +132,8 @@ export interface LoginRequestDTO {
 export interface MobileLoginResponseDTO {
   code?: number;
   message?: string;
+  mobile?: string;
+  openId?: string;
 }
 
 
@@ -178,4 +181,9 @@ export type UserRole = 'vip' | 'normal' | 'guest' | 'teacher' | 'agent' | 'audit
 // export interface BindCardInfo {
 //   cardNo: string;
 //   password: string;
-// }
+// }
+
+export interface WxLoginRequestDTO {
+  phoneCode: string;
+  loginCode: string;
+}

+ 4 - 4
src/uni_modules/uv-button/components/uv-button/uv-button.vue

@@ -34,7 +34,7 @@
 			@subscribe="onSubscribe"
 			@login="onLogin"
 			@im="onIm"
-		  hover-class="uv-button--active"
+		  :hover-class="hoverEffect ? 'uv-button--active' : ''"
 		  class="uv-button uv-reset-button"
 		  :style="[baseColor, $uv.addStyle(customStyle)]"
 		  @tap="clickHandler"
@@ -56,7 +56,7 @@
       :session-from="sessionFrom"
       :send-message-img="sendMessageImg"
       :show-message-card="showMessageCard"
-      :hover-class="!disabled && !loading ? 'uv-button--active' : ''"
+      :hover-class="!disabled && !loading && hoverEffect ? 'uv-button--active' : ''"
       class="uv-button uv-reset-button"
       :style="[baseColor, $uv.addStyle(customStyle)]"
       @tap="clickHandler"
@@ -104,9 +104,9 @@
       :hover-stay-time="Number(hoverStayTime)"
       class="uv-button"
       :hover-class="
-        !disabled && !loading && !color && (plain || type === 'info')
+        !disabled && !loading && !color && (plain || type === 'info') && hoverEffect
           ? 'uv-button--active--plain'
-          : !disabled && !loading && !plain
+          : !disabled && !loading && !plain && hoverEffect
           ? 'uv-button--active'
           : ''
       "

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov