feat: description组件 & 重构缓存监控
This commit is contained in:
parent
e87cc9db51
commit
83cfe991f5
7
apps/web-antd/src/components/description/index.ts
Normal file
7
apps/web-antd/src/components/description/index.ts
Normal 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);
|
210
apps/web-antd/src/components/description/src/description.vue
Normal file
210
apps/web-antd/src/components/description/src/description.vue
Normal 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>
|
47
apps/web-antd/src/components/description/src/typing.ts
Normal file
47
apps/web-antd/src/components/description/src/typing.ts
Normal 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];
|
@ -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];
|
||||||
|
}
|
@ -1,65 +1,104 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RedisInfo } from '#/api/monitor/cache';
|
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 {
|
interface IRedisInfo extends RedisInfo {
|
||||||
dbSize: string;
|
dbSize: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Object as PropType<IRedisInfo>,
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Descriptions
|
<Description @register="registerDescription" />
|
||||||
: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>
|
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user