2 Commitit 1bdfdf2c6a ... 73119d7eb3

Tekijä SHA1 Viesti Päivämäärä
  闪电 73119d7eb3 Merge branch 'master' of http://39.164.159.226:3000/hnsh-smart-steward/smart-steward-mobile 3 viikkoa sitten
  闪电 a16fc717c2 mod: 考试 3 viikkoa sitten

+ 1 - 0
src/api/dict/index.ts

@@ -1,4 +1,5 @@
1 1
 import { http } from '@/http/http'
2
+
2 3
 enum Api {
3 4
   root = '/system/dict/data',
4 5
 }

+ 24 - 0
src/api/exam/answer.ts

@@ -0,0 +1,24 @@
1
+import { http } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/examAnswerResult/result',
5
+  list = '/examAnswerResult/result/list',
6
+}
7
+
8
+function addAnswerResult(params: any) {
9
+  return http.post(Api.base, params)
10
+}
11
+
12
+async function getAnswerResult(examId: number) {
13
+  return http.get(`${Api.base}/${examId}`)
14
+}
15
+
16
+async function getAnswerResultList(params: any) {
17
+  return http.get(`${Api.list}`, params)
18
+}
19
+
20
+async function gradeExam(params: any) {
21
+  return http.post(`${Api.base}/grade`, params)
22
+}
23
+
24
+export { addAnswerResult, getAnswerResult, getAnswerResultList, gradeExam }

+ 67 - 0
src/api/exam/index.ts

@@ -0,0 +1,67 @@
1
+import { http } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/exam/exam',
5
+  list = '/exam/exam/list',
6
+}
7
+
8
+export interface Exam {
9
+  id: number
10
+  categoryId: number
11
+  categoryName: string
12
+  examName: string
13
+  paperId: number
14
+  examStartTime: string
15
+  examEndTime: string
16
+  examDuration: number
17
+  passScore: number
18
+  isLimit: number
19
+  isSubjective: number
20
+  examStatus: string
21
+  createTime: string
22
+  updateTime: string
23
+  delFlag: string
24
+}
25
+
26
+/**
27
+ * 获取试卷列表
28
+ */
29
+async function examList(params?: any) {
30
+  return http.get<Exam>(Api.list, params)
31
+}
32
+
33
+/**
34
+ * 获取试卷详情
35
+ */
36
+async function examDetail(id: number) {
37
+  return http.get<Exam>(`${Api.base}/${id}`)
38
+}
39
+
40
+// 我的待考试卷列表
41
+async function myPendingExamList(params?: any) {
42
+  return http.get<Exam>(`${Api.base}/my-start-list`, params)
43
+}
44
+
45
+// 我的已考试卷列表
46
+async function myEndExamList(params?: any) {
47
+  return http.get<Exam>(`${Api.base}/my-end-list`, params)
48
+}
49
+
50
+// 考试试卷详情
51
+async function examPaperDetail(id: number) {
52
+  return http.get<Exam>(`${Api.base}/paper/${id}`)
53
+}
54
+
55
+// 人工阅卷
56
+async function startExamList(params?: any) {
57
+  return http.get<Exam>(`${Api.base}/start-list`, params)
58
+}
59
+
60
+export {
61
+  examDetail,
62
+  examList,
63
+  examPaperDetail,
64
+  myEndExamList,
65
+  myPendingExamList,
66
+  startExamList,
67
+}

+ 45 - 0
src/api/exam/participant.ts

@@ -0,0 +1,45 @@
1
+import { http } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/examParticipant/participant',
5
+  list = '/examParticipant/participant/list',
6
+}
7
+
8
+function getParticipantList(params: any) {
9
+  return http.get(Api.list, params)
10
+}
11
+
12
+function getDetailParticipantList(params: any) {
13
+  return http.get(`${Api.base}/detail-list`, params)
14
+}
15
+
16
+function startExam(id: number) {
17
+  return http.post(
18
+    `${Api.base}/start/${id}`,
19
+    {},
20
+  )
21
+}
22
+
23
+function endExam(id: number) {
24
+  return http.post(
25
+    `${Api.base}/end/${id}`,
26
+    {},
27
+  )
28
+}
29
+
30
+async function getParticipantById(id: number) {
31
+  return http.get(`${Api.base}/${id}`)
32
+}
33
+
34
+async function deleteParticipant(id: number) {
35
+  return http.delete(`${Api.base}/${id}`)
36
+}
37
+
38
+export {
39
+  deleteParticipant,
40
+  endExam,
41
+  getDetailParticipantList,
42
+  getParticipantById,
43
+  getParticipantList,
44
+  startExam,
45
+}

+ 8 - 0
src/api/login.ts

@@ -28,6 +28,14 @@ export function login(loginForm: ILoginForm) {
28 28
 }
29 29
 
30 30
 /**
31
+ * 用户登录
32
+ * @param loginForm 登录表单
33
+ */
34
+export function authExamLogin(loginForm: ILoginForm) {
35
+  return http.post<IAuthLoginRes>('/exam/login', loginForm)
36
+}
37
+
38
+/**
31 39
  * 刷新token
32 40
  * @param refreshToken 刷新token
33 41
  */

+ 11 - 0
src/api/schedule/task.ts

@@ -5,6 +5,17 @@ enum Api {
5 5
   confirmBase = '/scheduleTasks/tasks/confirm',
6 6
   uploadBase = '/taskAttachment/attachment',
7 7
   photoBase = '/taskPhoto/photo',
8
+  // 修改计划时间
9
+  planTimeBase = '/scheduleTasks/tasks/setPlanTime',
10
+}
11
+
12
+/**
13
+ * 修改计划时间
14
+ * @param data
15
+ * @returns
16
+ */
17
+export function setPlanTime(id: number, data: any) {
18
+  return http.put(`${Api.planTimeBase}/${id}`, data)
8 19
 }
9 20
 
10 21
 export function transferTask(data: any) {

File diff suppressed because it is too large
+ 1985 - 0
src/pages/knowledge/exam/answer.vue


File diff suppressed because it is too large
+ 0 - 1183
src/pages/knowledge/exam/examcontent/index.vue


+ 0 - 116
src/pages/knowledge/exam/explanation/index.vue

@@ -1,116 +0,0 @@
1
-<script setup lang="ts"></script>
2
-<template>
3
-  <view class="knowledgebase_exam_explanation">
4
-    <view class="knowledgebase_exam_explanation_title">
5
-      考试名称考试名称考试名称考试考试名称考试名称考试名称
6
-    </view>
7
-    <view class="knowledgebase_exam_explanation_content">
8
-      <view class="knowledgebase_exam_explanation_content_title">
9
-        考试说明
10
-      </view>
11
-      <view class="knowledgebase_exam_explanation_content_text">
12
-        考试说明考试说明考试说明考试说明考试说明考试说明考试说明。
13
-      </view>
14
-    </view>
15
-    <view class="knowledgebase_exam_explanation_float">
16
-      <view class="knowledgebase_exam_explanation_float_title">
17
-        考试总分
18
-        <span class="knowledgebase_exam_explanation_float_title_score"
19
-          >100分</span
20
-        ></view
21
-      >
22
-      <view class="knowledgebase_exam_explanation_float_title">
23
-        及格分数
24
-        <span class="knowledgebase_exam_explanation_float_title_score"
25
-          >60分</span
26
-        ></view
27
-      >
28
-      <view class="knowledgebase_exam_explanation_float_title">
29
-        考试时长
30
-        <span class="knowledgebase_exam_explanation_float_title_score"
31
-          >90分钟</span
32
-        ></view
33
-      >
34
-    </view>
35
-    <wd-button class="knowledgebase_exam_explanation_float_button"
36
-      >开始考试</wd-button
37
-    >
38
-    <!-- <wd-button disabled class="knowledgebase_exam_explanation_float_button"
39
-      >考试将于2026-2-1 19:00开始</wd-button
40
-    > -->
41
-  </view>
42
-</template>
43
-<style scoped lang="scss">
44
-html,
45
-body {
46
-  overflow: hidden;
47
-}
48
-.knowledgebase_exam_explanation {
49
-  height: calc(100vh - 88rpx);
50
-  display: flex;
51
-  flex-direction: column;
52
-  // justify-content: space-between;
53
-  padding: 24rpx;
54
-  box-sizing: border-box;
55
-}
56
-.knowledgebase_exam_explanation_title {
57
-  font-family: Alibaba PuHuiTi 2;
58
-  font-weight: 500;
59
-  font-style: 65 Medium;
60
-  font-size: 48rpx;
61
-  leading-trim: NONE;
62
-  line-height: 48rpx;
63
-  letter-spacing: 0px;
64
-  color: #31373d;
65
-}
66
-.knowledgebase_exam_explanation_content {
67
-  margin-top: 80rpx;
68
-  width: 100%;
69
-  display: flex;
70
-  flex-direction: column;
71
-  gap: 32rpx;
72
-  .knowledgebase_exam_explanation_content_title {
73
-    font-weight: 500;
74
-    font-style: 65 Medium;
75
-    font-size: 36rpx;
76
-    leading-trim: NONE;
77
-    letter-spacing: 0px;
78
-    color: #31373d;
79
-  }
80
-  .knowledgebase_exam_explanation_content_text {
81
-    font-weight: 400;
82
-    font-style: 55 Regular;
83
-    font-size: 28rpx;
84
-    line-height: 48rpx;
85
-    letter-spacing: 0px;
86
-    color: #4e5969;
87
-  }
88
-}
89
-.knowledgebase_exam_explanation_float {
90
-  margin-top: 48rpx;
91
-  border-top: 1rpx solid #eeeeee;
92
-  padding-top: 48rpx;
93
-  box-sizing: border-box;
94
-  display: flex;
95
-  flex-direction: column;
96
-  gap: 32rpx;
97
-  .knowledgebase_exam_explanation_float_title {
98
-    font-weight: 400;
99
-    font-style: 55 Regular;
100
-    font-size: 28rpx;
101
-    letter-spacing: 0px;
102
-    color: #4e5969;
103
-    .knowledgebase_exam_explanation_float_title_score {
104
-      margin-left: 48rpx;
105
-      color: #31373d;
106
-    }
107
-  }
108
-}
109
-.knowledgebase_exam_explanation_float_button {
110
-  margin-top: auto;
111
-  width: 100%;
112
-  background-color: #215acd !important;
113
-  height: 80rpx !important;
114
-  border-radius: 8rpx !important;
115
-}
116
-</style>

+ 240 - 107
src/pages/knowledge/exam/index.vue

@@ -1,5 +1,7 @@
1 1
 <script setup lang="ts">
2
-import { ref } from 'vue'
2
+import { onMounted, ref } from 'vue'
3
+import { myEndExamList, myPendingExamList } from '@/api/exam'
4
+
3 5
 const examList = ref([
4 6
   {
5 7
     id: 1,
@@ -11,154 +13,270 @@ const examList = ref([
11 13
   },
12 14
 ])
13 15
 const activeId = ref(1)
14
-const waitingForTheExam = ref([
15
-  {
16
-    id: 1,
17
-    name: '考试1',
18
-    examTime: '2023-08-01 10:00:00',
19
-    Status: '进行中',
20
-  },
21
-  {
22
-    id: 2,
23
-    name: '考试2',
24
-    examTime: '2023-08-01 10:00:00',
25
-    Status: '未开始',
16
+const waitingForTheExam = ref([])
17
+const endExamList = ref([])
18
+
19
+// 滚动加载状态管理
20
+const isLoading = ref(false)
21
+const pendingTotal = ref(0)
22
+const endTotal = ref(0)
23
+
24
+const examStatusColorMap: any = {
25
+  pending: {
26
+    text: '待考试',
27
+    color: '#215ACD',
26 28
   },
27
-  {
28
-    id: 3,
29
-    name: '考试3',
30
-    examTime: '2023-08-01 10:00:00',
31
-    Status: '缺考',
29
+  ongoing: {
30
+    text: '进行中',
31
+    color: '#EB5E12',
32 32
   },
33
-  {
34
-    id: 4,
35
-    name: '考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4',
36
-    examTime: '2023-08-01 10:00:00',
37
-    Status: '缺考',
33
+  absent: {
34
+    text: '缺考',
35
+    color: '#A4AAB2',
38 36
   },
39
-])
40
-const Examtaken = ref([
41
-  {
42
-    id: 1,
43
-    name: '考试6',
44
-    examTime: '2023-08-01 10:00:00',
45
-    Status: '已出分',
46
-    Result: '及格',
47
-    Score: '80分',
48
-  },
49
-  {
50
-    id: 2,
51
-    name: '考试8',
52
-    examTime: '2023-08-01 10:00:00',
53
-    Status: '已出分',
54
-    Result: '不及格',
55
-    Score: '880分',
56
-  },
57
-  {
58
-    id: 3,
59
-    name: '考试9',
60
-    examTime: '2023-08-01 10:00:00',
61
-    Status: '待出分',
37
+}
38
+
39
+function getStatus(row: any) {
40
+  let status = ''
41
+  if (new Date(row.examStartTime) > new Date()) {
42
+    status = 'pending'
43
+  }
44
+  else if (new Date(row.examEndTime) > new Date()) {
45
+    status = 'ongoing'
46
+  }
47
+  else {
48
+    status = 'absent'
49
+  }
50
+  return status
51
+}
52
+
53
+// const reviewStatusOptions = [
54
+//   { label: '待批改', value: 'unreviewed', color: '#EB5E12' },
55
+//   { label: '批改中', value: 'reviewing', color: '#FFC107' },
56
+//   { label: '已批改', value: 'reviewed', color: '#38B865' },
57
+// ]
58
+
59
+const endStatusColorMap = {
60
+  0: {
61
+    text: '已出分',
62
+    color: '#38B865',
62 63
   },
63
-  {
64
-    id: 4,
65
-    name: '考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4考试4',
66
-    examTime: '2023-08-01 10:00:00',
67
-    Status: '缺考',
64
+  1: {
65
+    text: '待出分',
66
+    color: '#215ACD',
68 67
   },
69
-])
70
-const statusColorMap = {
71
-  进行中: '#EB5E12',
72
-  未开始: '#215ACD',
73
-  缺考: '#A4AAB2',
74
-  已出分: '#38B865',
75
-  待出分: '#215ACD',
76 68
 }
77
-const toExamDetail = (data) => {
69
+
70
+function getEndStatus(row: any) {
71
+  if (row.reviewStatus === 'unreviewed') {
72
+    return endStatusColorMap[1]
73
+  }
74
+  return endStatusColorMap[0]
75
+}
76
+
77
+function toExamDetail(data) {
78 78
   if (data.Status === '未开始') {
79 79
     uni.navigateTo({
80 80
       url: '/pages/knowledge/exam/explanation/index',
81 81
     })
82
-  } else if (data.Status === '进行中') {
82
+  }
83
+  else if (data.Status === '进行中') {
83 84
     uni.navigateTo({
84 85
       url: '/pages/knowledge/exam/examcontent/index',
85 86
     })
86 87
   }
87 88
 }
88
-const ExamtakenDetail = (data) => {
89
+function ExamtakenDetail(data) {
89 90
   if (data.Status === '待出分') {
90 91
     uni.navigateTo({
91 92
       url: '/pages/knowledge/exam/examresults/scoresnotreleased',
92 93
     })
93
-  } else if (data.Status === '已出分') {
94
+  }
95
+  else if (data.Status === '已出分') {
94 96
     uni.navigateTo({
95 97
       url: '/pages/knowledge/exam/examresults/index',
96 98
     })
97 99
   }
98 100
 }
101
+
102
+const page = ref({
103
+  currentPage: 1,
104
+  pageSize: 10,
105
+})
106
+
107
+async function getExamList(loadMore = false) {
108
+  try {
109
+    isLoading.value = true
110
+    if (activeId.value === 1) {
111
+      const res: any = await myPendingExamList({
112
+        pageNum: page.value.currentPage,
113
+        pageSize: page.value.pageSize,
114
+      })
115
+      if (res.code === 200) {
116
+        if (loadMore) {
117
+          waitingForTheExam.value = [...waitingForTheExam.value, ...(res?.rows || [])]
118
+        }
119
+        else {
120
+          waitingForTheExam.value = res?.rows
121
+        }
122
+        pendingTotal.value = res?.total
123
+      }
124
+    }
125
+    else if (activeId.value === 2) {
126
+      const res: any = await myEndExamList({
127
+        pageNum: page.value.currentPage,
128
+        pageSize: page.value.pageSize,
129
+      })
130
+      if (res.code === 200) {
131
+        if (loadMore) {
132
+          endExamList.value = [...endExamList.value, ...(res?.rows || [])]
133
+        }
134
+        else {
135
+          endExamList.value = res?.rows
136
+        }
137
+        endTotal.value = res?.total
138
+      }
139
+    }
140
+  }
141
+  catch (err) {
142
+    uni.showToast({
143
+      title: '加载失败',
144
+      icon: 'none',
145
+    })
146
+  }
147
+  finally {
148
+    isLoading.value = false
149
+  }
150
+}
151
+
152
+function changeTab(id) {
153
+  if (id === activeId.value) {
154
+    return
155
+  }
156
+  activeId.value = id
157
+  page.value.currentPage = 1
158
+  getExamList()
159
+}
160
+
161
+// 滚动加载更多
162
+async function handleLoadMore() {
163
+  if (isLoading.value)
164
+    return
165
+
166
+  const currentList = activeId.value === 1 ? waitingForTheExam.value : endExamList.value
167
+  const currentTotal = activeId.value === 1 ? pendingTotal.value : endTotal.value
168
+
169
+  if (currentList.length >= currentTotal) {
170
+    uni.showToast({
171
+      title: '没有更多数据了',
172
+      icon: 'none',
173
+    })
174
+    return
175
+  }
176
+
177
+  page.value.currentPage++
178
+  await getExamList(true)
179
+}
180
+
181
+function goDetail(item) {
182
+  if (activeId.value === 1) {
183
+    uni.navigateTo({
184
+      url: `/pages/knowledge/exam/start?id=${item.id}`,
185
+    })
186
+  }
187
+  else {
188
+    ExamtakenDetail(item)
189
+  }
190
+}
191
+
192
+onMounted(async () => {
193
+  await getExamList()
194
+})
99 195
 </script>
196
+
100 197
 <template>
101 198
   <view class="knowledgebase_exam">
102 199
     <view class="header_nav">
103 200
       <view
104 201
         v-for="(item, index) in examList"
105 202
         :key="index"
106
-        :class="['header_nav_item', { active: item.id === activeId }]"
107
-        @click="activeId = item.id"
203
+        class="header_nav_item" :class="[{ active: item.id === activeId }]"
204
+        @click="changeTab(item.id)"
108 205
       >
109 206
         {{ item.status }}
110
-        <view v-if="item.id === activeId" class="active_line"></view>
207
+        <view v-if="item.id === activeId" class="active_line" />
111 208
       </view>
112 209
     </view>
113
-    <view class="content">
114
-      <view
115
-        v-if="activeId === 1"
116
-        class="content_item"
117
-        v-for="(item, index) in waitingForTheExam"
118
-        @click="toExamDetail(item)"
119
-        :key="index"
120
-      >
121
-        <view class="content_item_title">
122
-          <span class="content_item_title_name">{{ item.name }}</span>
123
-          <span
124
-            class="content_item_title_status"
125
-            :style="{ 'background-color': statusColorMap[item.Status] }"
126
-            >{{ item.Status }}</span
127
-          >
210
+    <scroll-view
211
+      class="content"
212
+      :scroll-y="true"
213
+      @scrolltolower="handleLoadMore"
214
+    >
215
+      <view v-if="activeId === 1">
216
+        <view
217
+          v-for="(item, index) in waitingForTheExam"
218
+          :key="index"
219
+          class="content_item"
220
+          @click="goDetail(item)"
221
+        >
222
+          <view class="content_item_title">
223
+            <span class="content_item_title_name">{{ item.examName || '' }}</span>
224
+            <span
225
+              class="content_item_title_status"
226
+              :style="{ 'background-color': examStatusColorMap[getStatus(item)].color }"
227
+            >{{ examStatusColorMap[getStatus(item)].text }}</span>
228
+          </view>
229
+          <view class="content_item_time">
230
+            {{ item.examStartTime }}
231
+          </view>
128 232
         </view>
129
-        <view class="content_item_time">{{ item.examTime }}</view>
130 233
       </view>
234
+      <view v-else>
235
+        <view
236
+          v-for="(item, index) in endExamList"
237
+          :key="`else-${index}`"
238
+          class="content_item"
239
+          @click="goDetail(item)"
240
+        >
241
+          <view class="content_item_title">
242
+            <span class="content_item_title_name">{{ item.examName || '' }}</span>
243
+            <span
244
+              class="content_item_title_status"
245
+              :style="{ 'background-color': getEndStatus(item)?.color }"
246
+            >{{ getEndStatus(item)?.text }}</span>
247
+          </view>
248
+          <view v-if="getEndStatus(item)?.text === '已出分'" class="content_item_result_score">
249
+            <view class="content_item_result">
250
+              考试结果:<text
251
+                :style="{ color: item.examScore >= item.totalScore ? '#38B865' : '#EB5E12' }"
252
+              >
253
+                {{ item.examScore >= item.totalScore ? '及格' : '不及格' }}
254
+              </text>
255
+            </view>
256
+            <view class="content_item_score">
257
+              分数:<text>{{ item.examScore }}</text>
258
+            </view>
259
+          </view>
260
+          <view class="content_item_time">
261
+            {{ item.examStartTime }}
262
+          </view>
263
+        </view>
264
+      </view>
265
+      <!-- 加载中状态 -->
266
+      <view v-if="isLoading" class="load-more">
267
+        <text style="font-size: 24rpx; color: #86909c">加载中...</text>
268
+      </view>
269
+      <!-- 无更多数据提示 -->
131 270
       <view
132
-        v-else
133
-        class="content_item"
134
-        v-for="(item, index) in Examtaken"
135
-        @click="ExamtakenDetail(item)"
136
-        :key="'else-' + index"
271
+        v-if="!isLoading && (activeId === 1 ? waitingForTheExam.length : endExamList.length) > 0 && (activeId === 1 ? waitingForTheExam.length >= pendingTotal : endExamList.length >= endTotal)"
272
+        class="load-more"
137 273
       >
138
-        <view class="content_item_title">
139
-          <span class="content_item_title_name">{{ item.name }}</span>
140
-          <span
141
-            class="content_item_title_status"
142
-            :style="{ 'background-color': statusColorMap[item.Status] }"
143
-            >{{ item.Status }}</span
144
-          >
145
-        </view>
146
-        <view class="content_item_result_score" v-if="item.Status === '已出分'">
147
-          <view class="content_item_result"
148
-            >考试结果:<text
149
-              :style="{ color: item.Result === '及格' ? '#38B865' : '#EB5E12' }"
150
-              >{{ item.Result }}</text
151
-            ></view
152
-          >
153
-          <view class="content_item_score"
154
-            >分数:<text>{{ item.Score }}</text></view
155
-          >
156
-        </view>
157
-        <view class="content_item_time">{{ item.examTime }}</view>
274
+        <text style="font-size: 24rpx; color: #86909c">- 暂无更多数据 -</text>
158 275
       </view>
159
-    </view>
276
+    </scroll-view>
160 277
   </view>
161 278
 </template>
279
+
162 280
 <style scoped lang="scss">
163 281
 html,
164 282
 body {
@@ -171,6 +289,7 @@ body {
171 289
   align-items: center;
172 290
   padding: 24rpx;
173 291
   box-sizing: border-box;
292
+  min-height: 0;
174 293
 }
175 294
 .header_nav {
176 295
   width: 100%;
@@ -197,11 +316,16 @@ body {
197 316
   }
198 317
 }
199 318
 .content {
319
+  flex: 1;
200 320
   width: 100%;
201 321
   display: flex;
202 322
   flex-direction: column;
203 323
   gap: 24rpx;
204 324
   margin-top: 46rpx;
325
+  overflow-y: auto;
326
+  overflow-x: hidden;
327
+  box-sizing: border-box;
328
+  padding-bottom: 150rpx;
205 329
   .content_item {
206 330
     // height: 150rpx;
207 331
     border-bottom: 1px solid #eeeeee;
@@ -256,4 +380,13 @@ body {
256 380
     border-bottom: none;
257 381
   }
258 382
 }
383
+.load-more {
384
+  width: 100%;
385
+  height: 80rpx;
386
+  display: flex;
387
+  justify-content: center;
388
+  align-items: center;
389
+  margin-top: 20rpx;
390
+  margin-bottom: 20rpx;
391
+}
259 392
 </style>

+ 255 - 0
src/pages/knowledge/exam/start.vue

@@ -0,0 +1,255 @@
1
+<script setup lang="ts">
2
+import { onMounted, ref } from 'vue'
3
+import { useRoute } from 'vue-router'
4
+
5
+import { useMessage, useToast } from 'wot-design-uni'
6
+
7
+import { examDetail } from '@/api/exam'
8
+import { startExam } from '@/api/exam/participant'
9
+
10
+import { useAuthStore } from '@/store/auth'
11
+import { useTokenStore } from '@/store/token'
12
+
13
+const tokenStore: any = useTokenStore()
14
+const toast = useToast()
15
+const wotMessage = useMessage()
16
+const authStore = useAuthStore()
17
+
18
+const route = useRoute()
19
+// const id = ref(Number(route.query.id))
20
+const examId = ref(Number(route.query.id || '0'))
21
+console.log(tokenStore.tokenInfo, 'tokenStore.tokenInfo')
22
+const token = ref(tokenStore.tokenInfo?.token || '')
23
+
24
+const examInfo: any = ref({
25
+  examName: '',
26
+  totalScore: 100,
27
+  passScore: 60,
28
+  examDuration: '30分钟',
29
+  examDescription: '',
30
+})
31
+
32
+const loginUser: any = ref({
33
+  username: '',
34
+  password: '',
35
+})
36
+
37
+onMounted(async () => {
38
+  const res: any = await examDetail(examId.value)
39
+  if (res.code === 200) {
40
+    examInfo.value = res.data || {}
41
+  }
42
+})
43
+
44
+function startExamEvent() {
45
+  // 判断开始是否在进行中
46
+  if (examInfo.value.examStatus !== 'ongoing') {
47
+    toast.error('当前考试状态不是进行中,不能开始考试')
48
+    return
49
+  }
50
+
51
+  if (
52
+    examInfo.value.examStartTime
53
+    && new Date(examInfo.value.examStartTime).getTime() > Date.now()
54
+  ) {
55
+    toast.error('考试还未开始')
56
+    return
57
+  }
58
+
59
+  if (
60
+    examInfo.value.examEndTime
61
+    && new Date(examInfo.value.examEndTime).getTime() < Date.now()
62
+  ) {
63
+    toast.error('考试已结束')
64
+    return
65
+  }
66
+
67
+  // 增加一个二次确认提醒,提醒开始之后必须完成
68
+  wotMessage.confirm({
69
+    msg: `考试过程中不可以退出,完成之后需要点击提交试卷按钮`,
70
+    title: '考试提醒',
71
+  }).then(async () => {
72
+    try {
73
+      const res: any = await startExam(+(examId.value || '0'))
74
+      if (res.code !== 200) {
75
+        toast.error(`操作失败${res.msg}`)
76
+        return
77
+      }
78
+      const userType = res?.userType || ''
79
+      localStorage.setItem('exam:usertype', userType)
80
+      if (userType === 'exam') {
81
+        // router.replace({
82
+        //   name: 'Startexamin',
83
+        //   params: {
84
+        //     id: route.params.id,
85
+        //   },
86
+        //   query: {
87
+        //     pid: res.id,
88
+        //   },
89
+        // })
90
+        uni.navigateTo({
91
+          url: `/pages/knowledge/exam/answer?id=${examId.value}&pid=${res?.id}`,
92
+        })
93
+      }
94
+      else {
95
+        uni.navigateTo({
96
+          url: `/pages/knowledge/exam/answer?id=${examId.value}&pid=${res?.id}`,
97
+        })
98
+        // router.replace({
99
+        //   name: 'StartExamin',
100
+        //   params: {
101
+        //     id: route.params.id,
102
+        //   },
103
+        //   query: {
104
+        //     pid: res.id,
105
+        //   },
106
+        // })
107
+      }
108
+    }
109
+    catch (error) {
110
+      toast.error(`操作失败${error}`)
111
+    }
112
+  })
113
+}
114
+
115
+async function start() {
116
+  if (token.value) {
117
+    startExamEvent()
118
+  }
119
+  else {
120
+    if (!loginUser.value.username && !loginUser.value.password) {
121
+      toast.error('请输入姓名或手机号')
122
+      return
123
+    }
124
+
125
+    try {
126
+      await authStore.authExamLogin(loginUser.value)
127
+
128
+      token.value = tokenStore.tokenInfo?.token || ''
129
+      startExamEvent()
130
+    }
131
+    catch (error) {
132
+      toast.error(`操作失败${error}`)
133
+    }
134
+  }
135
+}
136
+</script>
137
+
138
+<template>
139
+  <view class="knowledgebase_exam_explanation">
140
+    <view class="knowledgebase_exam_explanation_title">
141
+      {{ examInfo.examName }}
142
+    </view>
143
+    <view class="knowledgebase_exam_explanation_content">
144
+      <view class="knowledgebase_exam_explanation_content_title">
145
+        考试说明
146
+      </view>
147
+      <view class="knowledgebase_exam_explanation_content_text">
148
+        {{ examInfo.examDescription }}
149
+      </view>
150
+    </view>
151
+    <view class="knowledgebase_exam_explanation_float">
152
+      <view class="knowledgebase_exam_explanation_float_title">
153
+        考试总分
154
+        <span class="knowledgebase_exam_explanation_float_title_score">
155
+          {{ examInfo.totalScore }}分
156
+        </span>
157
+      </view>
158
+      <view class="knowledgebase_exam_explanation_float_title">
159
+        及格分数
160
+        <span class="knowledgebase_exam_explanation_float_title_score">
161
+          {{ examInfo.passScore }}分
162
+        </span>
163
+      </view>
164
+      <view class="knowledgebase_exam_explanation_float_title">
165
+        考试时长
166
+        <span class="knowledgebase_exam_explanation_float_title_score">
167
+          {{ examInfo.examDuration }} 分钟
168
+        </span>
169
+      </view>
170
+    </view>
171
+    <wd-button class="knowledgebase_exam_explanation_float_button" @click="start">
172
+      开始考试
173
+    </wd-button>
174
+    <!-- <wd-button disabled class="knowledgebase_exam_explanation_float_button"
175
+      >考试将于2026-2-1 19:00开始</wd-button
176
+    > -->
177
+    <wd-toast />
178
+    <wd-message-box />
179
+  </view>
180
+</template>
181
+
182
+<style scoped lang="scss">
183
+html,
184
+body {
185
+  overflow: hidden;
186
+}
187
+.knowledgebase_exam_explanation {
188
+  height: calc(100vh - 88rpx);
189
+  display: flex;
190
+  flex-direction: column;
191
+  // justify-content: space-between;
192
+  padding: 24rpx;
193
+  box-sizing: border-box;
194
+}
195
+.knowledgebase_exam_explanation_title {
196
+  font-family: Alibaba PuHuiTi 2;
197
+  font-weight: 500;
198
+  font-style: 65 Medium;
199
+  font-size: 48rpx;
200
+  leading-trim: NONE;
201
+  line-height: 48rpx;
202
+  letter-spacing: 0px;
203
+  color: #31373d;
204
+}
205
+.knowledgebase_exam_explanation_content {
206
+  margin-top: 80rpx;
207
+  width: 100%;
208
+  display: flex;
209
+  flex-direction: column;
210
+  gap: 32rpx;
211
+  .knowledgebase_exam_explanation_content_title {
212
+    font-weight: 500;
213
+    font-style: 65 Medium;
214
+    font-size: 36rpx;
215
+    leading-trim: NONE;
216
+    letter-spacing: 0px;
217
+    color: #31373d;
218
+  }
219
+  .knowledgebase_exam_explanation_content_text {
220
+    font-weight: 400;
221
+    font-style: 55 Regular;
222
+    font-size: 28rpx;
223
+    line-height: 48rpx;
224
+    letter-spacing: 0px;
225
+    color: #4e5969;
226
+  }
227
+}
228
+.knowledgebase_exam_explanation_float {
229
+  margin-top: 48rpx;
230
+  border-top: 1rpx solid #eeeeee;
231
+  padding-top: 48rpx;
232
+  box-sizing: border-box;
233
+  display: flex;
234
+  flex-direction: column;
235
+  gap: 32rpx;
236
+  .knowledgebase_exam_explanation_float_title {
237
+    font-weight: 400;
238
+    font-style: 55 Regular;
239
+    font-size: 28rpx;
240
+    letter-spacing: 0px;
241
+    color: #4e5969;
242
+    .knowledgebase_exam_explanation_float_title_score {
243
+      margin-left: 48rpx;
244
+      color: #31373d;
245
+    }
246
+  }
247
+}
248
+.knowledgebase_exam_explanation_float_button {
249
+  margin-top: auto;
250
+  width: 100%;
251
+  background-color: #215acd !important;
252
+  height: 80rpx !important;
253
+  border-radius: 8rpx !important;
254
+}
255
+</style>

+ 8 - 4
src/pages/knowledge/exam/examcontent/components/submit.vue

@@ -1,5 +1,5 @@
1 1
 <script setup lang="ts">
2
-const handleClick = () => {
2
+function handleClick() {
3 3
   uni.navigateTo({
4 4
     url: '/pages/knowledge/exam/index',
5 5
   })
@@ -13,12 +13,16 @@ const handleClick = () => {
13 13
         <text class="icon-check">✓</text>
14 14
       </view>
15 15
     </view>
16
-    <view class="success-title">交卷成功</view>
16
+    <view class="success-title">
17
+      交卷成功
18
+    </view>
17 19
     <view class="success-message">
18 20
       请稍后在【我的考试-已考试】中查看成绩。
19 21
     </view>
20 22
     <view class="success-button">
21
-      <view class="button" @click="handleClick">查看我的考试</view>
23
+      <view class="button" @click="handleClick">
24
+        查看我的考试
25
+      </view>
22 26
     </view>
23 27
   </view>
24 28
 </template>
@@ -52,7 +56,7 @@ const handleClick = () => {
52 56
   }
53 57
 
54 58
   .success-title {
55
-     font-size: 36rpx;
59
+    font-size: 36rpx;
56 60
     font-weight: 500;
57 61
     color: #31373d;
58 62
     margin-bottom: 32rpx;

+ 32 - 22
src/pages/knowledge/index.vue

@@ -1,13 +1,14 @@
1 1
 <script setup lang="ts">
2
-import { ref, onMounted } from 'vue'
3
-import defimage from '@/assets/image.png'
2
+import { onMounted, ref } from 'vue'
4 3
 import { queryrecentviewlistAPI } from '@/api/knowledge/index'
4
+import defimage from '@/assets/image.png'
5
+
5 6
 const list = ref([])
6 7
 const isloading = ref(false)
7 8
 const pageNum = ref(1)
8 9
 const pageSize = ref(10)
9 10
 const total = ref(0)
10
-const queryrecentviewlistAPIfn = async (loadMore = false) => {
11
+async function queryrecentviewlistAPIfn(loadMore = false) {
11 12
   try {
12 13
     isloading.value = true
13 14
     const res = (await queryrecentviewlistAPI({
@@ -17,18 +18,21 @@ const queryrecentviewlistAPIfn = async (loadMore = false) => {
17 18
     if (res.code === 200) {
18 19
       if (loadMore) {
19 20
         list.value = [...list.value, ...(res?.rows || [])]
20
-      } else {
21
+      }
22
+      else {
21 23
         list.value = res?.rows
22 24
       }
23 25
       total.value = res?.total
24 26
     }
25 27
     console.log(res)
26
-  } catch (err) {
28
+  }
29
+  catch (err) {
27 30
     uni.showToast({
28 31
       title: '加载失败',
29 32
       icon: 'none',
30 33
     })
31
-  } finally {
34
+  }
35
+  finally {
32 36
     isloading.value = false
33 37
   }
34 38
 }
@@ -36,18 +40,18 @@ const queryrecentviewlistAPIfn = async (loadMore = false) => {
36 40
 onLoad(() => {
37 41
   queryrecentviewlistAPIfn()
38 42
 })
39
-const handleKnowledgeSearch = () => {
43
+function handleKnowledgeSearch() {
40 44
   uni.navigateTo({
41 45
     url: '/pages/knowledge/search/index',
42 46
   })
43 47
 }
44
-const handleMyExamination = () => {
48
+function handleMyExamination() {
45 49
   uni.navigateTo({
46 50
     url: '/pages/knowledge/exam/index',
47 51
   })
48 52
 }
49 53
 
50
-const handleLoadMore = async () => {
54
+async function handleLoadMore() {
51 55
   if (list.value.length >= total.value) {
52 56
     uni.showToast({
53 57
       title: '没有更多数据了',
@@ -59,20 +63,23 @@ const handleLoadMore = async () => {
59 63
   await queryrecentviewlistAPIfn(true)
60 64
 }
61 65
 </script>
66
+
62 67
 <template>
63 68
   <view class="knowledge">
64 69
     <view v-if="isloading" class="loading-container">
65
-      <wd-loading size="40px" color="#215ACD"></wd-loading>
70
+      <wd-loading size="40px" color="#215ACD" />
71
+    </view>
72
+    <view class="header_nav">
73
+      知识学院
66 74
     </view>
67
-    <view class="header_nav">知识学院</view>
68 75
     <view class="header_nav_item">
69 76
       <view class="header_nav_item_items" @tap="handleKnowledgeSearch">
70 77
         <span>知识搜索</span>
71
-        <view class="header_nav_item_items_icon left_items"></view>
78
+        <view class="header_nav_item_items_icon left_items" />
72 79
       </view>
73 80
       <view class="header_nav_item_items" @tap="handleMyExamination">
74 81
         <span>我的考试</span>
75
-        <view class="header_nav_item_items_icon right_items"></view>
82
+        <view class="header_nav_item_items_icon right_items" />
76 83
       </view>
77 84
     </view>
78 85
     <scroll-view
@@ -80,14 +87,16 @@ const handleLoadMore = async () => {
80 87
       :scroll-y="true"
81 88
       @scrolltolower="handleLoadMore"
82 89
     >
83
-      <view class="main_list_title">最近查看</view>
84
-      <view class="main_list_items" v-for="(item, index) in list" :key="index">
90
+      <view class="main_list_title">
91
+        最近查看
92
+      </view>
93
+      <view v-for="(item, index) in list" :key="index" class="main_list_items">
85 94
         <view class="main_list_items_icon">
86 95
           <text
87 96
             v-if="item?.icon?.includes('iconfont')"
88 97
             :class="[item.icon.split(',')[0]]"
89
-            :style="{ color: item.icon.split(',')[1], 'font-size': '48rpx' }"
90
-          ></text>
98
+            :style="{ 'color': item.icon.split(',')[1], 'font-size': '48rpx' }"
99
+          />
91 100
           <image
92 101
             v-else-if="item?.attachmentUrl.length > 0"
93 102
             style="width: 100%; height: 100%; border-radius: 20rpx"
@@ -99,7 +108,7 @@ const handleLoadMore = async () => {
99 108
             v-else
100 109
             class="iconfont icon-tushu"
101 110
             style="font-size: 48rpx; color: #1890ff"
102
-          ></text>
111
+          />
103 112
         </view>
104 113
         <view class="main_list_items_text">
105 114
           <span>{{ item.title }}</span>
@@ -107,10 +116,10 @@ const handleLoadMore = async () => {
107 116
         </view>
108 117
       </view>
109 118
       <view v-if="isloading && list.length > 0" class="load-more">
110
-        <wd-loading size="20px" color="#215ACD"></wd-loading>
111
-        <text style="margin-left: 10rpx; font-size: 24rpx; color: #86909c"
112
-          >加载中...</text
113
-        >
119
+        <wd-loading size="20px" color="#215ACD" />
120
+        <text style="margin-left: 10rpx; font-size: 24rpx; color: #86909c">
121
+          加载中...
122
+        </text>
114 123
       </view>
115 124
       <view
116 125
         v-if="!isloading && list.length > 0 && list.length >= total"
@@ -121,6 +130,7 @@ const handleLoadMore = async () => {
121 130
     </scroll-view>
122 131
   </view>
123 132
 </template>
133
+
124 134
 <style scoped lang="scss">
125 135
 html,
126 136
 body {

+ 66 - 6
src/pages/schedule/details/index.vue

@@ -5,7 +5,7 @@ import { storeToRefs } from 'pinia'
5 5
 import { onMounted, ref } from 'vue'
6 6
 import { useRoute, useRouter } from 'vue-router'
7 7
 import { useMessage, useToast } from 'wot-design-uni'
8
-import { closeTask, confirmDoneTask, getTaskDetail, transferTask } from '@/api/schedule/task'
8
+import { closeTask, confirmDoneTask, getTaskDetail, setPlanTime, transferTask } from '@/api/schedule/task'
9 9
 import { getAllUsers } from '@/api/system/users'
10 10
 
11 11
 import AttachmentList from '@/components/AttachmentList.vue'
@@ -40,6 +40,9 @@ const users = ref<any[]>([])
40 40
 const value = ref<string[]>([])
41 41
 const customShow = ref<string>('')
42 42
 
43
+// 计划时间修改相关
44
+const planTimeValue = ref('')
45
+
43 46
 // 切换更多内容显示/隐藏
44 47
 function toggleMore() {
45 48
   isExpanded.value = !isExpanded.value
@@ -243,13 +246,26 @@ async function init() {
243 246
   console.log(userInfo.value)
244 247
 
245 248
   if (taskInfo.value.status === 1) {
246
-    if (taskInfo.value.executorId === userInfo.value.user?.userId) {
249
+    const userId = Number(userInfo.value?.user?.userId)
250
+    const isExecutor = taskInfo.value.executorId === userId
251
+    const isCreator = userInfo.value?.user?.username === taskInfo.value.createBy
252
+    const isHeadquarters = userInfo.value?.user?.post?.type === '"headquarters"'
253
+    const isMobileTask = [].includes(taskInfo.value.formType)
254
+
255
+    if (isExecutor && !isMobileTask) {
247 256
       buttons.value.deal = true
248 257
     }
249
-    if (userInfo.value.user?.createBy === taskInfo.value.createBy || userInfo.value.user?.post?.type === '"headquarters"' || (taskInfo.value?.taskTemplate?.isMust === 0 && taskInfo.value.executorId === userInfo.value.user?.userId)) {
250
-      buttons.value.close = true
251 258
 
252
-      if (taskInfo.value.authStatus === 0) {
259
+    if (isCreator || isHeadquarters || isExecutor) {
260
+      if (taskInfo.value.executorId !== userId) {
261
+        buttons.value.close = true
262
+      }
263
+
264
+      if (taskInfo.value.taskTemplate?.isMust === 0) {
265
+        buttons.value.close = true
266
+      }
267
+
268
+      if (taskInfo.value.authStatus === 0 && taskInfo.value.isLicensable) {
253 269
         buttons.value.transfer = true
254 270
       }
255 271
     }
@@ -337,6 +353,38 @@ async function closeTaskEvent() {
337 353
   })
338 354
 }
339 355
 
356
+/**
357
+ * 处理计划时间变更
358
+ * @param value 选中的时间值
359
+ */
360
+async function handlePlanTimeChange({ value }: any) {
361
+  console.log('选择的计划时间:', value, new Date(taskInfo.value.startTime).getTime(), new Date(taskInfo.value.endTime).getTime())
362
+
363
+  // 判断时间比如大于任务开始时间,小于任务结束时间
364
+  if (value < new Date(taskInfo.value.startTime).getTime() || value > new Date(taskInfo.value.endTime).getTime()) {
365
+    toast.error('计划时间必须在任务开始时间和结束时间之间')
366
+    return
367
+  }
368
+
369
+  // 这里可以添加更新计划时间的API调用
370
+  try {
371
+    const res: any = await setPlanTime(taskInfo.value.id || 0, {
372
+      planTime: dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
373
+    })
374
+    if (res.code !== 200) {
375
+      toast.error(res.msg || '操作失败')
376
+      return
377
+    }
378
+    toast.success('计划时间修改成功')
379
+    // 重新初始化任务信息
380
+    init()
381
+  }
382
+  catch {
383
+    toast.error('操作失败')
384
+    return
385
+  }
386
+}
387
+
340 388
 onShow(async () => {
341 389
   await init()
342 390
 })
@@ -420,8 +468,20 @@ onMounted(async () => {
420 468
           <view class="guide-label">
421 469
             计划时间
422 470
           </view>
423
-          <view class="guide-value">
471
+          <view v-if="buttons.deal" class="guide-value flex items-center justify-between">
424 472
             {{ taskInfo.displayDate || '-' }}
473
+            <wd-datetime-picker
474
+              v-model="planTimeValue"
475
+              type="datetime"
476
+              title="选择计划时间"
477
+              :min-date="new Date().getTime()"
478
+              :max-date="new Date(taskInfo.endTime).getTime()"
479
+              @confirm="handlePlanTimeChange"
480
+            >
481
+              <wd-button type="text">
482
+                修改
483
+              </wd-button>
484
+            </wd-datetime-picker>
425 485
           </view>
426 486
         </view>
427 487
         <view class="guide-item">

+ 39 - 1
src/store/auth.ts

@@ -2,7 +2,7 @@ import type { ILoginForm } from '@/api/login'
2 2
 import type { IAuthLoginRes } from '@/api/types/login'
3 3
 import { defineStore } from 'pinia'
4 4
 import { ref } from 'vue'
5
-import { login as _login } from '@/api/login'
5
+import { authExamLogin as _authExamLogin, login as _login } from '@/api/login'
6 6
 import { useTokenStore } from './token'
7 7
 import { useUserStore } from './user'
8 8
 
@@ -61,6 +61,43 @@ export const useAuthStore = defineStore(
61 61
     }
62 62
 
63 63
     /**
64
+     * 用户登录
65
+     * @param loginForm 登录参数
66
+     * @returns 登录结果
67
+     */
68
+    const authExamLogin = async (loginForm: ILoginForm) => {
69
+      try {
70
+        loginLoading.value = true
71
+        const res = await _authExamLogin(loginForm)
72
+        // 根据不同的token类型输出相应信息
73
+        if ('token' in res) {
74
+          console.log('登录-res: ', res.token)
75
+        }
76
+        else if ('accessToken' in res) {
77
+          console.log('登录-res: ', res.accessToken)
78
+        }
79
+        // 直接传递整个res对象,它已经是IAuthLoginRes类型
80
+        await _postLogin(res)
81
+        uni.showToast({
82
+          title: '登录成功',
83
+          icon: 'success',
84
+        })
85
+        return res
86
+      }
87
+      catch (error) {
88
+        console.error('登录失败:', error)
89
+        uni.showToast({
90
+          title: '登录失败,请重试',
91
+          icon: 'error',
92
+        })
93
+        throw error
94
+      }
95
+      finally {
96
+        loginLoading.value = false
97
+      }
98
+    }
99
+
100
+    /**
64 101
      * 钉钉免登录
65 102
      * @param code 可选,钉钉授权码,如果不传则自动获取
66 103
      * @returns 登录结果
@@ -91,6 +128,7 @@ export const useAuthStore = defineStore(
91 128
     return {
92 129
       loginLoading,
93 130
       authLogin,
131
+      authExamLogin,
94 132
       authDingTalkLogin,
95 133
     }
96 134
   },

+ 27 - 0
src/store/token.ts

@@ -5,6 +5,7 @@ import type { IAuthLoginRes } from '@/api/types/login'
5 5
 import { defineStore } from 'pinia'
6 6
 import { computed, ref } from 'vue' // 修复:导入 computed
7 7
 import {
8
+  authExamLogin as _authExamLogin,
8 9
   dingTalkLogin as _dingTalkLogin,
9 10
   login as _login,
10 11
   logout as _logout,
@@ -154,6 +155,31 @@ export const useTokenStore = defineStore(
154 155
     }
155 156
 
156 157
     /**
158
+     * 考试登录
159
+     * @param loginForm 登录参数
160
+     * @returns 登录结果
161
+     */
162
+    const authExamLogin = async (loginForm: ILoginForm) => {
163
+      try {
164
+        const res = await _authExamLogin(loginForm)
165
+        await _postLogin(res)
166
+        uni.showToast({
167
+          title: '登录成功',
168
+          icon: 'success',
169
+        })
170
+        return res
171
+      }
172
+      catch (error) {
173
+        console.error('登录失败:', error)
174
+        uni.showToast({
175
+          title: '登录失败,请重试',
176
+          icon: 'error',
177
+        })
178
+        throw error
179
+      }
180
+    }
181
+
182
+    /**
157 183
      * 微信登录
158 184
      * 有的时候后端会用一个接口返回token和用户信息,有的时候会分开2个接口,一个获取token,一个获取用户信息
159 185
      * (各有利弊,看业务场景和系统复杂度),这里使用2个接口返回的来模拟
@@ -339,6 +365,7 @@ export const useTokenStore = defineStore(
339 365
       wxLogin,
340 366
       dingTalkLogin,
341 367
       logout,
368
+      authExamLogin,
342 369
 
343 370
       // 认证状态判断(最常用的)
344 371
       hasLogin: hasValidLogin,