开发记录

登录
token失效问题
日常项目都是用token作为登录凭证,token都具有有效期,用户长时间停留在当前页面未做任何操作情况下,超过有效期token则会失效,为了避免用户在进行繁杂的操作过程中token突然失效退出登录导致前面的操作都无效的问题,可以有以下解决方案:
- 设计用户自行暂存功能
- token自动刷新并继续请求当前接口 http请求保存当前请求,监测401状态值,调用刷新token接口并重新发送当前请求
- 监测用户鼠标移动,利用防抖定时请求用户信息接口
// 监听鼠标是否在移动,若在移动则定时请求用户信息接口以防在操作过程中token失效问题
window.addEventListener('mousemove', throttle(() => {
if (token) getUserInfo()
}, 1000 * 60 * 10))
审批流程
Activiti是一个开源的工作流引擎,用于在Java应用程序中集成业务流程管理(BPM)。在Activiti中,审批流程通常涉及以下步骤:
部署流程定义:将BPMN 2.0兼容的流程定义文件部署到Activiti引擎。 启动流程实例:使用流程定义创建一个新的流程实例。 任务列表:查询并获取参与者的任务。 完成任务:处理任务,可能会触发流程中的下一个节点。
动态获取菜单并根据平铺的菜单节点生成树
const permissions = [
{
"menuId": 1,
"name": "首页",
"permission": "/",
"parentId": 0,
"path": "/",
"sort": 0,
"type": "0", // 是否是菜单
"keepAlive": null,
"privilegeIcon": "home"
},
...
]
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
permission: boolean = true
): MenuItem {
return {
key,
icon,
children,
label,
type,
permission
} as MenuItem
}
const getFormatItem = (item, children) => {
return getItem(item?.name, item?.path, item?.parentId === 0 && item?.privilegeIcon ? <img src={`/images/menu-icon-${item?.privilegeIcon}.svg`} /> : '', children)
}
/**
格式化菜单树,对生成菜单包括子节点都进行排序以及添加图标,最后返回菜单
*/
const getFormatTree = (tree) => {
const res = [];
tree?.sort((a, b) => a?.sort - b?.sort)?.forEach((item) => {
if (item?.children && item.children.length) {
res.push(getFormatItem(item, getFormatTree(item?.children)?.sort((a, b) => a?.sort - b?.sort)))
} else res.push(getFormatItem(item))
})
return res;
}
/**
对每个节点递归查找父节点直到没有父节点位置,最后生成每个对象树
{
"menuId": 1,
"children": [
{
"menuId": 2,
"children": [
"parentId": 1
...
],
...
}
],
...
}
*/
const getObj = (data, menus) => {
if (data?.parentId === 0) {
return data;
} else {
const parent = menus.find((childItem) => childItem?.menuId === data?.parentId)
if (!parent) return null;
if (parent?.children && parent.children?.length > 0) {
const curItemExistIndex = parent.children?.findIndex((resItem) => resItem.menuId === data.menuId);
// 避免重复添加子菜单
if (curItemExistIndex !== -1) parent.children?.splice(curItemExistIndex, 1, data);
else parent.children.push(data);
} else parent.children = [data];
if (parent?.parentId === 0) {
return parent;
} else {
return getObj(parent, menus)
}
}
}
/**
生成菜单树
*/
const getTree = (menus) => {
const res = [];
menus?.forEach((item) => {
const curData = getObj(item, menus);
if (curData) {
const curItemExistIndex = res?.findIndex((resItem) => resItem.menuId === curData.menuId);
if (curItemExistIndex !== -1) res?.splice(curItemExistIndex, 1, curData);
else res.push(curData)
}
})
const formatTree = getFormatTree(res)
setMenus(formatTree)
}
const getMenus = () => {
// 获取菜单列表
const menus = permissions?.filter((item) => item?.type === '0')
// 菜单去重
let uniqueMenus = Array.from(menus.reduce((map, obj) => {
if (!map.has(obj.menuId)) map.set(obj.menuId, obj);
return map;
}, new Map()).values());
getTree(uniqueMenus)
}
antd Table拆分单元格
// 返回数据格式
const list = [
{
children: [
{
field1: 'xxx',
...
},
...
],
field1: 'xxx',
...
},
...
]
/*
以children中最终子元素作为一行,标记每行的rowSpan以及所属的groupIndex
*/
const formatTableData = (list) => {
const finalList = []
let rowIndex = 0;
list?.forEach((item, itemIndex) => {
const { children, ...otherItemParams } = item
const childrenLen = children?.length
if (childrenLen) {
children?.forEach((child, childIndex) => {
rowIndex++;
finalList.push({
rowIndex,
...child,
...otherItemParams,
rowSpan: childIndex === 0 ? childrenLen : 0,
groupIndex: itemIndex
})
})
} else {
rowIndex++;
finalList.push({ rowIndex, ...otherItemParams, rowSpan: 1, groupIndex: itemIndex })
}
})
return finalList
}
// 最后设置onCell
const columns = [
{
title: 'xxx',
dataIndex: 'xxx',
key: '',
onCell: (record) => ({
rowSpan: record?.rowSpan,
})
},
...
]
// 如果返回的数据中出现多个维度的,彼此之间没有从属关系的个情况下,比较每个children的长度,哪个子节点多就设为最小行单位
const list = [
{
children1: [
{
field1: 'xxx',
...
},
...
],
children2: [
{
field1: 'xxx',
...
},
...
],
field1: 'xxx',
...
},
...
];
const formatTableData = (list) => {
const finalList = []
let rowIndex = 0;
list?.forEach((item, itemIndex) => {
const { children1, children2, ...otherItemParams } = item
const children1Len = children1?.length
const children2Len = children2?.length
const array = children1Len >= children2Len ? children1?.map((child, childIndex) => {
const avgRow = Math.floor(children1Len / children2Len)
return {
children1Field1: child?.field1,
children2Field1: children2?.[Math.floor(childIndex / avgRow)]?.field1,
children2SingleRow: childIndex % avgRow === 0 ? (Math.floor(childIndex / avgRow) < children2Len ? avgRow : 1) : 0,
}
}) : children2?.map((child, childIndex) => {
...
}
})
if (array?.length) {
array?.forEach((arrayItem, arrayIndex) => {
rowIndex++;
finalList.push({
rowIndex,
...arrayItem,
...otherItemParams,
rowSpan: arrayIndex === 0 ? array?.length : 0,
groupIndex: itemIndex
})
})
} else {
rowIndex++;
finalList.push({ rowIndex, ...otherItemParams, rowSpan: 1, groupIndex: itemIndex })
}
})
return list
}
添加删除单元格就是添加删除子节点
mobx
MobX是一个简单、可扩展的状态管理库,通过透明的函数响应式编程(TFRP)使得状态管理变得简单且可扩展。它通过观察状态的变化来自动更新组件,而不需要手动编写更新逻辑。
MobX的核心概念和原理
MobX的核心概念包括:
Observable:将数据转变为可观察的,当数据变化时,自动通知依赖的数据。 Action:任何用来修改状态的操作,通过action修改状态后,会自动触发相关的更新。 Computed:根据现有状态计算得出的值,当依赖的状态变化时,自动重新计算。 Reaction:对状态变化做出的反应,用于执行副作用操作。 MobX通过使用响应式编程,当状态发生变化时,自动通知依赖的数据进行更新。这与Redux不同,Redux每次修改状态都需要显式地通知所有依赖的组件进行更新,而MobX可以更高效地处理依赖关系。
MobX的使用场景和优缺点
MobX适用于需要高效状态管理的场景,特别是在React应用中。它的优点包括: 上手容易:相比Redux等状态管理库,MobX的学习曲线较平缓。 性能优越:由于MobX只更新变化的部分,而不是全局重新渲染,因此在大型应用中表现更好。 灵活性高:可以组织状态为类或对象,而不是函数式的reducer。
缺点包括: 生态不如Redux丰富:虽然功能强大,但在一些复杂场景下的解决方案可能不如Redux多样。 兼容性问题:不同版本的MobX在语法和支持的浏览器上有所不同,需要特别注意版本兼容性。
页面搜索条件,页码查询接口请求等都可以写到mobx中,避免重复请求接口以及可以记住搜索条件避免重复搜索
布局
Grid布局
用于涉及多行多列
// 设置两行三列的网格布局
.parentBox {
display: grid;
grid-template-columns: 3fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 1rem;
}
// 第一个盒子占两行两列,第二个和第三个盒子分别占一行一列
.childBox1 {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.childBox2 {
grid-column: 3 / 4;
grid-row: 1 / 2;
}
.childBox3 {
grid-column: 3 / 4;
grid-row: 2 / 3;
}
flex布局
用于涉及一行多列
格式化金额
/**
* 千分位加逗号
*/
export function formatMoney(value) {
return value?.toString()?.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
获取选中文本
/**
* 获取选中文本
*/
export const getSelectedText = () => {
let selectedText = ''
if (window.getSelection) {
selectedText = window.getSelection().toString()
} else if (document.selection && document.selection.type !== 'Control') {
selectedText = document.selection.createRange().text
}
return selectedText
}
递归平铺路由
/**
* 平铺路由
*/
export const flattenRoutes = (routes) => {
let data: any = []
routes.forEach((item) => {
if (item.children) {
data = data.concat(flattenRoutes(item.children))
} else {
data.push(item)
}
})
return data
}