优化
This commit is contained in:
@@ -15,11 +15,11 @@ const shouldShowUser = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header
|
<header
|
||||||
class="fixed top-0 left-0 right-0 z-[100]
|
class="fixed top-0 left-0 right-0
|
||||||
flex items-center px-[30px]
|
flex items-center px-[30px]
|
||||||
bg-sidebar border-b border-sidebar-border
|
bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 shadow-sm
|
||||||
text-sidebar-foreground"
|
text-slate-900 dark:text-slate-50"
|
||||||
:style="{ height: 'var(--header-height)' }"
|
:style="{ height: 'var(--header-height)', zIndex: 'var(--z-header)' }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-md flex-1">
|
<div class="flex items-center gap-md flex-1">
|
||||||
<BrandLogo :size="40" />
|
<BrandLogo :size="40" />
|
||||||
|
|||||||
@@ -51,52 +51,33 @@ async function handleLogout() {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
class="group flex items-center gap-md rounded-full px-md py-1.5 pl-1.5
|
class="group flex items-center gap-md rounded-full px-md py-1.5 pl-1.5
|
||||||
bg-muted border border-border
|
bg-card border border-border
|
||||||
cursor-pointer outline-none
|
cursor-pointer outline-none
|
||||||
transition-all duration-250 ease-out
|
transition-all duration-200
|
||||||
hover:bg-accent hover:border-border hover:-translate-y-0.5
|
hover:bg-accent hover:border-primary/50
|
||||||
data-[state=open]:bg-accent data-[state=open]:border-border"
|
data-[state=open]:bg-accent data-[state=open]:border-primary/50"
|
||||||
>
|
>
|
||||||
<!-- 头像容器 -->
|
<!-- 头像 -->
|
||||||
<div class="relative w-9 h-9">
|
<Avatar class="w-8 h-8 flex-shrink-0">
|
||||||
<!-- 渐变环 - hover 时显示 -->
|
<AvatarImage v-if="userStore.displayAvatar" :src="userStore.displayAvatar" alt="avatar" />
|
||||||
<div
|
<AvatarFallback
|
||||||
class="absolute -inset-0.5 rounded-full opacity-0 group-hover:opacity-100
|
class="flex items-center justify-center text-primary-foreground font-bold text-sm"
|
||||||
transition-all duration-400 -z-10"
|
:style="{ background: avatarGradient }"
|
||||||
style="background: conic-gradient(from 0deg, rgba(59, 130, 246, 0.8), rgba(99, 102, 241, 0.8), rgba(139, 92, 246, 0.8), rgba(59, 130, 246, 0.8))"
|
>
|
||||||
/>
|
{{ userStore.displayName?.charAt(0)?.toUpperCase() || 'U' }}
|
||||||
<!-- 头像背景遮罩 -->
|
</AvatarFallback>
|
||||||
<div class="absolute inset-0 rounded-full bg-background -z-5" />
|
</Avatar>
|
||||||
|
|
||||||
<!-- 头像 -->
|
|
||||||
<Avatar class="w-9 h-9 relative z-10">
|
|
||||||
<AvatarImage v-if="userStore.displayAvatar" :src="userStore.displayAvatar" alt="avatar" />
|
|
||||||
<AvatarFallback
|
|
||||||
class="flex items-center justify-center text-primary-foreground font-bold text-[15px]"
|
|
||||||
:style="{ background: avatarGradient }"
|
|
||||||
>
|
|
||||||
{{ userStore.displayName?.charAt(0)?.toUpperCase() || 'U' }}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
|
|
||||||
<!-- 在线状态点 -->
|
|
||||||
<div
|
|
||||||
class="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-success
|
|
||||||
border-2 border-background z-20
|
|
||||||
shadow-[0_0_0_2px_oklch(from_var(--success)_l_c_h_/_0.3)]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 用户名 -->
|
<!-- 用户名 -->
|
||||||
<span class="text-sm font-medium text-foreground truncate max-w-[100px]">
|
<span class="text-sm font-medium text-foreground truncate">
|
||||||
{{ userStore.displayName || '用户' }}
|
{{ userStore.displayName || '用户' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- 下拉箭头 -->
|
<!-- 下拉箭头 -->
|
||||||
<Icon
|
<Icon
|
||||||
icon="lucide:chevron-down"
|
icon="lucide:chevron-down"
|
||||||
class="w-4 h-4 text-muted-foreground shrink-0 transition-transform duration-250
|
class="w-4 h-4 text-muted-foreground shrink-0 transition-transform duration-200
|
||||||
group-hover:rotate-180 group-data-[state=open]:rotate-180"
|
group-data-[state=open]:rotate-180"
|
||||||
/>
|
/>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { cva } from "class-variance-authority"
|
|||||||
export { default as Button } from "./Button.vue"
|
export { default as Button } from "./Button.vue"
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -22,11 +22,11 @@ export const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
"sm": "h-8 rounded-lg gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
"lg": "h-10 rounded-lg px-6 has-[>svg]:px-4",
|
||||||
"icon": "size-9",
|
"icon": "size-9 rounded-lg",
|
||||||
"icon-sm": "size-8",
|
"icon-sm": "size-8 rounded-lg",
|
||||||
"icon-lg": "size-10",
|
"icon-lg": "size-10 rounded-lg",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -23,16 +23,19 @@ const forwarded = useForwardPropsEmits(props, emits)
|
|||||||
data-slot="drawer-content"
|
data-slot="drawer-content"
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
|
'group/drawer-content bg-background fixed flex h-auto flex-col',
|
||||||
'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg',
|
'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg',
|
||||||
'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg',
|
'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg',
|
||||||
'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:sm:max-w-sm',
|
'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:sm:max-w-sm',
|
||||||
'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm',
|
'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
|
:style="{ zIndex: 'var(--z-modal)' }"
|
||||||
>
|
>
|
||||||
<div class="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
<div class="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||||
<slot />
|
<div class="p-4 overflow-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</DrawerPortal>
|
</DrawerPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const delegatedProps = reactiveOmit(props, "class")
|
|||||||
<DrawerOverlay
|
<DrawerOverlay
|
||||||
data-slot="drawer-overlay"
|
data-slot="drawer-overlay"
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
|
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 bg-black/80', props.class)"
|
||||||
|
:style="{ zIndex: 'var(--z-modal-backdrop)' }"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<ChevronLeftIcon />
|
<ChevronLeftIcon />
|
||||||
<span class="hidden sm:block">First</span>
|
<span class="hidden sm:block">首页</span>
|
||||||
</slot>
|
</slot>
|
||||||
</PaginationFirst>
|
</PaginationFirst>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<span class="hidden sm:block">Last</span>
|
<span class="hidden sm:block">末页</span>
|
||||||
<ChevronRightIcon />
|
<ChevronRightIcon />
|
||||||
</slot>
|
</slot>
|
||||||
</PaginationLast>
|
</PaginationLast>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<span class="hidden sm:block">Next</span>
|
<span class="hidden sm:block">下一页</span>
|
||||||
<ChevronRightIcon />
|
<ChevronRightIcon />
|
||||||
</slot>
|
</slot>
|
||||||
</PaginationNext>
|
</PaginationNext>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|||||||
>
|
>
|
||||||
<slot>
|
<slot>
|
||||||
<ChevronLeftIcon />
|
<ChevronLeftIcon />
|
||||||
<span class="hidden sm:block">Previous</span>
|
<span class="hidden sm:block">上一页</span>
|
||||||
</slot>
|
</slot>
|
||||||
</PaginationPrev>
|
</PaginationPrev>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationFirst,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLast,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious
|
||||||
|
} from '@/components/ui/pagination'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** 当前页码 */
|
||||||
|
current: number
|
||||||
|
/** 每页条数 */
|
||||||
|
pageSize: number
|
||||||
|
/** 总条数 */
|
||||||
|
total: number
|
||||||
|
/** 显示的页码按钮数量 */
|
||||||
|
siblingCount?: number
|
||||||
|
/** 是否显示首页/末页按钮 */
|
||||||
|
showEdges?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
siblingCount: 1,
|
||||||
|
showEdges: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:current': [page: number]
|
||||||
|
'change': [page: number]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const totalPages = computed(() => Math.ceil(props.total / props.pageSize))
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
if (page < 1 || page > totalPages.value || page === props.current) return
|
||||||
|
emit('update:current', page)
|
||||||
|
emit('change', page)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="total > 0" class="flex flex-wrap items-center justify-between gap-4 py-4 border-t">
|
||||||
|
<span class="text-sm text-muted-foreground shrink-0">
|
||||||
|
共 {{ total }} 条记录
|
||||||
|
</span>
|
||||||
|
<Pagination
|
||||||
|
v-slot="{ page }"
|
||||||
|
:items-per-page="pageSize"
|
||||||
|
:total="total"
|
||||||
|
:sibling-count="siblingCount"
|
||||||
|
:show-edges="showEdges"
|
||||||
|
:page="current"
|
||||||
|
@update:page="handlePageChange"
|
||||||
|
>
|
||||||
|
<PaginationContent v-slot="{ items }">
|
||||||
|
<PaginationFirst @click="handlePageChange(1)" />
|
||||||
|
<PaginationPrevious @click="handlePageChange(current - 1)" />
|
||||||
|
<template v-for="(item, index) in items" :key="index">
|
||||||
|
<PaginationItem
|
||||||
|
v-if="item.type === 'page'"
|
||||||
|
:value="item.value"
|
||||||
|
as-child
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
:variant="page === item.value ? 'default' : 'outline'"
|
||||||
|
size="icon-sm"
|
||||||
|
class="h-8 w-8"
|
||||||
|
>
|
||||||
|
{{ item.value }}
|
||||||
|
</Button>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationEllipsis v-else :index="index" />
|
||||||
|
</template>
|
||||||
|
<PaginationNext @click="handlePageChange(current + 1)" />
|
||||||
|
<PaginationLast @click="handlePageChange(totalPages)" />
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -6,3 +6,4 @@ export { default as PaginationItem } from "./PaginationItem.vue"
|
|||||||
export { default as PaginationLast } from "./PaginationLast.vue"
|
export { default as PaginationLast } from "./PaginationLast.vue"
|
||||||
export { default as PaginationNext } from "./PaginationNext.vue"
|
export { default as PaginationNext } from "./PaginationNext.vue"
|
||||||
export { default as PaginationPrevious } from "./PaginationPrevious.vue"
|
export { default as PaginationPrevious } from "./PaginationPrevious.vue"
|
||||||
|
export { default as TablePagination } from "./TablePagination.vue"
|
||||||
|
|||||||
@@ -122,6 +122,7 @@
|
|||||||
--color-text-secondary: oklch(0.42 0.006 260);
|
--color-text-secondary: oklch(0.42 0.006 260);
|
||||||
--color-text-muted: oklch(0.55 0.005 260);
|
--color-text-muted: oklch(0.55 0.005 260);
|
||||||
--color-text-disabled: oklch(0.72 0.003 260);
|
--color-text-disabled: oklch(0.72 0.003 260);
|
||||||
|
--color-text-inverse: oklch(0.99 0 0);
|
||||||
--color-border: oklch(0.92 0.002 260);
|
--color-border: oklch(0.92 0.002 260);
|
||||||
--color-primary-hover: var(--color-primary-400);
|
--color-primary-hover: var(--color-primary-400);
|
||||||
|
|
||||||
@@ -186,6 +187,18 @@
|
|||||||
--sidebar-width: 240px;
|
--sidebar-width: 240px;
|
||||||
--header-height: 56px;
|
--header-height: 56px;
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Z-Index 层级系统
|
||||||
|
======================================== */
|
||||||
|
--z-dropdown: 50;
|
||||||
|
--z-sticky: 100;
|
||||||
|
--z-fixed: 150;
|
||||||
|
--z-header: 200;
|
||||||
|
--z-modal-backdrop: 300;
|
||||||
|
--z-modal: 400;
|
||||||
|
--z-popover: 500;
|
||||||
|
--z-tooltip: 600;
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
动效
|
动效
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -277,6 +290,7 @@
|
|||||||
--color-text-secondary: oklch(0.68 0.006 260);
|
--color-text-secondary: oklch(0.68 0.006 260);
|
||||||
--color-text-muted: oklch(0.50 0.006 260);
|
--color-text-muted: oklch(0.50 0.006 260);
|
||||||
--color-text-disabled: oklch(0.36 0.006 260);
|
--color-text-disabled: oklch(0.36 0.006 260);
|
||||||
|
--color-text-inverse: oklch(0.12 0.004 260);
|
||||||
--color-border: oklch(0.26 0.006 260);
|
--color-border: oklch(0.26 0.006 260);
|
||||||
|
|
||||||
/* 主色阶 */
|
/* 主色阶 */
|
||||||
|
|||||||
@@ -175,10 +175,10 @@ function handleUse() {
|
|||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 24px;
|
padding: var(--space-6);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius);
|
||||||
background: var(--color-surface);
|
background: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-textarea {
|
.edit-textarea {
|
||||||
@@ -192,16 +192,16 @@ function handleUse() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: var(--space-2);
|
||||||
|
|
||||||
.left-actions {
|
.left-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-actions {
|
.right-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -617,7 +617,7 @@ onMounted(() => loadVoiceList())
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--space-4);
|
padding: var(--space-4);
|
||||||
background: var(--color-bg-card);
|
background: var(--card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
@@ -630,7 +630,7 @@ onMounted(() => loadVoiceList())
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
background: var(--color-bg-card);
|
background: var(--card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -641,8 +641,8 @@ onMounted(() => loadVoiceList())
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--space-3) var(--space-4);
|
padding: var(--space-3) var(--space-4);
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--border);
|
||||||
background: var(--color-bg-card);
|
background: var(--card);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传区域
|
// 上传区域
|
||||||
@@ -652,25 +652,25 @@ onMounted(() => loadVoiceList())
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--space-8) var(--space-4);
|
padding: var(--space-8) var(--space-4);
|
||||||
border: 2px dashed var(--color-border);
|
border: 2px dashed var(--border);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
background: var(--color-muted);
|
background: var(--muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--primary);
|
||||||
background: oklch(0.97 0.01 254.604);
|
background: oklch(0.97 0.01 254.604);
|
||||||
}
|
}
|
||||||
|
|
||||||
&--dragging {
|
&--dragging {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--primary);
|
||||||
background: oklch(0.95 0.02 254.604);
|
background: oklch(0.95 0.02 254.604);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
color: var(--color-primary);
|
color: var(--primary);
|
||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-3);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
@@ -678,13 +678,13 @@ onMounted(() => loadVoiceList())
|
|||||||
&__title {
|
&__title {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--color-foreground);
|
color: var(--foreground);
|
||||||
margin-bottom: var(--space-1);
|
margin-bottom: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__hint {
|
&__hint {
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
color: var(--color-muted-foreground);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,9 +694,9 @@ onMounted(() => loadVoiceList())
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--space-8) var(--space-4);
|
padding: var(--space-8) var(--space-4);
|
||||||
border: 2px solid var(--color-border);
|
border: 2px solid var(--border);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
background: var(--color-muted);
|
background: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-preview {
|
.upload-preview {
|
||||||
@@ -710,7 +710,7 @@ onMounted(() => loadVoiceList())
|
|||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--color-primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
@@ -723,7 +723,7 @@ onMounted(() => loadVoiceList())
|
|||||||
&__name {
|
&__name {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--color-foreground);
|
color: var(--foreground);
|
||||||
max-width: 220px;
|
max-width: 220px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@@ -202,12 +202,12 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 高级设置抽屉 -->
|
<!-- 高级设置模态框 -->
|
||||||
<Sheet v-model:open="showSettings" style="padding: 0 16px;">
|
<Dialog v-model:open="showSettings">
|
||||||
<SheetContent class="w-80">
|
<DialogContent class="max-w-md">
|
||||||
<SheetHeader class="pb-4 border-b border-border">
|
<DialogHeader>
|
||||||
<SheetTitle class="text-lg">高级设置</SheetTitle>
|
<DialogTitle>高级设置</DialogTitle>
|
||||||
</SheetHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div class="py-6 space-y-8">
|
<div class="py-6 space-y-8">
|
||||||
<!-- 裁剪模式 - 胶囊式切换 -->
|
<!-- 裁剪模式 - 胶囊式切换 -->
|
||||||
@@ -267,8 +267,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
<DialogFooter>
|
||||||
|
<Button variant="outline" @click="showSettings = false">
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<!-- 场景选择弹窗 -->
|
<!-- 场景选择弹窗 -->
|
||||||
<SceneSelectorModal
|
<SceneSelectorModal
|
||||||
@@ -313,11 +319,12 @@ import {
|
|||||||
SelectValue
|
SelectValue
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Dialog,
|
||||||
SheetContent,
|
DialogContent,
|
||||||
SheetHeader,
|
DialogFooter,
|
||||||
SheetTitle
|
DialogHeader,
|
||||||
} from '@/components/ui/sheet'
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||||
|
|
||||||
|
|||||||
@@ -174,43 +174,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div v-if="paginationConfig.total > 0" class="flex items-center justify-between py-4 border-t">
|
<TablePagination
|
||||||
<span class="text-sm text-muted-foreground">
|
:current="paginationConfig.current"
|
||||||
共 {{ paginationConfig.total }} 条记录
|
:page-size="paginationConfig.pageSize"
|
||||||
</span>
|
:total="paginationConfig.total"
|
||||||
<Pagination
|
@change="handlePageChange"
|
||||||
v-slot="{ page }"
|
/>
|
||||||
:items-per-page="paginationConfig.pageSize"
|
|
||||||
:total="paginationConfig.total"
|
|
||||||
:sibling-count="1"
|
|
||||||
show-edges
|
|
||||||
:page="paginationConfig.current"
|
|
||||||
@update:page="handlePageChange"
|
|
||||||
>
|
|
||||||
<PaginationContent v-slot="{ items }">
|
|
||||||
<PaginationFirst @click="handlePageChange(1)" />
|
|
||||||
<PaginationPrevious @click="handlePageChange(paginationConfig.current - 1)" />
|
|
||||||
<template v-for="(item, index) in items" :key="index">
|
|
||||||
<PaginationItem
|
|
||||||
v-if="item.type === 'page'"
|
|
||||||
:value="item.value"
|
|
||||||
as-child
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
:variant="page === item.value ? 'default' : 'outline'"
|
|
||||||
size="icon-sm"
|
|
||||||
class="h-8 w-8"
|
|
||||||
>
|
|
||||||
{{ item.value }}
|
|
||||||
</Button>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationEllipsis v-else :index="index" />
|
|
||||||
</template>
|
|
||||||
<PaginationNext @click="handlePageChange(paginationConfig.current + 1)" />
|
|
||||||
<PaginationLast @click="handlePageChange(Math.ceil(paginationConfig.total / paginationConfig.pageSize))" />
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -249,16 +218,7 @@ import { Progress } from '@/components/ui/progress'
|
|||||||
import { Alert } from '@/components/ui/alert'
|
import { Alert } from '@/components/ui/alert'
|
||||||
import { Spinner } from '@/components/ui/spinner'
|
import { Spinner } from '@/components/ui/spinner'
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import {
|
import { TablePagination } from '@/components/ui/pagination'
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationEllipsis,
|
|
||||||
PaginationFirst,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationLast,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious
|
|
||||||
} from '@/components/ui/pagination'
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -396,7 +356,7 @@ onMounted(fetchList)
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.task-page {
|
.task-page {
|
||||||
padding: var(--space-4);
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -404,17 +364,20 @@ onMounted(fetchList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-page__filters {
|
.task-page__filters {
|
||||||
padding: var(--space-4);
|
flex-shrink: 0;
|
||||||
background: var(--color-bg-card);
|
padding: var(--space-5);
|
||||||
|
background: var(--card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-page__content {
|
.task-page__content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--color-bg-card);
|
background: var(--card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
padding: var(--space-4);
|
border: 1px solid var(--border);
|
||||||
|
padding: var(--space-5);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="task-layout">
|
<div class="task-layout">
|
||||||
<!-- 顶部Tab栏 - 现代化设计 -->
|
<!-- 顶部Tab栏 -->
|
||||||
<div class="task-layout__header">
|
<div class="task-layout__header">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<Tabs v-model:model-value="currentType" class="w-auto">
|
<Tabs v-model:model-value="currentType" class="w-auto">
|
||||||
<TabsList class="h-11 bg-muted/50 p-1 gap-1">
|
<TabsList class="h-auto bg-transparent p-0 gap-2">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
v-for="item in NAV_ITEMS"
|
v-for="item in NAV_ITEMS"
|
||||||
:key="item.type"
|
:key="item.type"
|
||||||
:value="item.type"
|
:value="item.type"
|
||||||
class="h-9 px-4 gap-2 rounded-md transition-all data-[state=active]:bg-background data-[state=active]:shadow-sm data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:text-foreground"
|
class="h-9 px-4 gap-2 rounded-lg bg-transparent transition-all data-[state=active]:bg-primary data-[state=active]:text-white data-[state=inactive]:text-muted-foreground data-[state=inactive]:hover:bg-muted focus-visible:ring-0 focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<Icon :icon="item.icon" class="size-4" />
|
<Icon :icon="item.icon" class="size-4" />
|
||||||
<span class="font-medium">{{ item.label }}</span>
|
<span class="font-medium">{{ item.label }}</span>
|
||||||
@@ -80,10 +80,9 @@ const currentComponent = computed(() => {
|
|||||||
|
|
||||||
.task-layout__header {
|
.task-layout__header {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: var(--space-4) var(--space-6);
|
padding: 0 var(--space-4);
|
||||||
background: var(--card);
|
background: var(--color-bg-card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-layout__content {
|
.task-layout__content {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<Icon icon="lucide:search" class="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
<Icon icon="lucide:search" class="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
v-model="filters.keyword"
|
v-model="filters.keyword"
|
||||||
placeholder="搜索标题"
|
placeholder="搜索任务名称"
|
||||||
class="pl-9"
|
class="pl-9"
|
||||||
@keyup.enter="handleFilterChange"
|
@keyup.enter="handleFilterChange"
|
||||||
/>
|
/>
|
||||||
@@ -63,30 +63,29 @@
|
|||||||
|
|
||||||
<!-- 任务列表 -->
|
<!-- 任务列表 -->
|
||||||
<div class="task-page__content">
|
<div class="task-page__content">
|
||||||
<!-- 批量操作工具栏 -->
|
<!-- 批量操作栏 -->
|
||||||
<div class="batch-toolbar flex items-center gap-3 pb-3 border-b">
|
<div v-if="selectedRowKeys.length > 0" class="batch-actions">
|
||||||
<span v-if="selectedRowKeys.length > 0" class="text-sm">
|
<Alert class="flex items-center justify-between">
|
||||||
已选择 <strong class="text-primary">{{ selectedRowKeys.length }}</strong> 项
|
<div class="flex items-center gap-2">
|
||||||
</span>
|
<Icon icon="lucide:info" class="size-4" />
|
||||||
<Button
|
<span>已选中 {{ selectedRowKeys.length }} 项</span>
|
||||||
:disabled="!hasDownloadableSelected"
|
</div>
|
||||||
:loading="batchDownloading"
|
<div class="flex items-center gap-2">
|
||||||
size="sm"
|
<Button
|
||||||
class="text-white"
|
:disabled="!hasDownloadableSelected"
|
||||||
@click="handleBatchDownloadSelected"
|
:loading="batchDownloading"
|
||||||
>
|
size="sm"
|
||||||
<Icon icon="lucide:download" class="mr-1 size-4" />
|
@click="handleBatchDownloadSelected"
|
||||||
批量下载 ({{ downloadableCount }})
|
>
|
||||||
</Button>
|
<Icon icon="lucide:download" class="mr-1 size-4" />
|
||||||
<Button
|
批量下载 ({{ downloadableCount }})
|
||||||
variant="destructive"
|
</Button>
|
||||||
size="sm"
|
<Button variant="destructive" size="sm" @click="handleBatchDeleteSelected">
|
||||||
:disabled="selectedRowKeys.length === 0"
|
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
||||||
@click="handleBatchDeleteSelected"
|
批量删除
|
||||||
>
|
</Button>
|
||||||
<Icon icon="lucide:trash-2" class="mr-1 size-4" />
|
</div>
|
||||||
批量删除
|
</Alert>
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative min-h-[200px]">
|
<div class="relative min-h-[200px]">
|
||||||
@@ -251,43 +250,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div v-if="paginationConfig.total > 0" class="flex items-center justify-between py-4 border-t">
|
<TablePagination
|
||||||
<span class="text-sm text-muted-foreground">
|
:current="paginationConfig.current"
|
||||||
共 {{ paginationConfig.total }} 条记录
|
:page-size="paginationConfig.pageSize"
|
||||||
</span>
|
:total="paginationConfig.total"
|
||||||
<Pagination
|
@change="handlePageChange"
|
||||||
v-slot="{ page }"
|
/>
|
||||||
:items-per-page="paginationConfig.pageSize"
|
|
||||||
:total="paginationConfig.total"
|
|
||||||
:sibling-count="1"
|
|
||||||
show-edges
|
|
||||||
:page="paginationConfig.current"
|
|
||||||
@update:page="handlePageChange"
|
|
||||||
>
|
|
||||||
<PaginationContent v-slot="{ items }">
|
|
||||||
<PaginationFirst @click="handlePageChange(1)" />
|
|
||||||
<PaginationPrevious @click="handlePageChange(paginationConfig.current - 1)" />
|
|
||||||
<template v-for="(item, index) in items" :key="index">
|
|
||||||
<PaginationItem
|
|
||||||
v-if="item.type === 'page'"
|
|
||||||
:value="item.value"
|
|
||||||
as-child
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
:variant="page === item.value ? 'default' : 'outline'"
|
|
||||||
size="icon-sm"
|
|
||||||
class="h-8 w-8"
|
|
||||||
>
|
|
||||||
{{ item.value }}
|
|
||||||
</Button>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationEllipsis v-else :index="index" />
|
|
||||||
</template>
|
|
||||||
<PaginationNext @click="handlePageChange(paginationConfig.current + 1)" />
|
|
||||||
<PaginationLast @click="handlePageChange(Math.ceil(paginationConfig.total / paginationConfig.pageSize))" />
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -363,16 +331,7 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle
|
AlertDialogTitle
|
||||||
} from '@/components/ui/alert-dialog'
|
} from '@/components/ui/alert-dialog'
|
||||||
import {
|
import { TablePagination } from '@/components/ui/pagination'
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationEllipsis,
|
|
||||||
PaginationFirst,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationLast,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious
|
|
||||||
} from '@/components/ui/pagination'
|
|
||||||
import { MixTaskService } from '@/api/mixTask'
|
import { MixTaskService } from '@/api/mixTask'
|
||||||
import { formatDate } from '@/utils/file'
|
import { formatDate } from '@/utils/file'
|
||||||
import { useTaskList } from '@/views/system/task-management/composables/useTaskList'
|
import { useTaskList } from '@/views/system/task-management/composables/useTaskList'
|
||||||
@@ -620,7 +579,7 @@ onMounted(fetchList)
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.task-page {
|
.task-page {
|
||||||
padding: 0;
|
padding: var(--space-4);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -628,28 +587,22 @@ onMounted(fetchList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-page__filters {
|
.task-page__filters {
|
||||||
flex-shrink: 0;
|
padding: var(--space-4);
|
||||||
padding: var(--space-5);
|
background: var(--color-bg-card);
|
||||||
background: var(--card);
|
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-page__content {
|
.task-page__content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--card);
|
background: var(--color-bg-card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border: 1px solid var(--border);
|
padding: var(--space-4);
|
||||||
padding: var(--space-5);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.batch-toolbar {
|
.batch-actions {
|
||||||
flex-shrink: 0;
|
|
||||||
padding-bottom: var(--space-4);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -288,40 +288,49 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.task-list-container {
|
.task-list-container {
|
||||||
padding: 24px;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-section {
|
.filter-section {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: var(--space-5);
|
||||||
|
background: var(--card);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: var(--space-3);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper {
|
.table-wrapper {
|
||||||
background: white;
|
flex: 1;
|
||||||
border-radius: 12px;
|
background: var(--card);
|
||||||
border: 1px solid var(--color-gray-100);
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-section {
|
.pagination-section {
|
||||||
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 16px 0;
|
padding: var(--space-4);
|
||||||
margin-top: 16px;
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt-content {
|
.prompt-content {
|
||||||
padding: 16px;
|
padding: var(--space-4);
|
||||||
background: var(--color-gray-50);
|
background: var(--muted);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius);
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 14px;
|
font-size: var(--font-size-base);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -146,10 +146,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="profile-container">
|
<div class="profile-container">
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">个人中心</h1>
|
|
||||||
<p class="page-subtitle">管理您的账户信息和资源使用情况</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||||
<!-- 左侧:用户信息卡片 -->
|
<!-- 左侧:用户信息卡片 -->
|
||||||
@@ -299,33 +296,16 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.profile-container {
|
.profile-container {
|
||||||
padding: 24px;
|
padding: var(--space-6);
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.profile-content {
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-text);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-subtitle {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User Card */
|
|
||||||
.user-card {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||||
background: var(--color-bg-container, #fff);
|
background: var(--card);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
@@ -368,7 +348,7 @@ onMounted(async () => {
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: var(--color-text);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-role-badge {
|
.user-role-badge {
|
||||||
@@ -383,7 +363,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: var(--color-border-secondary, #f0f0f0);
|
background: var(--border);
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +375,7 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
border-bottom: 1px solid var(--color-border-secondary, #f0f0f0);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-item:last-child {
|
.detail-item:last-child {
|
||||||
@@ -403,11 +383,11 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
color: var(--color-text);
|
color: var(--foreground);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +395,7 @@ onMounted(async () => {
|
|||||||
.stat-card {
|
.stat-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
|
||||||
background: var(--color-bg-container, #fff);
|
background: var(--card);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
@@ -452,26 +432,26 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--color-text);
|
color: var(--foreground);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-unit {
|
.stat-unit {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--color-text-third);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-desc {
|
.stat-desc {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-text-third);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-progress {
|
.stat-progress {
|
||||||
@@ -489,7 +469,7 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-spinner {
|
.custom-spinner {
|
||||||
@@ -509,7 +489,7 @@ onMounted(async () => {
|
|||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
color: var(--color-text-third);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
@@ -522,7 +502,7 @@ onMounted(async () => {
|
|||||||
.activity-card {
|
.activity-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
|
||||||
background: var(--color-bg-container, #fff);
|
background: var(--card);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,12 +516,12 @@ onMounted(async () => {
|
|||||||
.activity-title {
|
.activity-title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text);
|
color: var(--foreground);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-count {
|
.record-count {
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +535,7 @@ onMounted(async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
border-bottom: 1px solid var(--color-border-secondary, #f0f0f0);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-item:last-child {
|
.record-item:last-child {
|
||||||
@@ -596,13 +576,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.record-reason {
|
.record-reason {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--color-text);
|
color: var(--foreground);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-time {
|
.record-time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-amount {
|
.record-amount {
|
||||||
@@ -625,11 +605,11 @@ onMounted(async () => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
border-top: 1px solid var(--color-border-secondary, #f0f0f0);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-info {
|
.page-info {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--muted-foreground);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user