调整卡片组件的样式,优化悬停效果,提升用户体验

This commit is contained in:
jxxghp
2025-04-01 18:52:07 +08:00
parent 6a6a3bd463
commit 3c261a2c29
14 changed files with 331 additions and 295 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ const getImgUrl = computed(() => {
:width="props.width"
class="ring-gray-500"
:class="{
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
'ring-1': imageLoaded,
}"
@click="goPlay"
+8 -1
View File
@@ -143,7 +143,13 @@ onUnmounted(() => {
</script>
<template>
<div>
<VCard variant="tonal" @click="openDownloaderInfoDialog">
<VHover v-slot="hover">
<VCard
v-bind="hover.props"
variant="tonal"
@click="openDownloaderInfoDialog"
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
>
<DialogCloseBtn @click="onClose" />
<span class="absolute top-3 right-12">
<IconBtn>
@@ -172,6 +178,7 @@ onUnmounted(() => {
</div>
</VCardText>
</VCard>
</VHover>
<VDialog v-if="downloaderInfoDialog" v-model="downloaderInfoDialog" scrollable max-width="40rem" persistent>
<VCard :title="`${props.downloader.name} - 配置`" class="rounded-t">
<DialogCloseBtn v-model="downloaderInfoDialog" />
+1 -1
View File
@@ -158,7 +158,7 @@ onMounted(async () => {
:height="props.height"
:width="props.width"
:class="{
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
}"
@click="goPlay"
>
+1 -1
View File
@@ -532,7 +532,7 @@ function onRemoveSubscribe() {
:width="props.width"
class="outline-none shadow ring-gray-500 rounded-lg"
:class="{
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
'ring-1': isImageLoaded,
}"
@click.stop="goMediaDetail(hover.isHovering ?? false)"
+1 -1
View File
@@ -79,7 +79,7 @@ function goPersonDetail() {
:width="personProps.width"
class="rounded-lg"
:class="{
'transition transform-cpu duration-300 scale-105': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1': hover.isHovering,
}"
@click.stop="goPersonDetail"
>
+17 -2
View File
@@ -147,7 +147,18 @@ const dropdownItems = ref([
<template>
<div>
<VCard :width="props.width" :height="props.height" @click="detailDialog = true" class="flex flex-col h-full">
<VHover>
<template #default="hover">
<VCard
v-bind="hover.props"
:width="props.width"
:height="props.height"
@click="detailDialog = true"
class="flex flex-col h-full"
:class="{
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
}"
>
<div
class="relative flex flex-row items-start pa-3 justify-between grow"
:style="{ background: `${backgroundColor}` }"
@@ -157,7 +168,9 @@ const dropdownItems = ref([
:style="{ background: `${backgroundColor}`, filter: 'brightness(0.5)' }"
></div>
<div class="relative flex-1 min-w-0">
<VCardTitle class="text-white text-lg px-2 text-shadow whitespace-nowrap overflow-hidden text-ellipsis ...">
<VCardTitle
class="text-white text-lg px-2 text-shadow whitespace-nowrap overflow-hidden text-ellipsis ..."
>
{{ props.plugin?.plugin_name }}
<span class="text-sm text-gray-200">v{{ props.plugin?.plugin_version }}</span>
</VCardTitle>
@@ -213,6 +226,8 @@ const dropdownItems = ref([
</div>
</VCardText>
</VCard>
</template>
</VHover>
<!-- 安装插件进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
<!-- 更新日志 -->
+3
View File
@@ -335,6 +335,9 @@ watch(
:height="props.height"
@click="openPluginDetail"
class="flex flex-col h-full"
:class="{
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
}"
>
<div
class="relative flex flex-row items-start pa-3 justify-between grow"
+1 -1
View File
@@ -45,7 +45,7 @@ function goPlay(isHovering: boolean | null = false) {
:width="props.width"
class="outline-none shadow ring-gray-500 rounded-lg"
:class="{
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
'ring-1': isImageLoaded,
}"
>
+2 -2
View File
@@ -390,14 +390,14 @@ onMounted(() => {
background: rgba(var(--v-theme-surface), 0.95);
border-radius: 10px;
border: 1px solid rgba(var(--v-theme-on-surface), 0.09);
transition: all 0.25s ease;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.site-card:hover {
transform: translateY(-2px);
transform: translateY(-4px);
border-color: rgba(var(--v-theme-primary), 0.2);
box-shadow: 0 3px 12px -6px rgba(0, 0, 0, 0.1);
}
+1 -1
View File
@@ -298,7 +298,7 @@ function onSubscribeEditRemove() {
class="flex flex-col rounded-lg h-full"
:class="{
'outline-dashed outline-1': props.media?.best_version && imageLoaded,
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
'opacity-70': subscribeState === 'S',
}"
min-height="170"
+1 -1
View File
@@ -102,7 +102,7 @@ function doDelete() {
:key="props.media?.id"
class="flex flex-col rounded-lg h-full"
:class="{
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
}"
min-height="170"
@click="showForkSubscribe"
+73 -71
View File
@@ -45,6 +45,12 @@ const movieSubscriptions = ref(0)
// 用户电视剧订阅数量
const tvShowSubscriptions = ref(0)
// 新增:用户状态背景颜色计算
const statusClass = computed(() => ({
'bg-error-lighten-4': !props.user.is_active, // 假设用户状态使用 is_active 字段
'border-error': !props.user.is_active, // 非活跃用户添加红色边框
}))
// 按用户查询订阅数量
async function fetchSubscriptions() {
try {
@@ -98,88 +104,84 @@ onMounted(() => {
})
</script>
<template>
<VCard>
<VCardText class="text-center pt-10 pb-3">
<VAvatar variant="flat" size="100" rounded>
<VImg :src="user.avatar || avatar1" alt="avatar" />
</VAvatar>
<h5 class="text-h5 mt-3">{{ user.name }}</h5>
<VChip size="small" class="mt-3" :class="{ 'text-error': user.is_superuser }">
<VHover v-slot="hover">
<VCard
v-bind="hover.props"
class="w-full h-full"
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering, ...statusClass }"
@click.stop="editUser"
>
<!-- 用户头像 -->
<VImg height="12rem" :src="user.avatar ?? avatar1" cover>
<div v-if="!user.is_active" class="img-overlay" />
</VImg>
<div class="flex flex-col">
<!-- 用户基本信息 -->
<VCardTitle class="pt-2">{{ user.name }}</VCardTitle>
<VCardSubtitle v-if="user.email" class="text-wrap">
<VIcon size="16">mdi-email</VIcon>
{{ user.email }}
</VCardSubtitle>
<!-- 订阅信息 -->
<VCardActions>
<div class="mt-3 flex gap-3" dense>
<VChip v-if="user.is_otp" size="small" color="error">
<VIcon>mdi-lock</VIcon>
</VChip>
<VChip size="small" color="info">
<VIcon left class="me-2">mdi-movie</VIcon>
{{ movieSubscriptions }}
</VChip>
<VChip size="small" color="warning">
<VIcon left class="me-2">mdi-television</VIcon>
{{ tvShowSubscriptions }}
</VChip>
</div>
</VCardActions>
</div>
<!-- 管理员标签 -->
<VChip
variant="elevated"
size="small"
class="absolute right-2 top-2"
:color="user.is_superuser ? 'primary' : 'secondary'"
>
{{ user.is_superuser ? '管理员' : '普通用户' }}
</VChip>
</VCardText>
<VCardText class="flex justify-center gap-6 pb-5">
<div class="d-flex align-center">
<VAvatar size="40" color="primary" rounded variant="tonal" class="me-4">
<VIcon size="24" icon="mdi-movie-open-outline"></VIcon>
</VAvatar>
<div>
<div class="text-h6">{{ movieSubscriptions }}</div>
<div class="text-sm text-no-wrap">电影订阅</div>
</div>
</div>
<div class="d-flex align-center">
<VAvatar size="40" color="primary" rounded variant="tonal" class="me-4">
<VIcon size="24" icon="mdi-television"></VIcon>
</VAvatar>
<div>
<div class="text-h6">{{ tvShowSubscriptions }}</div>
<div class="text-sm text-no-wrap">电视剧订阅</div>
</div>
</div>
</VCardText>
<VCardText class="pb-6">
<VDivider class="my-2">
<h5 class="text-h6">详情</h5>
</VDivider>
<VList lines="one">
<VListItem>
<VListItemTitle class="text-sm">
<span class="font-weight-medium">邮箱</span><span class="text-body-1"> {{ user.email }}</span>
</VListItemTitle>
</VListItem>
<VListItem>
<VListItemTitle class="text-sm">
<span class="font-weight-medium">状态</span
><span class="text-body-1">
<VChip size="small" :class="{ 'text-success': user.is_active }" variant="tonal">
{{ user.is_active ? '激活' : '已停用' }}
</VChip>
</span>
</VListItemTitle>
</VListItem>
<VListItem>
<VListItemTitle class="text-sm">
<span class="font-weight-medium">双重认证</span
><span class="text-body-1">
<VChip size="small" :class="{ 'text-success': user.is_otp }" variant="tonal">
{{ user.is_otp ? '已启用' : '未启用' }}
</VChip>
</span>
</VListItemTitle>
</VListItem>
</VList>
</VCardText>
<VCardText class="flex flex-row justify-center">
<VBtn v-if="currentUserIsSuperuser" color="primary" class="me-4" @click="editUser"> 编辑 </VBtn>
<!-- 删除按钮 -->
<div class="absolute bottom-2 w-full flex items-center justify-center">
<VBtn
v-if="currentUserIsSuperuser && props.user.id != currentLoginUserId"
v-show="hover.isHovering && currentUserIsSuperuser"
@click.stop="removeUser"
icon="mdi-delete"
color="error"
variant="outlined"
@click="removeUser"
>
删除
</VBtn>
</VCardText>
size="small"
class="shadow-xl"
/>
</div>
</VCard>
</VHover>
<!-- 用户编辑弹窗 -->
<UserAddEditDialog
v-if="userEditDialog"
v-model="userEditDialog"
:username="props.user?.name"
:usernames="props.users.map(item => item.name)"
:username="user?.name"
:usernames="users.map(item => item.name)"
oper="edit"
@save="onUserUpdate"
@close="userEditDialog = false"
/>
</template>
<style scoped>
.img-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.5);
z-index: 1;
}
</style>
+10 -1
View File
@@ -172,7 +172,15 @@ const resolveProgress = (item: Workflow) => {
</script>
<template>
<div class="h-full">
<VCard class="mx-auto h-full" @click="handleFlow(workflow)" :ripple="false" :loading="loading">
<VHover v-slot="hover">
<VCard
v-bind="hover.props"
class="mx-auto h-full"
@click="handleFlow(workflow)"
:ripple="false"
:loading="loading"
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
>
<VCardItem class="py-3" :class="`bg-${resolveStatusVariant(workflow?.state).color}`">
<template #prepend>
<VAvatar variant="text" class="me-2">
@@ -298,6 +306,7 @@ const resolveProgress = (item: Workflow) => {
</div>
</VCardText>
</VCard>
</VHover>
<!-- 流程对话框 -->
<WorkflowActionsDialog
v-if="flowDialog"
+1 -1
View File
@@ -218,7 +218,7 @@ html.v-overlay-scroll-blocked body {
}
.grid-user-card {
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
padding-block-end: 1rem;
}