277 lines
7.9 KiB
Markdown
277 lines
7.9 KiB
Markdown
# 工具函数
|
||
|
||
:::tip 说明
|
||
前端工具函数位于 `src/utils/` 目录,提供认证管理、本地存储、日期格式化、树结构操作、文件下载等通用能力。
|
||
:::
|
||
|
||
## auth.ts
|
||
|
||
认证与通用工具函数,位于 `src/utils/auth.ts`:
|
||
|
||
```typescript
|
||
// 外部链接判断
|
||
export function isExternal(path: string) {
|
||
return /^(https?:|mailto:|tel:)/.test(path);
|
||
}
|
||
|
||
// 路径规范化
|
||
export function getNormalPath(path: string) {
|
||
if (path.length === 0 || !path || path == 'undefined') return path;
|
||
const newPath = path.replace('//', '/');
|
||
return newPath[newPath.length - 1] === '/' ? newPath.slice(0, -1) : newPath;
|
||
}
|
||
|
||
// 文件流下载
|
||
export function streamFileDownload(file: any, fileName = '文件名称.zip') {
|
||
const blob = new Blob([file], { type: 'application/octet-stream;charset=UTF-8' });
|
||
const url = window.URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.setAttribute('download', fileName);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
window.URL.revokeObjectURL(url);
|
||
}
|
||
```
|
||
|
||
### 消息提示与确认弹窗
|
||
|
||
项目使用 NaiveUI 的 `useMessage` 和 `useDialog` 提供消息提示和确认弹窗:
|
||
|
||
```typescript
|
||
import { useMessage, useDialog } from 'naive-ui';
|
||
|
||
// 在 setup 中初始化
|
||
const message = useMessage();
|
||
const dialog = useDialog();
|
||
|
||
// 消息提示
|
||
message.success('操作成功');
|
||
message.error('操作失败');
|
||
message.warning('请注意');
|
||
|
||
// 确认弹窗
|
||
dialog.warning({
|
||
title: '提示',
|
||
content: '确定要删除?',
|
||
positiveText: '确定',
|
||
negativeText: '取消',
|
||
onPositiveClick: async () => {
|
||
// 执行删除操作
|
||
},
|
||
});
|
||
```
|
||
|
||
:::warning 注意
|
||
`useMessage` 和 `useDialog` 必须在 Vue 组件的 `setup` 中调用。如果需要在非组件环境(如 Axios 拦截器)中使用,可通过 `window.__message` 和 `window.__dialog` 全局引用访问。
|
||
:::
|
||
|
||
### 树结构操作
|
||
|
||
`auth.ts` 中还包含树结构转换工具:
|
||
|
||
```typescript
|
||
// 数组转树
|
||
export const arrayToTree = (
|
||
data: any[],
|
||
props = { id: 'id', parentId: 'pid', children: 'children' },
|
||
) => {
|
||
data = cloneDeep(data);
|
||
const { id, parentId, children } = props;
|
||
const result: any[] = [];
|
||
const map = new Map();
|
||
data.forEach((item) => {
|
||
map.set(item[id], item);
|
||
const parent = map.get(item[parentId]);
|
||
if (parent) {
|
||
parent[children] = parent[children] ?? [];
|
||
parent[children].push(item);
|
||
} else {
|
||
result.push(item);
|
||
}
|
||
});
|
||
return result;
|
||
};
|
||
|
||
// 树转数组(广度优先)
|
||
export const treeToArray = (data: any[], props = { children: 'children' }) => {
|
||
data = cloneDeep(data);
|
||
const newData = [];
|
||
const queue: any[] = [];
|
||
data.forEach((child) => queue.push(child));
|
||
while (queue.length) {
|
||
const item = queue.shift();
|
||
if (item[children]) {
|
||
item[children].forEach((child) => queue.push(child));
|
||
delete item[children];
|
||
}
|
||
newData.push(item);
|
||
}
|
||
return newData;
|
||
};
|
||
|
||
// 递归查找路径
|
||
export const findTreeByPath = (tree, path, result = []) => {
|
||
for (const node of tree) {
|
||
if (node.path === path) result.push(node);
|
||
if (node.children?.length) findTreeByPath(node.children, path, result);
|
||
}
|
||
return result;
|
||
};
|
||
```
|
||
|
||
## Storage.ts
|
||
|
||
本地存储封装,支持过期时间和 cookie,位于 `src/utils/Storage.ts`:
|
||
|
||
```typescript
|
||
const DEFAULT_CACHE_TIME = 7 * 24 * 60 * 60 * 1000; // 7 天
|
||
|
||
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => {
|
||
const Storage = class {
|
||
private storage = storage;
|
||
private prefixKey?: string = prefixKey;
|
||
|
||
private getKey(key: string) {
|
||
return `${this.prefixKey}${key}`.toUpperCase(); // 自动大写
|
||
}
|
||
|
||
// 设置缓存(支持过期时间)
|
||
set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
|
||
const stringData = JSON.stringify({
|
||
value,
|
||
expire: expire !== null ? Date.now() + expire : null,
|
||
});
|
||
this.storage.setItem(this.getKey(key), stringData);
|
||
}
|
||
|
||
// 读取缓存(过期自动删除)
|
||
get(key: string, def: any = null) {
|
||
const item = this.storage.getItem(this.getKey(key));
|
||
if (item) {
|
||
try {
|
||
const data = JSON.parse(item);
|
||
const { value, expire } = data;
|
||
if (expire === null || expire >= Date.now()) return value;
|
||
this.remove(key); // 过期删除
|
||
} catch (e) { return def; }
|
||
}
|
||
return def;
|
||
}
|
||
|
||
remove(key: string) { this.storage.removeItem(this.getKey(key)); }
|
||
clear(): void { this.storage.clear(); }
|
||
};
|
||
return new Storage();
|
||
};
|
||
|
||
export const storage = createStorage();
|
||
```
|
||
|
||
Store mutation-types 定义了存储键名:
|
||
|
||
```typescript
|
||
// src/store/mutation-types.ts
|
||
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户 token
|
||
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
|
||
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
|
||
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
|
||
export const FIRST_ROUTE = 'FIRST-ROUTE'; // 菜单第一个路由
|
||
```
|
||
|
||
## dateUtil.ts
|
||
|
||
日期格式化工具,基于 dayjs,位于 `src/utils/dateUtil.ts`:
|
||
|
||
```typescript
|
||
import dayjs from 'dayjs';
|
||
|
||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
||
const DATE_FORMAT = 'YYYY-MM-DD ';
|
||
|
||
export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
|
||
return dayjs(date).format(formatStr);
|
||
}
|
||
|
||
export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
|
||
return dayjs(date).format(formatStr);
|
||
}
|
||
```
|
||
|
||
## renderIcon 工具
|
||
|
||
图标渲染工具,用于在 TableAction 等组件中渲染 NaiveUI 图标:
|
||
|
||
```typescript
|
||
// src/utils/index.ts
|
||
import { h } from 'vue';
|
||
import { NIcon } from 'naive-ui';
|
||
|
||
export function renderIcon(icon) {
|
||
return () => h(NIcon, null, { default: () => h(icon) });
|
||
}
|
||
```
|
||
|
||
使用示例:
|
||
|
||
```typescript
|
||
import { renderIcon } from '@/utils';
|
||
import { FormOutlined, DeleteOutlined } from '@vicons/antd';
|
||
|
||
const actionColumn = {
|
||
actions: [
|
||
{ label: '编辑', icon: renderIcon(FormOutlined), type: 'info' },
|
||
{ label: '删除', icon: renderIcon(DeleteOutlined), type: 'error' },
|
||
],
|
||
};
|
||
```
|
||
|
||
## httpEnum.ts
|
||
|
||
HTTP 相关枚举定义,位于 `src/enums/httpEnum.ts`:
|
||
|
||
```typescript
|
||
export enum ResultEnum {
|
||
SUCCESS = 0, // 业务成功
|
||
ERROR = 1, // 业务失败
|
||
TIMEOUT = 401, // 登录超时
|
||
}
|
||
|
||
export enum RequestEnum {
|
||
GET = 'GET',
|
||
POST = 'POST',
|
||
PUT = 'PUT',
|
||
DELETE = 'DELETE',
|
||
}
|
||
|
||
export enum ContentTypeEnum {
|
||
JSON = 'application/json;charset=UTF-8',
|
||
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||
}
|
||
```
|
||
|
||
## pageEnum.ts
|
||
|
||
页面路径枚举,位于 `src/enums/pageEnum.ts`:
|
||
|
||
```typescript
|
||
export enum PageEnum {
|
||
BASE_LOGIN = '/login',
|
||
BASE_LOGIN_NAME = 'Login',
|
||
REDIRECT = '/redirect',
|
||
BASE_HOME = '/dashboard',
|
||
BASE_HOME_REDIRECT = '/dashboard/console',
|
||
ERROR_PAGE_NAME = 'ErrorPage',
|
||
}
|
||
```
|
||
|
||
:::tip 温馨提示
|
||
`Storage` 工具类自动将键名转为大写(如 `access-token` → `ACCESS-TOKEN`),存储时包含过期时间,读取时自动检查是否过期。项目使用 NaiveUI 的 `useMessage` 和 `useDialog` 提供消息提示和确认弹窗,`renderIcon` 工具函数用于在操作列等场景渲染 @vicons 图标。
|
||
:::
|
||
|
||
## 总结
|
||
|
||
工具函数库提供了 Storage(本地存储,支持过期)、auth(认证管理、树结构操作、文件下载)、dateUtil(日期格式化)、renderIcon(图标渲染)、httpEnum/pageEnum(枚举定义)等通用能力。消息提示使用 NaiveUI 的 `useMessage`,确认弹窗使用 `useDialog`,图标使用 `@vicons/antd` 库。
|