mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
调整卡片组件的样式,优化悬停效果,提升用户体验
This commit is contained in:
@@ -37,7 +37,7 @@ const getImgUrl = computed(() => {
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
class="ring-gray-500"
|
class="ring-gray-500"
|
||||||
:class="{
|
: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,
|
'ring-1': imageLoaded,
|
||||||
}"
|
}"
|
||||||
@click="goPlay"
|
@click="goPlay"
|
||||||
|
|||||||
@@ -143,35 +143,42 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard variant="tonal" @click="openDownloaderInfoDialog">
|
<VHover v-slot="hover">
|
||||||
<DialogCloseBtn @click="onClose" />
|
<VCard
|
||||||
<span class="absolute top-3 right-12">
|
v-bind="hover.props"
|
||||||
<IconBtn>
|
variant="tonal"
|
||||||
<VIcon class="cursor-move" icon="mdi-drag" />
|
@click="openDownloaderInfoDialog"
|
||||||
</IconBtn>
|
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
|
||||||
</span>
|
>
|
||||||
<VCardText class="flex justify-space-between align-center gap-4">
|
<DialogCloseBtn @click="onClose" />
|
||||||
<div class="align-self-start flex-1">
|
<span class="absolute top-3 right-12">
|
||||||
<div class="flex items-center">
|
<IconBtn>
|
||||||
<VBadge
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
||||||
v-if="props.downloader.default && props.downloader.enabled"
|
</IconBtn>
|
||||||
dot
|
</span>
|
||||||
inline
|
<VCardText class="flex justify-space-between align-center gap-4">
|
||||||
color="success"
|
<div class="align-self-start flex-1">
|
||||||
class="me-1"
|
<div class="flex items-center">
|
||||||
/>
|
<VBadge
|
||||||
<span class="text-h6">{{ downloader.name }}</span>
|
v-if="props.downloader.default && props.downloader.enabled"
|
||||||
|
dot
|
||||||
|
inline
|
||||||
|
color="success"
|
||||||
|
class="me-1"
|
||||||
|
/>
|
||||||
|
<span class="text-h6">{{ downloader.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 flex flex-wrap text-sm" v-if="props.downloader.enabled">
|
||||||
|
<span class="me-2">{{ `↑ ${formatFileSize(upload_rate, 1)}/s ` }}</span>
|
||||||
|
<span>{{ `↓ ${formatFileSize(download_rate, 1)}/s` }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex flex-wrap text-sm" v-if="props.downloader.enabled">
|
<div class="h-20">
|
||||||
<span class="me-2">{{ `↑ ${formatFileSize(upload_rate, 1)}/s ` }}</span>
|
<VImg :src="getIcon" cover class="mt-7 me-3" max-width="3rem" min-width="3rem" />
|
||||||
<span>{{ `↓ ${formatFileSize(download_rate, 1)}/s` }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</VCardText>
|
||||||
<div class="h-20">
|
</VCard>
|
||||||
<VImg :src="getIcon" cover class="mt-7 me-3" max-width="3rem" min-width="3rem" />
|
</VHover>
|
||||||
</div>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
<VDialog v-if="downloaderInfoDialog" v-model="downloaderInfoDialog" scrollable max-width="40rem" persistent>
|
<VDialog v-if="downloaderInfoDialog" v-model="downloaderInfoDialog" scrollable max-width="40rem" persistent>
|
||||||
<VCard :title="`${props.downloader.name} - 配置`" class="rounded-t">
|
<VCard :title="`${props.downloader.name} - 配置`" class="rounded-t">
|
||||||
<DialogCloseBtn v-model="downloaderInfoDialog" />
|
<DialogCloseBtn v-model="downloaderInfoDialog" />
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ onMounted(async () => {
|
|||||||
:height="props.height"
|
:height="props.height"
|
||||||
:width="props.width"
|
:width="props.width"
|
||||||
:class="{
|
: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"
|
@click="goPlay"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ function onRemoveSubscribe() {
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
class="outline-none shadow ring-gray-500 rounded-lg"
|
class="outline-none shadow ring-gray-500 rounded-lg"
|
||||||
:class="{
|
: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,
|
'ring-1': isImageLoaded,
|
||||||
}"
|
}"
|
||||||
@click.stop="goMediaDetail(hover.isHovering ?? false)"
|
@click.stop="goMediaDetail(hover.isHovering ?? false)"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ function goPersonDetail() {
|
|||||||
:width="personProps.width"
|
:width="personProps.width"
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
:class="{
|
:class="{
|
||||||
'transition transform-cpu duration-300 scale-105': hover.isHovering,
|
'transition transform-cpu duration-300 -translate-y-1': hover.isHovering,
|
||||||
}"
|
}"
|
||||||
@click.stop="goPersonDetail"
|
@click.stop="goPersonDetail"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -147,72 +147,87 @@ const dropdownItems = ref([
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VCard :width="props.width" :height="props.height" @click="detailDialog = true" class="flex flex-col h-full">
|
<VHover>
|
||||||
<div
|
<template #default="hover">
|
||||||
class="relative flex flex-row items-start pa-3 justify-between grow"
|
<VCard
|
||||||
:style="{ background: `${backgroundColor}` }"
|
v-bind="hover.props"
|
||||||
>
|
:width="props.width"
|
||||||
<div
|
:height="props.height"
|
||||||
class="absolute inset-0 bg-cover bg-center"
|
@click="detailDialog = true"
|
||||||
:style="{ background: `${backgroundColor}`, filter: 'brightness(0.5)' }"
|
class="flex flex-col h-full"
|
||||||
></div>
|
:class="{
|
||||||
<div class="relative flex-1 min-w-0">
|
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
|
||||||
<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>
|
<div
|
||||||
</VCardTitle>
|
class="relative flex flex-row items-start pa-3 justify-between grow"
|
||||||
<VCardText class="text-white text-sm px-2 py-0 text-shadow overflow-hidden line-clamp-3 ...">
|
:style="{ background: `${backgroundColor}` }"
|
||||||
{{ props.plugin?.plugin_desc }}
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-cover bg-center"
|
||||||
|
: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 ..."
|
||||||
|
>
|
||||||
|
{{ props.plugin?.plugin_name }}
|
||||||
|
<span class="text-sm text-gray-200">v{{ props.plugin?.plugin_version }}</span>
|
||||||
|
</VCardTitle>
|
||||||
|
<VCardText class="text-white text-sm px-2 py-0 text-shadow overflow-hidden line-clamp-3 ...">
|
||||||
|
{{ props.plugin?.plugin_desc }}
|
||||||
|
</VCardText>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex-shrink-0 self-center">
|
||||||
|
<VAvatar size="64">
|
||||||
|
<VImg
|
||||||
|
ref="imageRef"
|
||||||
|
:src="iconPath"
|
||||||
|
aspect-ratio="4/3"
|
||||||
|
cover
|
||||||
|
:class="{ shadow: isImageLoaded }"
|
||||||
|
@load="imageLoaded"
|
||||||
|
@error="imageLoadError = true"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<VCardText class="flex flex-none align-self-baseline py-3 w-full align-end">
|
||||||
|
<span>
|
||||||
|
<VIcon icon="mdi-github" class="me-1" />
|
||||||
|
<a :href="props.plugin?.author_url" target="_blank" @click.stop>
|
||||||
|
{{ props.plugin?.plugin_author }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span v-if="props.count" class="ms-3">
|
||||||
|
<VIcon icon="mdi-download" />
|
||||||
|
<span class="text-sm ms-1 mt-1">{{ props.count?.toLocaleString() }}</span>
|
||||||
|
</span>
|
||||||
|
<div class="me-n3 absolute bottom-1 right-3">
|
||||||
|
<IconBtn>
|
||||||
|
<VIcon icon="mdi-dots-vertical" />
|
||||||
|
<VMenu activator="parent" close-on-content-click>
|
||||||
|
<VList>
|
||||||
|
<VListItem
|
||||||
|
v-for="(item, i) in dropdownItems"
|
||||||
|
v-show="item.show"
|
||||||
|
:key="i"
|
||||||
|
variant="plain"
|
||||||
|
@click="item.props.click"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon :icon="item.props.prependIcon" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle v-text="item.title" />
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
|
</VMenu>
|
||||||
|
</IconBtn>
|
||||||
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</div>
|
</VCard>
|
||||||
<div class="relative flex-shrink-0 self-center">
|
</template>
|
||||||
<VAvatar size="64">
|
</VHover>
|
||||||
<VImg
|
|
||||||
ref="imageRef"
|
|
||||||
:src="iconPath"
|
|
||||||
aspect-ratio="4/3"
|
|
||||||
cover
|
|
||||||
:class="{ shadow: isImageLoaded }"
|
|
||||||
@load="imageLoaded"
|
|
||||||
@error="imageLoadError = true"
|
|
||||||
/>
|
|
||||||
</VAvatar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<VCardText class="flex flex-none align-self-baseline py-3 w-full align-end">
|
|
||||||
<span>
|
|
||||||
<VIcon icon="mdi-github" class="me-1" />
|
|
||||||
<a :href="props.plugin?.author_url" target="_blank" @click.stop>
|
|
||||||
{{ props.plugin?.plugin_author }}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span v-if="props.count" class="ms-3">
|
|
||||||
<VIcon icon="mdi-download" />
|
|
||||||
<span class="text-sm ms-1 mt-1">{{ props.count?.toLocaleString() }}</span>
|
|
||||||
</span>
|
|
||||||
<div class="me-n3 absolute bottom-1 right-3">
|
|
||||||
<IconBtn>
|
|
||||||
<VIcon icon="mdi-dots-vertical" />
|
|
||||||
<VMenu activator="parent" close-on-content-click>
|
|
||||||
<VList>
|
|
||||||
<VListItem
|
|
||||||
v-for="(item, i) in dropdownItems"
|
|
||||||
v-show="item.show"
|
|
||||||
:key="i"
|
|
||||||
variant="plain"
|
|
||||||
@click="item.props.click"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon :icon="item.props.prependIcon" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle v-text="item.title" />
|
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
</VMenu>
|
|
||||||
</IconBtn>
|
|
||||||
</div>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
<!-- 安装插件进度框 -->
|
<!-- 安装插件进度框 -->
|
||||||
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" />
|
||||||
<!-- 更新日志 -->
|
<!-- 更新日志 -->
|
||||||
|
|||||||
@@ -335,6 +335,9 @@ watch(
|
|||||||
:height="props.height"
|
:height="props.height"
|
||||||
@click="openPluginDetail"
|
@click="openPluginDetail"
|
||||||
class="flex flex-col h-full"
|
class="flex flex-col h-full"
|
||||||
|
:class="{
|
||||||
|
'transition transform-cpu duration-300 -translate-y-1 shadow-lg': hover.isHovering,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="relative flex flex-row items-start pa-3 justify-between grow"
|
class="relative flex flex-row items-start pa-3 justify-between grow"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function goPlay(isHovering: boolean | null = false) {
|
|||||||
:width="props.width"
|
:width="props.width"
|
||||||
class="outline-none shadow ring-gray-500 rounded-lg"
|
class="outline-none shadow ring-gray-500 rounded-lg"
|
||||||
:class="{
|
: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,
|
'ring-1': isImageLoaded,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -390,14 +390,14 @@ onMounted(() => {
|
|||||||
background: rgba(var(--v-theme-surface), 0.95);
|
background: rgba(var(--v-theme-surface), 0.95);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.09);
|
border: 1px solid rgba(var(--v-theme-on-surface), 0.09);
|
||||||
transition: all 0.25s ease;
|
transition: all 0.3s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-card:hover {
|
.site-card:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-4px);
|
||||||
border-color: rgba(var(--v-theme-primary), 0.2);
|
border-color: rgba(var(--v-theme-primary), 0.2);
|
||||||
box-shadow: 0 3px 12px -6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 3px 12px -6px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ function onSubscribeEditRemove() {
|
|||||||
class="flex flex-col rounded-lg h-full"
|
class="flex flex-col rounded-lg h-full"
|
||||||
:class="{
|
:class="{
|
||||||
'outline-dashed outline-1': props.media?.best_version && imageLoaded,
|
'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',
|
'opacity-70': subscribeState === 'S',
|
||||||
}"
|
}"
|
||||||
min-height="170"
|
min-height="170"
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function doDelete() {
|
|||||||
:key="props.media?.id"
|
:key="props.media?.id"
|
||||||
class="flex flex-col rounded-lg h-full"
|
class="flex flex-col rounded-lg h-full"
|
||||||
:class="{
|
: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"
|
min-height="170"
|
||||||
@click="showForkSubscribe"
|
@click="showForkSubscribe"
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ const movieSubscriptions = ref(0)
|
|||||||
// 用户电视剧订阅数量
|
// 用户电视剧订阅数量
|
||||||
const tvShowSubscriptions = 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() {
|
async function fetchSubscriptions() {
|
||||||
try {
|
try {
|
||||||
@@ -98,88 +104,84 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VCard>
|
<VHover v-slot="hover">
|
||||||
<VCardText class="text-center pt-10 pb-3">
|
<VCard
|
||||||
<VAvatar variant="flat" size="100" rounded>
|
v-bind="hover.props"
|
||||||
<VImg :src="user.avatar || avatar1" alt="avatar" />
|
class="w-full h-full"
|
||||||
</VAvatar>
|
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering, ...statusClass }"
|
||||||
<h5 class="text-h5 mt-3">{{ user.name }}</h5>
|
@click.stop="editUser"
|
||||||
<VChip size="small" class="mt-3" :class="{ 'text-error': user.is_superuser }">
|
>
|
||||||
|
<!-- 用户头像 -->
|
||||||
|
<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 ? '管理员' : '普通用户' }}
|
{{ user.is_superuser ? '管理员' : '普通用户' }}
|
||||||
</VChip>
|
</VChip>
|
||||||
</VCardText>
|
<!-- 删除按钮 -->
|
||||||
<VCardText class="flex justify-center gap-6 pb-5">
|
<div class="absolute bottom-2 w-full flex items-center justify-center">
|
||||||
<div class="d-flex align-center">
|
<VBtn
|
||||||
<VAvatar size="40" color="primary" rounded variant="tonal" class="me-4">
|
v-show="hover.isHovering && currentUserIsSuperuser"
|
||||||
<VIcon size="24" icon="mdi-movie-open-outline"></VIcon>
|
@click.stop="removeUser"
|
||||||
</VAvatar>
|
icon="mdi-delete"
|
||||||
<div>
|
color="error"
|
||||||
<div class="text-h6">{{ movieSubscriptions }}</div>
|
size="small"
|
||||||
<div class="text-sm text-no-wrap">电影订阅</div>
|
class="shadow-xl"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-center">
|
</VCard>
|
||||||
<VAvatar size="40" color="primary" rounded variant="tonal" class="me-4">
|
</VHover>
|
||||||
<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>
|
|
||||||
<VBtn
|
|
||||||
v-if="currentUserIsSuperuser && props.user.id != currentLoginUserId"
|
|
||||||
color="error"
|
|
||||||
variant="outlined"
|
|
||||||
@click="removeUser"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</VBtn>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
<!-- 用户编辑弹窗 -->
|
<!-- 用户编辑弹窗 -->
|
||||||
<UserAddEditDialog
|
<UserAddEditDialog
|
||||||
v-if="userEditDialog"
|
v-if="userEditDialog"
|
||||||
v-model="userEditDialog"
|
v-model="userEditDialog"
|
||||||
:username="props.user?.name"
|
:username="user?.name"
|
||||||
:usernames="props.users.map(item => item.name)"
|
:usernames="users.map(item => item.name)"
|
||||||
oper="edit"
|
oper="edit"
|
||||||
@save="onUserUpdate"
|
@save="onUserUpdate"
|
||||||
@close="userEditDialog = false"
|
@close="userEditDialog = false"
|
||||||
/>
|
/>
|
||||||
</template>
|
</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>
|
||||||
|
|||||||
@@ -172,132 +172,141 @@ const resolveProgress = (item: Workflow) => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<VCard class="mx-auto h-full" @click="handleFlow(workflow)" :ripple="false" :loading="loading">
|
<VHover v-slot="hover">
|
||||||
<VCardItem class="py-3" :class="`bg-${resolveStatusVariant(workflow?.state).color}`">
|
<VCard
|
||||||
<template #prepend>
|
v-bind="hover.props"
|
||||||
<VAvatar variant="text" class="me-2">
|
class="mx-auto h-full"
|
||||||
<VIcon
|
@click="handleFlow(workflow)"
|
||||||
v-if="workflow?.state === 'P'"
|
:ripple="false"
|
||||||
color="success"
|
:loading="loading"
|
||||||
size="x-large"
|
:class="{ 'transition transform-cpu duration-300 -translate-y-1': hover.isHovering }"
|
||||||
icon="mdi-play"
|
>
|
||||||
@click.stop="handleEnable(workflow)"
|
<VCardItem class="py-3" :class="`bg-${resolveStatusVariant(workflow?.state).color}`">
|
||||||
/>
|
<template #prepend>
|
||||||
<VIcon v-else color="warning" icon="mdi-pause" size="x-large" @click.stop="handlePause(workflow)" />
|
<VAvatar variant="text" class="me-2">
|
||||||
</VAvatar>
|
<VIcon
|
||||||
</template>
|
v-if="workflow?.state === 'P'"
|
||||||
<VCardTitle class="text-white">
|
color="success"
|
||||||
{{ workflow?.name }}
|
size="x-large"
|
||||||
</VCardTitle>
|
icon="mdi-play"
|
||||||
<VCardSubtitle class="text-white">{{ workflow?.description }}</VCardSubtitle>
|
@click.stop="handleEnable(workflow)"
|
||||||
<template #append>
|
/>
|
||||||
<IconBtn>
|
<VIcon v-else color="warning" icon="mdi-pause" size="x-large" @click.stop="handlePause(workflow)" />
|
||||||
<VIcon icon="mdi-vector-polyline-edit" @click.stop="handleFlow(workflow)" />
|
</VAvatar>
|
||||||
</IconBtn>
|
</template>
|
||||||
<IconBtn>
|
<VCardTitle class="text-white">
|
||||||
<VIcon icon="mdi-dots-vertical" />
|
{{ workflow?.name }}
|
||||||
<VMenu activator="parent" close-on-content-click>
|
</VCardTitle>
|
||||||
<VList>
|
<VCardSubtitle class="text-white">{{ workflow?.description }}</VCardSubtitle>
|
||||||
<VListItem variant="plain" base-color="primary" @click="handleEdit(workflow)">
|
<template #append>
|
||||||
<template #prepend>
|
<IconBtn>
|
||||||
<VIcon icon="mdi-note-edit" />
|
<VIcon icon="mdi-vector-polyline-edit" @click.stop="handleFlow(workflow)" />
|
||||||
</template>
|
</IconBtn>
|
||||||
<VListItemTitle>编辑任务</VListItemTitle>
|
<IconBtn>
|
||||||
</VListItem>
|
<VIcon icon="mdi-dots-vertical" />
|
||||||
<VListItem
|
<VMenu activator="parent" close-on-content-click>
|
||||||
v-if="workflow.current_action"
|
<VList>
|
||||||
variant="plain"
|
<VListItem variant="plain" base-color="primary" @click="handleEdit(workflow)">
|
||||||
base-color="info"
|
<template #prepend>
|
||||||
@click="handleRun(workflow, false)"
|
<VIcon icon="mdi-note-edit" />
|
||||||
>
|
</template>
|
||||||
<template #prepend>
|
<VListItemTitle>编辑任务</VListItemTitle>
|
||||||
<VIcon icon="mdi-play-speed" />
|
</VListItem>
|
||||||
</template>
|
<VListItem
|
||||||
<VListItemTitle>继续执行</VListItemTitle>
|
v-if="workflow.current_action"
|
||||||
</VListItem>
|
variant="plain"
|
||||||
<VListItem
|
base-color="info"
|
||||||
v-if="workflow.current_action"
|
@click="handleRun(workflow, false)"
|
||||||
variant="plain"
|
>
|
||||||
base-color="info"
|
<template #prepend>
|
||||||
@click="handleRun(workflow, true)"
|
<VIcon icon="mdi-play-speed" />
|
||||||
>
|
</template>
|
||||||
<template #prepend>
|
<VListItemTitle>继续执行</VListItemTitle>
|
||||||
<VIcon icon="mdi-replay" />
|
</VListItem>
|
||||||
</template>
|
<VListItem
|
||||||
<VListItemTitle>重新执行</VListItemTitle>
|
v-if="workflow.current_action"
|
||||||
</VListItem>
|
variant="plain"
|
||||||
<VListItem v-else variant="plain" base-color="info" @click="handleRun(workflow, true)">
|
base-color="info"
|
||||||
<template #prepend>
|
@click="handleRun(workflow, true)"
|
||||||
<VIcon icon="mdi-run" />
|
>
|
||||||
</template>
|
<template #prepend>
|
||||||
<VListItemTitle>立即执行</VListItemTitle>
|
<VIcon icon="mdi-replay" />
|
||||||
</VListItem>
|
</template>
|
||||||
<VListItem variant="plain" base-color="warning" @click="handleReset(workflow)">
|
<VListItemTitle>重新执行</VListItemTitle>
|
||||||
<template #prepend>
|
</VListItem>
|
||||||
<VIcon icon="mdi-restore-alert" />
|
<VListItem v-else variant="plain" base-color="info" @click="handleRun(workflow, true)">
|
||||||
</template>
|
<template #prepend>
|
||||||
<VListItemTitle>重置任务</VListItemTitle>
|
<VIcon icon="mdi-run" />
|
||||||
</VListItem>
|
</template>
|
||||||
<VListItem variant="plain" base-color="error" @click="handleDelete(workflow)">
|
<VListItemTitle>立即执行</VListItemTitle>
|
||||||
<template #prepend>
|
</VListItem>
|
||||||
<VIcon icon="mdi-delete" />
|
<VListItem variant="plain" base-color="warning" @click="handleReset(workflow)">
|
||||||
</template>
|
<template #prepend>
|
||||||
<VListItemTitle>删除任务</VListItemTitle>
|
<VIcon icon="mdi-restore-alert" />
|
||||||
</VListItem>
|
</template>
|
||||||
</VList>
|
<VListItemTitle>重置任务</VListItemTitle>
|
||||||
</VMenu>
|
</VListItem>
|
||||||
</IconBtn>
|
<VListItem variant="plain" base-color="error" @click="handleDelete(workflow)">
|
||||||
</template>
|
<template #prepend>
|
||||||
</VCardItem>
|
<VIcon icon="mdi-delete" />
|
||||||
<VDivider />
|
</template>
|
||||||
<VCardText>
|
<VListItemTitle>删除任务</VListItemTitle>
|
||||||
<div class="d-flex flex-column gap-y-4">
|
</VListItem>
|
||||||
<div class="d-flex flex-wrap gap-x-6">
|
</VList>
|
||||||
<div class="flex-1">
|
</VMenu>
|
||||||
<div class="mb-1">定时</div>
|
</IconBtn>
|
||||||
<h5 class="text-h6">{{ workflow?.timer }}</h5>
|
</template>
|
||||||
</div>
|
</VCardItem>
|
||||||
<div class="flex-1">
|
<VDivider />
|
||||||
<div class="mb-1">状态</div>
|
<VCardText>
|
||||||
<h5 class="text-h6" :class="`text-${resolveStatusVariant(workflow?.state).color}`">
|
<div class="d-flex flex-column gap-y-4">
|
||||||
{{ resolveStatusVariant(workflow?.state).text }}
|
<div class="d-flex flex-wrap gap-x-6">
|
||||||
</h5>
|
<div class="flex-1">
|
||||||
</div>
|
<div class="mb-1">定时</div>
|
||||||
</div>
|
<h5 class="text-h6">{{ workflow?.timer }}</h5>
|
||||||
<div class="d-flex flex-wrap gap-x-6">
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="mb-1">动作数</div>
|
<div class="mb-1">状态</div>
|
||||||
<div>
|
<h5 class="text-h6" :class="`text-${resolveStatusVariant(workflow?.state).color}`">
|
||||||
<VAvatar size="32" color="primary" variant="tonal">
|
{{ resolveStatusVariant(workflow?.state).text }}
|
||||||
<span class="text-sm">{{ workflow?.actions?.length }}</span>
|
</h5>
|
||||||
</VAvatar>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="d-flex flex-wrap gap-x-6">
|
||||||
<div class="mb-1">已执行次数</div>
|
<div class="flex-1">
|
||||||
<h5 class="text-h6">{{ workflow?.run_count }}</h5>
|
<div class="mb-1">动作数</div>
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<VAvatar size="32" color="primary" variant="tonal">
|
||||||
<div class="d-flex flex-wrap gap-x-6">
|
<span class="text-sm">{{ workflow?.actions?.length }}</span>
|
||||||
<div class="flex-1">
|
</VAvatar>
|
||||||
<div class="mb-1">进度</div>
|
|
||||||
<div class="d-flex align-center gap-5">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<VProgressLinear color="info" rounded :model-value="resolveProgress(workflow)" />
|
|
||||||
</div>
|
</div>
|
||||||
<span> {{ resolveProgress(workflow) }}% </span>
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="mb-1">已执行次数</div>
|
||||||
|
<h5 class="text-h6">{{ workflow?.run_count }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap gap-x-6">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="mb-1">进度</div>
|
||||||
|
<div class="d-flex align-center gap-5">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<VProgressLinear color="info" rounded :model-value="resolveProgress(workflow)" />
|
||||||
|
</div>
|
||||||
|
<span> {{ resolveProgress(workflow) }}% </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap gap-x-6" v-if="workflow?.result">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="mb-1">错误信息</div>
|
||||||
|
<div class="text-error">{{ workflow?.result }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-x-6" v-if="workflow?.result">
|
</VCardText>
|
||||||
<div class="flex-1">
|
</VCard>
|
||||||
<div class="mb-1">错误信息</div>
|
</VHover>
|
||||||
<div class="text-error">{{ workflow?.result }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
<!-- 流程对话框 -->
|
<!-- 流程对话框 -->
|
||||||
<WorkflowActionsDialog
|
<WorkflowActionsDialog
|
||||||
v-if="flowDialog"
|
v-if="flowDialog"
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ html.v-overlay-scroll-blocked body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grid-user-card {
|
.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;
|
padding-block-end: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user