瀏覽代碼

feat: 登录也

zhangjie 2 周之前
父節點
當前提交
03a4d7e84c

+ 1 - 1
.eslintrc.js

@@ -50,7 +50,7 @@ module.exports = {
     'vue/multi-word-component-names': 0,
     // Allow @ts-ignore comment
     '@typescript-eslint/ban-ts-comment': 0,
-    '@typescript-eslint/no-unused-vars': 0,
+    // '@typescript-eslint/no-unused-vars': 0,
     '@typescript-eslint/no-empty-function': 0,
     '@typescript-eslint/no-explicit-any': 0,
     'import/extensions': [

+ 25 - 24
components.d.ts

@@ -5,32 +5,33 @@
 // Read more: https://github.com/vuejs/core/pull/3399
 import '@vue/runtime-core'
 
-export {}
+export {};
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
-    ElButton: typeof import('element-plus/es')['ElButton']
-    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
-    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
-    ElDialog: typeof import('element-plus/es')['ElDialog']
-    ElForm: typeof import('element-plus/es')['ElForm']
-    ElFormItem: typeof import('element-plus/es')['ElFormItem']
-    ElIcon: typeof import('element-plus/es')['ElIcon']
-    ElInput: typeof import('element-plus/es')['ElInput']
-    ElOption: typeof import('element-plus/es')['ElOption']
-    ElResult: typeof import('element-plus/es')['ElResult']
-    ElSelect: typeof import('element-plus/es')['ElSelect']
-    ElUpload: typeof import('element-plus/es')['ElUpload']
-    FileUpload: typeof import('./src/components/file-upload/index.vue')['default']
-    Footer: typeof import('./src/components/footer/index.vue')['default']
-    ImportDialog: typeof import('./src/components/import-dialog/index.vue')['default']
-    RouterLink: typeof import('vue-router')['RouterLink']
-    RouterView: typeof import('vue-router')['RouterView']
-    SelectRangeDatetime: typeof import('./src/components/select-range-datetime/index.vue')['default']
-    SelectRangeTime: typeof import('./src/components/select-range-time/index.vue')['default']
-    SelectTask: typeof import('./src/components/select-task/index.vue')['default']
-    SelectTeaching: typeof import('./src/components/select-teaching/index.vue')['default']
-    SvgIcon: typeof import('./src/components/svg-icon/index.vue')['default']
-    UploadButton: typeof import('./src/components/upload-button/index.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton'];
+    ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'];
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker'];
+    ElDialog: typeof import('element-plus/es')['ElDialog'];
+    ElForm: typeof import('element-plus/es')['ElForm'];
+    ElFormItem: typeof import('element-plus/es')['ElFormItem'];
+    ElIcon: typeof import('element-plus/es')['ElIcon'];
+    ElInput: typeof import('element-plus/es')['ElInput'];
+    ElOption: typeof import('element-plus/es')['ElOption'];
+    ElResult: typeof import('element-plus/es')['ElResult'];
+    ElSelect: typeof import('element-plus/es')['ElSelect'];
+    ElUpload: typeof import('element-plus/es')['ElUpload'];
+    FileUpload: typeof import('./src/components/file-upload/index.vue')['default'];
+    Footer: typeof import('./src/components/footer/index.vue')['default'];
+    ImportDialog: typeof import('./src/components/import-dialog/index.vue')['default'];
+    RouterLink: typeof import('vue-router')['RouterLink'];
+    RouterView: typeof import('vue-router')['RouterView'];
+    SelectExam: typeof import('./src/components/select-exam/index.vue')['default'];
+    SelectRangeDatetime: typeof import('./src/components/select-range-datetime/index.vue')['default'];
+    SelectRangeTime: typeof import('./src/components/select-range-time/index.vue')['default'];
+    SelectTask: typeof import('./src/components/select-task/index.vue')['default'];
+    SelectTeaching: typeof import('./src/components/select-teaching/index.vue')['default'];
+    SvgIcon: typeof import('./src/components/svg-icon/index.vue')['default'];
+    UploadButton: typeof import('./src/components/upload-button/index.vue')['default'];
   }
 }

+ 6 - 102
src/api/base.ts

@@ -1,104 +1,8 @@
-import axios, { AxiosResponse } from 'axios';
-import type {
-  TeachingListPageParam,
-  TeachingListPageRes,
-  TeachingUpdateParams,
-  AgentListPageParam,
-  AgentListPageRes,
-  AgentUpdateParams,
-  AgentGuideUpdateParams,
-  RoomListPageParam,
-  RoomListPageRes,
-  RoomUpdateParams,
-} from './types/base';
-import { AbleParams } from './types/common';
+import axios from 'axios';
+import type { OptionItem } from './types/base';
 
-// 教学点管理
-// 教学点管理-查询
-export function teachingListPage(
-  params: TeachingListPageParam
-): Promise<TeachingListPageRes> {
-  return axios.post('/api/admin/teaching/page', params);
-}
-// 教学点管理-新增编辑
-export function updateTeaching(
-  datas: TeachingUpdateParams
-): Promise<{ id: number }> {
-  return axios.post('/api/admin/teaching/save', datas);
-}
-// 教学点管理-启用禁用
-export function ableTeaching(params: AbleParams): Promise<boolean> {
-  return axios.post('/api/admin/teaching/enable', {}, { params });
-}
-// 教学点管理-导入模板下载
-export function teachingTemplate(): Promise<AxiosResponse<Blob>> {
-  return axios.post(
-    '/api/admin/teaching/import/template',
-    {},
-    {
-      responseType: 'blob',
-    }
-  );
-}
-
-// 考点管理
-// 考点管理-查询
-export function agentListPage(
-  params: AgentListPageParam
-): Promise<AgentListPageRes> {
-  return axios.post('/api/admin/site/page', params);
-}
-// 考点管理-新增编辑
-export function updateAgent(datas: AgentUpdateParams): Promise<{ id: number }> {
-  return axios.post('/api/admin/site/save', datas);
-}
-// 考点管理-考点指引
-export function agentGuideDetail(id: number): Promise<string> {
-  return axios.post('/api/admin/site/guide', {}, { params: { id } });
-}
-// 考点管理-编辑指引
-export function updateAgentGuide(
-  datas: AgentGuideUpdateParams
-): Promise<{ id: number }> {
-  return axios.post('/api/admin/site/guide/save', datas);
-}
-// 考点管理-启用禁用
-export function ableAgent(params: AbleParams): Promise<boolean> {
-  return axios.post('/api/admin/site/enable', {}, { params });
-}
-// 考点管理-导入模板下载
-export function agentTemplate(): Promise<AxiosResponse<Blob>> {
-  return axios.post(
-    '/api/admin/site/import/template',
-    {},
-    {
-      responseType: 'blob',
-    }
-  );
-}
-
-// 考场管理
-// 考场管理-查询
-export function roomListPage(
-  params: RoomListPageParam
-): Promise<RoomListPageRes> {
-  return axios.post('/api/admin/room/page', params);
-}
-// 考场管理-新增编辑
-export function updateRoom(datas: RoomUpdateParams): Promise<{ id: number }> {
-  return axios.post('/api/admin/room/save', datas);
-}
-// 考场管理-启用禁用
-export function ableRoom(params: AbleParams): Promise<boolean> {
-  return axios.post('/api/admin/room/enable', {}, { params });
-}
-// 考场管理-导入模板下载
-export function roomTemplate(): Promise<AxiosResponse<Blob>> {
-  return axios.post(
-    '/api/admin/room/import/template',
-    {},
-    {
-      responseType: 'blob',
-    }
-  );
+// 通用查询
+// 通用查询-考试
+export function examQuery(): Promise<OptionItem[]> {
+  return axios.post('/api/admin/apply/exam/list', {});
 }

+ 2 - 1
src/api/interceptor.ts

@@ -4,6 +4,7 @@ import type {
   AxiosRequestConfig,
   AxiosResponse,
 } from 'axios';
+import { Loading } from '@element-plus/icons-vue';
 import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
 import type { MessageHandler } from 'element-plus';
 import { initSyncTime, fetchTime } from '../utils/syncServerTime';
@@ -53,7 +54,7 @@ axios.interceptors.request.use(
     if (!queue.length) {
       load = ElMessage({
         customClass: 'el-message-loading',
-        icon: 'el-message__icon el-icon-loading',
+        icon: Loading,
         message: '加载中...',
         duration: 0,
       });

+ 4 - 0
src/api/types/base.ts

@@ -1,5 +1,9 @@
 import { PageResult, PageParams } from './common';
 
+export interface OptionItem {
+  id: number;
+  name: string;
+}
 export interface TeachingListFilter {
   name: string;
   code: string;

二進制
src/assets/images/login-theme.jpg


二進制
src/assets/images/login-theme.png


+ 0 - 221
src/assets/style/base.css

@@ -1,221 +0,0 @@
-.box-justify {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-/* part */
-.part-box {
-  margin-bottom: 16px;
-  background-color: #fff;
-  border-radius: var(--border-radius);
-  padding: 16px;
-}
-.part-box.is-border {
-  border: 1px solid var(--color-border);
-}
-.part-box.is-border-bold {
-  border: 1px solid var(--color-border-bold);
-}
-.part-box.is-nopad {
-  padding: 0;
-}
-.part-box.is-filter {
-  padding-bottom: 4px;
-}
-.part-box-head {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  min-height: 30px;
-  margin: -10px 0 10px -10px;
-  color: var(--app-main-text-color);
-}
-.part-box-head > h3 {
-  font-size: 18px;
-}
-.filter-line .arco-input-wrapper,
-.filter-line .arco-select {
-  width: 200px;
-  padding-left: 8px;
-}
-.filter-line .arco-select-view-prefix,
-.filter-line .arco-input-prefix {
-  padding-right: 16px;
-  position: relative;
-  color: var(--color-text-dark-1);
-}
-.filter-line .arco-select-view-prefix::after,
-.filter-line .arco-input-prefix::after {
-  content: '';
-  display: block;
-  position: absolute;
-  width: 1px;
-  height: 14px;
-  background: var(--color-border);
-  right: 8px;
-  top: 50%;
-  margin-top: -7px;
-}
-.part-action {
-  margin-bottom: 16px;
-}
-.part-action .arco-btn-text {
-  padding: 5px 8px;
-  font-weight: 400;
-}
-.part-action .arco-space-item:not(:first-child) .arco-btn-text {
-  color: var(--color-text-dark);
-}
-.part-action .arco-divider {
-  margin: 0;
-  border-color: var(--color-border);
-}
-.page-table .arco-table-tr .arco-table-th {
-  color: var(--color-text-gray);
-  font-weight: 400;
-  background-color: #f7f7f7;
-  border-bottom: 1px solid var(--color-border);
-}
-.page-table .arco-table-tr .arco-table-td {
-  border-color: var(--color-border);
-  color: var(--color-text-dark);
-}
-.page-table .arco-table-cell {
-  padding: 12px;
-}
-.action-more .ant-btn-block {
-  display: block;
-  padding: 5px 8px;
-}
-.action-more .ant-popover-inner {
-  border-radius: 6px;
-  padding: 6px;
-  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.12);
-}
-.empty-none {
-  text-align: center;
-  padding: 10px 0;
-  height: 95px;
-  background-image: url(../images/bg-empty.png);
-  background-size: 98px 74px;
-  background-position: center;
-  background-repeat: no-repeat;
-}
-/* table */
-.table {
-  width: 100%;
-  border-spacing: 0;
-  border-collapse: collapse;
-  text-align: left;
-}
-.table.table-white {
-  background-color: #fff;
-}
-.table th {
-  padding: 12px;
-  line-height: 1.2;
-  letter-spacing: 1px;
-  color: var(--color-text-gray);
-  border: 1px solid var(--color-border);
-}
-.table td {
-  padding: 14px;
-  line-height: 1.2;
-  color: var(--color-text-dark);
-  border: 1px solid var(--color-border);
-}
-.table td.td-link span {
-  cursor: pointer;
-}
-.table td.td-link span:hover {
-  color: var(--color-text-gray);
-}
-.table .td-th {
-  font-weight: 600;
-  color: var(--color-text-gray);
-}
-.table--border {
-  border: 1px solid var(--color-border);
-  border-radius: 10px;
-}
-.table--border th {
-  background-color: #fcfcfd;
-  border: none;
-  border-bottom: 1px solid var(--color-border);
-}
-.table--border td {
-  border: none;
-  border-bottom: 1px solid var(--color-border);
-}
-/* tab-btns */
-.tab-btns {
-  margin-bottom: 15px;
-}
-.tab-btns .arco-btn {
-  border-top-right-radius: 8px;
-  border-top-left-radius: 8px;
-  border-bottom-right-radius: 0;
-  border-bottom-left-radius: 0;
-}
-.tab-btns .arco-btn:first-child {
-  border-bottom-left-radius: 8px;
-}
-.tab-btns .arco-btn:last-child {
-  border-bottom-right-radius: 8px;
-}
-/* btn */
-.btn-danger.arco-btn-text {
-  padding: 0 !important;
-  background-color: transparent !important;
-}
-.btn-danger.arco-btn-text:not(.arco-btn-disabled) {
-  color: var(--color-danger) !important;
-}
-.btn-danger.arco-btn-text:not(.arco-btn-disabled):hover {
-  font-weight: 600;
-}
-.btn-danger.arco-btn-disabled {
-  color: var(--color-text-gray-2);
-}
-.btn-primary.arco-btn-text {
-  padding: 0 !important;
-  background-color: transparent !important;
-}
-.btn-primary.arco-btn-text:not(.arco-btn-disabled):hover {
-  font-weight: 600;
-  color: var(--color-primary) !important;
-}
-.tips-info {
-  font-size: 14px;
-  line-height: 20px;
-  color: var(--color-text-gray);
-}
-.tips-info > i {
-  margin-right: 2px;
-}
-.tips-dark {
-  color: var(--color-text-gray);
-}
-.tips-success {
-  color: var(--color-success);
-}
-.tips-error {
-  color: var(--color-danger);
-}
-.tips-icon {
-  display: inline-block;
-  vertical-align: middle;
-  color: var(--color-text-gray-1);
-  font-size: 18px;
-  margin: 0 10px;
-  cursor: pointer;
-}
-.align-right {
-  text-align: right;
-}
-.mr-10 {
-  margin-right: 10px;
-}
-.ml-10 {
-  margin-left: 10px;
-}

+ 12 - 4
src/assets/style/pages.less

@@ -45,7 +45,7 @@
   width: 400px;
   height: 100%;
   border-radius: 30px 0 0 30px;
-  background-image: url(assets/images/login-theme.jpg);
+  background-image: url(assets/images/login-theme.png);
   background-size: 100% 100%;
   float: left;
   position: relative;
@@ -53,7 +53,7 @@
   > h2 {
     position: absolute;
     top: 192px;
-    left: 48px;
+    left: 35px;
     font-weight: bold;
     font-size: 42px;
     color: #ffffff;
@@ -64,7 +64,7 @@
   .login-webinfo {
     position: absolute;
     bottom: 48px;
-    left: 48px;
+    left: 35px;
 
     font-weight: 400;
     font-size: 14px;
@@ -80,7 +80,15 @@
   margin-left: 400px;
   height: 100%;
   overflow: hidden;
-  padding: 126px 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  padding: 20px 40px;
+
+  > div {
+    width: 100%;
+  }
 }
 .login-title {
   margin-bottom: 24px;

+ 6 - 1
src/components/file-upload/index.vue

@@ -15,6 +15,7 @@
       :auto-upload="autoUpload"
       :http-request="customRequest"
       :disabled="disabled"
+      :accept="accept"
       :before-upload="handleBeforeUpload"
       :on-change="handleFileChange"
       :on-error="handleError"
@@ -37,7 +38,7 @@
 </template>
 
 <script setup lang="ts">
-  import { ref } from 'vue';
+  import { ref, computed } from 'vue';
   import { fileMD5 } from '@/utils/md5';
   import { ElMessage } from 'element-plus';
   import type {
@@ -91,6 +92,10 @@
   const result = ref({ success: true, message: '' });
   const loading = ref(false);
 
+  const accept = computed(() => {
+    return props.format.map((item) => `.${item}`).join(',');
+  });
+
   function startUpload() {
     loading.value = true;
     uploadRef.value?.submit();

+ 2 - 0
src/components/index.ts

@@ -5,6 +5,7 @@ import SvgIcon from './svg-icon/index.vue';
 import SelectRangeDatetime from './select-range-datetime/index.vue';
 import SelectRangeTime from './select-range-time/index.vue';
 import UploadButton from './upload-button/index.vue';
+import SelectExam from './select-exam/index.vue';
 
 export default {
   install(Vue: App) {
@@ -12,5 +13,6 @@ export default {
     Vue.component('SelectRangeDatetime', SelectRangeDatetime);
     Vue.component('SelectRangeTime', SelectRangeTime);
     Vue.component('UploadButton', UploadButton);
+    Vue.component('SelectExam', SelectExam);
   },
 };

+ 22 - 18
src/components/select-task/index.vue → src/components/select-exam/index.vue

@@ -8,9 +8,6 @@
     v-bind="attrs"
     @change="onChange"
   >
-    <template v-if="prefix" #prefix>
-      <span style="padding-left: 12px">任务</span>
-    </template>
     <el-option
       v-for="item in optionList"
       :key="item.value"
@@ -22,21 +19,28 @@
 
 <script setup lang="ts">
   import { ref, useAttrs, watch } from 'vue';
-  // import { ElSelect, ElOption } from 'element-plus';
+  import { examQuery } from '@/api/base';
 
   defineOptions({
-    name: 'SelectTask',
+    name: 'SelectExam',
   });
   type ValueType = number | Array<number> | null;
 
-  const props = defineProps<{
-    modelValue: ValueType;
-    clearable?: boolean;
-    disabled?: boolean;
-    placeholder?: string;
-    multiple?: boolean;
-    prefix?: boolean;
-  }>();
+  const props = withDefaults(
+    defineProps<{
+      modelValue: ValueType;
+      clearable?: boolean;
+      disabled?: boolean;
+      placeholder?: string;
+      multiple?: boolean;
+    }>(),
+    {
+      clearable: true,
+      disabled: false,
+      placeholder: '请选择考试',
+      multiple: false,
+    }
+  );
   const emit = defineEmits(['update:modelValue', 'change']);
   const attrs = useAttrs();
 
@@ -47,11 +51,11 @@
 
   const selected = ref<number | Array<number> | undefined>();
   const optionList = ref<OptionListItem[]>([]);
-  const search = () => {
-    // TODO: get task list
-    // optionList.value = [].map((item) => {
-    //   return { ...item, value: item.id, label: item.name };
-    // });
+  const search = async () => {
+    const res = await examQuery();
+    optionList.value = res.map((item) => {
+      return { ...item, value: item.id, label: item.name };
+    });
   };
   search(); // Initial load
 

+ 4 - 4
src/router/guard/index.ts

@@ -1,7 +1,7 @@
 import type { Router } from 'vue-router';
 import { setRouteEmitter } from '@/utils/route-listener';
-import setupUserLoginInfoGuard from './userLoginInfo';
-import setupPermissionGuard from './permission';
+// import setupUserLoginInfoGuard from './userLoginInfo';
+// import setupPermissionGuard from './permission';
 
 function setupPageGuard(router: Router) {
   router.beforeEach(async (to) => {
@@ -12,6 +12,6 @@ function setupPageGuard(router: Router) {
 
 export default function createRouteGuard(router: Router) {
   setupPageGuard(router);
-  setupUserLoginInfoGuard(router);
-  setupPermissionGuard(router);
+  // setupUserLoginInfoGuard(router);
+  // setupPermissionGuard(router);
 }

+ 18 - 0
src/router/routes/modules/login.ts

@@ -13,6 +13,24 @@ const routes: AppRouteRecordRaw = {
         requiresAuth: false,
       },
     },
+    {
+      path: '/switch-exam',
+      name: 'SwitchExam',
+      component: () => import('@/views/login/login/SwitchExam.vue'),
+      meta: {
+        title: '切换考试',
+        requiresAuth: true,
+      },
+    },
+    {
+      path: '/reset-info',
+      name: 'ResetInfo',
+      component: () => import('@/views/login/login/ResetInfo.vue'),
+      meta: {
+        title: '修改个人信息',
+        requiresAuth: true,
+      },
+    },
   ],
 };
 

+ 1 - 0
src/store/modules/app/index.ts

@@ -48,6 +48,7 @@ const useAppStore = defineStore('app', {
     validRoutes: [],
     breadcrumbs: [],
     device: 'desktop',
+    examId: 0,
   }),
 
   getters: {

+ 1 - 0
src/store/modules/app/types.ts

@@ -25,4 +25,5 @@ export interface AppState {
   validRoutes: string[];
   breadcrumbs: string[];
   device: string;
+  examId: number;
 }

+ 33 - 2
src/views/login/home.vue

@@ -1,7 +1,38 @@
 <template>
   <div class="login-home">
-    <router-view></router-view>
+    <div class="login login-box">
+      <div class="login-theme">
+        <h2>
+          欢迎登录 <br />
+          高校考试管理平台
+        </h2>
+
+        <p class="login-webinfo">
+          <a href="http://www.qmth.com.cn" target="_blank"
+            >Copyright © 2024 启明泰和</a
+          >
+          <br />
+          <a href="https://beian.miit.gov.cn/" target="_blank">
+            鄂ICP备12000033号-3</a
+          >
+          <span v-if="appStore.version"> v{{ appStore.version }}</span>
+        </p>
+      </div>
+      <div class="login-body">
+        <div>
+          <router-view></router-view>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup lang="ts" name="LoginHome"></script>
+<script setup lang="ts">
+  import { useAppStore } from '@/store';
+
+  defineOptions({
+    name: 'LoginHome',
+  });
+
+  const appStore = useAppStore();
+</script>

+ 145 - 0
src/views/login/login/ResetInfo.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="login-title">
+    <h1>首次登录,请完善资料</h1>
+  </div>
+  <div class="login-form">
+    <el-form
+      ref="formRef"
+      size="large"
+      :model="formData"
+      :rules="rules"
+      label-width="0px"
+    >
+      <el-form-item label="" prop="name">
+        <el-input
+          v-model.trim="formData.name"
+          placeholder="请输入姓名"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="" prop="employeeId">
+        <el-input
+          v-model.trim="formData.employeeId"
+          placeholder="请输入工号"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="" prop="newPassword">
+        <el-input
+          v-model.trim="formData.newPassword"
+          type="password"
+          placeholder="请输入新密码"
+          show-password
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="" prop="confirmPassword">
+        <el-input
+          v-model.trim="formData.confirmPassword"
+          type="password"
+          placeholder="请再次输入新密码"
+          show-password
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" :loading="loading" @click="submit"
+          >确认</el-button
+        >
+        <el-button @click="goBack">回退</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive } from 'vue';
+  import { useRouter } from 'vue-router';
+  import type { FormInstance, FormRules } from 'element-plus';
+  import useLoading from '@/hooks/loading';
+  // import { updateUserProfile } from '@/api/user'; // 假设有更新用户信息的API
+  import { ElMessage } from 'element-plus';
+
+  const router = useRouter();
+  const formRef = ref<FormInstance>();
+  const { loading, setLoading } = useLoading();
+
+  const formData = reactive({
+    name: '',
+    employeeId: '',
+    newPassword: '',
+    confirmPassword: '',
+  });
+
+  const validateConfirmPassword = (rule: any, value: any, callback: any) => {
+    if (value === '') {
+      callback(new Error('请再次输入密码'));
+    } else if (value !== formData.newPassword) {
+      callback(new Error('两次输入的密码不一致'));
+    } else {
+      callback();
+    }
+  };
+
+  const rules: FormRules = {
+    name: [
+      {
+        required: true,
+        message: '请输入姓名',
+        trigger: 'blur',
+      },
+    ],
+    employeeId: [
+      {
+        required: true,
+        message: '请输入工号',
+        trigger: 'blur',
+      },
+    ],
+    newPassword: [
+      {
+        required: true,
+        message: '请输入新密码',
+        trigger: 'blur',
+      },
+      {
+        min: 6,
+        message: '密码长度不能少于6位',
+        trigger: 'blur',
+      },
+    ],
+    confirmPassword: [
+      {
+        required: true,
+        validator: validateConfirmPassword,
+        trigger: 'blur',
+      },
+    ],
+  };
+
+  async function submit() {
+    const err = await formRef.value?.validate();
+    if (err) {
+      setLoading(true);
+      try {
+        // const res = await updateUserProfile(formData); // 调用API更新用户信息
+        // if (res) {
+        ElMessage.success('资料完善成功,请重新登录');
+        // 清空store信息,跳转到登录页
+        // userStore.resetInfo();
+        // appStore.resetInfo();
+        router.push({ name: 'Login' }); // 假设登录页路由名称为 Login
+        // }
+      } catch (e) {
+        // console.error(e);
+      } finally {
+        setLoading(false);
+      }
+    }
+  }
+
+  function goBack() {
+    router.push({ name: 'Login' }); // 假设登录页路由名称为 Login
+  }
+</script>

+ 16 - 16
src/views/login/login/ResetPwd.vue

@@ -5,11 +5,11 @@
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     :show-close="false"
-    @open="modalBeforeOpen"
     width="500px"
+    @open="modalBeforeOpen"
   >
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
-      <!-- <el-form-item prop="oldPassword" label="旧密码">
+      <el-form-item prop="oldPassword" label="旧密码">
         <el-input
           v-model.trim="formData.oldPassword"
           type="password"
@@ -18,7 +18,7 @@
           clearable
         >
         </el-input>
-      </el-form-item> -->
+      </el-form-item>
       <el-form-item prop="password" label="新密码">
         <el-input
           v-model.trim="formData.password"
@@ -71,7 +71,7 @@
   const emit = defineEmits(['modified', 'cancel']);
 
   const defaultFormData = {
-    // oldPassword: '',
+    oldPassword: '',
     password: '',
     rePassword: '',
   };
@@ -81,18 +81,18 @@
   const formData = reactive<FormDataType>({ ...defaultFormData });
   const passwordRule: FormRules['password'] = [...password];
   const rules: FormRules = {
-    // oldPassword: password,
+    oldPassword: password,
     password: [
       ...passwordRule,
-      // {
-      //   validator: (value, callback) => {
-      //     if (value === formData.oldPassword) {
-      //       callback('新旧密码不可以相同');
-      //     } else {
-      //       callback();
-      //     }
-      //   },
-      // },
+      {
+        validator: (value, callback) => {
+          if (value === formData.oldPassword) {
+            callback('新旧密码不可以相同');
+          } else {
+            callback();
+          }
+        },
+      },
     ],
     rePassword: [
       ...passwordRule,
@@ -111,12 +111,12 @@
   /* confirm */
   const { loading, setLoading } = useLoading();
   async function confirm() {
-    const err = await formRef.value?.validate();
+    const err = await formRef.value?.validate().catch(() => {});
     if (err) return;
 
     setLoading(true);
     const datas = {
-      // oldPassword: formData.oldPassword,
+      oldPassword: formData.oldPassword,
       password: formData.password,
     };
     let res = true;

+ 59 - 0
src/views/login/login/SwitchExam.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="login-title">
+    <h1>选择考试</h1>
+    <p></p>
+  </div>
+  <div class="login-form">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="rules"
+      label-position="top"
+    >
+      <el-form-item label="" prop="exam">
+        <select-exam v-model="formData.examId" size="large" />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" @click="handleSubmit">确认</el-button>
+        <el-button @click="handleGoBack">回退</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive } from 'vue';
+  import { useRouter } from 'vue-router';
+  import type { FormInstance, FormRules } from 'element-plus';
+
+  const router = useRouter();
+  const formRef = ref<FormInstance>();
+  const formData = reactive({
+    examId: null,
+  });
+
+  const rules: FormRules = {
+    exam: [
+      {
+        required: true,
+        message: '请选择考试',
+      },
+    ],
+  };
+
+  // 确认按钮点击事件
+  const handleSubmit = async () => {
+    const valid = await formRef.value?.validate().catch(() => {});
+    if (!valid) return;
+    console.log('选择的考试ID:', formData.examId);
+    // 提交成功后的操作,例如跳转到其他页面
+  };
+
+  // 回退按钮点击事件
+  const handleGoBack = () => {
+    // 假设登录页面的路由名称是 'Login' 或路径是 '/login'
+    // 你可能需要根据项目的实际路由配置进行调整
+    router.push({ name: 'Login' }); // 或者 router.back(); 如果是从登录页跳转过来的
+  };
+</script>

+ 51 - 82
src/views/login/login/index.vue

@@ -1,74 +1,54 @@
 <template>
-  <div class="login login-box">
-    <div class="login-theme">
-      <h2>
-        欢迎登录 <br />
-        预约报名系统
-      </h2>
-
-      <p class="login-webinfo">
-        <a href="http://www.qmth.com.cn" target="_blank"
-          >Copyright © 2024 启明泰和</a
+  <div class="login-title">
+    <h1>输入信息</h1>
+    <p>请输入账号与密码</p>
+  </div>
+  <div class="login-form" @keyup.enter="submit">
+    <el-form
+      ref="formRef"
+      size="large"
+      :model="formData"
+      :rules="rules"
+      label-position="top"
+    >
+      <el-form-item prop="account">
+        <el-input
+          v-model.trim="formData.account"
+          placeholder="请输入账号"
+          clearable
         >
-        <!-- <br /> -->
-        <!-- <a href="https://beian.miit.gov.cn/" target="_blank">
-          启明泰和 鄂ICP备12000033号-9</a
-        > -->
-        <span v-if="appStore.version"> v{{ appStore.version }}</span>
-      </p>
-    </div>
-    <div class="login-body" @keyup.enter="submit">
-      <div class="login-title">
-        <h1>输入信息</h1>
-        <p>请输入账号与密码</p>
-      </div>
-      <div class="login-form">
-        <el-form
-          ref="formRef"
-          size="large"
-          :model="formData"
-          :rules="rules"
-          label-position="top"
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input
+          v-model.trim="formData.password"
+          type="password"
+          placeholder="请输入密码"
+          show-password
+          clearable
         >
-          <el-form-item prop="account">
-            <el-input
-              v-model.trim="formData.account"
-              placeholder="请输入账号"
-              clearable
-            >
-            </el-input>
-          </el-form-item>
-          <el-form-item prop="password">
-            <el-input
-              v-model.trim="formData.password"
-              type="password"
-              placeholder="请输入密码"
-              show-password
-              clearable
-            >
-            </el-input>
-          </el-form-item>
+        </el-input>
+      </el-form-item>
 
-          <el-form-item>
-            <el-button
-              type="primary"
-              :loading="loading"
-              style="width: 100%"
-              @click="submit"
-              >登录</el-button
-            >
-          </el-form-item>
-        </el-form>
-      </div>
-    </div>
+      <el-form-item>
+        <el-button
+          type="primary"
+          :loading="loading"
+          style="width: 100%"
+          @click="submit"
+          >登录</el-button
+        >
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="router.push({ name: 'SwitchExam' })"
+          >选择考试</el-button
+        >
+        <el-button type="primary" @click="router.push({ name: 'ResetInfo' })"
+          >个人信息</el-button
+        >
+      </el-form-item>
+    </el-form>
   </div>
-
-  <!-- ResetPwd -->
-  <ResetPwd
-    ref="resetPwdRef"
-    @modified="resetPwdSuccess"
-    @cancel="resetCancel"
-  />
 </template>
 
 <script lang="ts" setup>
@@ -80,15 +60,12 @@
   import { useAppStore, useUserStore } from '@/store';
   import { DEFAULT_ROUTE_NAME } from '@/router/constants';
 
-  import ResetPwd from './ResetPwd.vue';
-
   const router = useRouter();
   const appStore = useAppStore();
   const userStore = useUserStore();
   userStore.resetInfo();
   appStore.resetInfo();
 
-  const resetPwdRef = ref();
   const formRef = ref<FormInstance>();
   const formData = reactive({
     account: '',
@@ -109,20 +86,10 @@
     ],
   };
 
-  function resetPwdSuccess() {
-    router.push({
-      name: DEFAULT_ROUTE_NAME,
-    });
-  }
-
-  function resetCancel() {
-    userStore.resetInfo();
-  }
-
   /* submit */
   const { loading, setLoading } = useLoading();
   async function submit() {
-    const err = await formRef.value?.validate();
+    const err = await formRef.value?.validate().catch(() => {});
     if (err) return;
 
     setLoading(true);
@@ -132,9 +99,11 @@
 
     userStore.setInfo(data);
 
-    // 首次登录强制修改密码
+    // 首次登录强制修个人信息
     if (data.firstLogin) {
-      resetPwdRef.value?.open();
+      router.push({
+        name: 'ResetInfo',
+      });
       return;
     }