feat: enhance About component with feature display and loading states, and refactor feature handling in share drawers for improved user experience

This commit is contained in:
keven1024
2026-04-05 09:46:56 +08:00
parent 28abd8d1a2
commit 7f74441f5d
7 changed files with 54 additions and 11 deletions

View File

@@ -4,12 +4,14 @@ import { Skeleton } from '@/components/ui/skeleton'
import getFileSize from '~/lib/getFileSize'
import SparkMD5 from 'spark-md5'
import useMyAppConfig from '@/composables/useMyAppConfig'
import { useFeatureMeta } from '@/composables/useFeatureMeta'
import Progress from '~/components/ui/progress/Progress.vue'
import renderI18n from '~/lib/renderI18n'
import { I18nT } from 'vue-i18n'
const { locale } = useI18n()
const appConfig = useMyAppConfig()
const featureMeta = useFeatureMeta()
const { data, isLoading } = useQuery({
queryKey: ['about'],
queryFn: async () => {
@@ -107,6 +109,29 @@ const genUserAvatar = (email: string) => {
</div>
</div>
</template>
<template v-if="isLoading">
<Skeleton class="w-full h-24 rounded-xl" />
</template>
<template v-else>
<div class="rounded-xl bg-white/50 flex flex-col p-3 gap-3">
<div class="font-semibold">{{ t('page.about.enabledFeatures') }}</div>
<div v-if="featureMeta.length" class="flex flex-row flex-wrap gap-2">
<div
v-for="feature in featureMeta"
:key="feature.key"
class="flex flex-row items-center gap-2 rounded-full bg-black/5 px-2 py-1 text-sm font-medium"
>
<div class="flex size-6 items-center justify-center rounded-full text-black/80" :style="feature.style">
<component :is="feature.icon" class="size-3.5" />
</div>
<span>{{ feature.label }}</span>
</div>
</div>
<div v-else class="text-sm opacity-75">
{{ t('page.about.enabledFeaturesEmpty') }}
</div>
</div>
</template>
<template v-if="isLoading">
<Skeleton class="w-full h-16 rounded-xl" />
</template>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { cx } from 'class-variance-authority'
import showDrawer from '@/lib/showDrawer'
import FileShareHandle from '@/components/Preprocessing/FileShareHandle.vue'
import ImageConvertHandle from '@/components/Preprocessing/ImageConvertHandle.vue'
@@ -84,7 +83,7 @@ const actions = computed(() =>
}
"
>
<div :class="cx('size-14 flex justify-center items-center rounded-full mx-3', item?.className)">
<div class="size-14 flex justify-center items-center rounded-full mx-3" :style="item?.style">
<component :is="item?.icon" />
</div>
<div class="text-xs truncate w-full text-center">{{ item?.label }}</div>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { cx } from 'class-variance-authority'
import showDrawer from '@/lib/showDrawer'
import TextShareHandle from '@/components/Preprocessing/TextShareHandle.vue'
import { useFeatureMeta, type FeatureKey } from '@/composables/useFeatureMeta'
@@ -38,7 +37,8 @@ const actionHandlers: Partial<Record<FeatureKey, ActionHandler>> = {
const actions = computed(() =>
featureMeta.value
.filter((meta) => {
const handler = actionHandlers?.[meta.key]
const { key } = meta || {}
const handler = actionHandlers?.[key]
return handler && (!handler.condition || handler.condition())
})
.map((meta) => ({ ...meta, onClick: actionHandlers[meta.key]!.onClick }))
@@ -58,7 +58,7 @@ const actions = computed(() =>
}
"
>
<div :class="cx('size-14 flex justify-center items-center rounded-full mx-3', item?.className)">
<div class="size-14 flex justify-center items-center rounded-full mx-3" :style="item?.style">
<component :is="item?.icon" />
</div>
<div class="text-xs truncate w-full text-center">{{ item?.label }}</div>

View File

@@ -1,6 +1,7 @@
import { LucideShare, LucideImageMinus, LucideArrowRightLeft } from 'lucide-vue-next'
import useMyAppConfig from '@/composables/useMyAppConfig'
import type { FileHandleKey, TextHandleKey } from '../components/Preprocessing/types'
import generateRandomColors from '@/lib/generateRandomColors'
export type FeatureKey = FileHandleKey | TextHandleKey
@@ -8,7 +9,6 @@ export type FeatureMeta = {
key: FeatureKey
label: string
icon: any
className: string
}
const allFeatureMeta = (t: (key: string) => string): FeatureMeta[] => [
@@ -16,25 +16,21 @@ const allFeatureMeta = (t: (key: string) => string): FeatureMeta[] => [
key: 'file-share',
label: t('page.upload.file.handleType.file-share'),
icon: LucideShare,
className: 'bg-green-300',
},
{
key: 'file-image-compress',
label: t('page.upload.file.handleType.file-image-compress'),
icon: LucideImageMinus,
className: 'bg-red-300',
},
{
key: 'file-image-convert',
label: t('page.upload.file.handleType.file-image-convert'),
icon: LucideArrowRightLeft,
className: 'bg-purple-300',
},
{
key: 'text-share',
label: t('page.upload.text.handleType.text-share'),
icon: LucideShare,
className: 'bg-green-300',
},
]
@@ -44,6 +40,8 @@ export function useFeatureMeta() {
return computed(() => {
const enabledKeys = appConfig.value?.features ?? []
return allFeatureMeta(t).filter((meta) => enabledKeys.includes(meta.key))
const result = allFeatureMeta(t).filter((meta) => enabledKeys.includes(meta.key))
const colors = generateRandomColors(result.length)
return result.map((meta, index) => ({ ...meta, style: { backgroundColor: colors[index] } }))
})
}

View File

@@ -201,6 +201,8 @@
"title": "About",
"about": "About",
"systemInfo": "System Info",
"enabledFeatures": "Instance feature",
"enabledFeaturesEmpty": "No extra features are enabled for this instance",
"systemVersion": "System Version",
"storage": "Storage",
"analysis": "Analysis",

View File

@@ -201,6 +201,8 @@
"title": "关于",
"about": "关于",
"systemInfo": "系统信息",
"enabledFeatures": "实例功能",
"enabledFeaturesEmpty": "当前实例暂未启用额外功能",
"systemVersion": "系统版本",
"storage": "已托管的文件",
"analysis": "分析",

View File

@@ -0,0 +1,17 @@
function generateRandomColors(count: number, minHueDiff = 30) {
const colors: { h: number; s: number; l: number }[] = []
for (let i = 0; i < count; i++) {
let hue: number,
attempts = 0
const { h: previousHue } = colors?.[colors.length - 1] ?? {}
do {
hue = Math.random() * 360
attempts++
} while (attempts < 100 && previousHue !== undefined && Math.abs(previousHue - hue) < minHueDiff)
colors.push({ h: hue, s: 70, l: 75 })
}
return colors.map((c) => `hsl(${c.h}, ${c.s}%, ${c.l}%)`)
}
export default generateRandomColors