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

chore: update

上级 d88a1b98
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^0.27.2", "axios": "^0.27.2",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"element-plus": "^2.2.14", "element-plus": "^2.2.14",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
...@@ -1307,6 +1308,11 @@ ...@@ -1307,6 +1308,11 @@
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
}, },
"node_modules/countup.js": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/countup.js/-/countup.js-2.3.2.tgz",
"integrity": "sha512-dQ7F/CmKGjaO6cDfhtEXwsKVlXIpJ89dFs8PvkaZH9jBVJ2Z8GU4iwG/qP7MgY8qwr+1skbwR6qecWWQLUzB8Q=="
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
...@@ -5739,6 +5745,11 @@ ...@@ -5739,6 +5745,11 @@
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
}, },
"countup.js": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/countup.js/-/countup.js-2.3.2.tgz",
"integrity": "sha512-dQ7F/CmKGjaO6cDfhtEXwsKVlXIpJ89dFs8PvkaZH9jBVJ2Z8GU4iwG/qP7MgY8qwr+1skbwR6qecWWQLUzB8Q=="
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"ali-oss": "^6.17.1", "ali-oss": "^6.17.1",
"axios": "^0.27.2", "axios": "^0.27.2",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2",
"dayjs": "^1.11.5", "dayjs": "^1.11.5",
"element-plus": "^2.2.14", "element-plus": "^2.2.14",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
......
<script setup lang="ts">
import type { CountUpOptions } from 'countup.js'
import { CountUp } from 'countup.js'
interface Props {
endVal: number
options?: CountUpOptions
}
const props = defineProps<Props>()
const myRef = $ref<HTMLElement>()
let countUp: CountUp | null = null
onMounted(() => {
countUp = new CountUp(myRef, props.endVal, props.options)
if (!countUp.error) {
countUp.start()
} else {
console.error(countUp.error)
}
})
watch(
() => props.endVal,
value => {
countUp?.update(value)
}
)
</script>
<template>
<span ref="myRef"></span>
</template>
...@@ -3,12 +3,10 @@ export default { name: 'AppHeader' } ...@@ -3,12 +3,10 @@ export default { name: 'AppHeader' }
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import type { IMenuItem } from '@/types'
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 type { IMenuItem } from '@/types'
withDefaults(defineProps<{ hasTitle?: boolean }>(), {
hasTitle: true
})
const route = useRoute() const route = useRoute()
...@@ -17,39 +15,59 @@ const userInfo = userStore.user ...@@ -17,39 +15,59 @@ const userInfo = userStore.user
const menus = useMenuStore().menus const menus = useMenuStore().menus
const defaultActive = computed(() => {
// 扁平菜单
const flatMenuList: IMenuItem[] = menus.reduce((result: IMenuItem[], item) => {
result.push(item)
if (item.children) {
result = result.concat(item.children)
}
return result
}, [])
const found = flatMenuList.reverse().find(item => {
return route.path.includes(item.path)
})
return found ? found.path : '/'
})
const logout = async () => { const logout = async () => {
await userStore.logout() await userStore.logout()
location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(location.href)}` location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(location.href)}`
} }
function genNavClassName(data: IMenuItem) {
return route.fullPath.includes(data.path) ? 'is-active' : ''
}
</script> </script>
<template> <template>
<header class="app-header"> <header class="app-header">
<div class="app-header-left"> <div class="app-header-left">
<div class="logo"> <div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo-white.svg" /></router-link> <router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo.svg" /></router-link>
</div> </div>
<h1 class="app-name">数字营销综合实验室</h1> <h1 class="app-name">数字营销综合实验室</h1>
</div> </div>
<div class="app-header-nav"> <div class="app-header-nav">
<div <el-menu mode="horizontal" router :default-active="defaultActive">
class="app-header-nav-item" <template v-for="item in menus" :key="item.path">
v-for="(item, index) in menus" <el-sub-menu :index="item.path" :popper-offset="0" popper-class="sub-menu-popper" v-if="item.children">
:key="index" <template #title>
:class="genNavClassName(item)" {{ item.name }}
v-permission="item.tag" </template>
> <el-menu-item :index="subitem.path" v-for="subitem in item.children" :key="subitem.path">
<router-link :to="item.path">{{ item.name }}</router-link> {{ subitem.name }}
</div> </el-menu-item>
</el-sub-menu>
<el-menu-item :index="item.path" v-else>
{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</div> </div>
<div class="app-header-right"> <div class="app-header-right">
<el-dropdown v-if="userInfo"> <el-dropdown v-if="userInfo">
<div class="avatar"> <div class="user">
<img :src="userInfo.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'" /> <div class="avatar">
<img :src="userInfo.avatar || 'https://webapp-pub.ezijing.com/website/base/images/avatar.svg'" />
</div>
<p>{{ userInfo.name }}</p>
<el-icon><CaretBottom /></el-icon>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu style="width: 280px"> <el-dropdown-menu style="width: 280px">
...@@ -81,11 +99,10 @@ function genNavClassName(data: IMenuItem) { ...@@ -81,11 +99,10 @@ function genNavClassName(data: IMenuItem) {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 70px; height: 66px;
background-color: var(--main-color); background-color: #fff;
color: #fff;
.logo { .logo {
width: 120px; width: 145px;
} }
} }
.app-header-left { .app-header-left {
...@@ -94,34 +111,68 @@ function genNavClassName(data: IMenuItem) { ...@@ -94,34 +111,68 @@ function genNavClassName(data: IMenuItem) {
.app-name { .app-name {
margin-left: 20px; margin-left: 20px;
padding: 0 15px; padding: 0 15px;
line-height: 1; font-size: 28px;
border-left: 2px solid #fff; font-family: Source Han Sans CN;
color: #333;
font-weight: 500;
line-height: 30px;
border-left: 2px solid var(--main-color);
} }
} }
.app-header-nav { .app-header-nav {
margin: 0 40px;
flex: 1; flex: 1;
display: flex; height: 100%;
align-items: center; --el-menu-item-height: 63px;
// justify-content: center; --el-menu-item-font-size: 16px;
.el-menu--horizontal {
border-bottom: 0;
}
.el-sub-menu__title {
border-bottom: 0 !important;
}
.el-sub-menu__icon-arrow {
display: none;
}
.el-sub-menu {
font-weight: 500;
}
} }
.app-header-nav-item { .sub-menu-popper {
height: 45px; border: 0 !important;
a { border-radius: 0;
display: inline-block; border-top: 3px solid var(--main-color) !important;
font-size: 16px; .sub-menu-popper {
line-height: 45px; border-top: 0 !important;
padding: 0 18px; }
.el-menu-item {
padding: 0 25px !important;
} }
&:hover, .el-menu--popup {
&.is-active { min-width: 160px;
background: rgba(144, 6, 44, 0.39); padding: 18px 0;
color: #fff;
} }
} }
.app-header-right { .app-header-right {
display: flex; display: flex;
.user {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
border-radius: 21px;
padding: 4px;
p {
margin: 0 10px;
}
&:hover {
background-color: rgba(219, 219, 219, 0.23);
box-shadow: 2px 1px 8px 1px #e4e8eb;
}
}
.avatar { .avatar {
width: 40px; width: 40px;
height: 40px; height: 40px;
......
...@@ -7,7 +7,7 @@ import AppHeader from './Header.vue' ...@@ -7,7 +7,7 @@ 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 }>(), { withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), {
sidebar: true, sidebar: false,
hasTitle: true hasTitle: true
}) })
</script> </script>
...@@ -24,11 +24,13 @@ withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), { ...@@ -24,11 +24,13 @@ withDefaults(defineProps<{ sidebar?: boolean; hasTitle?: boolean }>(), {
<style lang="scss"> <style lang="scss">
.app-layout { .app-layout {
min-width: 1000px;
min-height: 100vh; min-height: 100vh;
background-color: #f8f8f8; margin: 0 auto;
background-color: #ecf2f7;
} }
.app-layout-container { .app-layout-container {
min-height: calc(100vh - 70px); min-height: calc(100vh - 66px);
display: flex; display: flex;
} }
</style> </style>
...@@ -108,7 +108,7 @@ function handleSubmit() { ...@@ -108,7 +108,7 @@ function handleSubmit() {
overflow-y: auto; overflow-y: auto;
} }
.tips { .tips {
padding: 40px; padding: 20px;
color: #555; color: #555;
text-align: center; text-align: center;
} }
......
import httpRequest from '@/utils/axios'
// 获取统计信息
export function getTopInfo() {
return httpRequest.get('/api/lab/v1/common/permission/top')
}
<script setup lang="ts"></script>
<template>
<div class="bg"></div>
<el-select placeholder="实验指导书"> </el-select>
<el-select placeholder="实验视频"> </el-select>
<el-select placeholder="实验答疑"> </el-select>
<el-select placeholder="实验成绩"> </el-select>
<el-select placeholder="大赛评分"> </el-select>
</template>
<style lang="scss" scoped>
.bg {
margin: 40px;
background: url(@/assets/images/home_bg.png) no-repeat center center;
background-size: contain;
height: 636px;
}
</style>
<script setup lang="ts"></script>
<template>
<div class="bg"></div>
<el-select placeholder="我的实验课程"> </el-select>
<el-select placeholder="我的陪练项目"> </el-select>
<el-select placeholder="我的大赛项目"> </el-select>
<el-select placeholder="我的大赛成绩"> </el-select>
</template>
<style lang="scss" scoped>
.bg {
background: url(@/assets/images/home_bg.png) no-repeat center center;
background-size: contain;
height: 636px;
}
</style>
<script setup lang="ts">
import CountUp from '@/components/CountUp.vue'
import { getTopInfo } from '../api'
interface InfoType {
book: number
class: number
experiment: number
learning: number
school: number
student: number
time: number
video: number
}
const detail = reactive<InfoType>({
book: 0,
class: 0,
experiment: 0,
learning: 0,
school: 0,
student: 0,
time: 0,
video: 0
})
function fetchInfo() {
getTopInfo().then(res => {
Object.assign(detail, res.data)
})
}
const list = $computed(() => {
return [
{ name: '服务学校', num: detail.school, unit: '个' },
{ name: '服务班级', num: detail.class, unit: '个' },
{ name: '服务学生', num: detail.student, unit: '个' },
{ name: '实验数量', num: detail.experiment, unit: '个' },
{ name: '实验指导书数量', num: detail.book, unit: '个' },
{ name: '操作视频数量', num: detail.video, unit: '个' },
{ name: '累计学习时长', num: detail.time, unit: '小时' },
{ name: '累计学习人次', num: detail.learning, unit: '人次' }
]
})
onMounted(() => {
fetchInfo()
})
</script>
<template>
<div class="total">
<template v-for="(item, index) in list" :key="index">
<div class="line"></div>
<dl>
<dt>
<span><CountUp :endVal="item.num"></CountUp></span>
<em>{{ item.unit }}</em>
</dt>
<dd>{{ item.name }}</dd>
</dl>
</template>
</div>
</template>
<style lang="scss">
.total {
display: flex;
justify-content: space-between;
dl {
text-align: left;
}
.line {
border-left: 1px dashed #d9d9d9;
&:first-child {
display: none;
}
}
dt {
span {
font-size: 30px;
font-weight: bold;
color: var(--main-color);
}
em {
padding-left: 10px;
font-size: 14px;
color: #666;
}
}
dd {
font-size: 16px;
font-family: Source Han Sans CN;
font-weight: 400;
color: #999;
}
}
</style>
<script setup lang="ts">
import Total from '../components/Total.vue'
// import StudentHome from '../components/StudentHome.vue'
import AdminHome from '../components/AdminHome.vue'
</script>
<template> <template>
<AppCard> <Total></Total>
<h1>欢迎进入清控紫荆教育数字经济实验室系列之:数字营销综合实验室</h1> <AdminHome></AdminHome>
</AppCard>
</template> </template>
...@@ -27,7 +27,7 @@ export default defineConfig(({ mode }) => ({ ...@@ -27,7 +27,7 @@ export default defineConfig(({ mode }) => ({
}, },
proxy: { proxy: {
'/api/lab': { '/api/lab': {
target: 'http://test-resource-api.ezijing.com:8001', target: 'https://resource-api-test.ezijing.com',
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api\/lab/, '') rewrite: path => path.replace(/^\/api\/lab/, '')
}, },
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论