一、菜單路由配置
1.umirc.ts
?中的路由配置
.umirc.ts
文件是 UmiJS 框架中的一個配置文件,用于配置應用的全局設置,包括但不限于路由、插件、樣式等。
import { defineConfig } from 'umi';
import config from './def/config';export default defineConfig({plugins: ['@umijs/plugins/dist/model', '@umijs/plugins/dist/request'],model: {},request: {},title: '客服管理后臺',publicPath: config.publicPath,favicons: [// 'https://domain.com/favicon.ico',//設置icon],esbuildMinifyIIFE: true,history: {type: 'hash',},routes: [{path: '/',component: '@/pages/business/index',},{path: '/public',component: '@/pages/publicInstance/index',layout: false,ignoreAuth: true,},{path: '/common',name: '通用',icon: 'menu',routes: [{path: '/common/business',name: '業務線管理',component: '@/pages/business/index',},{path: '/common/peopleManage',name: '人員管理配置',component: '@/pages/index',},{path: '/common/skills',name: '技能組管理配置',component: '@/pages/skills/index',},{path: '/common/dialogChange',name: '轉交配置',component: '@/pages/dialogChange/index',},],},// 其他路由配置...],
});
主要配置項解釋:
-
defineConfig
:- 這是一個從?
umi
?導入的函數,用來創建 Umi 配置對象。它幫助開發者更方便地編寫和組織配置。
- 這是一個從?
-
plugins
:- 定義使用的插件列表。這里使用了兩個插件:
@umijs/plugins/dist/model
?和?@umijs/plugins/dist/request
,它們分別用于狀態管理和請求處理。
- 定義使用的插件列表。這里使用了兩個插件:
-
routes
:- 定義應用的路由表,每個路由對象可以有以下屬性:
path
: 路由路徑。component
: 對應路徑下加載的組件。routes
: 子路由數組,允許嵌套路由。layout
: 是否使用布局,默認是?true
。當設置為?false
?時,表示該路由下的頁面不使用全局布局。ignoreAuth
: 忽略權限驗證,對于無需登錄即可訪問的公共頁面非常有用。
- 定義應用的路由表,每個路由對象可以有以下屬性:
示例分析:
-
根路徑
/
:- 當用戶訪問根路徑時,會加載?
@/pages/business/index
?組件。
- 當用戶訪問根路徑時,會加載?
-
免登錄頁面
/public
:- 訪問此路徑時,加載?
@/pages/publicInstance/index
?組件,且設置了?layout: false
?和?ignoreAuth: true
,意味著這個頁面不會使用全局布局,并且不需要進行權限驗證。
- 訪問此路徑時,加載?
-
通用模塊
/common
:- 包含四個子路由,每個子路由都有自己的?
path
、name
?和對應的?component
。這些子路由都歸屬于“通用”分類下,通過?icon
?屬性可以在側邊欄或導航中顯示相應的圖標。
- 包含四個子路由,每個子路由都有自己的?
2.menuConfig.ts
?中的菜單項配置
menuConfig.ts
文件則通常用于定義側邊欄或頂部導航欄的菜單結構。
import React from 'react';
import {BuildOutlined,
} from '@ant-design/icons';
import { MenuProps } from 'antd';// 定義菜單項類型
export type MenuItem = NonNullable<MenuProps['items']>[number];
export const menuData: MenuItem[] = [{key: 'common',icon: React.createElement(BuildOutlined),label: '通用',children: [{key: '/common/business',label: '業務線管理',},// ...更多菜單項],},// ...更多菜單組
];
1.?MenuProps['items']
這是從 Ant Design 的 MenuProps
類型中取出 items
屬性的類型。
1.?NonNullable<...>
NonNullable<T>
?是 TypeScript 內置的一個工具類型,用于從類型?T
?中排除?null
?和?undefined
。換句話說,它將類型?T
?中可能存在的?null
?或?undefined
?移除掉。
3.?[number]
?
- 從?
(MenuItemType)[]
?中提取出單個元素的類型,即?MenuItemType
。
二、hsf接口的調用
1、配置服務信息
import business from '@/pages/business';
import AccessProcess from '@/pages/entrance/accessProcess';
import { access } from 'fs';
import java from 'js-to-java';
import { request } from 'umi';export const isDaily = window.location.host.match(/localhost|.alibaba.net|-test.uc.alibaba-inc.com|30.211.81.4/,
);
export const version1 = isDaily ? '1.0.0.DAILY' : '1.0.0';
export const version2 = isDaily ? '2.0.1.DAILY' : '2.0.1';
export const version3 = isDaily ? '2.0.0.KF.DAILY' : '2.0.0.KF';// 固定的hsf服務信息
const servicer: any = {process: {appName: 'cs-xadmin',pathname: 'xxxx',version: isDaily ? '2.0.1.DAILY' : '2.0.1',},oldProcess: {appName: 'cs-xxflow',pathname: 'xxxx',version: isDaily ? '2.0.1.DAILY' : '2.0.1',},
};
/*** hsf通用調取方法* @param {string} key 對應服務key值(獲取pathname:version)* @param {string} className 類名* @param {string} action 方法名* @param {string} appName 應用名* @param {any} data 參數**/
export const hsfApi = async (key: string, { className, action, data }: any) => {// 如果是日常環境 & 路徑上帶有connectToLocalHsf參數,則調用本地hsf,把version中的DAILY 替換為 LOCALlet version = servicer[key]?.version;if (isDaily && window.location.hash.includes('connectHsf=local')) {version = servicer[key].version.replace('DAILY', 'LOCAL');}return request(`/api/hsf?action=${action}`, {method: 'POST',data: {pathname: `${servicer[key]?.pathname}:${version}`,action,appName: servicer[key].appName,data:data && className? [java(className, {...data,}),]: [],},});
};
pathname:
?appName:
2、封裝請求函數
// @ts-ignore
/* eslint-disable */
import { hsfApi } from '@/utils/api';/*** 分頁查詢技能組溢出規則* @param params 查詢參數*/
export async function pageList(params: {pageNo?: number;pageSize?: number;name?: string;fromSkillgroupId?: number;toSkillgroupId?: number;
}) {return hsfApi('onlineDispatch', {className: 'com.uc.cs.xadmin.client.param.dispatchschedule.PageOnlineDispatchScheduleParam',action: 'pageList',data: {...params,}});
className:?
action:?
?
參數解釋:
-
'onlineDispatch'
:- 表示要調用的服務名,對應你在?
api.ts
?中配置的?servicer
?對象里的鍵值。 - 會去查找該服務的應用名、路徑、版本等信息。
- 表示要調用的服務名,對應你在?
-
className
:- 表示你這次請求的參數在后端對應的 Java 類型。
-
action: 'pageList'
:- 表示你要調用的方法名,即后端服務提供的某個接口方法。
-
data: { ...params }
:- 把傳入的?
params
?參數展開并傳入請求體中。 - 最終會被包裝成一個 Java 對象發送到后端
- 把傳入的?
三、頁面開發
1、父組件
(1)懶加載
import React from 'react';
const AddEditModal = React.lazy(() => import('./components/AddEditModal'));
?React.lazy
是 React 提供的一個用于實現代碼分割的功能。通過 React.lazy
和動態 import()
語法,你可以按需加載組件,而不是在應用初始化時就加載所有組件。
(2)useMemo
const schema = useMemo(() => getOnlineDispatchSchema(groupValues), [groupValues]);
useMemo
是一個用于記憶化的 Hook,它接收兩個參數:
- 一個創建函數:該函數返回需要被記憶化的值。
- 一個依賴項數組:只有當這些依賴項發生變化時,才會重新計算記憶化的值;否則將返回之前記憶化的結果。
const columns: any = useMemo(() => { {title: '最后修改信息',dataIndex: 'modifyInfo', // 可以不綁定真實字段,僅占位width: 200,render: (_: any, record: any) => {const modifyTime = dayjs(record.modifyTime).format('YYYY-MM-DD HH:mm:ss');const modifier = record.modifier || '未知';return (<div style={{ lineHeight: 1.5 }}><div>{modifyTime}</div><div>{modifier}</div></div>);},},
}
(3)render
render: (_: any, record: any) => {}
?的用法
-
_
:當前列的值。有時你可能不需要使用這個值,所以通常用下劃線_
來表示忽略這個參數。 -
record
:當前行的所有數據。這是一個對象,包含了該行所有字段的信息。
(4)請求緩存避免發起多個相同請求?
// 在組件外部創建緩存
const skillGroupsCache = {data: null as SkillGroup[] | null,promise: null as Promise<SkillGroup[] | null> | null // 允許Promise返回null
};const fetchSkillGroups = async () => {if (skillGroupsCache.data) return skillGroupsCache.data;if (skillGroupsCache.promise) return skillGroupsCache.promise;skillGroupsCache.promise = (async () => {try {const result = await index({/* 參數 */});skillGroupsCache.data = result.code === 200 ? result.data.rows?.map((v: SkillGroupResponse) => ({ label: v.name, value: v.id })) || []: [];return skillGroupsCache.data;} catch (error) {console.error('獲取技能組失敗:', error);return [];} finally {skillGroupsCache.promise = null;}})();return skillGroupsCache.promise;
};
緩存對象?skillGroupsCache
-
data
:用于存儲從服務器獲取到的技能組數據。如果已經成功獲取了數據,則直接使用緩存的數據,無需再次發起網絡請求。 -
promise
:用于存儲正在進行中的異步請求,這樣可以確保在同一個數據獲取過程中,如果有多個地方同時請求相同的數據,它們將共享同一個請求結果,而不是各自發起新的請求。
-
檢查緩存數據:
- 首先檢查?
skillGroupsCache.data
?是否已經有值。如果有,說明之前已經成功獲取過數據,直接返回緩存的數據,避免重復請求。
- 首先檢查?
-
檢查進行中的請求:
- 如果沒有緩存的數據,但?
skillGroupsCache.promise
?不為空,說明當前有一個正在進行中的請求。在這種情況下,直接返回這個正在進行中的請求(Promise),所有調用者將等待同一個請求的結果,而不是各自發起新的請求。
- 如果沒有緩存的數據,但?
-
發起新請求:
- 如果既沒有緩存的數據也沒有正在進行中的請求,則創建一個新的異步請求來獲取數據,并將其存儲在?
skillGroupsCache.promise
?中。 - 在請求成功后,將獲取到的數據存儲在?
skillGroupsCache.data
?中,并清空?skillGroupsCache.promise
。 - 如果請求失敗,記錄錯誤信息并返回一個空數組。
- 如果既沒有緩存的數據也沒有正在進行中的請求,則創建一個新的異步請求來獲取數據,并將其存儲在?
-
清理:
- 無論請求成功還是失敗,在?
finally
?塊中都會將?skillGroupsCache.promise
?設置為?null
,以便后續的請求可以正常發起。
- 無論請求成功還是失敗,在?
(5)初始化狀態鎖
// 添加初始化狀態鎖const initializedRef = useRef(false);// 初始化數據useEffect(() => {console.log('initializedRef.current',initializedRef.current);if (initializedRef.current) return;initializedRef.current = true;const initData = async () => {setSpinning(true);try {setTableHeight(useInitTableHeight(-10));const [groups, pageData] = await Promise.all([fetchSkillGroups(),fetchPageList({ pageNo: 1, pageSize: 20 }) // 合并初始化請求]);setGroupValues(groups || []);setRawTableData(pageData.data.data.list || []);// setInitialized(true);} catch (error) {console.error('初始化失敗:', error);} finally {setSpinning(false);}};initData();
}, []);
通過 if (initializedRef.current) return;
來判斷是否已經執行過初始化操作。如果已經初始化則直接返回,不重復執行初始化邏輯。
使用?useRef
?而不是?useState
?的原因
-
useRef
:useRef
返回一個可變的引用對象,其.current
屬性在組件的整個生命周期內保持不變。修改.current
屬性不會觸發組件的重新渲染。 -
useState
:每次調用setState
函數都會導致組件重新渲染。如果我們使用useState
來管理初始化狀態,那么每當更新該狀態時,都會導致組件重新渲染,這可能會引起性能問題或意外的行為。
(6)??和...運算符
// 構造請求參數const payload: Record<string, any> = {pageNo: current ?? 1,pageSize: pageSize ?? 20,...(onlineDispatchName && { name: onlineDispatchName }),...(overflowSkillGroup && { fromSkillgroupId: overflowSkillGroup }),...(inflowSkillGroup && { toSkillgroupId: inflowSkillGroup }),...(modifier && { modifier }),};
??對比邏輯或運算符 (||
)
在 ES2020 之前,開發者通常使用邏輯或運算符 (||
) 來提供默認值。但是,||
運算符會在左側操作數是任何假值(如 0
, false
, ''
等)時也返回右側的默認值
let current = 0;
console.log(current || 1); // 輸出: 1 (可能不符合預期)
console.log(current ?? 1); // 輸出: 0 (符合預期)
(7)枚舉列表轉換成對象
/*** 格式化enums* @param list - 所需要格式化的列表* list格式: [{value: "offline", desc: "下線"}, {value: "online", desc: "上線"}]* @returns 返回格式化后的enums {offline: "下線", online: "上線"}*/
const formatEnums = (list: any, label: string = 'desc', code: string = 'code') => {if (!list || list.length === 0) return null;return (list?.reduce((acc: any, cur: any) => {acc[cur[code]] = cur[label];return acc;}, {}) || null);
};