update ServerDashboard
This commit is contained in:
193
src/components/ProgressGroup.vue
Normal file
193
src/components/ProgressGroup.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="progress-group">
|
||||
<div
|
||||
class="bar"
|
||||
:class="{
|
||||
heap: mode === 'heap',
|
||||
splice: mode === 'splice'
|
||||
}"
|
||||
>
|
||||
<template v-if="mode === 'heap'">
|
||||
<div
|
||||
v-for="(item, index) in heapProgressList"
|
||||
:key="`heap-${index}`"
|
||||
class="layer"
|
||||
:style="{
|
||||
width: `${item.value}%`,
|
||||
background: item.color || 'var(--td-brand-color)'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(item, index) in spliceProgressList"
|
||||
:key="`splice-${index}`"
|
||||
class="segment"
|
||||
:style="{
|
||||
width: `${item.value}%`,
|
||||
background: item.color || 'var(--td-brand-color)'
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<div v-if="Toolkit.isNotEmpty(note)" class="note" v-text="note" />
|
||||
</div>
|
||||
<div v-if="Toolkit.isNotEmpty(legendList)" class="legends">
|
||||
<div
|
||||
v-for="(item, index) in legendList"
|
||||
:key="`legend-${index}`"
|
||||
class="legend"
|
||||
>
|
||||
<div class="block" :style="{ background: item.color }" />
|
||||
<div v-text="item.legend" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Toolkit } from "timi-web";
|
||||
|
||||
export interface ProgressItem {
|
||||
color?: string;
|
||||
value: number;
|
||||
legend?: string;
|
||||
}
|
||||
|
||||
type ProgressMode = "heap" | "splice";
|
||||
|
||||
interface RenderProgressItem {
|
||||
color?: string;
|
||||
value: number;
|
||||
legend?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: "ProgressGroup"
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
mode?: ProgressMode;
|
||||
maxValue?: number;
|
||||
progress?: ProgressItem[];
|
||||
note?: string;
|
||||
}>(),
|
||||
{
|
||||
mode: "heap",
|
||||
maxValue: 1,
|
||||
progress: () => [],
|
||||
note: ""
|
||||
}
|
||||
);
|
||||
|
||||
const normalizedProgressList = computed<RenderProgressItem[]>(() => {
|
||||
const maxValue = 0 < props.maxValue ? props.maxValue : 100;
|
||||
return props.progress.map((item) => {
|
||||
const value = Math.min(Math.max(item.value / maxValue * 100, 0), 100);
|
||||
return {
|
||||
...item,
|
||||
value
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const heapProgressList = computed<RenderProgressItem[]>(() => {
|
||||
return normalizedProgressList.value.filter((item) => 0 < item.value)
|
||||
.slice()
|
||||
.sort((left, right) => right.value - left.value);
|
||||
});
|
||||
|
||||
const spliceProgressList = computed<RenderProgressItem[]>(() => {
|
||||
let rest = 100;
|
||||
return normalizedProgressList.value.filter((item) => 0 < item.value).map((item) => {
|
||||
if (rest < 1) {
|
||||
return {
|
||||
...item,
|
||||
value: 0
|
||||
};
|
||||
}
|
||||
const width = Math.min(item.value, rest);
|
||||
rest -= width;
|
||||
return {
|
||||
...item,
|
||||
value: width
|
||||
};
|
||||
}).filter((item) => 0 < item.value);
|
||||
});
|
||||
|
||||
const legendList = computed<RenderProgressItem[]>(() => {
|
||||
return normalizedProgressList.value.filter(item => Toolkit.isNotEmpty(item.legend));
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.progress-group {
|
||||
gap: .5rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bar {
|
||||
width: 100%;
|
||||
height: 1.25rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--td-progress-track-bg-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
|
||||
border-radius: .1875rem;
|
||||
|
||||
&.heap {
|
||||
.layer {
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&.splice {
|
||||
display: flex;
|
||||
|
||||
.segment {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.layer,
|
||||
.segment {
|
||||
transition: width 320ms var(--tui-bezier);
|
||||
}
|
||||
|
||||
.note {
|
||||
top: 50%;
|
||||
right: .25rem;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
font-size: .8rem;
|
||||
transform: translate(0, -50%);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.legends {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.legend {
|
||||
gap: .375rem;
|
||||
display: flex;
|
||||
font-size: .75rem;
|
||||
align-items: center;
|
||||
color: var(--td-text-color-secondary, var(--app-sub, #8899a8));
|
||||
|
||||
.block {
|
||||
width: .8rem;
|
||||
height: .8rem;
|
||||
border-radius: .125rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,63 +0,0 @@
|
||||
<template>
|
||||
<section class="placeholder">
|
||||
<div class="box">
|
||||
<p class="tag">当前组件</p>
|
||||
<h1 class="title" v-text="title"></h1>
|
||||
<p class="desc" v-text="description"></p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.placeholder {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 1.2rem;
|
||||
|
||||
.box {
|
||||
gap: .9rem;
|
||||
display: flex;
|
||||
min-height: 12rem;
|
||||
padding: 1.2rem;
|
||||
border-radius: 1rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--app-line);
|
||||
background: linear-gradient(160deg, rgba(255, 255, 255, .92), rgba(255, 255, 255, .72));
|
||||
box-shadow: 0 .4rem 1.2rem rgba(17, 32, 56, .08);
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin: 0;
|
||||
color: var(--app-primary);
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin: 0;
|
||||
color: var(--app-sub);
|
||||
font-size: .95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .placeholder {
|
||||
.box {
|
||||
background: linear-gradient(160deg, rgba(29, 37, 47, .96), rgba(22, 28, 36, .88));
|
||||
box-shadow: 0 .4rem 1.2rem rgba(0, 0, 0, .28);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
src/components/TCellInfo.vue
Normal file
90
src/components/TCellInfo.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<t-cell class="t-cell-info" :class="{ 'has-description': hasDescription }">
|
||||
<template #title>
|
||||
<div class="content">
|
||||
<slot name="label">
|
||||
<div class="label" v-text="label"></div>
|
||||
</slot>
|
||||
<slot name="value">
|
||||
<div class="value clip-text light-gray" v-text="value"></div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<slot name="description">
|
||||
<slot>
|
||||
<div v-if="description" class="description" v-text="description"></div>
|
||||
</slot>
|
||||
</slot>
|
||||
</template>
|
||||
</t-cell>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
value?: string;
|
||||
description?: string;
|
||||
}>(),
|
||||
{
|
||||
label: '',
|
||||
value: '',
|
||||
description: '',
|
||||
},
|
||||
);
|
||||
|
||||
const slots = useSlots();
|
||||
const { label, value, description } = toRefs(props);
|
||||
const hasDescription = computed(() => {
|
||||
return Boolean(props.description?.trim() || slots.description || slots.default);
|
||||
});
|
||||
|
||||
defineSlots<{
|
||||
default?: () => unknown;
|
||||
label?: () => unknown;
|
||||
value?: () => unknown;
|
||||
description?: () => unknown;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.t-cell-info {
|
||||
|
||||
:deep(.t-cell__title) {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:deep(.t-cell__description) {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.has-description {
|
||||
:deep(.t-cell__description) {
|
||||
margin-top: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: .8rem;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user