feat: 字典功能
This commit is contained in:
53
apps/web-antd/src/api/base.ts
Normal file
53
apps/web-antd/src/api/base.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { requestClient } from './request';
|
||||||
|
|
||||||
|
export type ID = number | string;
|
||||||
|
export type IDS = (number | string)[];
|
||||||
|
|
||||||
|
export interface BaseEntity {
|
||||||
|
createBy?: string;
|
||||||
|
createDept?: string;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询参数
|
||||||
|
* @param pageNum 当前页
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param orderByColumn 排序字段
|
||||||
|
* @param isAsc 是否升序
|
||||||
|
*/
|
||||||
|
export interface PageQuery {
|
||||||
|
isAsc?: boolean;
|
||||||
|
orderByColumn?: string;
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: contentType
|
||||||
|
*/
|
||||||
|
export enum ContentTypeEnum {
|
||||||
|
// form-data upload
|
||||||
|
FORM_DATA = 'multipart/form-data;charset=UTF-8',
|
||||||
|
// form-data qs
|
||||||
|
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
|
// json
|
||||||
|
JSON = 'application/json;charset=UTF-8',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用下载接口 封装一层
|
||||||
|
* @param url 请求地址
|
||||||
|
* @param data 请求参数
|
||||||
|
* @returns blob二进制
|
||||||
|
*/
|
||||||
|
export function commonExport(url: string, data: Record<string, any>) {
|
||||||
|
return requestClient.post<Blob>(url, data, {
|
||||||
|
data,
|
||||||
|
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
|
||||||
|
isTransformResponse: false,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
17
apps/web-antd/src/api/system/dict/dict-data-model.d.ts
vendored
Normal file
17
apps/web-antd/src/api/system/dict/dict-data-model.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export interface DictData {
|
||||||
|
createBy: string;
|
||||||
|
createTime: string;
|
||||||
|
cssClass: string;
|
||||||
|
default: boolean;
|
||||||
|
dictCode: number;
|
||||||
|
dictLabel: string;
|
||||||
|
dictSort: number;
|
||||||
|
dictType: string;
|
||||||
|
dictValue: string;
|
||||||
|
isDefault: string;
|
||||||
|
listClass: string;
|
||||||
|
remark: string;
|
||||||
|
status: string;
|
||||||
|
updateBy?: any;
|
||||||
|
updateTime?: any;
|
||||||
|
}
|
75
apps/web-antd/src/api/system/dict/dict-data.ts
Normal file
75
apps/web-antd/src/api/system/dict/dict-data.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import type { DictData } from './dict-data-model';
|
||||||
|
|
||||||
|
import type { ID, IDS, PageQuery } from '#/api/base';
|
||||||
|
|
||||||
|
import { commonExport } from '#/api/base';
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
enum Api {
|
||||||
|
dictDataExport = '/system/dict/data/export',
|
||||||
|
dictDataList = '/system/dict/data/list',
|
||||||
|
root = '/system/dict/data',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要是DictTag组件使用
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @returns 字典数据
|
||||||
|
*/
|
||||||
|
export function dictDataInfo(dictType: string) {
|
||||||
|
return requestClient.get<DictData[]>(`${Api.root}/type/${dictType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns 字典数据列表
|
||||||
|
*/
|
||||||
|
export function dictDataList(params?: PageQuery) {
|
||||||
|
return requestClient.get<DictData[]>(Api.dictDataList, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字典数据
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns blob
|
||||||
|
*/
|
||||||
|
export function dictDataExport(data: any) {
|
||||||
|
return commonExport(Api.dictDataExport, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
* @param dictIds 字典ID Array
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictDataRemove(dictIds: IDS) {
|
||||||
|
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictDataAdd(data: any) {
|
||||||
|
return requestClient.postWithMsg<void>(Api.root, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictDataUpdate(data: any) {
|
||||||
|
return requestClient.putWithMsg<void>(Api.root, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询字典数据详细
|
||||||
|
* @param dictCode 字典编码
|
||||||
|
* @returns 字典数据
|
||||||
|
*/
|
||||||
|
export function dictDetailInfo(dictCode: ID) {
|
||||||
|
return requestClient.get<DictData>(`${Api.root}/${dictCode}`);
|
||||||
|
}
|
8
apps/web-antd/src/api/system/dict/dict-type-model.d.ts
vendored
Normal file
8
apps/web-antd/src/api/system/dict/dict-type-model.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface DictType {
|
||||||
|
createTime: string;
|
||||||
|
dictId: number;
|
||||||
|
dictName: string;
|
||||||
|
dictType: string;
|
||||||
|
remark: string;
|
||||||
|
status: string;
|
||||||
|
}
|
84
apps/web-antd/src/api/system/dict/dict-type.ts
Normal file
84
apps/web-antd/src/api/system/dict/dict-type.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { DictType } from './dict-type-model';
|
||||||
|
|
||||||
|
import type { ID, IDS, PageQuery } from '#/api/base';
|
||||||
|
|
||||||
|
import { commonExport } from '#/api/base';
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
enum Api {
|
||||||
|
dictOptionSelectList = '/system/dict/type/optionselect',
|
||||||
|
dictTypeExport = '/system/dict/type/export',
|
||||||
|
dictTypeList = '/system/dict/type/list',
|
||||||
|
dictTypeRefreshCache = '/system/dict/type/refreshCache',
|
||||||
|
root = '/system/dict/type',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典类型列表
|
||||||
|
* @param params 请求参数
|
||||||
|
* @returns list
|
||||||
|
*/
|
||||||
|
export function dictList(params?: PageQuery) {
|
||||||
|
return requestClient.get<DictType[]>(Api.dictTypeList, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字典类型列表
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns blob
|
||||||
|
*/
|
||||||
|
export function dictExport(data: any) {
|
||||||
|
return commonExport(Api.dictTypeExport, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除字典类型
|
||||||
|
* @param dictIds 字典类型id数组
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictTypeRemove(dictIds: IDS) {
|
||||||
|
return requestClient.deleteWithMsg<void>(`${Api.root}/${dictIds.join(',')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新字典缓存
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function refreshDictTypeCache() {
|
||||||
|
return requestClient.deleteWithMsg<void>(Api.dictTypeRefreshCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictTypeAdd(data: any) {
|
||||||
|
return requestClient.postWithMsg<void>(Api.root, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
* @param data 表单参数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function dictTypeUpdate(data: any) {
|
||||||
|
return requestClient.putWithMsg<void>(Api.root, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询详情
|
||||||
|
* @param dictId 字典类型id
|
||||||
|
* @returns 信息
|
||||||
|
*/
|
||||||
|
export function dictTypeInfo(dictId: ID) {
|
||||||
|
return requestClient.get<DictType>(`${Api.root}/${dictId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下拉框 返回值和list一样
|
||||||
|
* @returns options
|
||||||
|
*/
|
||||||
|
export function dictOptionSelectList() {
|
||||||
|
return requestClient.get<DictType[]>(Api.dictOptionSelectList);
|
||||||
|
}
|
5
apps/web-antd/src/components/Dict/index.ts
Normal file
5
apps/web-antd/src/components/Dict/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { withInstall } from '#/utils';
|
||||||
|
|
||||||
|
import dictTag from './src/index.vue';
|
||||||
|
|
||||||
|
export const DictTag = withInstall(dictTag);
|
44
apps/web-antd/src/components/Dict/src/data.tsx
Normal file
44
apps/web-antd/src/components/Dict/src/data.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { type VNode } from 'vue';
|
||||||
|
|
||||||
|
import { Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
|
interface TagType {
|
||||||
|
[key: string]: { color: string; label: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagTypes: TagType = {
|
||||||
|
cyan: { color: 'cyan', label: 'cyan' },
|
||||||
|
danger: { color: 'error', label: '危险(danger)' },
|
||||||
|
/** 由于和elementUI不同 用于替换颜色 */
|
||||||
|
default: { color: 'default', label: '默认(default)' },
|
||||||
|
green: { color: 'green', label: 'green' },
|
||||||
|
info: { color: 'default', label: '信息(info)' },
|
||||||
|
orange: { color: 'orange', label: 'orange' },
|
||||||
|
/** 自定义预设 color可以为16进制颜色 */
|
||||||
|
pink: { color: 'pink', label: 'pink' },
|
||||||
|
primary: { color: 'processing', label: '主要(primary)' },
|
||||||
|
purple: { color: 'purple', label: 'purple' },
|
||||||
|
red: { color: 'red', label: 'red' },
|
||||||
|
success: { color: 'success', label: '成功(success)' },
|
||||||
|
warning: { color: 'warning', label: '警告(warning)' },
|
||||||
|
};
|
||||||
|
|
||||||
|
// 字典选择使用 { label: string; value: string }[]
|
||||||
|
interface Options {
|
||||||
|
label: string | VNode;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tagSelectOptions() {
|
||||||
|
const selectArray: Options[] = [];
|
||||||
|
Object.keys(tagTypes).forEach((key) => {
|
||||||
|
if (!tagTypes[key]) return;
|
||||||
|
const label = tagTypes[key].label;
|
||||||
|
const color = tagTypes[key].color;
|
||||||
|
selectArray.push({
|
||||||
|
label: <Tag color={color}>{label}</Tag>,
|
||||||
|
value: key,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return selectArray;
|
||||||
|
}
|
61
apps/web-antd/src/components/Dict/src/index.vue
Normal file
61
apps/web-antd/src/components/Dict/src/index.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DictData } from '#/api/system/dict/dict-data-model';
|
||||||
|
|
||||||
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { tagTypes } from './data';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DictTag' });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
dicts: DictData[]; // dict数组
|
||||||
|
value: number | string; // value
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
dicts: undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.dicts,
|
||||||
|
(value) => {
|
||||||
|
console.log('dicts change', value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const color = computed<string>(() => {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
const current = props.dicts.find((item) => item.dictValue == props.value);
|
||||||
|
const listClass = current?.listClass ?? '';
|
||||||
|
// 是否为默认的颜色
|
||||||
|
const isDefault = Reflect.has(tagTypes, listClass);
|
||||||
|
// 判断是默认还是自定义颜色
|
||||||
|
if (isDefault) {
|
||||||
|
// 这里做了antd - element-plus的兼容
|
||||||
|
return tagTypes[listClass]!.color;
|
||||||
|
}
|
||||||
|
return listClass;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssClass = computed<string>(() => {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
const current = props.dicts.find((item) => item.dictValue == props.value);
|
||||||
|
return current?.cssClass ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = computed<number | string>(() => {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
const current = props.dicts.find((item) => item.dictValue == props.value);
|
||||||
|
return current?.dictLabel ?? 'unknown';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Tag v-if="color" :class="cssClass" :color="color">{{ label }}</Tag>
|
||||||
|
<div v-if="!color" :class="cssClass">{{ label }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
99
apps/web-antd/src/store/dict.ts
Normal file
99
apps/web-antd/src/store/dict.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import type { DictData } from '#/api/system/dict/dict-data-model';
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* antd使用 select和radio通用
|
||||||
|
*/
|
||||||
|
export interface Option {
|
||||||
|
disabled?: boolean;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dictToOptions(data: DictData[]): Option[] {
|
||||||
|
return data.map((item) => ({
|
||||||
|
label: item.dictLabel,
|
||||||
|
value: item.dictValue,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDictStore = defineStore('app-dict', () => {
|
||||||
|
/**
|
||||||
|
* 一般是dictTag使用
|
||||||
|
*/
|
||||||
|
const dictMap = reactive(new Map<string, DictData[]>());
|
||||||
|
/**
|
||||||
|
* select radio radioButton使用 只能为固定格式(Option)
|
||||||
|
*/
|
||||||
|
const dictOptionsMap = reactive(new Map<string, Option[]>());
|
||||||
|
|
||||||
|
function getDict(dictName: string): DictData[] {
|
||||||
|
if (!dictName) return [];
|
||||||
|
// 没有key 添加一个空数组
|
||||||
|
if (!dictMap.has(dictName)) {
|
||||||
|
dictMap.set(dictName, reactive([]));
|
||||||
|
}
|
||||||
|
// 这里拿到的就不可能为空了
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return dictMap.get(dictName)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDictOptions(dictName: string): Option[] {
|
||||||
|
if (!dictName) return [];
|
||||||
|
// 没有key 添加一个空数组
|
||||||
|
if (!dictOptionsMap.has(dictName)) {
|
||||||
|
dictOptionsMap.set(dictName, []);
|
||||||
|
}
|
||||||
|
// 这里拿到的就不可能为空了
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
return dictOptionsMap.get(dictName)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCache() {
|
||||||
|
dictMap.clear();
|
||||||
|
dictOptionsMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心逻辑
|
||||||
|
*
|
||||||
|
* 不能直接粗暴使用set 会导致之前return的空数组跟现在的数组指向不是同一个地址 数据也就为空了
|
||||||
|
*
|
||||||
|
* 判断是否已经存在key 并且数组长度为0 说明该次要处理的数据是return的空数组 直接push(不修改指向)
|
||||||
|
* 否则 直接set
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function setDictInfo(dictName: string, dictValue: DictData[]) {
|
||||||
|
if (dictMap.has(dictName) && dictMap.get(dictName)?.length === 0) {
|
||||||
|
dictMap.get(dictName)?.push(...dictValue);
|
||||||
|
} else {
|
||||||
|
dictMap.set(dictName, dictValue);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
dictOptionsMap.has(dictName) &&
|
||||||
|
dictOptionsMap.get(dictName)?.length === 0
|
||||||
|
) {
|
||||||
|
dictOptionsMap.get(dictName)?.push(...dictToOptions(dictValue));
|
||||||
|
} else {
|
||||||
|
dictOptionsMap.set(dictName, dictToOptions(dictValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function $reset() {
|
||||||
|
dictMap.clear();
|
||||||
|
dictOptionsMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
$reset,
|
||||||
|
dictMap,
|
||||||
|
dictOptionsMap,
|
||||||
|
getDict,
|
||||||
|
getDictOptions,
|
||||||
|
resetCache,
|
||||||
|
setDictInfo,
|
||||||
|
};
|
||||||
|
});
|
55
apps/web-antd/src/utils/dict.ts
Normal file
55
apps/web-antd/src/utils/dict.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import type { DictData } from '#/api/system/dict/dict-data-model';
|
||||||
|
|
||||||
|
import { dictDataInfo } from '#/api/system/dict/dict-data';
|
||||||
|
import { type Option, useDictStore } from '#/store/dict';
|
||||||
|
// todo 重复代码的封装
|
||||||
|
/**
|
||||||
|
* 添加一个字典请求状态的缓存
|
||||||
|
*
|
||||||
|
* 主要解决多次请求重复api的问题(不能用abortController 会导致除了第一个其他的获取的全为空)
|
||||||
|
* 比如在一个页面 index表单 modal drawer总共会请求三次 但是获取的都是一样的数据
|
||||||
|
*/
|
||||||
|
const dictRequestCache = new Map<string, Promise<DictData[] | void>>();
|
||||||
|
|
||||||
|
export function getDict(dictName: string): DictData[] {
|
||||||
|
const { getDict, setDictInfo } = useDictStore();
|
||||||
|
// 这里拿到
|
||||||
|
const dictList = getDict(dictName);
|
||||||
|
if (
|
||||||
|
dictList.length === 0 && // 检查请求状态缓存
|
||||||
|
!dictRequestCache.has(dictName)
|
||||||
|
) {
|
||||||
|
dictRequestCache.set(
|
||||||
|
dictName,
|
||||||
|
dictDataInfo(dictName).then((resp) => {
|
||||||
|
// 缓存到store 这样就不用重复获取了
|
||||||
|
// 内部处理了push的逻辑 这里不用push
|
||||||
|
setDictInfo(dictName, resp);
|
||||||
|
// 移除请求状态缓存
|
||||||
|
dictRequestCache.delete(dictName);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dictList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDictOptions(dictName: string): Option[] {
|
||||||
|
const { getDictOptions, setDictInfo } = useDictStore();
|
||||||
|
const dictOptionList = getDictOptions(dictName);
|
||||||
|
if (
|
||||||
|
dictOptionList.length === 0 && // 检查请求状态缓存
|
||||||
|
!dictRequestCache.has(dictName)
|
||||||
|
) {
|
||||||
|
dictRequestCache.set(
|
||||||
|
dictName,
|
||||||
|
dictDataInfo(dictName).then((resp) => {
|
||||||
|
// 缓存到store 这样就不用重复获取了
|
||||||
|
// 内部处理了push的逻辑 这里不用push
|
||||||
|
setDictInfo(dictName, resp);
|
||||||
|
// 移除请求状态缓存
|
||||||
|
dictRequestCache.delete(dictName);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return dictOptionList;
|
||||||
|
}
|
180
apps/web-antd/src/utils/index.ts
Normal file
180
apps/web-antd/src/utils/index.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import type {
|
||||||
|
RouteLocationNormalized,
|
||||||
|
RouteRecordNormalized,
|
||||||
|
} from 'vue-router';
|
||||||
|
|
||||||
|
import type { App, Component } from 'vue';
|
||||||
|
import { unref } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
intersectionWith,
|
||||||
|
isArray,
|
||||||
|
isEqual,
|
||||||
|
isObject,
|
||||||
|
mergeWith,
|
||||||
|
unionWith,
|
||||||
|
} from 'lodash-es';
|
||||||
|
|
||||||
|
export const noop = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Set ui mount node
|
||||||
|
*/
|
||||||
|
export function getPopupContainer(node?: HTMLElement): HTMLElement {
|
||||||
|
return (node?.parentNode as HTMLElement) ?? document.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the object as a parameter to the URL
|
||||||
|
* @param baseUrl url
|
||||||
|
* @param obj
|
||||||
|
* @returns {string}
|
||||||
|
* eg:
|
||||||
|
* let obj = {a: '3', b: '4'}
|
||||||
|
* setObjToUrlParams('www.baidu.com', obj)
|
||||||
|
* ==>www.baidu.com?a=3&b=4
|
||||||
|
*/
|
||||||
|
export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||||
|
let parameters = '';
|
||||||
|
for (const key in obj) {
|
||||||
|
parameters += `${key}=${encodeURIComponent(obj[key])}&`;
|
||||||
|
}
|
||||||
|
parameters = parameters.replace(/&$/, '');
|
||||||
|
return /\?$/.test(baseUrl)
|
||||||
|
? baseUrl + parameters
|
||||||
|
: baseUrl.replace(/\/?$/, '?') + parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively merge two objects.
|
||||||
|
* 递归合并两个对象。
|
||||||
|
*
|
||||||
|
* @param source The source object to merge from. 要合并的源对象。
|
||||||
|
* @param target The target object to merge into. 目标对象,合并后结果存放于此。
|
||||||
|
* @param mergeArrays How to merge arrays. Default is "replace".
|
||||||
|
* 如何合并数组。默认为replace。
|
||||||
|
* - "union": Union the arrays. 对数组执行并集操作。
|
||||||
|
* - "intersection": Intersect the arrays. 对数组执行交集操作。
|
||||||
|
* - "concat": Concatenate the arrays. 连接数组。
|
||||||
|
* - "replace": Replace the source array with the target array. 用目标数组替换源数组。
|
||||||
|
* @returns The merged object. 合并后的对象。
|
||||||
|
*/
|
||||||
|
export function deepMerge<
|
||||||
|
T extends null | object | undefined,
|
||||||
|
U extends null | object | undefined,
|
||||||
|
>(
|
||||||
|
source: T,
|
||||||
|
target: U,
|
||||||
|
mergeArrays: 'concat' | 'intersection' | 'replace' | 'union' = 'replace',
|
||||||
|
): T & U {
|
||||||
|
if (!target) {
|
||||||
|
return source as T & U;
|
||||||
|
}
|
||||||
|
if (!source) {
|
||||||
|
return target as T & U;
|
||||||
|
}
|
||||||
|
return mergeWith({}, source, target, (sourceValue, targetValue) => {
|
||||||
|
if (isArray(targetValue) && isArray(sourceValue)) {
|
||||||
|
switch (mergeArrays) {
|
||||||
|
case 'concat': {
|
||||||
|
return [...sourceValue, ...targetValue];
|
||||||
|
}
|
||||||
|
case 'intersection': {
|
||||||
|
return intersectionWith(sourceValue, targetValue, isEqual);
|
||||||
|
}
|
||||||
|
case 'replace': {
|
||||||
|
return targetValue;
|
||||||
|
}
|
||||||
|
case 'union': {
|
||||||
|
return unionWith(sourceValue, targetValue, isEqual);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(
|
||||||
|
`Unknown merge array strategy: ${mergeArrays as string}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isObject(targetValue) && isObject(sourceValue)) {
|
||||||
|
return deepMerge(sourceValue, targetValue, mergeArrays);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openWindow(
|
||||||
|
url: string,
|
||||||
|
opt?: {
|
||||||
|
noopener?: boolean;
|
||||||
|
noreferrer?: boolean;
|
||||||
|
target?: '_blank' | '_self' | string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const { noopener = true, noreferrer = true, target = '__blank' } = opt || {};
|
||||||
|
const feature: string[] = [];
|
||||||
|
|
||||||
|
noopener && feature.push('noopener=yes');
|
||||||
|
noreferrer && feature.push('noreferrer=yes');
|
||||||
|
|
||||||
|
window.open(url, target, feature.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamic use hook props
|
||||||
|
export function getDynamicProps<T extends Record<string, unknown>, U>(
|
||||||
|
props: T,
|
||||||
|
): Partial<U> {
|
||||||
|
const ret: Record<string, any> = {};
|
||||||
|
|
||||||
|
Object.keys(props).forEach((key) => {
|
||||||
|
ret[key] = unref((props as Record<string, any>)[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret as Partial<U>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRawRoute(
|
||||||
|
route: RouteLocationNormalized,
|
||||||
|
): RouteLocationNormalized {
|
||||||
|
if (!route) return route;
|
||||||
|
const { matched, ...opt } = route;
|
||||||
|
return {
|
||||||
|
...opt,
|
||||||
|
matched: (matched
|
||||||
|
? matched.map((item) => ({
|
||||||
|
meta: item.meta,
|
||||||
|
name: item.name,
|
||||||
|
path: item.path,
|
||||||
|
}))
|
||||||
|
: undefined) as RouteRecordNormalized[],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/vant-ui/vant/issues/8302
|
||||||
|
interface EventShim {
|
||||||
|
new (...args: any[]): {
|
||||||
|
$props: {
|
||||||
|
onClick?: (...args: any[]) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WithInstall<T> = {
|
||||||
|
install(app: App): void;
|
||||||
|
} & EventShim &
|
||||||
|
T;
|
||||||
|
|
||||||
|
export type CustomComponent = { displayName?: string } & Component;
|
||||||
|
|
||||||
|
export const withInstall = <T extends CustomComponent>(
|
||||||
|
component: T,
|
||||||
|
alias?: string,
|
||||||
|
) => {
|
||||||
|
(component as Record<string, unknown>).install = (app: App) => {
|
||||||
|
const compName = component.name || component.displayName;
|
||||||
|
if (!compName) return;
|
||||||
|
app.component(compName, component);
|
||||||
|
if (alias) {
|
||||||
|
app.config.globalProperties[alias] = component;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return component as WithInstall<T>;
|
||||||
|
};
|
@@ -1,7 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { Button, Card } from 'ant-design-vue';
|
import { Button, Card, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DictTag } from '#/components/Dict';
|
||||||
|
import { useDictStore } from '#/store/dict';
|
||||||
|
import { getDict, getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('keepAlive测试 -> 挂载了');
|
console.log('keepAlive测试 -> 挂载了');
|
||||||
@@ -17,10 +21,19 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => intervalId && clearInterval(intervalId));
|
onBeforeUnmount(() => intervalId && clearInterval(intervalId));
|
||||||
|
|
||||||
|
const sexOptions = getDictOptions('sys_user_sex');
|
||||||
|
const disabledDict = getDict('sys_normal_disable');
|
||||||
|
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(dictStore.dictMap);
|
||||||
|
console.log(dictStore.dictOptionsMap);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="m-[8px]">
|
<div class="m-[16px] flex flex-col gap-[16px]">
|
||||||
<Card title="测试keepAlive">
|
<Card title="测试keepAlive">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<Button type="primary" v-access:code="['system:user:list']">
|
<Button type="primary" v-access:code="['system:user:list']">
|
||||||
@@ -29,5 +42,16 @@ onBeforeUnmount(() => intervalId && clearInterval(intervalId));
|
|||||||
</template>
|
</template>
|
||||||
<p>当前计数: {{ count }}</p>
|
<p>当前计数: {{ count }}</p>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="字典测试">
|
||||||
|
<div class="flex items-center gap-[16px]">
|
||||||
|
<Select
|
||||||
|
:options="sexOptions"
|
||||||
|
class="w-[200px]"
|
||||||
|
placeholder="请选择性别"
|
||||||
|
/>
|
||||||
|
<DictTag :dicts="disabledDict" value="0" />
|
||||||
|
<DictTag :dicts="disabledDict" value="1" />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Reference in New Issue
Block a user