feat: 优化

This commit is contained in:
2026-03-16 00:43:08 +08:00
parent 6639a751bc
commit 6d891b855e
8 changed files with 125 additions and 450 deletions

View File

@@ -13,7 +13,7 @@ const emit = defineEmits(['back'])
</script>
<template>
<div class="basic-layout">
<div class="flex flex-col h-full overflow-hidden rounded-xl border bg-card">
<LayoutHeader
:title="title"
:show-back="showBack"
@@ -25,30 +25,12 @@ const emit = defineEmits(['back'])
</LayoutHeader>
<!-- 页面内容 -->
<div class="basic-layout__content">
<div class="flex-1 flex flex-col overflow-x-hidden overflow-y-auto bg-card p-4">
<slot></slot>
</div>
</div>
</template>
<style scoped lang="less">
.basic-layout {
height: 100%;
display: flex;
flex-direction: column;
background: var(--color-bg-card);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-lg);
overflow: hidden;
}
.basic-layout__content {
flex: 1;
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
background: var(--color-bg-card);
padding: var(--space-4);
}
/* 使用 Tailwind 类,已移除旧样式 */
</style>

View File

@@ -29,10 +29,10 @@ const handleBack = () => {
</script>
<template>
<div class="card-layout">
<LayoutHeader
:title="title"
:show-back="showBack"
<div class="flex flex-col h-full overflow-hidden rounded-xl border bg-card">
<LayoutHeader
:title="title"
:show-back="showBack"
@back="handleBack"
>
<template #extra>
@@ -41,13 +41,19 @@ const handleBack = () => {
</LayoutHeader>
<!-- 卡片内容 -->
<div class="card-layout__card" :class="{ 'no-padding': !showPadding }">
<div v-if="!$slots.title && title" class="card-header">
<div class="flex-1 flex flex-col overflow-hidden bg-card">
<div
v-if="!$slots.title && title"
class="px-6 py-4 bg-muted/50 border-b text-sm font-medium text-muted-foreground uppercase tracking-wider"
>
{{ title }}
</div>
<slot v-else name="title"></slot>
<div class="card-layout__content">
<div
class="flex-1 overflow-auto bg-card"
:class="{ 'p-0': !showPadding, 'p-4': showPadding }"
>
<slot></slot>
</div>
</div>
@@ -55,45 +61,5 @@ const handleBack = () => {
</template>
<style scoped lang="less">
.card-layout {
height: 100%;
display: flex;
flex-direction: column;
background: var(--color-bg-card);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-lg);
overflow: hidden;
}
.card-layout__card {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--color-bg-card);
&.no-padding {
.card-layout__content {
padding: 0;
}
}
}
.card-header {
padding: var(--space-4) var(--space-6);
background: var(--color-gray-50);
border-bottom: 1px solid var(--color-gray-200);
font-size: var(--font-size-base);
font-weight: 500;
color: var(--color-gray-600);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.card-layout__content {
flex: 1;
padding: var(--space-4);
overflow: auto;
background: var(--color-bg-card);
}
/* 使用 Tailwind 类,已移除旧样式 */
</style>

View File

@@ -1,5 +1,6 @@
<script setup>
import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import { Icon } from '@iconify/vue'
import { Button } from '@/components/ui/button'
import LayoutHeader from './LayoutHeader.vue'
defineOptions({ name: 'FormLayout' })
@@ -39,26 +40,14 @@ const props = defineProps({
// Emits
const emit = defineEmits(['submit', 'cancel', 'back'])
// Methods
const handleSubmit = () => {
emit('submit')
}
const handleCancel = () => {
emit('cancel')
}
const handleBack = () => {
emit('back')
}
</script>
<template>
<div class="form-layout">
<LayoutHeader
:title="title"
:show-back="showBack"
@back="handleBack"
<div class="flex flex-col h-full overflow-hidden rounded-xl border bg-card">
<LayoutHeader
:title="title"
:show-back="showBack"
@back="emit('back')"
>
<template #extra>
<slot name="extra"></slot>
@@ -66,32 +55,31 @@ const handleBack = () => {
</LayoutHeader>
<!-- 表单内容 -->
<div class="form-layout__content">
<div class="form-container">
<div class="flex-1 overflow-auto bg-muted/50 flex items-center justify-center p-4">
<div class="w-full max-w-[600px] rounded-xl border bg-card p-10">
<slot></slot>
</div>
</div>
<!-- 底部操作栏 -->
<div v-if="showFooter" class="form-layout__footer">
<div class="footer-content">
<a-space :size="12">
<a-button v-if="showCancel" @click="handleCancel">
{{ cancelText }}
</a-button>
<a-button
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
{{ submitText }}
</a-button>
</a-space>
<div v-if="showFooter" class="shrink-0 border-t px-6 py-4 bg-card">
<div class="max-w-[600px] mx-auto flex justify-end gap-3">
<Button v-if="showCancel" variant="outline" @click="emit('cancel')">
{{ cancelText }}
</Button>
<Button :disabled="submitLoading" @click="emit('submit')">
<Icon v-if="submitLoading" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
{{ submitText }}
</Button>
</div>
</div>
</div>
</template>
<style scoped lang="less">
/* 已移除旧样式,使用 Tailwind 类 */
</style>
<style scoped lang="less">
.form-layout {
height: 100%;

View File

@@ -1,5 +1,4 @@
<script setup>
import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import LayoutHeader from './LayoutHeader.vue'
defineOptions({ name: 'FullWidthLayout' })
@@ -34,27 +33,27 @@ const handleBack = () => {
</script>
<template>
<div class="full-width-layout">
<div v-if="$slots.header || showBack" class="full-width-layout__header-wrapper">
<LayoutHeader
:show-back="showBack"
<div class="flex flex-col h-full overflow-hidden bg-card">
<div v-if="$slots.header || showBack" class="shrink-0">
<LayoutHeader
:show-back="showBack"
:ghost="headerGhost"
:padding="headerPadding"
@back="handleBack"
>
<template #header v-if="$slots.header">
<slot name="header"></slot>
<slot name="header"></slot>
</template>
<template #extra>
<slot name="extra"></slot>
</template>
</LayoutHeader>
</div>
<!-- 全宽内容 -->
<div
class="full-width-layout__content"
:class="{ 'no-padding': !showPadding }"
class="flex-1 overflow-auto bg-background"
:class="{ 'p-0': !showPadding, 'p-4': showPadding }"
>
<slot></slot>
</div>
@@ -62,25 +61,5 @@ const handleBack = () => {
</template>
<style scoped lang="less">
.full-width-layout {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--color-bg-card);
}
.full-width-layout__header-wrapper {
flex-shrink: 0;
}
.full-width-layout__content {
flex: 1;
overflow: auto;
background: var(--color-bg);
&.no-padding {
padding: 0;
}
}
/* 使用 Tailwind 类,已移除旧样式 */
</style>

View File

@@ -1,5 +1,6 @@
<script setup>
import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import { Icon } from '@iconify/vue'
import { Button } from '@/components/ui/button'
defineOptions({ name: 'LayoutHeader' })
@@ -30,98 +31,36 @@ const handleBack = () => {
</script>
<template>
<div
class="layout-header"
:class="{ 'layout-header--ghost': ghost }"
<div
class="flex justify-between items-center gap-4 border-b bg-card shrink-0 min-h-14 transition-all"
:class="{
'border-border': !ghost,
'border-transparent bg-transparent': ghost,
'px-4 py-3': !padding,
'dark:border-border dark:bg-card dark:text-foreground': !ghost
}"
:style="padding ? { padding } : {}"
>
<div class="header-left">
<a-button v-if="showBack" type="text" @click="handleBack" class="back-btn">
<template #icon>
<ArrowLeftOutlined />
</template>
</a-button>
<div class="header-content">
<div class="flex items-center gap-4 flex-1 min-w-0">
<Button
v-if="showBack"
variant="ghost"
size="icon"
class="h-8 w-8"
@click="handleBack"
>
<Icon icon="lucide:arrow-left" class="w-4 h-4" />
</Button>
<div class="flex-1 min-w-0 flex items-center">
<slot name="header">
<h1 v-if="title" class="header-title">{{ title }}</h1>
<h1 v-if="title" class="text-lg font-semibold text-foreground m-0 leading-snug tracking-tight">
{{ title }}
</h1>
</slot>
</div>
</div>
<div class="header-right">
<div class="flex items-center gap-2 shrink-0">
<slot name="extra"></slot>
</div>
</div>
</template>
<style scoped lang="less">
.layout-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-4);
padding: var(--space-4) var(--space-6);
border-bottom: 1px solid var(--color-gray-200);
background: var(--color-bg-card);
flex-shrink: 0;
min-height: 56px;
transition: all var(--duration-base);
&--ghost {
background: transparent;
border-bottom: none;
}
}
.header-left {
display: flex;
align-items: center;
gap: var(--space-4);
flex: 1;
min-width: 0;
}
.header-content {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
}
.back-btn {
padding: var(--space-1);
border: none;
background: transparent;
color: var(--color-gray-600);
border-radius: var(--radius-base);
cursor: pointer;
transition: all var(--duration-fast);
font-size: 16px;
line-height: 1;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background: var(--color-gray-100);
color: var(--color-gray-900);
}
}
.header-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--color-gray-900);
margin: 0;
line-height: 1.4;
letter-spacing: -0.01em;
}
.header-right {
display: flex;
align-items: center;
gap: var(--space-2);
flex-shrink: 0;
}
</style>

View File

@@ -1,6 +1,7 @@
<script setup>
import { ref, watch } from 'vue'
import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import { Icon } from '@iconify/vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import LayoutHeader from './LayoutHeader.vue'
defineOptions({ name: 'TabLayout' })
@@ -48,14 +49,14 @@ const handleBack = () => {
</script>
<template>
<div class="tab-layout">
<div v-if="$slots.header || showBack" class="tab-layout__header-wrapper">
<LayoutHeader
:show-back="showBack"
<div class="flex flex-col h-full overflow-hidden rounded-xl border bg-card">
<div v-if="$slots.header || showBack" class="shrink-0">
<LayoutHeader
:show-back="showBack"
@back="handleBack"
>
<template #header v-if="$slots.header">
<slot name="header"></slot>
<slot name="header"></slot>
</template>
<template #extra>
<slot name="extra"></slot>
@@ -64,75 +65,26 @@ const handleBack = () => {
</div>
<!-- 标签页导航 -->
<div class="tab-nav">
<button
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ 'active': activeTabKey === tab.key }"
@click="handleTabChange(tab.key)"
>
{{ tab.tab }}
</button>
</div>
<Tabs v-model="activeTabKey" class="w-full" @update:modelValue="handleTabChange">
<TabsList class="w-full justify-start border-b bg-muted/50 px-4">
<TabsTrigger
v-for="tab in tabs"
:key="tab.key"
:value="tab.key"
class="data-[state=active]:bg-card"
>
{{ tab.tab }}
</TabsTrigger>
</TabsList>
<!-- 标签页内容 -->
<div class="tab-content">
<slot :name="activeTabKey"></slot>
</div>
<!-- 标签页内容 -->
<TabsContent :value="activeTabKey" class="flex-1 overflow-auto p-4">
<slot :name="activeTabKey"></slot>
</TabsContent>
</Tabs>
</div>
</template>
<style scoped lang="less">
.tab-layout {
height: 100%;
display: flex;
flex-direction: column;
background: var(--color-bg-card);
border: 1px solid var(--color-gray-200);
border-radius: var(--radius-lg);
overflow: hidden;
}
.tab-layout__header-wrapper {
flex-shrink: 0;
}
.tab-nav {
display: flex;
border-bottom: 1px solid var(--color-gray-200);
background: var(--color-gray-50);
flex-shrink: 0;
}
.tab-item {
padding: var(--space-2) var(--space-6);
font-size: var(--font-size-base);
font-weight: 500;
color: var(--color-gray-600);
border: none;
background: transparent;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all var(--duration-fast);
white-space: nowrap;
&:hover {
background: var(--color-gray-100);
color: var(--color-gray-900);
}
&.active {
color: var(--color-primary-500);
border-bottom-color: var(--color-primary-500);
background: var(--color-bg-card);
}
}
.tab-content {
flex: 1;
overflow: auto;
background: var(--color-bg-card);
padding: var(--space-4);
}
/* 使用 Tailwind + shadcn 组件,已移除旧样式 */
</style>