Prechádzať zdrojové kódy

build: 登录接口调试

chaooo 2 rokov pred
rodič
commit
8939ab08fc

+ 8 - 17
src/api/auth/index.ts

@@ -1,6 +1,6 @@
-import request from '@/utils/request';
-import { AxiosPromise } from 'axios';
-import { LoginData, UserInfo, LoginResult } from './types';
+import request from "@/utils/request";
+import { AxiosPromise } from "axios";
+import { LoginData, LoginResult } from "./types";
 
 /**
  * 登录API
@@ -10,27 +10,18 @@ import { LoginData, UserInfo, LoginResult } from './types';
  */
 export function loginApi(data: LoginData): AxiosPromise<LoginResult> {
   return request({
-    url: '/api/v1/auth/login',
-    method: 'post',
-    params: data
+    url: "/board/v1/login",
+    method: "post",
+    data: data,
   });
 }
-/**
- * 登录成功后获取用户信息(昵称、头像、权限集合和角色集合)
- */
-export function getUserInfo(): AxiosPromise<UserInfo> {
-	return request({
-		url: '/api/v1/users/me',
-		method: 'get'
-	});
-}
 
 /**
  * 注销API
  */
 export function logoutApi() {
   return request({
-    url: '/api/v1/auth/logout',
-    method: 'delete'
+    url: "/board/v1/logout",
+    method: "post",
   });
 }

+ 21 - 25
src/api/auth/types.ts

@@ -3,17 +3,13 @@
  */
 export interface LoginData {
   /**
-   * 用户名
+   * 手机号
    */
-  username?: string;
+  phone?: string;
   /**
    * 密码
    */
   password?: string;
-  /**
-   * 是否自动登录
-   */
-  auto?: boolean;
 }
 
 /**
@@ -23,29 +19,29 @@ export interface LoginResult {
   /**
    * 访问token
    */
-  accessToken?: string;
+  token: string;
+  /**
+   * 姓名
+   */
+  name: string;
+  /**
+   * 加密后的电话号码
+   */
+  phone: string;
   /**
-   * 过期时间(单位:毫秒)
+   * 学校号
    */
-  expires?: number;
+  num: string;
   /**
-   * 刷新token
+   * 头像
    */
-  refreshToken?: string;
+  avatar: string;
   /**
-   * token 类型
+   * 角色
    */
-  tokenType?: string;
+  role: string;
+  /**
+   * 权限
+   */
+  perms: string[];
 }
-
-/**
- * 登录用户信息
- */
-export interface UserInfo {
-	userId: number;
-	nickname: string;
-	avatar: string;
-	roles: string[];
-	perms: string[];
-	schoolId: number;
-}

+ 12 - 12
src/api/school/index.ts

@@ -1,23 +1,23 @@
-import request from '@/utils/request';
-import { AxiosPromise } from 'axios';
-import { SchoolList, ClassList} from './types';
+import request from "@/utils/request";
+import { AxiosPromise } from "axios";
+import { SchoolList, ClassList } from "./types";
 
 /**
  * 获取学校列表
  */
 export function getSchoolList(): AxiosPromise<SchoolList[]> {
-	return request({
-		url: 'http://www.smadmin.com/schools/index',
-		method: 'get'
-	});
+  return request({
+    url: "/board/v1/schools",
+    method: "get",
+  });
 }
 /**
  * 获取班级列表
  */
 export function getClassList(id: number): AxiosPromise<ClassList[]> {
-	return request({
-		url: 'http://www.smadmin.com/schools/grade-lists',
-		method: 'get',
-		params: {school_id: id}
-	});
+  return request({
+    url: "/board/v1/grade",
+    method: "get",
+    params: { school_id: id },
+  });
 }

+ 19 - 21
src/layout/components/Navbar.vue

@@ -49,34 +49,29 @@ function logout() {
 /**
  * 学校数据
  */
-const schoolData = ref<SchoolList[]>([
-  {
-    school_id: 0,
-    name: "全部学校全部学校全部学校全部学校",
-  },
-  {
-    school_id: 1,
-    name: "学校1",
-  },
-]);
-const schoolNumber = ref(0);
-function querySchoolList() {
+const schoolData = ref<SchoolList[]>();
+const schoolId = ref(0);
+async function getSchoolData() {
   getSchoolList()
     .then(({ data }) => {
-      schoolData.value.concat(data);
+      schoolData.value = data;
+      if (schoolId.value == 0) {
+        schoolId.value = data[0].school_id;
+        userStore.changeSchool(schoolId.value);
+      }
     })
     .catch((error) => {
       console.log(error);
     });
 }
 onMounted(() => {
-  // querySchoolList();
+  getSchoolData();
 });
 watch(
-  () => schoolNumber.value,
+  () => schoolId.value,
   (newValue, oldValue) => {
-    console.log("schoolNumber", newValue, oldValue);
-    userStore.changeSchool(schoolNumber.value);
+    // console.log("schoolNumber", newValue, oldValue);
+    userStore.changeSchool(newValue);
   }
 );
 </script>
@@ -107,9 +102,9 @@ watch(
       <!-- 学校选择下拉框 -->
       <div class="nav-select">
         <el-select
-          v-model="schoolNumber"
+          v-model="schoolId"
           size="large"
-          placeholder="全部学校"
+          placeholder="请选择学校"
           :suffix-icon="CaretBottom"
         >
           <el-option
@@ -123,8 +118,8 @@ watch(
       <!-- 用户头像 -->
       <div class="avatar-container">
         <span class="spl">|</span>
-        <img :src="userStore.avatar + '?imageView2/1/w/80/h/80'" />
-        <span class="">{{ userStore.nickname }}</span>
+        <img src="" alt="头像" />
+        <span class="">{{ userStore.nickname + " " + userStore.phone }}</span>
         <span @click="logout"
           ><svg-icon icon-class="exit" color="#006eff" size="20px"
         /></span>
@@ -169,7 +164,10 @@ watch(
     img {
       width: 40px;
       height: 40px;
+      line-height: 40px;
+      text-align: center;
       border-radius: 20px;
+      background: #494949;
     }
     span {
       margin-left: 12px;

+ 15 - 23
src/permission.ts

@@ -22,31 +22,23 @@ router.beforeEach(async (to, from, next) => {
       NProgress.done();
     } else {
       const userStore = useUserStoreHook();
-      console.log("userStore.routeStatus", userStore.routeStatus);
       // 未加载过动态路由
       if (!userStore.routeStatus) {
-        /**
-         * userStore
-         * ...
-         * const userInfo = useStorage("userInfo", "");
-         * ...
-         * userInfo.value = JSON.stringify(data);
-         * ...
-         *   function getInfo() {
-         *     return new Promise<UserInfo>((resolve, reject) => {
-         * 			const userInfoStr = localStorage.getItem("userInfo");
-         * 			const userInfo = JSON.parse(userInfoStr);
-         * 			resolve({roles: userInfo.roles});
-         *     });
-         *   }
-         */
-        const { roles } = await userStore.getInfo();
-        const accessRoutes = await permissionStore.generateRoutes(roles);
-        accessRoutes.forEach((route) => {
-          router.addRoute(route);
-        });
-        userStore.setRouteStatus(true);
-        next({ ...to, replace: true });
+        try {
+          const role = await userStore.getInfo();
+          const accessRoutes = await permissionStore.generateRoutes(role);
+          accessRoutes.forEach((route) => {
+            router.addRoute(route);
+          });
+          // 设置路由加载标志
+          userStore.setRouteStatus(true);
+          next({ ...to, replace: true });
+        } catch (error) {
+          // 移除 token 并跳转登录页
+          await userStore.resetToken();
+          next(`/login?redirect=${to.path}`);
+          NProgress.done();
+        }
       } else {
         next();
       }

+ 10 - 11
src/store/modules/permission.ts

@@ -8,7 +8,7 @@ const modules = import.meta.glob("../../views/**/**.vue");
 const Layout = () => import("@/layout/school.vue");
 const Admin = () => import("@/layout/admin.vue");
 // 角色【后台管理员】拥有的权限路由
-const schoolRoutes: RouteRecordRaw[] = JSON.parse(
+const adminRoutes: RouteRecordRaw[] = JSON.parse(
   JSON.stringify([
     {
       path: "/",
@@ -37,7 +37,7 @@ const schoolRoutes: RouteRecordRaw[] = JSON.parse(
   ])
 );
 // 角色【学校负责人】拥有的权限路由
-const adminRoutes: RouteRecordRaw[] = JSON.parse(
+const schoolRoutes: RouteRecordRaw[] = JSON.parse(
   JSON.stringify([
     {
       path: "/",
@@ -200,7 +200,7 @@ const adminRoutes: RouteRecordRaw[] = JSON.parse(
  * @param roles 用户角色集合
  * @returns 返回用户有权限的异步(动态)路由
  */
-const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
+const filterAsyncRoutes = (routes: RouteRecordRaw[], role: string | null) => {
   const asyncRoutes: RouteRecordRaw[] = [];
 
   routes.forEach((route) => {
@@ -208,10 +208,8 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
 
     if (tmpRoute.component?.toString() == "SchoolIndex") {
       tmpRoute.component = Layout;
-      console.log("SchoolIndex");
     } else if (tmpRoute.component?.toString() == "AdminIndex") {
       tmpRoute.component = Admin;
-      console.log("AdminIndex");
     } else {
       const component = modules[`../../views/${tmpRoute.component}.vue`];
       if (component) {
@@ -222,7 +220,7 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
     }
 
     if (tmpRoute.children) {
-      tmpRoute.children = filterAsyncRoutes(tmpRoute.children, roles);
+      tmpRoute.children = filterAsyncRoutes(tmpRoute.children, role);
     }
 
     asyncRoutes.push(tmpRoute);
@@ -246,18 +244,19 @@ export const usePermissionStore = defineStore("permission", () => {
    * @param roles 用户角色集合
    * @returns
    */
-  function generateRoutes(roles: string[]) {
+  function generateRoutes(role: string | null) {
     return new Promise<RouteRecordRaw[]>((resolve, reject) => {
+      console.log("role", role);
       // 角色【后台管理员】拥有权限
-      if (roles.includes("ADMIN")) {
+      if (role && role == "ADMIN") {
         // 根据角色获取有访问权限的路由
-        const accessedRoutes = filterAsyncRoutes(adminRoutes, roles);
+        const accessedRoutes = filterAsyncRoutes(adminRoutes, role);
         setRoutes(accessedRoutes);
         resolve(accessedRoutes);
       }
       // 角色【学校负责人】拥有权限
-      else if (roles.includes("SCHOOL")) {
-        const accessedRoutes = filterAsyncRoutes(schoolRoutes, roles);
+      else if (role && role == "SCHOOL") {
+        const accessedRoutes = filterAsyncRoutes(schoolRoutes, role);
         setRoutes(accessedRoutes);
         resolve(accessedRoutes);
       } else {

+ 55 - 51
src/store/modules/user.ts

@@ -1,66 +1,66 @@
 import { defineStore } from "pinia";
-
-import { loginApi, logoutApi, getUserInfo } from "@/api/auth";
+import { loginApi, logoutApi } from "@/api/auth";
 import { resetRouter } from "@/router";
 import { store } from "@/store";
-
-import { LoginData, UserInfo } from "@/api/auth/types";
-
 import { useStorage } from "@vueuse/core";
+import { LoginData, LoginResult } from "@/api/auth/types";
 
 export const useUserStore = defineStore("user", () => {
   // state
-  const userId = ref();
   const token = useStorage("accessToken", "");
+  const userInfo = useStorage("userInfo", "");
+  const schoolId = useStorage("schoolId", 0);
+  const routeStatus = ref(false);
   const nickname = ref("");
-  const avatar = ref("");
-  const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
+  const phone = ref("");
+  const role = ref(""); // 用户角色 → 判断路由权限
   const perms = ref<Array<string>>([]); // 用户权限编码集合 → 判断按钮权限
-  const schoolId = ref(0);
-  /**
-   * 登录调用
-   *
-   * @param {LoginData}
-   * @returns
-   */
+
+  // 登录调用
   function login(loginData: LoginData) {
     return new Promise<void>((resolve, reject) => {
       loginApi(loginData)
-        .then((response) => {
-          const { tokenType, accessToken } = response.data;
-          token.value = tokenType + " " + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
+        .then(({ data }) => {
+          userInfo.value = JSON.stringify(data);
+          setUserData(data);
           resolve();
         })
         .catch((error) => {
+          console.log(error);
           reject(error);
         });
     });
   }
 
-  // 获取信息(用户昵称、头像、角色集合、权限集合)
+  // 动态路由调用
   function getInfo() {
-    return new Promise<UserInfo>((resolve, reject) => {
-      getUserInfo()
-        .then(({ data }) => {
-          if (!data) {
-            return reject("Verification failed, please Login again.");
-          }
-          if (!data.roles || data.roles.length <= 0) {
-            reject("getUserInfo: roles must be a non-null array!");
-          }
-          userId.value = data.userId;
-          nickname.value = data.nickname;
-          avatar.value = data.avatar;
-          roles.value = data.roles;
-          perms.value = data.perms;
-          resolve(data);
-        })
-        .catch((error) => {
-          reject(error);
-        });
+    return new Promise<string>((resolve, reject) => {
+      const userInfoStr = localStorage.getItem("userInfo");
+      if (null != userInfoStr) {
+        const userInfo = JSON.parse(userInfoStr);
+        const role = setUserData(userInfo);
+        resolve(role);
+      } else {
+        reject("本地数据丢失,请重新登录!");
+      }
     });
   }
 
+  // 设置用户信息
+  function setUserData(data: LoginResult) {
+    token.value = data.token;
+    nickname.value = data.name;
+    phone.value = data.phone;
+    // todo: 接口写好后需要修改
+    if (data.role) {
+      role.value = data.role;
+    } else {
+      role.value = "SCHOOL";
+    }
+    perms.value = data.perms;
+    return role.value;
+  }
+
   // 注销
   function logout() {
     return new Promise<void>((resolve, reject) => {
@@ -80,33 +80,37 @@ export const useUserStore = defineStore("user", () => {
   // 重置
   function resetToken() {
     token.value = "";
+    userInfo.value = "";
     nickname.value = "";
-    avatar.value = "";
-    roles.value = [];
+    phone.value = "";
+    role.value = "";
     perms.value = [];
   }
+
   // 切换学校
-  function changeSchool(id: number){
-	  schoolId.value = id;
+  function changeSchool(id: number) {
+    schoolId.value = id;
+  }
+
+  // 路由加载后设置
+  function setRouteStatus(status: boolean) {
+    routeStatus.value = status;
   }
 
   return {
     token,
+    routeStatus,
+    schoolId,
     nickname,
-    avatar,
-    roles,
+    phone,
+    role,
     perms,
     login,
-    getInfo,
     logout,
+    getInfo,
     resetToken,
+    setRouteStatus,
     changeSchool,
-    /**
-     * 当前登录用户ID
-     */
-    userId,
-	schoolId,
-
   };
 });
 

+ 16 - 15
src/utils/request.ts

@@ -1,11 +1,11 @@
-import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
-import { useUserStoreHook } from '@/store/modules/user';
+import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios";
+import { useUserStoreHook } from "@/store/modules/user";
 
 // 创建 axios 实例
 const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,
   timeout: 50000,
-  headers: { 'Content-Type': 'application/json;charset=utf-8' }
+  headers: { "Content-Type": "application/json;charset=utf-8" },
 });
 
 // 请求拦截器
@@ -13,7 +13,8 @@ service.interceptors.request.use(
   (config: InternalAxiosRequestConfig) => {
     const userStore = useUserStoreHook();
     if (userStore.token) {
-      config.headers.Authorization = userStore.token;
+      //config.headers.Authorization = userStore.token;
+      config.headers["access-token"] = userStore.token;
     }
     return config;
   },
@@ -25,8 +26,8 @@ service.interceptors.request.use(
 // 响应拦截器
 service.interceptors.response.use(
   (response: AxiosResponse) => {
-    const { code, msg } = response.data;
-    if (code === '00000') {
+    const { status, message } = response.data;
+    if (status === 1) {
       return response.data;
     }
     // 响应数据为二进制流处理(Excel导出)
@@ -34,23 +35,23 @@ service.interceptors.response.use(
       return response;
     }
 
-    ElMessage.error(msg || '系统出错');
-    return Promise.reject(new Error(msg || 'Error'));
+    ElMessage.error(message || "系统出错");
+    return Promise.reject(new Error(message || "Error"));
   },
   (error: any) => {
     if (error.response.data) {
-      const { code, msg } = error.response.data;
+      const { status, message } = error.response.data;
       // token 过期,重新登录
-      if (code === 'A0230') {
-        ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {
-          confirmButtonText: '确定',
-          type: 'warning'
+      if (status === 10) {
+        ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", {
+          confirmButtonText: "确定",
+          type: "warning",
         }).then(() => {
           localStorage.clear();
-          window.location.href = '/';
+          window.location.href = "/";
         });
       } else {
-        ElMessage.error(msg || '系统出错');
+        ElMessage.error(message || "系统出错");
       }
     }
     return Promise.reject(error.message);

+ 0 - 1
src/views/charts-components/PercentBarChart.vue

@@ -42,7 +42,6 @@ const props = defineProps({
 
 const labelFormatter = (params) => {
   // 显示人数 加 百分比
-  console.log(params);
   if (params.value > 0) {
     return props.data?.[params.dataIndex] + "人 (" + params.value + "%)";
   } else {

+ 31 - 19
src/views/login/index.vue

@@ -9,21 +9,19 @@
       <div class="title">
         <span>登录</span>
       </div>
-      <el-form-item prop="username">
-        <span class="m-2">
-          <svg-icon size="30px" icon-class="username" /> </span
+      <el-form-item prop="phone">
+        <span class="m-2"> <svg-icon size="30px" icon-class="username" /> </span
         >|
         <el-input
           ref="username"
-          v-model="loginData.username"
+          v-model="loginData.phone"
           class="flex-1"
           placeholder="请输入登录账号"
           name="username"
         />
       </el-form-item>
       <el-form-item prop="password">
-        <span class="m-2">
-          <svg-icon size="30px" icon-class="password" /> </span
+        <span class="m-2"> <svg-icon size="30px" icon-class="password" /> </span
         >|
         <el-input
           v-model="loginData.password"
@@ -51,17 +49,11 @@
       </el-button>
 
       <el-checkbox
-        v-model="loginData.auto"
+        v-model="autoLogin"
         label="自动登录"
         size="small"
         fill="#727272"
       />
-
-      <!-- 账号密码提示 -->
-      <div class="mt-4 text-white text-sm">
-        <span>admin</span>,
-        <span class="ml-4">123456</span>
-      </div>
     </el-form>
   </div>
 </template>
@@ -92,15 +84,20 @@ const passwordVisible = ref(false);
  * 登录表单引用
  */
 const loginFormRef = ref(ElForm);
-
+const autoLogin = ref(true);
 const loginData = ref<LoginData>({
-  username: "admin",
-  password: "123456",
-  auto: true,
+  // phone: "18770033942",
+  // password: "123456",
+  phone: localStorage.getItem("autoName")
+    ? atob(localStorage.getItem("autoName"))
+    : "",
+  password: localStorage.getItem("autoPass")
+    ? atob(localStorage.getItem("autoPass"))
+    : "",
 });
 
 const loginRules = {
-  username: [{ required: true, trigger: "blur", validator: usernameValidator }],
+  phone: [{ required: true, trigger: "blur", validator: usernameValidator }],
   password: [{ required: true, trigger: "blur", validator: passwordValidator }],
 };
 
@@ -108,6 +105,12 @@ const loginRules = {
  * 用户名校验
  */
 function usernameValidator(rule: any, value: any, callback: any) {
+  if (value * 1 === 0) {
+    callback(new Error("输入的手机号码不能为空"));
+  }
+  if (!new RegExp(/^1[3-9][0-9]{9}$/).test(value)) {
+    callback(new Error("输入的手机号码格式错误"));
+  }
   callback();
 }
 /**
@@ -127,6 +130,14 @@ function handleLogin() {
   loginFormRef.value.validate((valid: boolean) => {
     if (valid) {
       loading.value = true;
+      if (autoLogin.value) {
+        // 自动登录存入本地存储
+        localStorage.setItem("autoName", btoa(<string>loginData.value.phone));
+        localStorage.setItem(
+          "autoPass",
+          btoa(<string>loginData.value.password)
+        );
+      }
       userStore
         .login(loginData.value)
         .then(() => {
@@ -143,7 +154,8 @@ function handleLogin() {
           );
           router.push({ path: redirect, query: otherQueryParams });
         })
-        .catch(() => {
+        .catch((error) => {
+          console.log("登录", error);
           // 验证失败,重新生成验证码
           new Error("您输入的密码错误");
         })

+ 2 - 2
vite.config.ts

@@ -43,8 +43,8 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
       proxy: {
         // 反向代理解决跨域
         [env.VITE_APP_BASE_API]: {
-          target: "http://vapi.youlai.tech", // 线上接口地址
-          // target: 'http://localhost:8989',  // 本地接口地址 , 后端工程仓库地址:https://gitee.com/youlaiorg/youlai-boot
+          //target: "http://vapi.youlai.tech", // 线上接口地址
+          target: "http://devapi.shuimuai.com/", // 本地接口地址
           changeOrigin: true,
           rewrite: (path) =>
             path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址