Преглед изворни кода

重构布局组件,优化默认布局结构,移除冗余代码,添加Header、Menu和Main组件以提升可读性和功能性;更新Logo组件样式,增强视觉效果;重构菜单组件,添加动态菜单列表和自定义滚动条样式;更新样式文件,调整全局样式和变量,提升整体一致性。

xbx пре 6 дана
родитељ
комит
5ef7901afe

Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/library.svg


+ 23 - 15
src/layout/components/Logo/index.vue

@@ -1,14 +1,15 @@
 <template>
-  <div class="flex items-center p-4 transition-all duration-300 overflow-hidden">
-    <SvgIcon 
-      name="logo" 
-      class="mr-3 transition-transform duration-300 hover:scale-110" 
-      size="32px" 
+  <div
+    class="flex items-center p-4 transition-all duration-300 overflow-hidden min-h-[60px]"
+  >
+    <SvgIcon
+      name="logo"
+      class="mr-3 transition-transform duration-300 hover:scale-110"
+      size="26px"
     />
-    <h1 
-      v-if="!collapsed" 
-      class="text-lg font-semibold m-0 whitespace-nowrap overflow-hidden text-ellipsis transition-all duration-300 hover:font-bold"
-      :style="{ color: 'var(--el-text-color-primary)' }"
+    <h1
+      v-if="!collapsed"
+      class="logo-title text-lg font-semibold m-0 whitespace-nowrap overflow-hidden text-ellipsis"
     >
       多人语音合成
     </h1>
@@ -19,19 +20,26 @@
 import SvgIcon from '@/components/Icon/Index.vue';
 
 defineOptions({
-  name: 'Logo'
-})
-
+  name: 'Logo',
+});
 
 defineProps({
   // 是否折叠,折叠时不显示标题
   collapsed: {
     type: Boolean,
-    default: false
-  }
+    default: false,
+  },
 });
 </script>
 
 <style scoped>
 /* 只保留必要的样式,其他都通过 Tailwind 类实现 */
-</style> 
+.logo-title {
+  background-image: linear-gradient(90deg, #409eff, #67c23a);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+  transition: all 0.3s;
+}
+
+</style>

+ 14 - 0
src/layout/components/Main/index.vue

@@ -0,0 +1,14 @@
+<template>
+  <main class="main-layout flex flex-col flex-1 overflow-hidden h-screen">
+    <Header />
+    <div class="flex-1 overflow-y-auto bg-white">
+      <div class="p-4 md:p-6 ">
+        <router-view />
+      </div>
+    </div>
+  </main>
+</template>
+<script lang="ts" setup>
+import Header from '../header/index.vue';
+</script>
+<style lang="scss" scoped></style>

+ 14 - 1
src/layout/components/header/index.vue

@@ -1,5 +1,18 @@
 <template>
-    <div>我是头部</div>
+    <header class="sticky top-0 z-50 w-full border-b border-gray-300">
+       <div class="container flex h-16 items-center space-x-0.5 justify-between px-7 border-box">
+        <div></div>
+        <el-dropdown placement="bottom">
+            <div class="text-[12px] !border-none">我是用户名</div>
+            <template #dropdown>
+                <el-dropdown-menu>
+                    <el-dropdown-item>退出登录</el-dropdown-item>
+                </el-dropdown-menu>
+            </template>
+        </el-dropdown>
+        
+       </div>
+    </header>
 </template>
 <script setup lang="ts">  
  defineOptions({

+ 66 - 6
src/layout/components/menu/index.vue

@@ -1,8 +1,68 @@
 <template>
-    <div>我是菜单</div>
+    <aside class="hidden md:block border-r border-gray-100 h-full w-[200px]">
+        <div
+            class="fixed top-0 left-0 flex h-screen overflow-y-auto custom-scrollbar flex-col transition-all duration-300 ease-in-out z-20 w-[200px] bg-[#edf3ff] border-r border-[#EAEEFF]">
+            <Logo />
+            <div class="flex-shrink-0 h-auto pt-[10px] relative flex flex-col justify-between pb-[24px] box-border">
+                <div v-for="item in menuList" :key="item.key">
+                    <div class="text-[12px] uppercase tracking-wider font-medium text-gray-500">
+                        <p class="leading-[18px] py-3 px-4 text-[11px]">{{ item.title }}</p>
+                        <div v-if="item.children">
+                            <router-link 
+                                v-for="child in item.children" 
+                                :key="child.key" 
+                                :to="child.url"
+                                class="flex items-center px-4 py-2.5 text-[14px] font-medium transition-colors" 
+                                :class="[
+                                    $route.path.includes(child.url) 
+                                        ? 'bg-blue-50 text-blue-600 hover:bg-blue-50' 
+                                        : 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'
+                                ]"
+                            >
+                                <component :is="child.icon" class="!h-5 !w-5 mr-2 transition-colors" />
+                                <p class="text-[14px] leading-[18px] font-medium">
+                                    {{ child.title }}
+                                </p>
+                            </router-link>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="transition-all duration-300 ease-in-out ml-[200px]"></div>
+    </aside>
 </template>
-<script setup lang="ts">  
- defineOptions({
-    name: 'Menu'
- })
-</script>
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRoute } from 'vue-router'
+import Logo from '../Logo/index.vue'
+import { menuList } from './menu.tsx'
+
+defineOptions({
+    name: 'Menu',
+})
+
+const route = useRoute()
+</script>
+
+<style scoped>
+/* 自定义滚动条样式 */
+.custom-scrollbar::-webkit-scrollbar {
+    width: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+    background-color: rgba(237, 243, 255, 0.5);
+    border-radius: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+    background-color: rgba(64, 158, 255, 0.2);
+    border-radius: 4px;
+    transition: all 0.3s;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+    background-color: rgba(64, 158, 255, 0.4);
+}
+</style>

+ 88 - 0
src/layout/components/menu/menu.tsx

@@ -0,0 +1,88 @@
+// 菜单数据类型定义
+import {
+  Notebook,
+  Headset,
+  Setting,
+  List,
+  Microphone,
+  User,
+  Folder,
+  UserFilled,
+} from '@element-plus/icons-vue';
+import SvgIcon from '@/components/Icon/Index.vue';
+
+import { h } from 'vue';
+
+export interface SubMenuItem {
+  title: string; // 子菜单标题
+  icon?: any; // 子菜单图标(可选)
+  key: string; // 唯一标识
+  url: string; // 子菜单链接
+}
+
+export interface MenuItem {
+  title: string; // 主菜单标题
+  icon?: any; // 主菜单图标(可选)
+  key: string; // 唯一标识
+  children?: SubMenuItem[]; // 子菜单列表
+}
+
+// 菜单数据,包含主菜单和子菜单
+export const menuList: MenuItem[] = [
+  {
+    title: '书库管理',
+    key: 'library',
+    children: [
+      {
+        title: '书库列表',
+        icon: h(Notebook),
+        key: 'LibraryStack',
+        url: '/library/stack',
+      },
+    ],
+  },
+  {
+    title: '音频管理',
+    icon: h(Headset),
+    key: 'audio',
+    children: [
+      {
+        title: '版本管理',
+        icon: h(Setting),
+        key: 'version-manage',
+        url: '/audio/version-manage',
+      },
+      {
+        title: '有声任务列表',
+        icon: h(List),
+        key: 'audio-task-list',
+        url: '/audio/task-list',
+      },
+    ],
+  },
+  {
+    title: '音色库',
+    icon: h(Microphone),
+    key: 'voice',
+    children: [
+      {
+        title: '音色管理',
+        icon: h(User),
+        key: 'voice-manage',
+        url: '/voice/manage',
+      },
+      {
+        title: '分类管理',
+        icon: h(Folder),
+        key: 'category-manage',
+        url: '/voice/category-manage',
+      },
+      {
+        title: '音色组管理',
+        icon: h(UserFilled),
+        key: 'voice-group-manage',
+        url: '/voice/group-manage',
+      },
+    ],
+  },
+];

+ 12 - 31
src/layout/default-layout.vue

@@ -1,47 +1,28 @@
 <template>
-  <div class="default-layout overflow-auto h-full bg-white bg-[length:100%_auto] bg-no-repeat bg-left-top relative">
-    <Header />
+  <div class="default-layout overflow-auto h-screen bg-white  bg-no-repeat bg-left-top relative flex">
     <Menu />
+    <Main />
+    
   </div>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
 import { defineComponent, ref } from 'vue';
 import Header from './components/header/index.vue';
-import Menu from './components/menu/index.vue';
+import Menu from './components/Menu/index.vue';
+import Main from './components/Main/index.vue';
+
+defineOptions({
+  name: 'DefaultLayout'
+})
 
 
-export default defineComponent({
-  name: 'DefaultLayout',
-  setup() {
-    const showHeader = ref(true);
-    const showFooter = ref(true);
-    
-    return {
-      showHeader,
-      showFooter
-    };
-  }
-});
 </script>
 
 <style lang="scss" scoped>
 .default-layout {
-  display: flex;
-  flex-direction: column;
-  min-height: 100vh;
-  
-  .layout-header {
-    /* 头部样式 */
-  }
-  
-  .layout-content {
-    flex: 1;
-    padding: 20px;
-  }
+ 
   
-  .layout-footer {
-    /* 底部样式 */
-  }
+ 
 }
 </style> 

+ 42 - 2
src/styles/index.css

@@ -1,5 +1,5 @@
 /* 导入 Tailwind CSS */
-@import "tailwindcss"; 
+@import 'tailwindcss';
 
 :root {
   --el-color-primary: #1677ff !important;
@@ -8,4 +8,44 @@
   --el-color-primary-light-7: #91caff !important;
   --el-color-primary-light-9: #e6f4ff !important;
   --el-color-primary-dark-2: #0958d9 !important;
-}
+  --color-border: #e1e1ef;
+}
+
+html,
+body {
+  font-family:
+    Arial,
+    Helvetica,
+    sans-serif,
+    ui-sans-serif,
+    system-ui,
+    sans-serif,
+    Apple Color Emoji,
+    Segoe UI Emoji,
+    Segoe UI Symbol,
+    Noto Color Emoji;
+}
+
+
+
+
+
+
+
+/* 处理默认的focus-visible样式 */
+:focus-visible {
+  outline: none;
+}
+
+/* 处理其他focus样式 */
+:focus {
+  outline: none;
+}
+
+/* 移除webkit中输入框的默认外观 */
+input,
+textarea {
+  -webkit-appearance: none;
+  appearance: none;
+}
+

+ 1 - 1
src/styles/variables.scss

@@ -57,7 +57,7 @@ $light-text-color: #213547;
 $light-bg-color: #ffffff;
 
 // 字体设置
-$font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+$font-family: ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
 $font-size-base: 16px;
 $line-height-base: 24px;
 

+ 3 - 0
src/types/components.d.ts

@@ -15,6 +15,9 @@ declare module 'vue' {
     ElButton: typeof import('element-plus/es')['ElButton']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElRow: typeof import('element-plus/es')['ElRow']

+ 3 - 149
src/views/library/stack/index.vue

@@ -1,155 +1,9 @@
 <template>
-  <div class="library-stack-container">
-  
-    测试啊
-  </div>
+  <div class="library-stack-container">测试啊</div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue'
-
-// 定义数据类型
-interface SearchForm {
-  name: string;
-  status: string;
-}
-
-interface TableItem {
-  id: number;
-  name: string;
-  bookCount: number;
-  createdTime: string;
-  status: string;
-}
-
-// 搜索表单
-const searchForm = reactive<SearchForm>({
-  name: '',
-  status: ''
-})
-
-// 表格数据
-const tableData = ref<TableItem[]>([
-  {
-    id: 1,
-    name: '主要书库',
-    bookCount: 1250,
-    createdTime: '2023-08-15',
-    status: 'normal'
-  },
-  {
-    id: 2,
-    name: '历史文献',
-    bookCount: 850,
-    createdTime: '2023-08-16',
-    status: 'normal'
-  },
-  {
-    id: 3,
-    name: '科技资料',
-    bookCount: 620,
-    createdTime: '2023-08-17',
-    status: 'disabled'
-  }
-])
-
-// 加载状态
-const loading = ref(false)
-
-// 分页相关
-const currentPage = ref(1)
-const pageSize = ref(10)
-const total = ref(100)
-
-// 生命周期钩子
-onMounted(() => {
-  // 初始化数据,可以调用API获取数据
-  // fetchData()
-})
-
-// 搜索方法
-const handleSearch = () => {
-  console.log('搜索条件:', searchForm)
-  currentPage.value = 1
-  // fetchData()
-}
-
-// 重置搜索
-const resetSearch = () => {
-  searchForm.name = '';
-  searchForm.status = '';
-  currentPage.value = 1
-  // fetchData()
-}
-
-// 编辑
-const handleEdit = (row: TableItem) => {
-  console.log('编辑', row)
-}
-
-// 查看
-const handleView = (row: TableItem) => {
-  console.log('查看', row)
-}
-
-// 删除
-const handleDelete = (row: TableItem) => {
-  console.log('删除', row)
-  // 可以调用删除API
-}
-
-// 分页大小变化
-const handleSizeChange = (val: number) => {
-  pageSize.value = val
-  // fetchData()
-}
-
-// 页码变化
-const handleCurrentChange = (val: number) => {
-  currentPage.value = val
-  // fetchData()
-}
-
-// 获取数据方法(实际项目中可以调用API)
-/*
-const fetchData = async () => {
-  loading.value = true
-  try {
-    // 调用API获取数据
-    // const res = await api.getLibraryList({
-    //   page: currentPage.value,
-    //   pageSize: pageSize.value,
-    //   ...searchForm
-    // })
-    // tableData.value = res.data.list
-    // total.value = res.data.total
-  } catch (error) {
-    console.error('获取数据失败', error)
-  } finally {
-    loading.value = false
-  }
-}
-*/
+import { ref, reactive, onMounted } from 'vue';
 </script>
 
-<style scoped lang="scss">
-.library-stack-container {
-  padding: 20px;
-
-  .card-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-  }
-
-  .search-container {
-    margin-bottom: 20px;
-  }
-
-  .pagination-container {
-    margin-top: 20px;
-    display: flex;
-    justify-content: flex-end;
-  }
-}
-</style>
+<style scoped lang="scss"></style>