提交 861d956d authored 作者: 王鹏飞's avatar 王鹏飞

feat: 新增app配置

上级 0d8397a9
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" /> <link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>商业数据分析实验室</title> <title></title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vant/area-data": "^1.3.2", "@vant/area-data": "^1.3.2",
"@vueuse/core": "^9.3.1", "@vueuse/core": "^9.3.1",
"@vueuse/head": "^0.9.8",
"@vueuse/math": "^9.3.1", "@vueuse/math": "^9.3.1",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^1.1.3", "axios": "^1.1.3",
...@@ -911,6 +912,19 @@ ...@@ -911,6 +912,19 @@
} }
} }
}, },
"node_modules/@vueuse/head": {
"version": "0.9.8",
"resolved": "https://registry.npmjs.org/@vueuse/head/-/head-0.9.8.tgz",
"integrity": "sha512-zt8+JksoVFKxRvmABlaUHA62w+8nOcD8cJnaJ0+SHcr6xaIP3GXgh7/n2TzUoWw4l3d9UxRNs+tapgHdsQ7RbA==",
"dependencies": {
"@vueuse/shared": "^9.3.0",
"@zhead/schema": "^0.8.5",
"@zhead/schema-vue": "^0.8.5"
},
"peerDependencies": {
"vue": ">=2.7 || >=3"
}
},
"node_modules/@vueuse/math": { "node_modules/@vueuse/math": {
"version": "9.3.1", "version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/math/-/math-9.3.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/math/-/math-9.3.1.tgz",
...@@ -1000,6 +1014,41 @@ ...@@ -1000,6 +1014,41 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/@zhead/schema": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema/-/schema-0.8.5.tgz",
"integrity": "sha512-1S3Otr2zpl1zwP72dNseVXQNG9tnTQ6hHUEUYwINvBjRj6bHcUwdE+Itc9OLxnGAJT/7p8P7GHGo5sshXJNJsA==",
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@zhead/schema-raw": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema-raw/-/schema-raw-0.8.5.tgz",
"integrity": "sha512-Aq+9mksf5zbtj7HYluT6PVyfpQ6z7mja9MzjFxg76Vt+Q9i0oL1XN6ZYaCXImWRafwbyAxjFQ5aUCVyFn79OpA==",
"dependencies": {
"@zhead/schema": "0.8.5"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
}
},
"node_modules/@zhead/schema-vue": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema-vue/-/schema-vue-0.8.5.tgz",
"integrity": "sha512-6aXjYy3fZVeYBLrHcJQqzqwzC/2tafRO5UxZEgBHnryRnzeLNZV6nTptDvIPWiJObMoJTK21vbg3gkfLNQg84g==",
"dependencies": {
"@vueuse/shared": "^9.2.0",
"@zhead/schema": "0.8.5",
"@zhead/schema-raw": "0.8.5"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
},
"peerDependencies": {
"vue": ">=2.7 || >=3"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.0", "version": "8.8.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz",
...@@ -5611,6 +5660,16 @@ ...@@ -5611,6 +5660,16 @@
} }
} }
}, },
"@vueuse/head": {
"version": "0.9.8",
"resolved": "https://registry.npmjs.org/@vueuse/head/-/head-0.9.8.tgz",
"integrity": "sha512-zt8+JksoVFKxRvmABlaUHA62w+8nOcD8cJnaJ0+SHcr6xaIP3GXgh7/n2TzUoWw4l3d9UxRNs+tapgHdsQ7RbA==",
"requires": {
"@vueuse/shared": "^9.3.0",
"@zhead/schema": "^0.8.5",
"@zhead/schema-vue": "^0.8.5"
}
},
"@vueuse/math": { "@vueuse/math": {
"version": "9.3.1", "version": "9.3.1",
"resolved": "https://registry.npmjs.org/@vueuse/math/-/math-9.3.1.tgz", "resolved": "https://registry.npmjs.org/@vueuse/math/-/math-9.3.1.tgz",
...@@ -5654,6 +5713,29 @@ ...@@ -5654,6 +5713,29 @@
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.6.tgz",
"integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ==" "integrity": "sha512-HHXP9hskkFQHy8QxxUXkS7946FFIhYVfGqsk0WLwllmexN9x/+R4UBLvurHEuyXRfVEObVR8APuQehykLviwSQ=="
}, },
"@zhead/schema": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema/-/schema-0.8.5.tgz",
"integrity": "sha512-1S3Otr2zpl1zwP72dNseVXQNG9tnTQ6hHUEUYwINvBjRj6bHcUwdE+Itc9OLxnGAJT/7p8P7GHGo5sshXJNJsA=="
},
"@zhead/schema-raw": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema-raw/-/schema-raw-0.8.5.tgz",
"integrity": "sha512-Aq+9mksf5zbtj7HYluT6PVyfpQ6z7mja9MzjFxg76Vt+Q9i0oL1XN6ZYaCXImWRafwbyAxjFQ5aUCVyFn79OpA==",
"requires": {
"@zhead/schema": "0.8.5"
}
},
"@zhead/schema-vue": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@zhead/schema-vue/-/schema-vue-0.8.5.tgz",
"integrity": "sha512-6aXjYy3fZVeYBLrHcJQqzqwzC/2tafRO5UxZEgBHnryRnzeLNZV6nTptDvIPWiJObMoJTK21vbg3gkfLNQg84g==",
"requires": {
"@vueuse/shared": "^9.2.0",
"@zhead/schema": "0.8.5",
"@zhead/schema-raw": "0.8.5"
}
},
"acorn": { "acorn": {
"version": "8.8.0", "version": "8.8.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.8.0.tgz",
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"@tinymce/tinymce-vue": "^5.0.0", "@tinymce/tinymce-vue": "^5.0.0",
"@vant/area-data": "^1.3.2", "@vant/area-data": "^1.3.2",
"@vueuse/core": "^9.3.1", "@vueuse/core": "^9.3.1",
"@vueuse/head": "^0.9.8",
"@vueuse/math": "^9.3.1", "@vueuse/math": "^9.3.1",
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^1.1.3", "axios": "^1.1.3",
......
<script setup>
import { useHead } from '@vueuse/head'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
useHead({
title: appConfig.title
})
</script>
<template> <template>
<RouterView /> <RouterView />
</template> </template>
......
<script lang="ts">
export default { name: 'AppFooter' }
</script>
<template>
<footer class="app-footer">
<p>技术支持:清控紫荆(北京)教育科技股份有限公司</p>
</footer>
</template>
<style lang="scss">
.app-footer {
padding: 10px 0;
font-size: 14px;
line-height: 30px;
text-align: center;
background-color: #fff;
}
</style>
...@@ -7,7 +7,8 @@ import type { IMenuItem } from '@/types' ...@@ -7,7 +7,8 @@ import type { IMenuItem } from '@/types'
import { CaretBottom } from '@element-plus/icons-vue' import { CaretBottom } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useMenuStore } from '@/stores/menu' import { useMenuStore } from '@/stores/menu'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
...@@ -15,7 +16,14 @@ const userStore = useUserStore() ...@@ -15,7 +16,14 @@ const userStore = useUserStore()
const userInfo = userStore.user const userInfo = userStore.user
const menuStore = useMenuStore() const menuStore = useMenuStore()
const menus = $computed(() => menuStore.menus) const menus = $computed(() => {
// 大赛系统隐藏学生端、教师端的菜单
if (appConfig.isGame && userStore.role?.id !== 6) {
return []
} else {
return menuStore.menus
}
})
const defaultActive = computed(() => { const defaultActive = computed(() => {
// 扁平菜单 // 扁平菜单
...@@ -52,10 +60,15 @@ function handleClick(path: string) { ...@@ -52,10 +60,15 @@ function handleClick(path: string) {
<template> <template>
<header class="app-header"> <header class="app-header">
<div class="app-header-left"> <div class="app-header-left">
<template v-if="appConfig.logo">
<div class="logo"> <div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg" /></router-link> <router-link to="/"><img :src="appConfig.logo" /></router-link>
</div> </div>
<h1 class="app-name">商业数据分析实验室</h1> <div class="line"></div>
</template>
<h1 class="app-name">
<router-link to="/">{{ appConfig.title }}</router-link>
</h1>
</div> </div>
<div class="app-header-nav"> <div class="app-header-nav">
<el-menu mode="horizontal" router :default-active="defaultActive"> <el-menu mode="horizontal" router :default-active="defaultActive">
...@@ -65,8 +78,7 @@ function handleClick(path: string) { ...@@ -65,8 +78,7 @@ function handleClick(path: string) {
:popper-offset="0" :popper-offset="0"
popper-class="sub-menu-popper" popper-class="sub-menu-popper"
v-permission="item.tag" v-permission="item.tag"
v-if="item.children" v-if="item.children">
>
<template #title> <template #title>
{{ item.name }} {{ item.name }}
</template> </template>
...@@ -75,8 +87,7 @@ function handleClick(path: string) { ...@@ -75,8 +87,7 @@ function handleClick(path: string) {
v-for="subitem in item.children" v-for="subitem in item.children"
:key="subitem.path" :key="subitem.path"
v-permission="subitem.tag" v-permission="subitem.tag"
@click="handleClick(subitem.path)" @click="handleClick(subitem.path)">
>
{{ subitem.name }} {{ subitem.name }}
</el-menu-item> </el-menu-item>
</el-sub-menu> </el-sub-menu>
...@@ -134,15 +145,17 @@ function handleClick(path: string) { ...@@ -134,15 +145,17 @@ function handleClick(path: string) {
.app-header-left { .app-header-left {
display: flex; display: flex;
align-items: center; align-items: center;
.line {
margin: 0 20px;
height: 30px;
border-left: 2px solid var(--main-color);
}
.app-name { .app-name {
margin-left: 20px;
padding: 0 15px;
font-size: 28px; font-size: 28px;
font-family: Source Han Sans CN; font-family: Source Han Sans CN;
color: #333; color: #333;
font-weight: 500; font-weight: 500;
line-height: 30px; line-height: 30px;
border-left: 2px solid var(--main-color);
white-space: nowrap; white-space: nowrap;
} }
} }
......
...@@ -6,19 +6,22 @@ export default { name: 'AppLayout' } ...@@ -6,19 +6,22 @@ export default { name: 'AppLayout' }
import AppHeader from './Header.vue' import AppHeader from './Header.vue'
import AppAside from './Aside.vue' import AppAside from './Aside.vue'
import AppMain from './Main.vue' import AppMain from './Main.vue'
withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), { import AppFooter from './Footer.vue'
withDefaults(defineProps<{ sidebar?: boolean; footer?: boolean }>(), {
sidebar: false, sidebar: false,
hasTitle: true footer: false
}) })
</script> </script>
<template> <template>
<div class="app-layout"> <div class="app-layout">
<app-header :hasTitle="hasTitle"></app-header> <AppHeader></AppHeader>
<div class="app-layout-container"> <div class="app-layout-container">
<app-aside v-if="sidebar"></app-aside> <AppAside v-if="sidebar"></AppAside>
<app-main></app-main> <AppMain></AppMain>
</div> </div>
<AppFooter v-if="footer"></AppFooter>
</div> </div>
</template> </template>
...@@ -28,9 +31,12 @@ withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), { ...@@ -28,9 +31,12 @@ withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), {
min-height: 100vh; min-height: 100vh;
margin: 0 auto; margin: 0 auto;
background-color: #ecf2f7; background-color: #ecf2f7;
display: flex;
flex-direction: column;
} }
.app-layout-container { .app-layout-container {
min-height: calc(100vh - 66px); // min-height: calc(100vh - 66px);
flex: 1;
display: flex; display: flex;
} }
</style> </style>
const appConfigList = [
{
title: '商业数据分析实验室',
logo: 'https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg',
hosts: ['saas-lab']
},
{
isGame: true,
title: '商业数据分析竞赛平台',
hosts: ['saas-game']
}
]
export function useAppConfig() {
const found = appConfigList.find(item => {
return item.hosts.find(host => location.host.includes(host))
})
const appConfig = found || appConfigList[0]
return { ...appConfig }
}
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import { createHead } from '@vueuse/head'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
...@@ -7,7 +8,6 @@ import router from './router' ...@@ -7,7 +8,6 @@ import router from './router'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import '@/assets/styles/element/index.scss' import '@/assets/styles/element/index.scss'
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
import AppCard from '@/components/base/AppCard.vue' import AppCard from '@/components/base/AppCard.vue'
import AppList from '@/components/base/AppList.vue' import AppList from '@/components/base/AppList.vue'
import AppUpload from '@/components/base/AppUpload.vue' import AppUpload from '@/components/base/AppUpload.vue'
...@@ -17,6 +17,7 @@ import modules from './modules' ...@@ -17,6 +17,7 @@ import modules from './modules'
import { permissionDirective } from '@/utils/permission' import { permissionDirective } from '@/utils/permission'
const app = createApp(App) const app = createApp(App)
const head = createHead()
// 注册公共组件 // 注册公共组件
app.component('AppCard', AppCard).component('AppList', AppList).component('AppUpload', AppUpload) app.component('AppCard', AppCard).component('AppList', AppList).component('AppUpload', AppUpload)
app.directive('permission', permissionDirective) app.directive('permission', permissionDirective)
...@@ -25,6 +26,7 @@ modules({ router }) ...@@ -25,6 +26,7 @@ modules({ router })
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(head)
app.use(ElementPlus, { locale: zhCn }) app.use(ElementPlus, { locale: zhCn })
app.mount('#app') app.mount('#app')
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue' import AppLayout from '@/components/layout/Index.vue'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
export const routes: Array<RouteRecordRaw> = [ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
component: AppLayout, component: AppLayout,
props: { sidebar: false }, props: { sidebar: false, footer: appConfig.isGame },
children: [{ path: '', component: () => import('./views/Index.vue') }] children: [{ path: '', component: () => import('./views/Index.vue') }]
} }
] ]
import type { SystemDictionary } from '@/types'
export interface ExperimentItem { export interface ExperimentItem {
id: string id: string
name: string name: string
......
<script setup lang="ts">
import AppMessage from '@/components/Message.vue'
import Total from '../components/Total.vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const AdminHome = defineAsyncComponent(() => import('../components/AdminHome.vue'))
const TeacherHome = defineAsyncComponent(() => import('../components/TeacherHome.vue'))
const StudentHome = defineAsyncComponent(() => import('../components/StudentHome.vue'))
</script>
<template>
<div class="home">
<Total></Total>
<AdminHome v-if="userStore.role?.id === 6"></AdminHome>
<TeacherHome v-else-if="userStore.role?.id === 5"></TeacherHome>
<StudentHome v-else></StudentHome>
</div>
<AppMessage></AppMessage>
</template>
<style lang="scss" scoped>
.home {
margin: 0 30px;
}
</style>
<script setup lang="ts"></script>
<template>
<div></div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import AppMessage from '@/components/Message.vue' import { useAppConfig } from '@/composables/useAppConfig'
import Total from '../components/Total.vue' const appConfig = useAppConfig()
import { useUserStore } from '@/stores/user'
const userStore = useUserStore() const DefaultHome = defineAsyncComponent(() => import('./Default.vue'))
const AdminHome = defineAsyncComponent(() => import('../components/AdminHome.vue')) const GameHome = defineAsyncComponent(() => import('./Game.vue'))
const TeacherHome = defineAsyncComponent(() => import('../components/TeacherHome.vue'))
const StudentHome = defineAsyncComponent(() => import('../components/StudentHome.vue'))
</script> </script>
<template> <template>
<div class="home"> <component :is="appConfig.isGame ? GameHome : DefaultHome"></component>
<Total></Total>
<AdminHome v-if="userStore.role?.id === 6"></AdminHome>
<TeacherHome v-else-if="userStore.role?.id === 5"></TeacherHome>
<StudentHome v-else></StudentHome>
</div>
<AppMessage></AppMessage>
</template> </template>
<style lang="scss" scoped>
.home {
margin: 0 30px;
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论