feat: 首页架构

This commit is contained in:
MTrun 2021-12-18 16:36:43 +08:00
parent 44667a89f0
commit 90e45f6c23
31 changed files with 548 additions and 62 deletions

View File

@ -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: {
borderRadius
}
}
const lightObject = {
common: {
...commonObj.common
}
}
const dartObject = {
common: {
primaryColor: designStore.appTheme,
borderRadius
...commonObj.common
},
LoadingBar: {
colorLoading: designStore.appTheme
}
}
return designStore.getDarkTheme ? dartObject : lightObject
}
)
const getDarkTheme = computed(() =>
designStore.getDarkTheme ? darkTheme : undefined
)
</script>
<style lang="scss"></style>

View File

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

View File

@ -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>

View File

@ -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 />

View File

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

View File

@ -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>

View File

@ -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]
])

View File

@ -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 {

View File

@ -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

View File

@ -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'

View File

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

View File

@ -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,
);

View File

@ -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)
);

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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) })
}

View File

@ -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')
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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)
}
])
}

View File

@ -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>