Browse Source

feat: basic authentication and basic data

Signed-off-by: carlos <568187512@qq.com>
carlos 2 months ago
parent
commit
bcfa8daccd

+ 2 - 2
index.html

@@ -1,8 +1,8 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>双预防系统</title>
   </head>

BIN
public/favicon.ico


+ 0 - 1
public/vite.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

+ 5 - 2
src/apis/auth.ts

@@ -1,11 +1,14 @@
 import request from '@/utils/request';
 
-export const LoginPageApi = {
+export const AuthApi = {
   async login(params: Auth.LoginParams): Promise<Auth.LoginResponse> {
     return request.post('user/login', params);
   },
+  async getCurrentUser(): Promise<Auth.User> {
+    return request.get('user/my-detail');
+  },
 };
 
 export default {
-  LoginPageApi,
+  AuthApi,
 };

+ 107 - 0
src/apis/basic.ts

@@ -0,0 +1,107 @@
+import request from '@/utils/request';
+
+export const BasicApi = {
+  // Predefine APIs
+  async getPredefineList(type: string): Promise<BasicData.Predefine[]> {
+    return request.get('basic/predefine/all', { params: { type } });
+  },
+  async addPredefine(data: Omit<BasicData.Predefine, 'id'>): Promise<void> {
+    return request.post('basic/predefine/add', data);
+  },
+  async deletePredefine(type: string, value: string): Promise<void> {
+    return request.delete('basic/predefine/remove', { params: { type, value } });
+  },
+
+  // Product Type APIs
+  async deleteType(name: string): Promise<void> {
+    return request.delete('basic/product/remove-type', { params: { name } });
+  },
+
+  // Unit APIs
+  async getUnitList(): Promise<BasicData.Unit[]> {
+    return request.get('basic/product/units');
+  },
+  async updateUnit(data: Partial<BasicData.Unit>): Promise<void> {
+    return request.post('basic/product/update-unit', data);
+  },
+  async addUnit(data: Partial<BasicData.Unit>): Promise<void> {
+    return request.post('basic/product/add-unit', data);
+  },
+  async deletedUnit(id: number): Promise<void> {
+    return request.delete('basic/product/remove-unit', { params: { id } });
+  },
+
+  // Equipment APIs
+  async getEquipmentList(): Promise<BasicData.Equipment[]> {
+    return request.get('basic/equipment/all');
+  },
+  async updateEquipment(data: Partial<BasicData.Equipment>): Promise<void> {
+    return request.post('basic/equipment/update', data);
+  },
+  async addEquipment(data: Partial<BasicData.Equipment>): Promise<void> {
+    return request.post('basic/equipment/add', data);
+  },
+  async deletedEquipment(id: number): Promise<void> {
+    return request.get('basic/equipment/remove', { params: { id } });
+  },
+
+  // Operation APIs
+  async getOperationList(): Promise<BasicData.Operation[]> {
+    return request.get('basic/operation/all');
+  },
+  async updateOperation(data: Partial<BasicData.Operation>): Promise<void> {
+    return request.post('basic/operation/update', data);
+  },
+  async addOperation(data: Partial<BasicData.Operation>): Promise<void> {
+    return request.post('basic/operation/add', data);
+  },
+  async deletedOperation(id: number): Promise<void> {
+    return request.get('basic/operation/remove', { params: { id } });
+  },
+
+  // Position APIs
+  async getPositionList(): Promise<BasicData.Position[]> {
+    return request.get('basic/position/all');
+  },
+  async updatePosition(data: Partial<BasicData.Position>): Promise<void> {
+    return request.post('basic/position/update', data);
+  },
+  async addPosition(data: Partial<BasicData.Position>): Promise<void> {
+    return request.post('basic/position/add', data);
+  },
+  async deletedPosition(id: number): Promise<void> {
+    return request.get('basic/position/remove', { params: { id } });
+  },
+
+  // Department APIs
+  async getDepartmentList(): Promise<BasicData.Department[]> {
+    return request.get('hr/department/all');
+  },
+  async addDepartment(data: Partial<BasicData.Department>): Promise<void> {
+    return request.post('hr/department/add', data);
+  },
+  async updateDepartment(data: Partial<BasicData.Department>): Promise<void> {
+    return request.post('hr/department/update', data);
+  },
+  async removeDepartment(id: number): Promise<void> {
+    return request.get('hr/department/remove', { params: { id } });
+  },
+
+  // Job APIs
+  async getJobList(): Promise<BasicData.Job[]> {
+    return request.get('basic/job/all');
+  },
+  async addJob(data: Partial<BasicData.Job>): Promise<void> {
+    return request.post('basic/job/add', data);
+  },
+  async updateJob(data: Partial<BasicData.Job>): Promise<void> {
+    return request.post('basic/job/update', data);
+  },
+  async removeJob(id: number): Promise<void> {
+    return request.get('basic/job/remove', { params: { id } });
+  },
+};
+
+export default {
+  BasicApi,
+};

+ 16 - 0
src/components/privateRoute.tsx

@@ -0,0 +1,16 @@
+import { useAuth } from '@/hooks/useAuth';
+import { Navigate } from 'react-router-dom';
+
+const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
+  const { isAuthenticated } = useAuth();
+
+  if (!isAuthenticated) {
+    // If the user is not authenticated, redirect to login
+    return <Navigate to="/login" />;
+  }
+
+  // Otherwise, render the protected component
+  return children;
+};
+
+export default PrivateRoute;

+ 8 - 4
src/components/user-info/index.tsx

@@ -1,8 +1,12 @@
 import SettingIcon from '@/assets/icons/setting.svg?react';
+import useBasic from '@/hooks/useBasic';
+import { useUserStore } from '@/store/user';
 import { ArrowSize8 } from '@nutui/icons-react';
 import styles from './user-info.module.scss';
 
 export default function UserInfo() {
+  const { userInfo } = useUserStore();
+  const { getDepartmentName, getJobName } = useBasic();
   return (
     <div>
       <div className="flex justify-end pr-4 pt-3 -mb-4">
@@ -13,16 +17,16 @@ export default function UserInfo() {
           <img className="rounded-full overflow-hidden" src="https://loremflickr.com/80/80" alt="" />
         </div>
         <div className={styles.info}>
-          <div className={styles.name}>佟大为</div>
+          <div className={styles.name}>{userInfo?.name}</div>
           <div className={styles.department}>
-            <span>学校</span>
+            <span>{userInfo?.company}</span>
             <span className="ml-2">
               <ArrowSize8 height={12} className="relative -top-[2px]" />
             </span>
           </div>
           <div>
-            <span className={styles.tag}>企业管理和法务合约部</span>
-            <span className={styles.tag}>变电巡检工</span>
+            <span className={styles.tag}>{getDepartmentName(userInfo?.department || 0)}</span>
+            <span className={styles.tag}>{getJobName(userInfo?.job || 0)}</span>
           </div>
         </div>
       </div>

+ 20 - 0
src/context/authContext.tsx

@@ -0,0 +1,20 @@
+import { useUserStore } from '@/store/user';
+import React, { createContext, useState } from 'react';
+
+const AuthContext = createContext({
+  isAuthenticated: false,
+  login: () => {},
+  logout: () => {},
+});
+
+export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
+  const { getToken } = useUserStore();
+  const [isAuthenticated, setIsAuthenticated] = useState(!!getToken());
+
+  const login = () => setIsAuthenticated(true);
+  const logout = () => setIsAuthenticated(false);
+
+  return <AuthContext.Provider value={{ isAuthenticated, login, logout }}>{children}</AuthContext.Provider>;
+};
+
+export default AuthContext;

+ 6 - 0
src/hooks/useAuth.ts

@@ -0,0 +1,6 @@
+import AuthContext from '@/context/authContext';
+import { useContext } from 'react';
+
+export const useAuth = () => {
+  return useContext(AuthContext);
+};

+ 96 - 0
src/hooks/useBasic.ts

@@ -0,0 +1,96 @@
+import { BasicApi } from '@/apis/basic';
+import { useMount, useRequest } from 'ahooks';
+import { useCallback } from 'react';
+
+function useBasic() {
+  const { data: jobs = [], loading: jobsLoading, runAsync: getJobs } = useRequest(BasicApi.getJobList);
+  const {
+    data: departments = [],
+    loading: departmentsLoading,
+    runAsync: getDepartments,
+  } = useRequest(BasicApi.getDepartmentList, {
+    manual: true,
+  });
+  const {
+    data: equipments = [],
+    loading: equipmentsLoading,
+    runAsync: getEquipments,
+  } = useRequest(BasicApi.getEquipmentList, {
+    manual: true,
+  });
+  const {
+    data: operations = [],
+    loading: operationsLoading,
+    runAsync: getOperations,
+  } = useRequest(BasicApi.getOperationList, {
+    manual: true,
+  });
+  const {
+    data: positions = [],
+    loading: positionsLoading,
+    runAsync: getPositions,
+  } = useRequest(BasicApi.getPositionList, {
+    manual: true,
+  });
+  const {
+    data: units = [],
+    loading: unitsLoading,
+    runAsync: getUnits,
+  } = useRequest(BasicApi.getUnitList, {
+    manual: true,
+  });
+
+  const getJobName = useCallback(
+    (jobId: number) => {
+      return jobs.find(job => job.id === jobId)?.name || '';
+    },
+    [jobs]
+  );
+
+  const getDepartmentName = useCallback(
+    (departmentId: number) => {
+      return departments.find(department => department.id === departmentId)?.name || '';
+    },
+    [departments]
+  );
+
+  useMount(() => {
+    getJobs();
+    getDepartments();
+    getEquipments();
+    getOperations();
+    getPositions();
+    getUnits();
+  });
+
+  return {
+    jobs,
+    jobsLoading,
+    getJobs,
+
+    departments,
+    departmentsLoading,
+    getDepartments,
+
+    equipments,
+    equipmentsLoading,
+    getEquipments,
+
+    operations,
+    operationsLoading,
+    getOperations,
+
+    positions,
+    positionsLoading,
+    getPositions,
+
+    units,
+    unitsLoading,
+    getUnits,
+
+    getJobName,
+    getDepartmentName,
+  };
+}
+
+export default useBasic;

+ 4 - 2
src/main.tsx

@@ -5,12 +5,14 @@ import { StrictMode } from 'react';
 import { createRoot } from 'react-dom/client';
 import { RouterProvider } from 'react-router-dom';
 import 'virtual:uno.css';
+import { AuthProvider } from './context/authContext';
 import './index.css';
 import { future, router } from './routes/index.tsx';
-
 // setRootPixel(46);
 createRoot(document.getElementById('root')!).render(
   <StrictMode>
-    <RouterProvider future={future} router={router} />
+    <AuthProvider>
+      <RouterProvider future={future} router={router} />
+    </AuthProvider>
   </StrictMode>
 );

+ 8 - 3
src/pages/login/index.tsx

@@ -1,5 +1,6 @@
-import { LoginPageApi } from '@/apis/auth';
+import { AuthApi } from '@/apis/auth';
 import loginBg from '@/assets/images/login-bg.png';
+import { useAuth } from '@/hooks/useAuth';
 import { useUserStore } from '@/store/user';
 import { Check } from '@nutui/icons-react';
 import { Button, Input, Toast } from '@nutui/nutui-react';
@@ -19,16 +20,19 @@ const inputStyle = {
 
 function LoginPage() {
   const navigate = useNavigate();
-  const { setToken, setStringProperty, username, password } = useUserStore();
+  const { login: loginAuth } = useAuth();
+  const { setToken, setStringProperty, username, password, updateUserInfo } = useUserStore();
   const [isRemember, { toggle: toggleRemember }] = useToggle(!!password);
   const [state, setState] = useState({
     username: username || '',
     password: password || '',
   });
-  const { loading, runAsync } = useRequest(LoginPageApi.login, {
+
+  const { loading, runAsync } = useRequest(AuthApi.login, {
     manual: true,
     onSuccess: data => {
       setToken(data.token);
+      loginAuth();
       if (isRemember) {
         setStringProperty('username', state.username);
         setStringProperty('password', state.password);
@@ -36,6 +40,7 @@ function LoginPage() {
         setStringProperty('username', '');
         setStringProperty('password', '');
       }
+      updateUserInfo();
       navigate('/portal');
     },
     onError: error => {

+ 3 - 0
src/pages/portal/index.tsx

@@ -5,6 +5,7 @@ import RiskCardIcon from '@/assets/images/portal/risk-card.png';
 import NavBar from '@/components/nav-bar';
 import NoData from '@/components/no-data';
 import UserInfo from '@/components/user-info';
+import useBasic from '@/hooks/useBasic';
 import { ArrowSize8 } from '@nutui/icons-react';
 import { Button } from '@nutui/nutui-react';
 import clsx from 'clsx';
@@ -12,6 +13,7 @@ import { useState } from 'react';
 import './portal.scss';
 
 export default function PortalPage() {
+  useBasic();
   const [activeRiskLevel, setActiveRiskLevel] = useState('R1');
   return (
     <>
@@ -142,6 +144,7 @@ export default function PortalPage() {
               <div className="py-3 text-nowrap overflow-y-hidden overflow-x-auto">
                 {['R1', 'R2', 'R2*', 'R3', 'R4'].map(item => (
                   <button
+                    key={item}
                     className={clsx('risk-level-button', item === activeRiskLevel && 'active')}
                     onClick={() => setActiveRiskLevel(item)}
                   >

+ 6 - 1
src/routes/index.tsx

@@ -1,3 +1,4 @@
+import PrivateRoute from '@/components/privateRoute';
 import LoginPage from '@/pages/login';
 import { createBrowserRouter } from 'react-router-dom';
 import ErrorPage from '../pages/error';
@@ -21,7 +22,11 @@ export const router = createBrowserRouter(
     },
     {
       path: '/portal',
-      element: <PortalPage />,
+      element: (
+        <PrivateRoute>
+          <PortalPage />
+        </PrivateRoute>
+      ),
     },
     {
       path: '/',

+ 8 - 1
src/store/user.ts

@@ -1,3 +1,4 @@
+import { AuthApi } from '@/apis/auth';
 import { aesDecrypt, aesEncrypt, ENCRYPT_SECRET } from '@/utils/encrypt';
 import { create } from 'zustand';
 import { createJSONStorage, persist, StateStorage } from 'zustand/middleware';
@@ -11,6 +12,7 @@ interface UserState {
   setToken: (token: string) => void;
   getToken: () => string;
   setUserInfo: (userInfo: UserState['userInfo']) => void;
+  updateUserInfo: () => Promise<void>;
   setStringProperty: <T extends keyof UserState>(key: T, value: UserState[T]) => void;
 }
 const initData: Partial<UserState> = {
@@ -37,7 +39,12 @@ export const useUserStore = create<UserState>()(
     (set, get) => ({
       ...initData,
       setUserInfo: (userInfo: UserState['userInfo']) => set({ userInfo }),
-      setToken: (token: string) => set({ token: aesEncrypt(token, ENCRYPT_SECRET) }),
+      updateUserInfo: async () => {
+        const user = await AuthApi.getCurrentUser();
+        return set({ userInfo: { ...user } });
+      },
+
+      setToken: (token: string) => set({ token }),
       getToken: () => {
         return get().token || '';
       },

+ 59 - 0
src/types/basic.d.ts

@@ -0,0 +1,59 @@
+declare namespace BasicData {
+  interface Predefine {
+    id: number;
+    type: string;
+    value: string;
+    seq: number;
+  }
+
+  interface PostList {
+    data: Post[];
+    total: number;
+  }
+
+  interface PostGroupList {
+    data: PostGroup[];
+    total: number;
+  }
+
+  interface Unit {
+    id: number;
+    name: string;
+    type: number;
+    category: number;
+  }
+
+  interface Position {
+    id: number;
+    name: string;
+    type: number;
+    category: number;
+  }
+
+  interface Equipment {
+    id: number;
+    name: string;
+    type: number;
+    category: number;
+  }
+
+  interface Operation {
+    id: number;
+    name: string;
+    type: number;
+    category: number;
+    // line: string;
+  }
+
+  interface Department {
+    id: number;
+    name: string;
+    // company: string;
+  }
+  interface Job {
+    id: number;
+    name: string;
+    department: number;
+    // company: string;
+  }
+}

+ 5 - 0
vite.config.ts

@@ -32,6 +32,11 @@ export default defineConfig({
       sass: {
         api: 'modern',
         additionalData: `@nutui/nutui-react/dist/styles/variables.scss`,
+        silenceDeprecations: ['legacy-js-api', 'color-functions'],
+      },
+      scss: {
+        api: 'modern',
+        silenceDeprecations: ['legacy-js-api'],
       },
     },
   },