소스 검색

完成合规评测 修改密码页面

yuanmingze 2 달 전
부모
커밋
0c3c5cfab1

+ 3 - 0
components.d.ts

@@ -19,11 +19,14 @@ declare module 'vue' {
     WdFormItem: typeof import('@wot-ui/ui/components/wd-form-item/wd-form-item.vue')['default']
     WdIcon: typeof import('@wot-ui/ui/components/wd-icon/wd-icon.vue')['default']
     WdInput: typeof import('@wot-ui/ui/components/wd-input/wd-input.vue')['default']
+    WdMessageBox: typeof import('@wot-ui/ui/components/wd-message-box/wd-message-box.vue')['default']
     WdNavbar: typeof import('@wot-ui/ui/components/wd-navbar/wd-navbar.vue')['default']
     WdNoticeBar: typeof import('@wot-ui/ui/components/wd-notice-bar/wd-notice-bar.vue')['default']
     WdPicker: typeof import('@wot-ui/ui/components/wd-picker/wd-picker.vue')['default']
     WdPopup: typeof import('@wot-ui/ui/components/wd-popup/wd-popup.vue')['default']
     WdProgress: typeof import('@wot-ui/ui/components/wd-progress/wd-progress.vue')['default']
+    WdRadio: typeof import('@wot-ui/ui/components/wd-radio/wd-radio.vue')['default']
+    WdRadioGroup: typeof import('@wot-ui/ui/components/wd-radio-group/wd-radio-group.vue')['default']
     WdSearch: typeof import('@wot-ui/ui/components/wd-search/wd-search.vue')['default']
     WdTag: typeof import('@wot-ui/ui/components/wd-tag/wd-tag.vue')['default']
     WdToast: typeof import('@wot-ui/ui/components/wd-toast/wd-toast.vue')['default']

+ 2 - 0
src/components/auth/AuthInputItem.vue

@@ -14,6 +14,7 @@
         :password="passwordToggle && isPassword"
         :maxlength="maxlength"
         :placeholder="placeholder"
+        :disabled="disabled"
         @focus="onFocus"
         @blur="onBlur"
       />
@@ -57,6 +58,7 @@ interface Props {
   maxlength?: number
   error?: string
   passwordToggle?: boolean
+  disabled?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {

+ 566 - 0
src/pages-mine/compliance-evaluation/ability-test.vue

@@ -0,0 +1,566 @@
+<template>
+  <view class="ability-test-page">
+    <view class="ability-test-header">
+      <view class="ability-test-header-title">能力测试</view>
+      <view class="ability-test-header-desc">请认真完成以下合规测评内容</view>
+    </view>
+
+    <view v-for="quizItem in pkgQuizList" :key="quizItem.quiz.quizId" class="quiz-card">
+      <view class="quiz-card-header">
+        <view class="quiz-card-title">
+          {{ quizItem.quiz.title }}
+        </view>
+
+        <view class="quiz-card-meta">
+          <text>共 {{ quizItem.items.length }} 题</text>
+        </view>
+      </view>
+
+      <view v-for="(item, index) in quizItem.items" :key="item.itemId" class="question-card">
+        <view class="question-title-row">
+          <view class="question-index">
+            {{ index + 1 }}
+          </view>
+
+          <view class="question-title">
+            {{ item.label }}
+          </view>
+        </view>
+
+        <radio-group class="question-radio-group" @change="handleRadioChange(item, $event)">
+          <label
+            v-for="optionItem in item.options"
+            :key="optionItem.no"
+            class="option-item"
+            :class="item.userAnswer === optionItem.no ? 'option-item-active' : ''"
+          >
+            <radio
+              class="option-radio"
+              :value="optionItem.no"
+              :checked="item.userAnswer === optionItem.no"
+              color="#2f73ff"
+            />
+
+            <view class="option-content">
+              <text class="option-no">{{ optionItem.no }}</text>
+              <text class="option-text">{{ optionItem.text }}</text>
+            </view>
+          </label>
+        </radio-group>
+      </view>
+    </view>
+
+    <view v-if="!pkgQuizList.length" class="empty">
+      {{ loading ? '加载中...' : '暂无测试内容' }}
+    </view>
+
+    <view class="ability-test-footer">
+      <button
+        class="submit-button"
+        :class="submitting ? 'submit-button-disabled' : ''"
+        :disabled="submitting"
+        hover-class="submit-button-hover"
+        @click="submitFn"
+      >
+        {{ submitting ? '提交中...' : '提 交' }}
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+import { onLoad } from '@dcloudio/uni-app'
+
+import type {
+  QuizInfo,
+  QuizItem,
+  QuizPltDetailResponse,
+  QuizPltSubmitParams,
+} from '@/services/modules/mine/compliance-evaluation'
+import {
+  getQuizPltAvailApi,
+  getQuizPltDetailsApi,
+  quizTestResultCreateApi,
+} from '@/services/modules/mine/compliance-evaluation'
+
+interface PageQuery {
+  fromHome?: string
+}
+
+interface RadioChangeEvent {
+  detail: {
+    value: string
+  }
+}
+
+interface AnswerQuizItem extends QuizItem {
+  userAnswer: string
+}
+
+interface AnswerQuizDetail extends Omit<QuizPltDetailResponse, 'items'> {
+  quizId: number
+  items: AnswerQuizItem[]
+}
+
+interface PassedQuizResult extends AnswerQuizDetail {
+  finalMark: number
+}
+
+const fromHome = ref('')
+const loading = ref(false)
+const submitting = ref(false)
+
+const originPkgQuizList = ref<AnswerQuizDetail[]>([])
+const pkgQuizList = ref<AnswerQuizDetail[]>([])
+const passedQuizResultMap = ref<Record<number, PassedQuizResult>>({})
+
+const cloneData = <T,>(data: T): T => {
+  return JSON.parse(JSON.stringify(data))
+}
+
+const normalizeQuizDetail = (data: QuizPltDetailResponse): AnswerQuizDetail => {
+  return {
+    ...data,
+    quizId: data.quiz.quizId,
+    items: data.items.map((item) => ({
+      ...item,
+      userAnswer: '',
+    })),
+  }
+}
+
+const handleRadioChange = (item: AnswerQuizItem, event: RadioChangeEvent) => {
+  item.userAnswer = event.detail.value
+}
+
+const validateAnswered = () => {
+  const hasUnanswered = pkgQuizList.value.some((quizItem) => {
+    return quizItem.items.some((item) => !item.userAnswer)
+  })
+
+  if (!hasUnanswered) return true
+
+  uni.showToast({
+    title: '请完成所有题目后提交',
+    icon: 'none',
+  })
+
+  return false
+}
+
+const calcQuizMark = (quizItem: AnswerQuizDetail) => {
+  return quizItem.items.reduce((total, item) => {
+    return item.userAnswer === item.answer ? total + item.mark : total
+  }, 0)
+}
+
+const buildSubmitItems = (quizItem: PassedQuizResult) => {
+  return quizItem.items.map((item) => {
+    const { userAnswer, ...rest } = item
+
+    return {
+      ...rest,
+      answered: userAnswer,
+    }
+  })
+}
+
+const buildSubmitParams = (): QuizPltSubmitParams => {
+  const quizResults = originPkgQuizList.value.map((originItem) => {
+    const quizId = originItem.quiz.quizId
+    const passedQuizItem = passedQuizResultMap.value[quizId]
+    const quiz = originItem.quiz
+
+    return {
+      quizId: quiz.quizId,
+      title: quiz.title,
+      itemQty: quiz.expand.itemQty,
+      totalMark: quiz.expand.totalMark,
+      passingMark: quiz.expand.passingMark,
+      finalMark: passedQuizItem.finalMark,
+      items: buildSubmitItems(passedQuizItem),
+    }
+  })
+
+  return {
+    quizResults,
+  }
+}
+
+const resetCurrentQuizAnswers = () => {
+  pkgQuizList.value = pkgQuizList.value.map((quizItem) => ({
+    ...quizItem,
+    items: quizItem.items.map((item) => ({
+      ...item,
+      userAnswer: '',
+    })),
+  }))
+}
+
+const updatePassedQuizResult = () => {
+  pkgQuizList.value.forEach((quizItem) => {
+    const finalMark = calcQuizMark(quizItem)
+    const passingMark = quizItem.quiz.expand.passingMark
+
+    if (finalMark >= passingMark) {
+      passedQuizResultMap.value[quizItem.quiz.quizId] = {
+        ...cloneData(quizItem),
+        finalMark,
+      }
+    }
+  })
+}
+
+const getFailedQuizList = () => {
+  return originPkgQuizList.value.filter((quizItem) => {
+    return !passedQuizResultMap.value[quizItem.quiz.quizId]
+  })
+}
+
+const isEveryQuizPassed = () => {
+  return originPkgQuizList.value.every((quizItem) => {
+    return !!passedQuizResultMap.value[quizItem.quiz.quizId]
+  })
+}
+
+const cancelFn = () => {
+  uni.navigateBack()
+}
+
+const confirmFn = () => {
+  pkgQuizList.value = cloneData(getFailedQuizList())
+  resetCurrentQuizAnswers()
+}
+
+const showFailTip = () => {
+  uni.showModal({
+    title: '测试未通过',
+    content: '您可以选择重新进行答题',
+    confirmText: '重新测试',
+    cancelText: '返回',
+    success: (result) => {
+      if (result.confirm) {
+        confirmFn()
+        return
+      }
+
+      cancelFn()
+    },
+  })
+}
+
+const submitFn = async () => {
+  if (submitting.value) return
+  if (!validateAnswered()) return
+
+  updatePassedQuizResult()
+
+  if (!isEveryQuizPassed()) {
+    showFailTip()
+    return
+  }
+
+  try {
+    submitting.value = true
+
+    const params = buildSubmitParams()
+    const res = await quizTestResultCreateApi(params)
+
+    if (res.code !== 0) {
+      uni.showToast({
+        title: '提交失败',
+        icon: 'none',
+      })
+      return
+    }
+
+    uni.showToast({
+      title: '测验通过',
+      icon: 'none',
+    })
+
+    if (fromHome.value === 'home') {
+      uni.reLaunch({
+        url: '/pages/index/index',
+      })
+      return
+    }
+
+    uni.reLaunch({
+      url: '/pages/mine/index',
+    })
+  } catch (error) {
+    console.error('quizTestResultCreateApi error:', error)
+    uni.showToast({
+      title: '提交失败',
+      icon: 'none',
+    })
+  } finally {
+    submitting.value = false
+  }
+}
+
+const getPltAvailQuizListFn = async () => {
+  try {
+    loading.value = true
+
+    const res = await getQuizPltAvailApi()
+
+    if (res.code !== 0) {
+      uni.showToast({
+        title: '获取测试列表失败',
+        icon: 'none',
+      })
+      return
+    }
+
+    const quizList: QuizInfo[] = res.data || []
+
+    const detailList = await Promise.all(
+      quizList.map(async (quizItem) => {
+        const detailRes = await getQuizPltDetailsApi(quizItem.quizId)
+
+        if (detailRes.code !== 0 || !detailRes.data) {
+          return null
+        }
+
+        return normalizeQuizDetail(detailRes.data)
+      })
+    )
+
+    const validDetailList = detailList.filter(Boolean) as AnswerQuizDetail[]
+
+    originPkgQuizList.value = cloneData(validDetailList)
+    pkgQuizList.value = cloneData(validDetailList)
+    resetCurrentQuizAnswers()
+  } catch (error) {
+    console.error('getPltAvailQuizListFn error:', error)
+
+    uni.showToast({
+      title: '获取测试列表失败',
+      icon: 'none',
+    })
+  } finally {
+    loading.value = false
+  }
+}
+
+onLoad((query?: PageQuery) => {
+  fromHome.value = query?.fromHome || ''
+  getPltAvailQuizListFn()
+})
+</script>
+
+<style lang="scss" scoped>
+.ability-test-page {
+  min-height: 100vh;
+  padding: 28rpx 28rpx 180rpx;
+  box-sizing: border-box;
+  background: linear-gradient(180deg, #f1f6ff 0%, #f7f8fa 360rpx, #f7f8fa 100%);
+}
+
+.ability-test-header {
+  padding: 38rpx 32rpx 42rpx;
+  margin-bottom: 28rpx;
+  box-sizing: border-box;
+  border-radius: 28rpx;
+  background: linear-gradient(135deg, #2f73ff 0%, #438dff 100%);
+  box-shadow: 0 20rpx 44rpx rgba(47, 115, 255, 0.22);
+}
+
+.ability-test-header-title {
+  font-size: 44rpx;
+  font-weight: 700;
+  line-height: 60rpx;
+  color: #ffffff;
+}
+
+.ability-test-header-desc {
+  margin-top: 12rpx;
+  font-size: 28rpx;
+  line-height: 40rpx;
+  color: rgba(255, 255, 255, 0.84);
+}
+
+.quiz-card {
+  margin-bottom: 28rpx;
+  border-radius: 28rpx;
+  background-color: #ffffff;
+  box-shadow: 0 18rpx 44rpx rgba(32, 52, 89, 0.06);
+  overflow: hidden;
+}
+
+.quiz-card-header {
+  padding: 34rpx 30rpx 28rpx;
+  box-sizing: border-box;
+  border-bottom: 1rpx solid #eef1f6;
+}
+
+.quiz-card-title {
+  font-size: 38rpx;
+  font-weight: 700;
+  line-height: 54rpx;
+  color: #202938;
+}
+
+.quiz-card-meta {
+  display: flex;
+  align-items: center;
+  margin-top: 12rpx;
+  font-size: 26rpx;
+  line-height: 36rpx;
+  color: #8a94a6;
+}
+
+.question-card {
+  padding: 32rpx 30rpx 36rpx;
+  box-sizing: border-box;
+  border-bottom: 1rpx solid #f0f2f6;
+}
+
+.question-card:last-child {
+  border-bottom: none;
+}
+
+.question-title-row {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 26rpx;
+}
+
+.question-index {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  width: 46rpx;
+  height: 46rpx;
+  margin-right: 18rpx;
+  border-radius: 14rpx;
+  font-size: 26rpx;
+  font-weight: 700;
+  color: #2f73ff;
+  background-color: #eef4ff;
+}
+
+.question-title {
+  flex: 1;
+  min-width: 0;
+  font-size: 34rpx;
+  font-weight: 600;
+  line-height: 48rpx;
+  color: #263041;
+}
+
+.question-radio-group {
+  width: 100%;
+}
+
+.option-item {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  margin-bottom: 20rpx;
+  padding: 24rpx;
+  box-sizing: border-box;
+  border: 2rpx solid #eef1f6;
+  border-radius: 20rpx;
+  background-color: #f8faff;
+}
+
+.option-item:last-child {
+  margin-bottom: 0;
+}
+
+.option-item-active {
+  border-color: #2f73ff;
+  background-color: #eef4ff;
+  box-shadow: 0 12rpx 26rpx rgba(47, 115, 255, 0.1);
+}
+
+.option-radio {
+  flex-shrink: 0;
+  transform: scale(0.86);
+}
+
+.option-content {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  min-width: 0;
+  margin-left: 12rpx;
+}
+
+.option-no {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  width: 42rpx;
+  height: 42rpx;
+  margin-right: 18rpx;
+  border-radius: 50%;
+  font-size: 24rpx;
+  font-weight: 700;
+  color: #2f73ff;
+  background-color: #ffffff;
+}
+
+.option-text {
+  flex: 1;
+  min-width: 0;
+  font-size: 30rpx;
+  line-height: 42rpx;
+  color: #303846;
+}
+
+.empty {
+  padding: 120rpx 0;
+  text-align: center;
+  font-size: 28rpx;
+  color: #9aa3b2;
+}
+
+.ability-test-footer {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 20;
+  padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
+  box-sizing: border-box;
+  background-color: #ffffff;
+  box-shadow: 0 -8rpx 24rpx rgba(33, 57, 96, 0.06);
+}
+
+.submit-button {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 88rpx;
+  padding: 0;
+  border: none;
+  border-radius: 44rpx;
+  font-size: 32rpx;
+  font-weight: 600;
+  line-height: 88rpx;
+  color: #ffffff;
+  background: linear-gradient(135deg, #2f73ff 0%, #438dff 100%);
+  box-shadow: 0 14rpx 28rpx rgba(47, 115, 255, 0.24);
+}
+
+.submit-button::after {
+  border: none;
+}
+
+.submit-button-hover {
+  opacity: 0.86;
+}
+
+.submit-button-disabled {
+  opacity: 0.65;
+}
+</style>

+ 303 - 0
src/pages-mine/compliance-evaluation/index.vue

@@ -0,0 +1,303 @@
+<template>
+  <view class="compliance-evaluation-page">
+    <view class="compliance-evaluation-header">
+      <view class="compliance-evaluation-header-title">合规评测</view>
+      <view class="compliance-evaluation-header-desc">请认真完成以下合规测评内容</view>
+    </view>
+
+    <view v-for="quizItem in testResults" :key="quizItem.quiz.quizId" class="quiz-item">
+      <view class="quiz-item-header">
+        <view class="quiz-title-wrap">
+          <view class="quiz-title-mark"></view>
+          <text class="quiz-title">
+            {{ quizItem.quiz.title }}
+          </text>
+        </view>
+
+        <text
+          class="quiz-status"
+          :class="quizItem.valid ? 'quiz-status-success' : 'quiz-status-danger'"
+        >
+          {{ quizItem.valid ? '已通过' : '未通过' }}
+        </text>
+      </view>
+
+      <view class="quiz-score-panel">
+        <view class="quiz-score-item">
+          <text class="quiz-score-label">总分</text>
+          <text class="quiz-score-value">
+            {{ quizItem.quiz.expand.totalMark }}
+          </text>
+        </view>
+
+        <view class="quiz-score-divider"></view>
+
+        <view class="quiz-score-item">
+          <text class="quiz-score-label">测试分数</text>
+          <text class="quiz-score-value">
+            {{ formatFinalMark(quizItem.finalMark) }}
+          </text>
+        </view>
+      </view>
+    </view>
+
+    <view v-if="!testResults.length" class="empty"> 暂无测评内容 </view>
+
+    <view class="compliance-evaluation-footer">
+      <button
+        class="compliance-evaluation-button"
+        hover-class="compliance-evaluation-button-hover"
+        @click="handleToQuiz"
+      >
+        {{ buttonText }}
+      </button>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+
+import { onLoad } from '@dcloudio/uni-app'
+
+import type { AbilityTestResult } from '@/services/modules/mine/compliance-evaluation'
+import { getQuizPltTestResultApi } from '@/services/modules/mine/compliance-evaluation'
+
+interface PageQuery {
+  fromHome?: string
+}
+
+const required = ref(false)
+const testResults = ref<AbilityTestResult[]>([])
+const fromHome = ref(false)
+
+const hasFinalMark = (finalMark: AbilityTestResult['finalMark'] | null | undefined) => {
+  return finalMark !== null && finalMark !== undefined
+}
+
+const buttonText = computed(() => {
+  const hasTested = testResults.value.some((item) => hasFinalMark(item.finalMark))
+
+  return hasTested ? '重新测评' : '开始测评'
+})
+
+const formatFinalMark = (finalMark: AbilityTestResult['finalMark'] | null | undefined) => {
+  return hasFinalMark(finalMark) ? finalMark : '无'
+}
+
+const handleToQuiz = () => {
+  uni.navigateTo({
+    url: `/pages-mine/compliance-evaluation/ability-test?from=mine&fromHome=${fromHome.value ? 'home' : ''}`,
+  })
+}
+
+const getPltQuizResult = async () => {
+  try {
+    const res = await getQuizPltTestResultApi()
+
+    if (res.code !== 0) {
+      uni.showToast({
+        title: '获取测评结果失败',
+        icon: 'none',
+      })
+      return
+    }
+
+    required.value = res.data.required
+    testResults.value = res.data.testResults || []
+  } catch (error) {
+    console.error('getQuizPltTestResultApi error:', error)
+
+    uni.showToast({
+      title: '获取测评结果失败',
+      icon: 'none',
+    })
+  }
+}
+
+onLoad((query?: PageQuery) => {
+  fromHome.value = !!query?.fromHome
+  getPltQuizResult()
+})
+</script>
+
+<style lang="scss" scoped>
+.compliance-evaluation-page {
+  min-height: 100vh;
+  padding: 28rpx 28rpx 180rpx;
+  box-sizing: border-box;
+  background: linear-gradient(180deg, #f1f6ff 0%, #f7f8fa 360rpx, #f7f8fa 100%);
+}
+
+.compliance-evaluation-header {
+  padding: 38rpx 32rpx 42rpx;
+  margin-bottom: 28rpx;
+  box-sizing: border-box;
+  border-radius: 28rpx;
+  background: linear-gradient(135deg, #2f73ff 0%, #438dff 100%);
+  box-shadow: 0 20rpx 44rpx rgba(47, 115, 255, 0.22);
+}
+
+.compliance-evaluation-header-title {
+  font-size: 44rpx;
+  font-weight: 700;
+  line-height: 60rpx;
+  color: #ffffff;
+}
+
+.compliance-evaluation-header-desc {
+  margin-top: 12rpx;
+  font-size: 28rpx;
+  line-height: 40rpx;
+  color: rgba(255, 255, 255, 0.84);
+}
+
+.quiz-item {
+  position: relative;
+  padding: 32rpx 28rpx;
+  margin-bottom: 24rpx;
+  box-sizing: border-box;
+  border-radius: 28rpx;
+  background-color: #ffffff;
+  box-shadow: 0 18rpx 44rpx rgba(32, 52, 89, 0.06);
+  overflow: hidden;
+}
+
+.quiz-item-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 54rpx;
+}
+
+.quiz-title-wrap {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  min-width: 0;
+  padding-right: 24rpx;
+}
+
+.quiz-title-mark {
+  flex-shrink: 0;
+  width: 10rpx;
+  height: 36rpx;
+  margin-right: 16rpx;
+  border-radius: 999rpx;
+  background: linear-gradient(180deg, #2f73ff 0%, #73a7ff 100%);
+}
+
+.quiz-title {
+  flex: 1;
+  min-width: 0;
+  font-size: 36rpx;
+  font-weight: 700;
+  line-height: 50rpx;
+  color: #202938;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.quiz-status {
+  flex-shrink: 0;
+  padding: 8rpx 20rpx;
+  border-radius: 999rpx;
+  font-size: 26rpx;
+  font-weight: 600;
+  line-height: 36rpx;
+}
+
+.quiz-status-success {
+  color: #18b66a;
+  background-color: #e9f8f0;
+}
+
+.quiz-status-danger {
+  color: #f04438;
+  background-color: #fff0ef;
+}
+
+.quiz-score-panel {
+  display: flex;
+  align-items: center;
+  height: 116rpx;
+  margin-top: 28rpx;
+  padding: 0 28rpx;
+  box-sizing: border-box;
+  border-radius: 22rpx;
+  background: linear-gradient(180deg, #f7faff 0%, #f3f6fb 100%);
+}
+
+.quiz-score-item {
+  flex: 1;
+  min-width: 0;
+}
+
+.quiz-score-label {
+  display: block;
+  font-size: 26rpx;
+  line-height: 36rpx;
+  color: #98a2b3;
+}
+
+.quiz-score-value {
+  display: block;
+  margin-top: 8rpx;
+  font-size: 36rpx;
+  font-weight: 700;
+  line-height: 44rpx;
+  color: #263041;
+}
+
+.quiz-score-divider {
+  width: 1rpx;
+  height: 58rpx;
+  margin: 0 32rpx;
+  background-color: #dfe6f0;
+}
+
+.empty {
+  padding: 120rpx 0;
+  text-align: center;
+  font-size: 28rpx;
+  color: #9aa3b2;
+}
+
+.compliance-evaluation-footer {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 20;
+  padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
+  box-sizing: border-box;
+  background-color: #ffffff;
+  box-shadow: 0 -8rpx 24rpx rgba(33, 57, 96, 0.06);
+}
+
+.compliance-evaluation-button {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 88rpx;
+  padding: 0;
+  border: none;
+  border-radius: 44rpx;
+  font-size: 32rpx;
+  font-weight: 600;
+  line-height: 88rpx;
+  color: #ffffff;
+  background: linear-gradient(135deg, #2f73ff 0%, #438dff 100%);
+  box-shadow: 0 14rpx 28rpx rgba(47, 115, 255, 0.24);
+}
+
+.compliance-evaluation-button::after {
+  border: none;
+}
+
+.compliance-evaluation-button-hover {
+  opacity: 0.86;
+}
+</style>

+ 14 - 0
src/pages.json

@@ -91,6 +91,20 @@
             "navigationStyle": "default"
           }
         },
+        {
+          "path": "compliance-evaluation/index",
+          "style": {
+            "navigationBarTitleText": "合规评测",
+            "navigationStyle": "default"
+          }
+        },
+        {
+          "path": "compliance-evaluation/ability-test",
+          "style": {
+            "navigationBarTitleText": "能力测试",
+            "navigationStyle": "default"
+          }
+        },
         {
           "path": "agreement/index",
           "style": {

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

@@ -223,7 +223,7 @@ const getPltQuizResult = async (): Promise<boolean> => {
         async success(res) {
           if (res.confirm) {
             uni.navigateTo({
-              url: '/pages/quiz-plt-test/index',
+              url: '/pages-mine/compliance-evaluation/index?fromHome=home',
             })
           }
           if (res.cancel) {

+ 2 - 3
src/pages/mine/mine.config.ts

@@ -50,16 +50,15 @@ export const functionList = [
     key: 'exam',
     txt: '合规测评',
     icon: 'https://yy-cloud-oss.oss-cn-beijing.aliyuncs.com/img/hgcp.jpg',
-    action: 'detail',
+    path: '/pages-mine/compliance-evaluation/index',
   },
   {
     key: 'password',
     txt: '修改密码',
     icon: 'https://yy-cloud-oss.oss-cn-beijing.aliyuncs.com/img/xgmm.png',
-    action: 'detail',
+    path: '/pages/reset-password/index?type=mine',
   },
 ]
-
 export const rolesList = [
   {
     name: '全职学术信息沟通专员',

+ 22 - 0
src/pages/reset-password/index.vue

@@ -18,6 +18,7 @@
           v-model="form.username"
           type="number"
           :maxlength="11"
+          :disabled="phoneDisabled"
           placeholder="请输入账号"
           :error="errors.username"
           @clear="clearFieldError('username')"
@@ -64,17 +65,22 @@
 <script setup lang="ts">
 import { reactive } from 'vue'
 
+import { onLoad } from '@dcloudio/uni-app'
+
 import { debounce } from '@/plugins/debounce'
 
 import { getPwdCodeForNoAuthApi, updUserPwdApi } from '@/services/modules/login/index'
 
 import { useCodeCountdown } from '@/composables/useCodeCountdown'
 import { useFormValidator } from '@/composables/useFormValidator'
+import { useUserStore } from '@/stores/modules/user'
 
 import { logingRules } from '@/validators/authFormRules'
 
 import AuthInputItem from '@/components/auth/AuthInputItem.vue'
 
+const userStore = useUserStore()
+
 const form = reactive({
   username: '',
   code: '',
@@ -87,6 +93,22 @@ const handleClickLeft = () => {
   uni.navigateBack()
 }
 
+const phoneDisabled = ref(false)
+
+onLoad(() => {
+  initCurrentUserPhone()
+})
+
+const initCurrentUserPhone = () => {
+  const currentUserInfo = userStore.currentUserInfo
+
+  if (!currentUserInfo?.phone) return
+
+  form.username = currentUserInfo.phone
+  phoneDisabled.value = true
+  clearFieldError('username')
+}
+
 const { countdown, disabled: codeDisabled, start: startCountdown } = useCodeCountdown()
 
 const toast = (title: string, duration = 800) => uni.showToast({ title, icon: 'none', duration })

+ 23 - 0
src/services/modules/mine/compliance-evaluation/index.ts

@@ -0,0 +1,23 @@
+import http from '../../../index'
+import type {
+  QuizPltAvailResponse,
+  QuizPltDetailResponse,
+  QuizPltSubmitParams,
+  QuizPltTestResultResponse,
+} from './type'
+
+export const getQuizPltTestResultApi = () => {
+  return http.get<QuizPltTestResultResponse>('/admin/api/quiz/plt/test/result')
+}
+
+export const getQuizPltAvailApi = () => {
+  return http.get<QuizPltAvailResponse>('/admin/quiz/plt/avail/list')
+}
+
+export const getQuizPltDetailsApi = (quizId: number) => {
+  return http.get<QuizPltDetailResponse>(`/admin/quiz/details?quizId=${quizId}`)
+}
+
+export const quizTestResultCreateApi = (data: QuizPltSubmitParams) => {
+  return http.post<QuizPltDetailResponse>(`/admin/api/quiz/plt/test/result/create`, data)
+}

+ 114 - 0
src/services/modules/mine/compliance-evaluation/type.d.ts

@@ -0,0 +1,114 @@
+// services/modules/mine/compliance-evaluation/types.ts
+
+/**
+ * 合规测评结果
+ */
+export interface QuizPltTestResultResponse {
+  required: boolean
+  testResults: AbilityTestResult[]
+}
+
+/**
+ * 单个测评结果
+ */
+export interface AbilityTestResult {
+  expiryDate: string
+  finalMark: number | null
+  quiz: QuizInfo
+  valid: boolean
+}
+
+/**
+ * 可用测评列表
+ */
+export type QuizPltAvailResponse = QuizInfo[]
+
+/**
+ * 测评详情
+ */
+export interface QuizPltDetailResponse {
+  items: QuizItem[]
+  quiz: QuizInfo
+}
+
+/**
+ * 测评基础信息
+ */
+export interface QuizInfo {
+  quizId: number
+  serialNumber: string
+  title: string
+  introduction: string
+  createBy: string
+  createTime: string
+  enterpriseId: number
+  expand: QuizExpand
+  sourceType: string
+  state: string
+  updateBy: string
+  updateTime: string
+  valid?: boolean
+}
+
+/**
+ * 测评扩展信息
+ */
+export interface QuizExpand {
+  itemQty: number
+  totalMark: number
+  passingMark: number
+}
+
+/**
+ * 测评题目
+ */
+export interface QuizItem {
+  itemId: number
+  quizSerialNumber: string
+  label: string
+  answer: string
+  mark: number
+  options: QuizItemOption[]
+  widget: number
+  createBy: string
+  createTime: string
+  updateBy: string
+  updateTime: string
+}
+
+/**
+ * 题目选项
+ */
+export interface QuizItemOption {
+  no: string
+  text: string
+}
+
+/**
+ * 提交测评参数
+ */
+export interface QuizPltSubmitParams {
+  quizResults: QuizSubmitResult[]
+}
+
+/**
+ * 提交时单个测评结果
+ */
+export interface QuizSubmitResult {
+  quizId: number
+  title: string
+  itemQty: number
+  totalMark: number
+  passingMark: number
+  finalMark: number
+  items: QuizSubmitItem[]
+}
+
+/**
+ * 提交时单个题目
+ *
+
+ */
+export interface QuizSubmitItem extends QuizItem {
+  answered: string
+}

+ 0 - 2
src/stores/modules/user.ts

@@ -92,8 +92,6 @@ export const useUserStore = defineStore('user', {
 
       this.userInfoList = userInfoList
 
-      console.log('userInfoList', userInfoList[0])
-
       const index = this.currentUserInfoIndex ?? 0
       const currentUserInfo = userInfoList[index]