Browse Source

实现在调试环境和后端交互,登录后显示主页面

dengdx 2 months ago
parent
commit
2c1f65c096

+ 15 - 0
package-lock.json

@@ -13,6 +13,7 @@
         "@tarojs/components": "4.1.1",
         "@tarojs/helper": "4.1.1",
         "@tarojs/plugin-framework-react": "4.1.1",
+        "@tarojs/plugin-http": "^4.1.1",
         "@tarojs/plugin-platform-alipay": "4.1.1",
         "@tarojs/plugin-platform-h5": "4.1.1",
         "@tarojs/plugin-platform-harmony-hybrid": "4.1.1",
@@ -27,6 +28,7 @@
         "@tarojs/taro": "4.1.1",
         "antd": "^5.25.3",
         "antd-mobile": "^5.39.0",
+        "axios": "^1.9.0",
         "react": "^18.0.0",
         "react-dom": "^18.0.0",
         "react-intl": "^7.1.11",
@@ -4470,6 +4472,19 @@
         }
       }
     },
+    "node_modules/@tarojs/plugin-http": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/@tarojs/plugin-http/-/plugin-http-4.1.1.tgz",
+      "integrity": "sha512-9GNCPSk5I/K4AAblBE5zUOM+5nx7QI5XATiyCw/uIe0d/Ltq6E6uIJXMvZ7e9Q2ihSHYOyvOYeUIufe8kWkB7g==",
+      "engines": {
+        "node": ">= 18"
+      },
+      "peerDependencies": {
+        "@tarojs/runtime": "4.1.1",
+        "@tarojs/shared": "4.1.1",
+        "@tarojs/taro": "4.1.1"
+      }
+    },
     "node_modules/@tarojs/plugin-platform-alipay": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/@tarojs/plugin-platform-alipay/-/plugin-platform-alipay-4.1.1.tgz",

+ 2 - 0
package.json

@@ -44,6 +44,7 @@
     "@tarojs/components": "4.1.1",
     "@tarojs/helper": "4.1.1",
     "@tarojs/plugin-framework-react": "4.1.1",
+    "@tarojs/plugin-http": "^4.1.1",
     "@tarojs/plugin-platform-alipay": "4.1.1",
     "@tarojs/plugin-platform-h5": "4.1.1",
     "@tarojs/plugin-platform-harmony-hybrid": "4.1.1",
@@ -58,6 +59,7 @@
     "@tarojs/taro": "4.1.1",
     "antd": "^5.25.3",
     "antd-mobile": "^5.39.0",
+    "axios": "^1.9.0",
     "react": "^18.0.0",
     "react-dom": "^18.0.0",
     "react-intl": "^7.1.11",

+ 1 - 0
src/API/config.ts

@@ -0,0 +1 @@
+export const API_BASE_URL = 'http://localhost:10086/dr/api/v1/';

+ 91 - 0
src/API/security/userAction.md

@@ -0,0 +1,91 @@
+## 请求的base url
+
+base url 在config.ts文件中定义并导出
+
+## 请求PATH:
+
+pub/login
+
+## 请求头示例
+
+名称:Authorization
+
+类型:string
+
+必填:true
+
+默认值:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTAwNTU5MzYsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.LLveItQ_K0vvcmyN4qLQHxaOcz3EzY7ZeUQ97hR9R9w
+描述:认证信息,使用 Bearer Token 方式进行身份验证。
+
+---
+
+名称:Language
+
+类型:string
+
+必填:true
+
+默认值:en
+
+描述:语言选项,可选值为 en 或 zh。
+
+---
+
+名称:Product
+
+类型:string
+
+必填:true
+
+默认值:DROS
+
+描述:产品类型,可选值为 DROS 或 VETDROS。
+
+---
+
+名称:Source
+
+类型:string
+
+必填:true
+
+默认值:Electron
+
+描述:来源类型,可选值为 Electron、Browser 或 Android。
+
+## 请求体
+
+| **名称** | **类型** | **必填** | **默认值** | **描述** |
+| :------- | :------- | :------- | :--------- | :------- |
+| username | string   | true     | admin      | 用户名   |
+| password | string   | true     | 123456     | 密码     |
+
+## 请求成功结果
+
+```json
+{
+  "code": "0x000000",
+  "description": "Success",
+  "solution": "",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTEyNzc1ODgsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.0qIzacOIf0-YluwAlrkaNJ9lf9w8IKneWEXh_mjUoN4",
+    "expire": 1751277588,
+    "uid": 1,
+    "name": "admin",
+    "avatar": ""
+  }
+}
+```
+
+其中code字段不为0可以作为失败的判断依据
+
+## 请求失败结果
+
+```json
+{
+  "code": "0x010101",
+  "data": {},
+  "description": "Invalid username or password",
+  "solution": ""
+}
+```

+ 24 - 0
src/API/security/userActions.ts

@@ -0,0 +1,24 @@
+import axios from 'axios';
+import { API_BASE_URL } from '../config';
+
+// 创建带默认请求头的axios实例
+const axiosInstance = axios.create({
+  baseURL: API_BASE_URL,
+  headers: {
+    Authorization:
+      'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTAwNTU5MzYsImlkIjoxLCJuYW1lIjoiYWRtaW4ifQ.LLveItQ_K0vvcmyN4qLQHxaOcz3EzY7ZeUQ97hR9R9w',
+    Language: 'en',
+    Product: 'DROS',
+    Source: 'browser',
+  },
+});
+
+// 登录接口
+export function login(username: string, password: string) {
+  return axiosInstance.post('/pub/login', {
+    username,
+    password,
+  });
+}
+
+// ...根据 userActions.md 继续添加其它接口方法...

+ 1 - 1
src/layouts/BasicLayout.tsx

@@ -88,7 +88,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = () => {
       }
     >
       {/* 登录遮罩层 */}
-      {userInfo.id === null && <Login />}
+      {userInfo.uid === 0 && <Login />}
       <Layout
         style={{
           minHeight: '100vh',

+ 38 - 15
src/pages/security/Login.tsx

@@ -1,24 +1,47 @@
 import React from 'react';
-import { Row, Col, Form, Input, Button, Card } from 'antd';
+import { Row, Col, Form, Input, Button, Card, message } from 'antd';
+import { useDispatch } from 'react-redux';
+import { login as loginApi } from '../../API/security/userActions';
+import { setUserInfo, type UserInfoState } from '../../states/user_info'; // 同时导入 setUserInfo 和类型 UserInfoState
 
 const Login: React.FC = () => {
   const [form] = Form.useForm();
+  const dispatch = useDispatch();
 
-  // type LoginFormValues = {
-  //     username: string;
-  //     password: string;
-  // };
+  // 2. 登录请求函数
+  const loginRequest = async (values: {
+    username: string;
+    password: string;
+  }) => {
+    const res = await loginApi(values.username, values.password);
+    return res.data;
+  };
 
-  interface LoginResponse {
-    token: string;
-    expire: string;
-    uid: string;
-    name: string;
-    avatar: string;
-  }
-  const handleFinish = (values: LoginResponse) => {
-    // TODO: 登录逻辑
-    console.log('Login:', values);
+  // 3. handleFinish 处理流程
+  const handleFinish = async (values: {
+    username: string;
+    password: string;
+  }) => {
+    try {
+      const result = await loginRequest(values);
+      if (result.code === '0x000000') {
+        // 4. 转换为 UserInfoState 类型
+        const userInfo: UserInfoState = {
+          token: result.data.token,
+          expire: result.data.expire,
+          uid: result.data.uid,
+          name: result.data.name,
+          avatar: result.data.avatar,
+        };
+        // 5. 分发 redux action
+        dispatch(setUserInfo(userInfo));
+        message.success('登录成功'); //todo 更详细的提示与异步过程
+      } else {
+        message.error(result.description || '登录失败');
+      }
+    } catch (e) {
+      message.error(`网络错误: ${e instanceof Error ? e.message : '未知错误'}`);
+    }
   };
 
   return (

+ 16 - 22
src/states/user_info/index.ts

@@ -1,17 +1,19 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 
 interface UserInfoState {
-  id: string | null;
+  token: string;
+  expire: number;
+  uid: number;
   name: string;
-  email: string;
-  avatarUrl?: string;
+  avatar: string;
 }
 
 const initialState: UserInfoState = {
-  id: null,
+  token: '',
+  expire: 0,
+  uid: 0,
   name: '',
-  email: '',
-  avatarUrl: undefined,
+  avatar: '',
 };
 
 const userInfoSlice = createSlice({
@@ -22,29 +24,21 @@ const userInfoSlice = createSlice({
       return { ...action.payload };
     },
     clearUserInfo(state) {
-      state.id = null;
+      state.token = '';
+      state.expire = 0;
+      state.uid = 0;
       state.name = '';
-      state.email = '';
-      state.avatarUrl = undefined;
+      state.avatar = '';
     },
     updateUserName(state, action: PayloadAction<string>) {
       state.name = action.payload;
     },
-    updateUserEmail(state, action: PayloadAction<string>) {
-      state.email = action.payload;
-    },
-    updateUserAvatar(state, action: PayloadAction<string | undefined>) {
-      state.avatarUrl = action.payload;
-    },
   },
 });
 
-export const {
-  setUserInfo,
-  clearUserInfo,
-  updateUserName,
-  updateUserEmail,
-  updateUserAvatar,
-} = userInfoSlice.actions;
+export const { setUserInfo, clearUserInfo, updateUserName } =
+  userInfoSlice.actions;
 
 export default userInfoSlice.reducer;
+
+export type { UserInfoState };