Quellcode durchsuchen

feat: 首页架构

MTrun vor 3 Jahren
Ursprung
Commit
90e45f6c23

+ 21 - 7
src/App.vue

@@ -2,8 +2,8 @@
   <n-config-provider
     :locale="zhCN"
     :theme="getDarkTheme"
-    :theme-overrides="getThemeOverrides"
     :date-locale="dateZhCN"
+    :theme-overrides="getThemeOverrides"
   >
     <app-provider>
       <router-view />
@@ -18,7 +18,7 @@ import {
   dateZhCN,
   darkTheme,
   NConfigProvider,
-  GlobalThemeOverrides,
+  GlobalThemeOverrides
 } from 'naive-ui'
 import { AppProvider } from '@/components/Application'
 import { useDesignStore } from '@/store/modules/designStore/designStore'
@@ -26,22 +26,36 @@ import { borderRadius } from '@/settings/designSetting'
 
 const designStore = useDesignStore()
 
+// 返回暗黑主题
+const getDarkTheme = computed(() =>
+  designStore.getDarkTheme ? darkTheme : undefined
+)
+
+// 主题配置
 const getThemeOverrides = computed(
   (): GlobalThemeOverrides => {
-    return {
+    const commonObj = {
       common: {
-        primaryColor: designStore.appTheme,
         borderRadius
+      }
+    }
+    const lightObject = {
+      common: {
+        ...commonObj.common
+      }
+    }
+    const dartObject = {
+      common: {
+        primaryColor: designStore.appTheme,
+        ...commonObj.common
       },
       LoadingBar: {
         colorLoading: designStore.appTheme
       }
     }
+    return designStore.getDarkTheme ? dartObject : lightObject
   }
 )
-const getDarkTheme = computed(() =>
-  designStore.getDarkTheme ? darkTheme : undefined
-)
 </script>
 
 <style lang="scss"></style>

+ 3 - 0
src/components/Doc/index.ts

@@ -0,0 +1,3 @@
+import Doc from './index.vue';
+
+export { Doc };

+ 16 - 0
src/components/Doc/index.vue

@@ -0,0 +1,16 @@
+<template>
+  <n-button quaternary @click="handleClick" title="说明文档">
+    <n-icon size="20" :depth="1">
+      <DocumentTextIcon />
+    </n-icon>
+  </n-button>
+</template>
+
+<script lang="ts" setup>
+import { openDoc } from '@/utils/page'
+import { DocumentText as DocumentTextIcon } from '@vicons/ionicons5'
+
+const handleClick = () => {
+  openDoc()
+}
+</script>

+ 1 - 1
src/components/ThemeSelect/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <n-button quaternary @click="changeTheme">
+  <n-button quaternary @click="changeTheme" title="主题">
     <n-icon size="20" :depth="1">
       <MoonIcon v-if="designStore.darkTheme" />
       <SunnyIcon v-else />

+ 3 - 0
src/components/UserInfo/index.ts

@@ -0,0 +1,3 @@
+import UserInfo from './index.vue';
+
+export { UserInfo };

+ 103 - 0
src/components/UserInfo/index.vue

@@ -0,0 +1,103 @@
+<template>
+  <n-dropdown
+    trigger="hover"
+    @select="handleSelect"
+    :show-arrow="true"
+    :options="options"
+  >
+    <n-button quaternary circle>
+      <n-icon size="20" :depth="1">
+        <PersonOutlineIcon v-show="fallback" />
+      </n-icon>
+      <n-avatar
+        v-show="!fallback"
+        round
+        size="small"
+        :src="imageUrl"
+        @error="errorHandle"
+      />
+    </n-button>
+  </n-dropdown>
+</template>
+
+<script lang="ts" setup>
+import { h, ref } from 'vue'
+import { NAvatar, NText } from 'naive-ui'
+import { renderIcon } from '@/utils/index'
+import { openDoc, logout } from '@/utils/page'
+import {
+  Person as PersonOutlineIcon,
+  LogOutOutline as LogOutOutlineIcon,
+  DocumentText as DocumentTextIcon
+} from '@vicons/ionicons5'
+
+const imageUrl = 'https://www.naiveui.com/assets/naivelogo.93278402.svg'
+
+// 是否失败
+const fallback = ref(false)
+
+// 用户图标渲染
+const renderUserInfo = () => {
+  return h(
+    'div',
+    {
+      style: 'display: flex; align-items: center; padding: 8px 12px;'
+    },
+    [
+      h(NAvatar, {
+        round: true,
+        style: 'margin-right: 12px;',
+        src: imageUrl
+      }),
+      h('div', null, [
+        h('div', null, [
+          h(NText, { depth: 2 }, { default: () => '奔跑的面条' })
+        ])
+      ])
+    ]
+  )
+}
+
+const options = [
+  {
+    label: '我的信息',
+    key: 'info',
+    type: 'render',
+    render: renderUserInfo
+  },
+  {
+    type: 'divider',
+    key: 'd1'
+  },
+  {
+    label: '说明文档',
+    key: 'doc',
+    icon: renderIcon(DocumentTextIcon)
+  },
+  {
+    type: 'divider',
+    key: 'd2'
+  },
+  {
+    label: '退出登录',
+    key: 'logout',
+    icon: renderIcon(LogOutOutlineIcon)
+  }
+]
+
+// 图片渲染错误
+const errorHandle = (e: Event) => {
+  fallback.value = true
+}
+
+const handleSelect = (key: string) => {
+  switch (key) {
+    case 'doc':
+      openDoc()
+      break
+    case 'logout':
+      logout()
+      break
+  }
+}
+</script>

+ 8 - 4
src/enums/pageEnum.ts

@@ -1,4 +1,4 @@
-import { ResultEnum } from "@/enums/httpEnum"
+import { ResultEnum } from '@/enums/httpEnum'
 
 export enum PageEnum {
   // 登录
@@ -13,14 +13,18 @@ export enum PageEnum {
   BASE_HOME = '/project',
   BASE_HOME_NAME = 'Project',
 
+  // 模板市场
+  BASE_HOME_Template_Market = '/project/templateMarket',
+  BASE_HOME_Template_Market_NAME = 'Project-TemplateMarket',
+
   // 错误
   ERROR_PAGE_NAME_403 = 'ErrorPage403',
   ERROR_PAGE_NAME_404 = 'ErrorPage404',
-  ERROR_PAGE_NAME_500 = 'ErrorPage500',
+  ERROR_PAGE_NAME_500 = 'ErrorPage500'
 }
 
 export const ErrorPageNameMap = new Map([
   [ResultEnum.NOT_FOUND, PageEnum.ERROR_PAGE_NAME_404],
   [ResultEnum.SERVER_FORBIDDEN, PageEnum.ERROR_PAGE_NAME_403],
-  [ResultEnum.SERVER_ERROR, PageEnum.ERROR_PAGE_NAME_500],
-])
+  [ResultEnum.SERVER_ERROR, PageEnum.ERROR_PAGE_NAME_500]
+])

+ 7 - 8
src/layout/components/Header/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="go-header">
+  <n-layout-header bordered class="go-header">
     <header class="go-header-box">
       <div class="li">
         <n-space>
@@ -8,15 +8,14 @@
       </div>
       <div class="ri">
         <n-space>
-          <slot name="right">
-            <LangSelect />
-            <ThemeSelect />
-          </slot>
+          <slot name="ri-left"> </slot>
+          <LangSelect />
+          <ThemeSelect />
+          <slot name="ri-right"> </slot>
         </n-space>
       </div>
     </header>
-    <n-divider class="go-header-divider" />
-  </div>
+  </n-layout-header>
 </template>
 
 <script setup lang="ts">
@@ -30,7 +29,7 @@ import { LangSelect } from '@/components/LangSelect'
     display: flex;
     justify-content: space-between;
     align-items: center;
-    padding: 0 40px;
+    padding: 0 60px;
     height: $--header-height;
   }
   &-divider {

+ 7 - 6
src/router/modules/project.router.ts

@@ -1,13 +1,14 @@
-import { RouteRecordRaw } from 'vue-router';
+import { RouteRecordRaw } from 'vue-router'
+import { PageEnum } from '@/enums/pageEnum'
 
 const projectRoutes: RouteRecordRaw = {
-  path: '/project',
-  name: 'Project',
+  path: PageEnum.BASE_HOME,
+  name: PageEnum.BASE_HOME_NAME,
   component: () => import('@/views/project/index.vue'),
   meta: {
     title: '项目',
-    isRoot: true,
+    isRoot: true
   }
-};
+}
 
-export default projectRoutes;
+export default projectRoutes

+ 7 - 1
src/settings/designSetting.ts

@@ -38,7 +38,7 @@ export const appThemeList: string[] = [
 ]
 
 export const theme = {
-  //深色主题
+  // 默认是否开启深色主题
   darkTheme: true,
   //系统主题色
   appTheme: '#63e2b7',
@@ -46,6 +46,12 @@ export const theme = {
   appThemeList
 }
 
+// 侧边栏宽度
+export const asideWidth = '240'
+
+// 侧边栏缩小后的宽度
+export const asideCollapsedWidth = '60'
+
 // 修改边框圆角
 export const borderRadius = '8px'
 

+ 4 - 0
src/settings/pathConst.ts

@@ -0,0 +1,4 @@
+// 项目文档地址
+export const docPath = "http://www.mtruning.club/"
+
+export const giteeSourceCodePath = "https://gitee.com/MTrun/go-view/"

+ 3 - 2
src/styles/common/_dark.scss

@@ -4,7 +4,8 @@ $dark: (
   //背景
     background-color: $--color-dark-bg-1,
   //渐变背景
-    background-image: linear-gradient(120deg, $--color-dark-bg-1 0%, $--color-dark-bg-2 100%),
+    background-image:
+    linear-gradient(120deg, $--color-dark-bg-1 0%, $--color-dark-bg-1 100%),
   //毛玻璃
-    filter-color: $--filter-color-login-dark
+    filter-color: $--filter-color-login-dark,
 );

+ 6 - 2
src/styles/common/_light.scss

@@ -4,7 +4,11 @@ $light: (
   //背景
     background_color: $--color-light-fill-3,
   //渐变背景
-    background-image: linear-gradient(120deg, $--color-text-1 0%, $--color-text-1 100%),
+    background-image:
+    linear-gradient(120deg, $--color-text-1 0%, $--color-text-1 100%),
   //毛玻璃
-    filter-color: $--filter-color-login-light
+    filter-color: $--filter-color-login-light,
+  // 侧边栏
+    aside-bg: #fff,
+    aside-color: rgb(239, 239, 245)
 );

+ 8 - 1
src/styles/common/mixins/mixins.scss

@@ -33,7 +33,7 @@
 }
 
 //获取背景颜色
-@mixin filter-color($target) {
+@mixin filter-bg-color($target) {
   @include themeify {
     background-color: themed($target);
   }
@@ -52,3 +52,10 @@
     color: themed($target);
   }
 }
+
+//获取边框颜色
+@mixin filter-border-color($target) {
+  @include themeify {
+    border-color: themed($target);
+  }
+}

+ 28 - 0
src/styles/common/style.scss

@@ -14,3 +14,31 @@
   border-radius: $--border-radius-base;
   overflow: hidden;
 }
+
+// todo 使用 scss 循环写一套完整的
+// margin
+.go-mt-0 {
+  margin-top: 0;
+}
+
+.go-mb-0 {
+  margin-bottom: 0;
+}
+
+.go-mx-0 {
+  @extend .go-mt-0;
+  @extend .go-mb-0;
+}
+
+.go-pt-0 {
+  padding-top: 0;
+}
+
+.go-pb-0 {
+  padding-bottom: 0;
+}
+
+.go-px-0 {
+  @extend .go-pt-0;
+  @extend .go-pb-0;
+}

+ 1 - 1
src/styles/common/var.scss

@@ -19,7 +19,7 @@ $--color-light-fill-4: #c9cdd4;
 
 // 黑色
 $--color-dark-black: #000;
-$--color-dark-bg-1: #17171a;
+$--color-dark-bg-1: #18181c;
 $--color-dark-bg-2: #232324;
 $--color-dark-bg-3: #2a2a2b;
 $--color-dark-bg-4: #313132;

+ 2 - 2
src/utils/index.ts

@@ -1,4 +1,4 @@
-import { h } from 'vue'
+import { h, DefineComponent } from 'vue'
 import { NIcon } from 'naive-ui'
 
 /**
@@ -14,7 +14,7 @@ export function getUUID(randomLength: number) {
 /**
  * * render 图标
  */
-export const renderIcon = (icon: typeof NIcon) => {
+export const renderIcon = (icon: any) => {
   return () => h(NIcon, null, { default: () => h(icon) })
 }
 

+ 45 - 13
src/utils/page.ts

@@ -1,18 +1,7 @@
 import { ResultEnum } from '@/enums/httpEnum'
-import { ErrorPageNameMap } from '@/enums/pageEnum'
+import { ErrorPageNameMap, PageEnum } from '@/enums/pageEnum'
 import router from '@/router'
-
-/**
- * * 错误页重定向
- * @param icon
- * @returns
- */
-export const redirectErrorPage = (code: ResultEnum) => {
-  if (!code) return false
-  const pageName = ErrorPageNameMap.get(code)
-  if (!pageName) return false
-  routerTurnByName(pageName)
-}
+import { docPath, giteeSourceCodePath } from '@/settings/pathConst'
 
 /**
  * * 根据名字跳转路由
@@ -29,3 +18,46 @@ export const routerTurnByName = (pageName: string, isReplace?: boolean) => {
     name: pageName
   })
 }
+
+/**
+ * * 错误页重定向
+ * @param icon
+ * @returns
+ */
+export const redirectErrorPage = (code: ResultEnum) => {
+  if (!code) return false
+  const pageName = ErrorPageNameMap.get(code)
+  if (!pageName) return false
+  routerTurnByName(pageName)
+}
+
+/**
+ * * 退出
+ */
+export const logout = () => {
+  routerTurnByName(PageEnum.BASE_LOGIN_NAME)
+}
+
+/**
+ * * 打开项目文档
+ * @param url
+ */
+export const openDoc = () => {
+  window.open(docPath, 'blank')
+}
+
+/**
+ * * 打开码云仓库地址
+ * @param url
+ */
+export const openGiteeSourceCode = () => {
+  window.open(giteeSourceCodePath, 'blank')
+}
+
+/**
+ * * 新开页面
+ * @param url
+ */
+export const openNewWindow = (url: string) => {
+  window.open(url, 'blank')
+}

+ 5 - 0
src/utils/style.ts

@@ -1,5 +1,10 @@
 import { useDesignStore } from '@/store/modules/designStore/designStore'
 
+/**
+ * * 修改主题色
+ * @param themeName 主题名称
+ * @returns 
+ */
 export const setHtmlTheme = (themeName?: string) => {
   const e = window.document.documentElement
   if (themeName) {

+ 6 - 2
src/views/login/index.vue

@@ -6,7 +6,7 @@
         <transition-group name="list-complete">
           <template v-for="item in bgList" :key="item">
             <div class="bg-img-box-li list-complete-item">
-              <n-collapse-transition :appear="true" :show="show">
+              <n-collapse-transition :appear="true" :show="showBg">
                 <img :src="getImageUrl(item, 'chart')" alt="chart" />
               </n-collapse-transition>
             </div>
@@ -141,12 +141,16 @@ const message = useMessage()
 const loading = ref(false)
 const autoLogin = ref(true)
 const show = ref(false)
+const showBg = ref(false)
 const designStore = useDesignStore()
 const { t } = useI18n()
 
 onMounted(() => {
   setTimeout(() => {
     show.value = true
+  }, 300)
+  setTimeout(() => {
+    showBg.value = true
   }, 100)
 })
 
@@ -274,7 +278,7 @@ $carousel-image-height: 60vh;
 
       &-card {
         @extend .go-background-filter;
-        @include filter-color('filter-color');
+        @include filter-bg-color('filter-color');
         box-shadow: 0 0 20px 5px rgba(40, 40, 40, 0.5);
       }
 

+ 30 - 4
src/views/project/index.vue

@@ -1,9 +1,35 @@
 <template>
-  <div>
-    <h1>首页</h1>
+  <div class="go-project">
+    <n-layout has-sider position="absolute">
+      <n-space vertical>
+        <Sider />
+      </n-space>
+      <n-layout>
+        <Header />
+        <n-layout
+          class="content-top"
+          position="absolute"
+          :native-scrollbar="false"
+        >
+          <n-layout-content>
+            <router-view></router-view>
+          </n-layout-content>
+        </n-layout>
+      </n-layout>
+    </n-layout>
   </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { Sider } from './layout/components/Sider'
+import { Header } from './layout/components/Header/index'
+</script>
 
-<style scoped></style>
+<style lang="scss" scoped>
+@include go(project) {
+  .content-top {
+    top: $--header-height;
+    margin-top: 1px;
+  }
+}
+</style>

+ 3 - 0
src/views/project/layout/components/AsideFooter/index.ts

@@ -0,0 +1,3 @@
+import AsideFooter from './index.vue'
+
+export { AsideFooter }

+ 44 - 0
src/views/project/layout/components/AsideFooter/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="go-aside-footer">
+    <n-divider class="go-mt-0" />
+    <n-space justify="space-around" :wrap="false">
+      <n-button secondary @click="handleDoc">
+        <template #icon>
+          <n-icon size="18">
+            <HelpOutlineIcon />
+          </n-icon>
+        </template>
+        <n-text>帮助中心</n-text>
+      </n-button>
+      <n-button secondary @click="handleCode">
+        <template #icon>
+          <n-icon size="18">
+            <CodeSlashIcon />
+          </n-icon>
+        </template>
+        <n-text>仓库地址</n-text>
+      </n-button>
+    </n-space>
+  </div>
+</template>
+<script setup lang="ts">
+import { openDoc, openGiteeSourceCode } from '@/utils/page'
+
+import {
+  HelpCircleOutline as HelpOutlineIcon,
+  CodeSlash as CodeSlashIcon
+} from '@vicons/ionicons5'
+
+const handleDoc = () => {
+  openDoc()
+}
+const handleCode = () => {
+  openGiteeSourceCode()
+}
+</script>
+
+<style lang="scss" scoped>
+@include go('aside-footer') {
+  padding-bottom: 20px;
+}
+</style>

+ 3 - 0
src/views/project/layout/components/Create/index.ts

@@ -0,0 +1,3 @@
+import Create from './index.vue'
+
+export { Create }

+ 25 - 0
src/views/project/layout/components/Create/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <n-button v-if="collapsed" ghost type="primary" size="small">
+    <template #icon>
+      <n-icon>
+        <DuplicateIcon />
+      </n-icon>
+    </template>
+  </n-button>
+  <n-button v-else ghost type="primary" size="large">
+    <template #icon>
+      <n-icon>
+        <DuplicateIcon />
+      </n-icon>
+    </template>
+    <span>
+      新建项目
+    </span>
+  </n-button>
+</template>
+<script setup lang="ts">
+import { Duplicate as DuplicateIcon } from '@vicons/ionicons5'
+const props = defineProps({
+  collapsed: Boolean
+})
+</script>

+ 3 - 0
src/views/project/layout/components/Header/index.ts

@@ -0,0 +1,3 @@
+import Header from './index.vue'
+
+export { Header }

+ 13 - 0
src/views/project/layout/components/Header/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <Header>
+    <template #ri-left>
+    </template>
+    <template #ri-right>
+      <UserInfo />
+    </template>
+  </Header>
+</template>
+<script setup lang="ts">
+import { Header } from '@/layout/components/Header'
+import { UserInfo } from '@/components/UserInfo'
+</script>

+ 3 - 0
src/views/project/layout/components/Sider/index.ts

@@ -0,0 +1,3 @@
+import Sider from './index.vue'
+
+export { Sider }

+ 82 - 0
src/views/project/layout/components/Sider/index.vue

@@ -0,0 +1,82 @@
+<template>
+  <n-layout-sider
+    class="go-project-layout-sider"
+    bordered
+    inverted
+    collapse-mode="width"
+    :collapsed="collapsed"
+    :native-scrollbar="false"
+    :collapsed-width="asideCollapsedWidth"
+    :width="asideWidth"
+    @collapse="collapsed = true"
+    @expand="collapsed = false"
+  >
+    <div class="go-project-sider-flex">
+      <aside>
+        <n-space vertical class="go-project-sider-top">
+          <Create :collapsed="collapsed" />
+        </n-space>
+        <n-menu
+          :value="menuValue"
+          :options="menuOptions"
+          :collapsed-width="asideCollapsedWidth"
+          :collapsed-icon-size="22"
+          @update:value="handleUpdateValue"
+        />
+      </aside>
+      <!-- 底部提示 -->
+      <div class="sider-bottom">
+        <AsideFooter />
+      </div>
+    </div>
+  </n-layout-sider>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { Create } from '../Create/index'
+import { AsideFooter } from '../AsideFooter/index'
+import { asideWidth, asideCollapsedWidth } from '@/settings/designSetting'
+import { menuOptionsInit } from './menu'
+import { useRoute } from 'vue-router'
+
+const collapsed = ref(false)
+
+const route = useRoute()
+const routeRame = computed(() => route.name)
+const menuValue = ref(routeRame)
+
+const menuOptions = menuOptionsInit()
+</script>
+
+<style lang="scss" scoped>
+$siderHeight: 100vh;
+
+@include go(project) {
+  &-sider {
+    &-top {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      flex-direction: column;
+      margin-top: 30px;
+      margin-bottom: 20px;
+    }
+    &-flex {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      height: $siderHeight;
+    }
+  }
+  &-layout-sider {
+    height: $siderHeight;
+    @include filter-bg-color('aside-bg');
+    @include filter-border-color('aside-color');
+  }
+  .content-top {
+    top: $--header-height;
+    margin-top: 1px;
+  }
+}
+</style>

+ 58 - 0
src/views/project/layout/components/Sider/menu.ts

@@ -0,0 +1,58 @@
+import { reactive, h } from 'vue'
+import { renderIcon } from '@/utils'
+import { RouterLink } from 'vue-router'
+import { PageEnum } from '@/enums/pageEnum'
+import { MenuOption, MenuGroupOption } from 'naive-ui'
+import { FolderOpen as FolderOpenIcon, LogoAppleAppstore as LogoAppleAppstoreIcon, } from '@vicons/ionicons5'
+
+export const renderMenuLabel = (option: MenuOption | MenuGroupOption) => {
+  return option.label
+}
+
+export const menuOptionsInit = () => {
+  return reactive([
+    {
+      key: 'divider-1',
+      type: 'divider'
+    },
+    {
+      type: 'group',
+      label: '我的',
+      key: 'people',
+      children: [
+        {
+          label: () =>
+            h(
+              RouterLink,
+              {
+                to: {
+                  name: PageEnum.BASE_HOME_NAME
+                }
+              },
+              { default: () => '全部项目' }
+            ),
+          key: PageEnum.BASE_HOME_NAME,
+          icon: renderIcon(FolderOpenIcon)
+        }
+      ]
+    },
+    {
+      key: 'divider-1',
+      type: 'divider'
+    },
+    {
+      label: () =>
+        h(
+          RouterLink,
+          {
+            to: {
+              name: PageEnum.BASE_HOME_NAME
+            }
+          },
+          { default: () => '模板市场' }
+        ),
+      key: 'store',
+      icon: renderIcon(LogoAppleAppstoreIcon)
+    }
+  ])
+}

+ 0 - 8
src/views/redirect/index.vue

@@ -13,12 +13,4 @@ const router = useRouter()
 const goHome = () => {
   router.replace({ path: '/' })
 }
-// onBeforeMount(() => {
-//   const { params, query } = route
-//   const { path } = params
-//   router.replace({
-//     path: '/' + (Array.isArray(path) ? path.join('/') : path),
-//     query
-//   })
-// })
 </script>