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">
|
||||
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(() => {
|
||||
console.log('keepAlive测试 -> 挂载了');
|
||||
@@ -17,10 +21,19 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="m-[8px]">
|
||||
<div class="m-[16px] flex flex-col gap-[16px]">
|
||||
<Card title="测试keepAlive">
|
||||
<template #extra>
|
||||
<Button type="primary" v-access:code="['system:user:list']">
|
||||
@@ -29,5 +42,16 @@ onBeforeUnmount(() => intervalId && clearInterval(intervalId));
|
||||
</template>
|
||||
<p>当前计数: {{ count }}</p>
|
||||
</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>
|
||||
</template>
|
||||
|
Reference in New Issue
Block a user