czh-20260429-实现任务管理的前端功能

This commit is contained in:
zhihao
2026-04-30 09:24:45 +08:00
parent dd88adab73
commit 2641522dcd
35 changed files with 8355 additions and 0 deletions
@@ -0,0 +1,155 @@
<template>
<div class="avatar-display" :class="{ 'avatar-display-completed': completed }">
<template v-if="names.length === 0">
<span class="avatar-empty">-</span>
</template>
<template v-else-if="names.length === 1">
<div class="avatar-single-wrap">
<div class="avatar-circle" :style="{ background: getColor(names[0]), width: size + 'px', height: size + 'px' }">
<span class="avatar-text" :style="{ fontSize: fontSize + 'px' }">{{ names[0].charAt(0) }}</span>
</div>
<span v-if="showName" class="avatar-name">{{ names[0] }}</span>
</div>
</template>
<template v-else-if="names.length <= maxShow">
<div
v-for="(name, idx) in names"
:key="idx"
class="avatar-circle"
:style="{ background: getColor(name), width: size + 'px', height: size + 'px', marginLeft: idx > 0 ? overlap + 'px' : '0', zIndex: idx + 1 }"
>
<span class="avatar-text" :style="{ fontSize: fontSize + 'px' }">{{ name.charAt(0) }}</span>
</div>
</template>
<template v-else>
<div
v-for="(name, idx) in names.slice(0, maxShow - 1)"
:key="idx"
class="avatar-circle"
:style="{ background: getColor(name), width: size + 'px', height: size + 'px', marginLeft: idx > 0 ? overlap + 'px' : '0', zIndex: idx + 1 }"
>
<span class="avatar-text" :style="{ fontSize: fontSize + 'px' }">{{ name.charAt(0) }}</span>
</div>
<div
class="avatar-overflow"
:style="{ width: size + 'px', height: size + 'px', marginLeft: overlap + 'px', fontSize: overflowFontSize + 'px', zIndex: names.length }"
>
+{{ names.length - (maxShow - 1) }}
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { getAvatarColor } from '../types';
const props = withDefaults(
defineProps<{
names: string;
size?: number;
maxShow?: number;
overlap?: number;
showName?: boolean;
completed?: boolean;
}>(),
{
size: 24,
maxShow: 5,
overlap: -8,
showName: true,
completed: false,
},
);
const fontSize = computed(() => Math.max(10, Math.round(props.size * 0.54)));
const overflowFontSize = computed(() => Math.max(8, Math.round(props.size * 0.42)));
const parsedNames = computed(() => {
if (!props.names) return [];
return props.names
.split(/[,,、]/)
.map((s) => s.trim())
.filter(Boolean);
});
const names = computed(() => parsedNames.value);
function getColor(name: string): string {
return getAvatarColor(name);
}
</script>
<style lang="less" scoped>
.avatar-display {
display: flex;
align-items: center;
gap: 0;
overflow: hidden;
}
.avatar-display-completed {
opacity: 0.5;
}
.avatar-single-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px 2px 2px;
background: #f5f6f7;
border-radius: 14px;
max-width: 100%;
overflow: hidden;
}
.avatar-circle {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
box-shadow: 0 0 0 2px #fff;
}
.avatar-text {
color: #fff;
font-weight: 500;
line-height: 1;
font-size: 14px !important;
}
.avatar-name {
color: #646a73;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
}
.avatar-count {
font-size: 14px;
color: #646a73;
margin-left: 4px;
white-space: nowrap;
}
.avatar-overflow {
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #8c8c8c;
font-weight: 500;
position: relative;
box-shadow: 0 0 0 2px #fff;
flex-shrink: 0;
}
.avatar-empty {
color: #c0c4cc;
font-size: 14px;
}
</style>