mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-10 03:33:14 +08:00
refactor(components): 移除废弃的Description组件并改用原生antd组件
移除已废弃的Description组件及其相关类型定义和hook,替换为直接使用antd原生Descriptions组件 更新user-reset-pwd-modal和user-info-modal使用新的实现方式
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
export { default as Description } from './src/description.vue';
|
|
||||||
export * from './src/typing';
|
|
||||||
export { useDescription } from './src/useDescription';
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
<script lang="tsx">
|
|
||||||
import type { CardSize } from 'antdv-next/es/card/Card';
|
|
||||||
import type { DescriptionsProps } from 'antdv-next/es/descriptions';
|
|
||||||
|
|
||||||
import type { CSSProperties, PropType, Slots } from 'vue';
|
|
||||||
|
|
||||||
import type { DescInstance, DescItem, DescriptionProps } from './typing';
|
|
||||||
|
|
||||||
import { computed, defineComponent, ref, toRefs, unref, useAttrs } from 'vue';
|
|
||||||
|
|
||||||
import { Card, Descriptions } from 'antdv-next';
|
|
||||||
import { get, isFunction } from 'lodash-es';
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
bordered: { default: true, type: Boolean },
|
|
||||||
column: {
|
|
||||||
default: () => {
|
|
||||||
return { lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 };
|
|
||||||
},
|
|
||||||
type: [Number, Object],
|
|
||||||
},
|
|
||||||
data: { type: Object },
|
|
||||||
schema: {
|
|
||||||
default: () => [],
|
|
||||||
type: Array as PropType<DescItem[]>,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: 'small',
|
|
||||||
type: String,
|
|
||||||
validator: (v: string) =>
|
|
||||||
['default', 'middle', 'small', undefined].includes(v),
|
|
||||||
},
|
|
||||||
title: { default: '', type: String },
|
|
||||||
useCollapse: { default: true, type: Boolean },
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated 使用antd原生组件替代 下个版本将会移除
|
|
||||||
*/
|
|
||||||
export default defineComponent({
|
|
||||||
emits: ['register'],
|
|
||||||
// eslint-disable-next-line vue/order-in-components
|
|
||||||
name: 'Description',
|
|
||||||
// eslint-disable-next-line vue/order-in-components
|
|
||||||
props,
|
|
||||||
setup(props, { emit, slots }) {
|
|
||||||
const propsRef = ref<null | Partial<DescriptionProps>>(null);
|
|
||||||
|
|
||||||
const prefixCls = 'description';
|
|
||||||
const attrs = useAttrs();
|
|
||||||
|
|
||||||
// Custom title component: get title
|
|
||||||
const getMergeProps = computed(() => {
|
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
...(unref(propsRef) as any),
|
|
||||||
} as DescriptionProps;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getProps = computed(() => {
|
|
||||||
const opt = {
|
|
||||||
...unref(getMergeProps),
|
|
||||||
title: undefined,
|
|
||||||
};
|
|
||||||
return opt as DescriptionProps;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: Whether to setting title
|
|
||||||
*/
|
|
||||||
const useWrapper = computed(() => !!unref(getMergeProps).title);
|
|
||||||
|
|
||||||
const getDescriptionsProps = computed(() => {
|
|
||||||
return { ...unref(attrs), ...unref(getProps) } as DescriptionsProps;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description:设置desc
|
|
||||||
*/
|
|
||||||
function setDescProps(descProps: Partial<DescriptionProps>): void {
|
|
||||||
// Keep the last setDrawerProps
|
|
||||||
propsRef.value = {
|
|
||||||
...(unref(propsRef) as Record<string, any>),
|
|
||||||
...descProps,
|
|
||||||
} as Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent line breaks
|
|
||||||
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
|
|
||||||
if (!labelStyle && !labelMinWidth) {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelStyles: CSSProperties = {
|
|
||||||
...labelStyle,
|
|
||||||
minWidth: `${labelMinWidth}px `,
|
|
||||||
};
|
|
||||||
return <div style={labelStyles}>{label}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderItem() {
|
|
||||||
const { data, schema } = unref(getProps);
|
|
||||||
return unref(schema)
|
|
||||||
.map((item) => {
|
|
||||||
const { contentMinWidth, field, render, show, span } = item;
|
|
||||||
|
|
||||||
if (show && isFunction(show) && !show(data)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getContent = () => {
|
|
||||||
const _data = unref(getProps)?.data;
|
|
||||||
if (!_data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const getField = get(_data, field);
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (getField && !toRefs(_data).hasOwnProperty(field)) {
|
|
||||||
return isFunction(render) ? render!('', _data) : '';
|
|
||||||
}
|
|
||||||
return isFunction(render)
|
|
||||||
? render!(getField, _data)
|
|
||||||
: (getField ?? '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const width = contentMinWidth;
|
|
||||||
return (
|
|
||||||
<Descriptions.Item
|
|
||||||
key={field}
|
|
||||||
label={renderLabel(item)}
|
|
||||||
span={span}
|
|
||||||
>
|
|
||||||
{() => {
|
|
||||||
if (!contentMinWidth) {
|
|
||||||
return getContent();
|
|
||||||
}
|
|
||||||
const style: CSSProperties = {
|
|
||||||
minWidth: `${width}px`,
|
|
||||||
};
|
|
||||||
return <div style={style}>{getContent()}</div>;
|
|
||||||
}}
|
|
||||||
</Descriptions.Item>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter((item) => !!item);
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderDesc = () => {
|
|
||||||
return (
|
|
||||||
<Descriptions
|
|
||||||
class={`${prefixCls}`}
|
|
||||||
{...(unref(getDescriptionsProps) as any)}
|
|
||||||
>
|
|
||||||
{renderItem()}
|
|
||||||
</Descriptions>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderContainer = () => {
|
|
||||||
const content = props.useCollapse ? (
|
|
||||||
renderDesc()
|
|
||||||
) : (
|
|
||||||
<div>{renderDesc()}</div>
|
|
||||||
);
|
|
||||||
// Reduce the dom level
|
|
||||||
if (!props.useCollapse) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const { canExpand, helpMessage } = unref(getCollapseOptions);
|
|
||||||
const { title } = unref(getMergeProps);
|
|
||||||
|
|
||||||
function getSlot(slots: Slots, slot = 'default', data?: any) {
|
|
||||||
if (!slots || !Reflect.has(slots, slot)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!isFunction(slots[slot])) {
|
|
||||||
console.error(`${slot} is not a function!`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const slotFn = slots[slot];
|
|
||||||
if (!slotFn) return null;
|
|
||||||
const params = { ...data };
|
|
||||||
return slotFn(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card size={props.size as CardSize} title={title}>
|
|
||||||
{{
|
|
||||||
default: () => content,
|
|
||||||
extra: () => getSlot(slots, 'extra'),
|
|
||||||
}}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const methods: DescInstance = {
|
|
||||||
setDescProps,
|
|
||||||
};
|
|
||||||
|
|
||||||
emit('register', methods);
|
|
||||||
return () => (unref(useWrapper) ? renderContainer() : renderDesc());
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import type { DescriptionsProps } from 'antdv-next/es/descriptions';
|
|
||||||
import type { JSX } from 'vue/jsx-runtime';
|
|
||||||
|
|
||||||
import type { CSSProperties, VNode } from 'vue';
|
|
||||||
|
|
||||||
import type { Recordable } from '@vben/types';
|
|
||||||
|
|
||||||
export interface DescItem {
|
|
||||||
labelMinWidth?: number;
|
|
||||||
contentMinWidth?: number;
|
|
||||||
labelStyle?: CSSProperties;
|
|
||||||
field: string;
|
|
||||||
label: JSX.Element | string | VNode;
|
|
||||||
// Merge column
|
|
||||||
span?: number;
|
|
||||||
show?: (...arg: any) => boolean;
|
|
||||||
// render
|
|
||||||
render?: (
|
|
||||||
val: any,
|
|
||||||
data: Recordable<any>,
|
|
||||||
) => Element | JSX.Element | number | string | undefined | VNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DescriptionProps extends DescriptionsProps {
|
|
||||||
// Whether to include the collapse component
|
|
||||||
useCollapse?: boolean;
|
|
||||||
/**
|
|
||||||
* item configuration
|
|
||||||
* @type DescItem
|
|
||||||
*/
|
|
||||||
schema: DescItem[];
|
|
||||||
/**
|
|
||||||
* 数据
|
|
||||||
* @type object
|
|
||||||
*/
|
|
||||||
data: Recordable<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DescInstance {
|
|
||||||
setDescProps(descProps: Partial<DescriptionProps>, delay?: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Register = (descInstance: DescInstance) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description:
|
|
||||||
*/
|
|
||||||
export type UseDescReturnType = [Register, DescInstance];
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import type {
|
|
||||||
DescInstance,
|
|
||||||
DescriptionProps,
|
|
||||||
UseDescReturnType,
|
|
||||||
} from './typing';
|
|
||||||
|
|
||||||
import { getCurrentInstance, ref, unref } from 'vue';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated 使用antd原生组件替代 下个版本将会移除
|
|
||||||
*/
|
|
||||||
export function useDescription(
|
|
||||||
props?: Partial<DescriptionProps>,
|
|
||||||
): UseDescReturnType {
|
|
||||||
if (!getCurrentInstance()) {
|
|
||||||
throw new Error(
|
|
||||||
'useDescription() can only be used inside setup() or functional components!',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const desc = ref<DescInstance | null>(null);
|
|
||||||
const loaded = ref(false);
|
|
||||||
|
|
||||||
function register(instance: DescInstance) {
|
|
||||||
// if (unref(loaded) && import.meta.env.PROD) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
desc.value = instance;
|
|
||||||
props && instance.setDescProps(props);
|
|
||||||
loaded.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const methods: DescInstance = {
|
|
||||||
setDescProps: (
|
|
||||||
descProps: Partial<DescriptionProps>,
|
|
||||||
delay = false,
|
|
||||||
): void => {
|
|
||||||
if (!delay) {
|
|
||||||
unref(desc)?.setDescProps(descProps);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 奇怪的问题 在modal中需要setTimeout才会生效
|
|
||||||
setTimeout(() => unref(desc)?.setDescProps(descProps));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return [register, methods];
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
|
import type { DescriptionsProps } from 'antdv-next';
|
||||||
|
|
||||||
import type { User } from '#/api/system/user/model';
|
import type { User } from '#/api/system/user/model';
|
||||||
|
|
||||||
import { computed, shallowRef } from 'vue';
|
import { computed, shallowRef } from 'vue';
|
||||||
@@ -12,7 +14,8 @@ import duration from 'dayjs/plugin/duration';
|
|||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
import { findUserInfo } from '#/api/system/user';
|
import { findUserInfo } from '#/api/system/user';
|
||||||
import { renderDict } from '#/utils/render';
|
import { DictTag } from '#/components/dict';
|
||||||
|
import { getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
@@ -83,70 +86,74 @@ const diffLoginTime = computed(() => {
|
|||||||
const diffText = dayjs.duration(diffSeconds, 'seconds').humanize();
|
const diffText = dayjs.duration(diffSeconds, 'seconds').humanize();
|
||||||
return diffText;
|
return diffText;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusOptions = getDictOptions(DictEnum.SYS_NORMAL_DISABLE);
|
||||||
|
const items = computed<DescriptionsProps['items']>(() => {
|
||||||
|
if (!currentUser.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const { userId, status } = currentUser.value;
|
||||||
|
return [
|
||||||
|
{ label: 'userId', content: userId },
|
||||||
|
{
|
||||||
|
label: '用户状态',
|
||||||
|
content: <DictTag dicts={statusOptions} value={status} />,
|
||||||
|
},
|
||||||
|
{ label: '用户信息', content: mixInfo.value },
|
||||||
|
{ label: '手机号', content: currentUser.value.phonenumber || '-' },
|
||||||
|
{ label: '邮箱', content: currentUser.value.email || '-' },
|
||||||
|
{
|
||||||
|
label: '岗位',
|
||||||
|
content: (
|
||||||
|
<div class="flex flex-wrap gap-0.5">
|
||||||
|
{currentUser.value.postNames.map((item) => (
|
||||||
|
<Tag key={item}>{item}</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '权限',
|
||||||
|
content: (
|
||||||
|
<div class="flex flex-wrap gap-0.5">
|
||||||
|
{currentUser.value.roleNames.map((item) => (
|
||||||
|
<Tag key={item}>{item}</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ label: '创建时间', content: currentUser.value.createTime },
|
||||||
|
{ label: '上次登录IP', content: currentUser.value.loginIp || '-' },
|
||||||
|
{ label: '上次登录时间', content: currentUser.value.loginDate || '-' },
|
||||||
|
{
|
||||||
|
label: '上次登录时间',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<span>{currentUser.value.loginDate ?? '-'}</span>
|
||||||
|
<Tag
|
||||||
|
bordered={false}
|
||||||
|
class="ml-2"
|
||||||
|
color="processing"
|
||||||
|
v-if="diffLoginTime"
|
||||||
|
>
|
||||||
|
{diffLoginTime}前
|
||||||
|
</Tag>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ label: '备注', content: currentUser.value.remark || '-' },
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicModal :footer="false" :fullscreen-button="false" title="用户信息">
|
<BasicModal :footer="false" :fullscreen-button="false" title="用户信息">
|
||||||
<Descriptions v-if="currentUser" size="small" :column="1" bordered>
|
<Descriptions
|
||||||
<Descriptions.Item label="userId">
|
v-if="currentUser"
|
||||||
{{ currentUser.userId }}
|
size="small"
|
||||||
</Descriptions.Item>
|
:column="1"
|
||||||
<Descriptions.Item label="用户状态">
|
bordered
|
||||||
<component
|
:items="items"
|
||||||
:is="renderDict(currentUser.status, DictEnum.SYS_NORMAL_DISABLE)"
|
/>
|
||||||
/>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="用户信息">
|
|
||||||
{{ mixInfo }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="手机号">
|
|
||||||
{{ currentUser.phonenumber || '-' }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="邮箱">
|
|
||||||
{{ currentUser.email || '-' }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="岗位">
|
|
||||||
<div
|
|
||||||
v-if="currentUser.postNames.length > 0"
|
|
||||||
class="flex flex-wrap gap-0.5"
|
|
||||||
>
|
|
||||||
<Tag v-for="item in currentUser.postNames" :key="item">
|
|
||||||
{{ item }}
|
|
||||||
</Tag>
|
|
||||||
</div>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="权限">
|
|
||||||
<div
|
|
||||||
v-if="currentUser.roleNames.length > 0"
|
|
||||||
class="flex flex-wrap gap-0.5"
|
|
||||||
>
|
|
||||||
<Tag v-for="item in currentUser.roleNames" :key="item">
|
|
||||||
{{ item }}
|
|
||||||
</Tag>
|
|
||||||
</div>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="创建时间">
|
|
||||||
{{ currentUser.createTime }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="上次登录IP">
|
|
||||||
{{ currentUser.loginIp ?? '-' }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="上次登录时间">
|
|
||||||
<span>{{ currentUser.loginDate ?? '-' }}</span>
|
|
||||||
<Tag
|
|
||||||
class="ml-2"
|
|
||||||
v-if="diffLoginTime"
|
|
||||||
:bordered="false"
|
|
||||||
color="processing"
|
|
||||||
>
|
|
||||||
{{ diffLoginTime }}前
|
|
||||||
</Tag>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="备注">
|
|
||||||
{{ currentUser.remark ?? '-' }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { DescriptionsProps } from 'antdv-next';
|
||||||
|
|
||||||
import type { ResetPwdParam, User } from '#/api/system/user/model';
|
import type { ResetPwdParam, User } from '#/api/system/user/model';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal, z } from '@vben/common-ui';
|
import { useVbenModal, z } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Descriptions, DescriptionsItem } from 'antdv-next';
|
import { Descriptions } from 'antdv-next';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { userResetPassword } from '#/api/system/user';
|
import { userResetPassword } from '#/api/system/user';
|
||||||
@@ -86,6 +88,17 @@ async function handleClosed() {
|
|||||||
await formApi.resetForm();
|
await formApi.resetForm();
|
||||||
currentUser.value = null;
|
currentUser.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const items = computed<DescriptionsProps['items']>(() => {
|
||||||
|
if (!currentUser.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{ label: '用户ID', content: currentUser.value.userId },
|
||||||
|
{ label: '用户名', content: currentUser.value.userName },
|
||||||
|
{ label: '昵称', content: currentUser.value.nickName },
|
||||||
|
];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -95,17 +108,7 @@ async function handleClosed() {
|
|||||||
title="重置密码"
|
title="重置密码"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-[12px]">
|
<div class="flex flex-col gap-[12px]">
|
||||||
<Descriptions v-if="currentUser" size="small" :column="1" bordered>
|
<Descriptions size="small" :column="1" bordered :items="items" />
|
||||||
<DescriptionsItem label="用户ID">
|
|
||||||
{{ currentUser.userId }}
|
|
||||||
</DescriptionsItem>
|
|
||||||
<DescriptionsItem label="用户名">
|
|
||||||
{{ currentUser.userName }}
|
|
||||||
</DescriptionsItem>
|
|
||||||
<DescriptionsItem label="昵称">
|
|
||||||
{{ currentUser.nickName }}
|
|
||||||
</DescriptionsItem>
|
|
||||||
</Descriptions>
|
|
||||||
<BasicForm />
|
<BasicForm />
|
||||||
</div>
|
</div>
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
|
|||||||
Reference in New Issue
Block a user