feat: description组件 & 重构缓存监控

This commit is contained in:
dap 2024-09-09 16:46:58 +08:00
parent e87cc9db51
commit 83cfe991f5
5 changed files with 386 additions and 47 deletions

View File

@ -0,0 +1,7 @@
import { withInstall } from '#/utils';
import description from './src/description.vue';
export * from './src/typing';
export { useDescription } from './src/useDescription';
export const Description = withInstall(description);

View File

@ -0,0 +1,210 @@
<script lang="tsx">
import type { CardSize } from 'ant-design-vue/es/card/Card';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
import type { DescInstance, DescItem, DescriptionProps } from './typing';
import {
computed,
type CSSProperties,
defineComponent,
type PropType,
ref,
type Slots,
toRefs,
unref,
useAttrs,
} from 'vue';
import { Card, Descriptions } from 'ant-design-vue';
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 },
};
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>

View File

@ -0,0 +1,47 @@
import type { Recordable } from '@vben/types';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions';
import type { JSX } from 'vue/jsx-runtime';
import type { CSSProperties, VNode } from 'vue';
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>): void;
}
export type Register = (descInstance: DescInstance) => void;
/**
* @description:
*/
export type UseDescReturnType = [Register, DescInstance];

View File

@ -0,0 +1,36 @@
import type {
DescInstance,
DescriptionProps,
UseDescReturnType,
} from './typing';
import { getCurrentInstance, ref, unref } from 'vue';
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>): void => {
unref(desc)?.setDescProps(descProps);
},
};
return [register, methods];
}

View File

@ -1,65 +1,104 @@
<script setup lang="ts">
import type { RedisInfo } from '#/api/monitor/cache';
import type { PropType } from 'vue';
import { onMounted, type PropType, watch } from 'vue';
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
import {
type DescItem,
Description,
useDescription,
} from '#/components/description';
interface IRedisInfo extends RedisInfo {
dbSize: string;
}
defineProps({
const props = defineProps({
data: {
required: true,
type: Object as PropType<IRedisInfo>,
},
});
const descSchemas: DescItem[] = [
{ field: 'redis_version', label: 'redis版本' },
{
field: 'redis_mode',
label: 'redis模式',
render(value) {
return value === 'standalone' ? '单机模式' : '集群模式';
},
},
{
field: 'tcp_port',
label: 'tcp端口',
},
{
field: 'connected_clients',
label: '客户端数',
},
{
field: 'uptime_in_days',
label: '运行时间',
render(value) {
return `${value}`;
},
},
{
field: 'used_memory_human',
label: '使用内存',
},
{
field: 'used_cpu_user_children',
label: '使用CPU',
render(value) {
return Number.parseFloat(value).toFixed(2);
},
},
{
field: 'maxmemory_human',
label: '内存配置',
},
{
field: 'aof_enabled',
label: 'AOF是否开启',
render(value) {
return value === '0' ? '否' : '是';
},
},
{
field: 'rdb_last_bgsave_status',
label: 'RDB是否成功',
},
{
field: 'dbSize',
label: 'Key数量',
},
{
field: 'instantaneous_input_kbps',
label: '网络入口/出口',
render(_, data) {
const { instantaneous_input_kbps, instantaneous_output_kbps } = data;
return `${instantaneous_input_kbps}kps/${instantaneous_output_kbps}kps`;
},
},
];
const [registerDescription, { setDescProps }] = useDescription({
column: { lg: 4, md: 3, sm: 1, xl: 4, xs: 1 },
schema: descSchemas,
});
onMounted(() => setDescProps({ data: props.data }));
watch(
() => props.data,
(data) => {
setDescProps({ data });
},
);
</script>
<template>
<Descriptions
:column="{ xs: 1, sm: 1, md: 3, lg: 4, xl: 4 }"
bordered
size="small"
>
<DescriptionsItem label="redis版本">
{{ data.redis_version }}
</DescriptionsItem>
<DescriptionsItem label="redis模式">
{{ data.redis_mode === 'standalone' ? '单机模式' : '集群模式' }}
</DescriptionsItem>
<DescriptionsItem label="tcp端口">
{{ data.tcp_port }}
</DescriptionsItem>
<DescriptionsItem label="客户端数">
{{ data.connected_clients }}
</DescriptionsItem>
<DescriptionsItem label="运行时间">
{{ `${data.uptime_in_days}` }}
</DescriptionsItem>
<DescriptionsItem label="使用内存">
{{ data.used_memory_human }}
</DescriptionsItem>
<DescriptionsItem label="使用CPU">
{{ parseFloat(data.used_cpu_user_children!).toFixed(2) }}
</DescriptionsItem>
<DescriptionsItem label="内存配置">
{{ data.maxmemory_human }}
</DescriptionsItem>
<DescriptionsItem label="AOF是否开启">
{{ data.aof_enabled === '0' ? '否' : '是' }}
</DescriptionsItem>
<DescriptionsItem label="RDB是否成功">
{{ data.rdb_last_bgsave_status }}
</DescriptionsItem>
<DescriptionsItem label="Key数量">
{{ data.dbSize }}
</DescriptionsItem>
<DescriptionsItem label="网络入口/出口">
{{
`${data.instantaneous_input_kbps}kps/${data.instantaneous_output_kbps}kps`
}}
</DescriptionsItem>
</Descriptions>
<Description @register="registerDescription" />
</template>