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

chore: update

上级 80a0d6e3
......@@ -8,6 +8,7 @@
"name": "saas-learn",
"version": "0.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.0.6",
"@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^8.9.3",
"axios": "^0.27.2",
......@@ -34,8 +35,8 @@
"npm-run-all": "^4.1.5",
"sass": "^1.53.0",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.9.2",
"vite": "^3.0.0",
"unplugin-auto-import": "^0.9.3",
"vite": "^3.0.2",
"vue-tsc": "^0.38.5"
}
},
......@@ -4916,21 +4917,21 @@
}
},
"node_modules/unimport": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.4.tgz",
"integrity": "sha512-gYWGl4LUIasAbV7PXCaByuB6NcerrHRIRICooXtAFI7JCGY74CYGVq080P/yg7C4AYs+RYuJQdYsaRAJvG7bsQ==",
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.5.tgz",
"integrity": "sha512-DnmiSt/HQIfhdcxOy4CGqwZDBh3WHg33euX1ge4X8hvquKBmw2PFvhoAJaBKxscOz0oYosoPoPT4tkDZWHhV0Q==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^4.2.1",
"escape-string-regexp": "^5.0.0",
"fast-glob": "^3.2.11",
"local-pkg": "^0.4.1",
"local-pkg": "^0.4.2",
"magic-string": "^0.26.2",
"mlly": "^0.5.4",
"pathe": "^0.3.2",
"scule": "^0.2.1",
"strip-literal": "^0.4.0",
"unplugin": "^0.7.1"
"unplugin": "^0.7.2"
}
},
"node_modules/unimport/node_modules/escape-string-regexp": {
......@@ -5008,17 +5009,17 @@
}
},
"node_modules/unplugin-auto-import": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.9.2.tgz",
"integrity": "sha512-cihfSyWtDyOvpD+bKQ77XSJF2Ix3N3ueatd59slBKgl995fbfExf2qXk/KialZE4/pPNjdGNEJl5ZwAhYj+e1g==",
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.9.3.tgz",
"integrity": "sha512-S3fC/kp98v+HhELCCG4jm4fhd/BbXhhcmFxxQ/JHXefLPtz9WTCOsSq3pq7U4D94xJ0eyZOPo/56Y9iUf3kskw==",
"dev": true,
"dependencies": {
"@antfu/utils": "^0.5.2",
"@rollup/pluginutils": "^4.2.1",
"local-pkg": "^0.4.1",
"local-pkg": "^0.4.2",
"magic-string": "^0.26.2",
"unimport": "^0.4.0",
"unplugin": "^0.7.0"
"unimport": "^0.4.5",
"unplugin": "^0.7.2"
},
"engines": {
"node": ">=14"
......@@ -5174,9 +5175,9 @@
}
},
"node_modules/vite": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.0.tgz",
"integrity": "sha512-M7phQhY3+fRZa0H+1WzI6N+/onruwPTBTMvaj7TzgZ0v2TE+N2sdLKxJOfOv9CckDWt5C4HmyQP81xB4dwRKzA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.2.tgz",
"integrity": "sha512-TAqydxW/w0U5AoL5AsD9DApTvGb2iNbGs3sN4u2VdT1GFkQVUfgUldt+t08TZgi23uIauh1TUOQJALduo9GXqw==",
"dev": true,
"dependencies": {
"esbuild": "^0.14.47",
......@@ -5188,7 +5189,7 @@
"vite": "bin/vite.js"
},
"engines": {
"node": ">=14.18.0"
"node": "^14.18.0 || >=16.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
......@@ -9015,21 +9016,21 @@
}
},
"unimport": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.4.tgz",
"integrity": "sha512-gYWGl4LUIasAbV7PXCaByuB6NcerrHRIRICooXtAFI7JCGY74CYGVq080P/yg7C4AYs+RYuJQdYsaRAJvG7bsQ==",
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/unimport/-/unimport-0.4.5.tgz",
"integrity": "sha512-DnmiSt/HQIfhdcxOy4CGqwZDBh3WHg33euX1ge4X8hvquKBmw2PFvhoAJaBKxscOz0oYosoPoPT4tkDZWHhV0Q==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^4.2.1",
"escape-string-regexp": "^5.0.0",
"fast-glob": "^3.2.11",
"local-pkg": "^0.4.1",
"local-pkg": "^0.4.2",
"magic-string": "^0.26.2",
"mlly": "^0.5.4",
"pathe": "^0.3.2",
"scule": "^0.2.1",
"strip-literal": "^0.4.0",
"unplugin": "^0.7.1"
"unplugin": "^0.7.2"
},
"dependencies": {
"escape-string-regexp": {
......@@ -9074,17 +9075,17 @@
}
},
"unplugin-auto-import": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.9.2.tgz",
"integrity": "sha512-cihfSyWtDyOvpD+bKQ77XSJF2Ix3N3ueatd59slBKgl995fbfExf2qXk/KialZE4/pPNjdGNEJl5ZwAhYj+e1g==",
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.9.3.tgz",
"integrity": "sha512-S3fC/kp98v+HhELCCG4jm4fhd/BbXhhcmFxxQ/JHXefLPtz9WTCOsSq3pq7U4D94xJ0eyZOPo/56Y9iUf3kskw==",
"dev": true,
"requires": {
"@antfu/utils": "^0.5.2",
"@rollup/pluginutils": "^4.2.1",
"local-pkg": "^0.4.1",
"local-pkg": "^0.4.2",
"magic-string": "^0.26.2",
"unimport": "^0.4.0",
"unplugin": "^0.7.0"
"unimport": "^0.4.5",
"unplugin": "^0.7.2"
},
"dependencies": {
"magic-string": {
......@@ -9221,9 +9222,9 @@
}
},
"vite": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.0.tgz",
"integrity": "sha512-M7phQhY3+fRZa0H+1WzI6N+/onruwPTBTMvaj7TzgZ0v2TE+N2sdLKxJOfOv9CckDWt5C4HmyQP81xB4dwRKzA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.2.tgz",
"integrity": "sha512-TAqydxW/w0U5AoL5AsD9DApTvGb2iNbGs3sN4u2VdT1GFkQVUfgUldt+t08TZgi23uIauh1TUOQJALduo9GXqw==",
"dev": true,
"requires": {
"esbuild": "^0.14.47",
......
......@@ -13,6 +13,7 @@
"deploy": "node ./deploy.js"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.6",
"@tinymce/tinymce-vue": "^5.0.0",
"@vueuse/core": "^8.9.3",
"axios": "^0.27.2",
......@@ -39,8 +40,8 @@
"npm-run-all": "^4.1.5",
"sass": "^1.53.0",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.9.2",
"vite": "^3.0.0",
"unplugin-auto-import": "^0.9.3",
"vite": "^3.0.2",
"vue-tsc": "^0.38.5"
}
}
import type { IMenuItem } from '@/types'
import { Collection, ChatDotSquare, FolderAdd, Setting } from '@element-plus/icons-vue'
export const menus: IMenuItem[] = [
{
name: '学习',
path: '/course'
path: '/course',
icon: Collection
},
{
name: '论坛',
path: '/bbs'
path: '/bbs',
icon: ChatDotSquare
},
{
name: '收藏',
path: '/favorites'
path: '/favorites',
icon: FolderAdd
},
{
name: '设置',
path: '/settings'
path: '/settings',
icon: Setting
}
]
......@@ -20,6 +20,7 @@ defineProps<{ title?: string }>()
<style lang="scss">
.app-card {
height: 100%;
background: #fff;
box-shadow: 0 1px 6px 0 rgb(228 232 235 / 20%);
border-radius: 6px;
......
<script lang="ts">
const DEFAULT_OPTIONS = {
controls: true,
autoplay: true,
autoplay: false,
fluid: true,
playbackRates: [0.5, 1, 1.5, 2],
restoreEl: true
......@@ -81,15 +81,13 @@ function initPlayer() {
}
function changeSrc(src: string | { src: string; type?: string }) {
if (!player) return
console.log(1)
console.log(player)
if (!player.paused()) {
console.log(2)
player.pause()
}
// if (!player.paused()) {
// console.log(2)
// player.pause()
// }
player.src(src)
player.load()
player.play()
// player.load()
// player.play()
}
onMounted(() => {
initPlayer()
......
......@@ -10,13 +10,9 @@ import type { IMenuItem } from '@/types'
const router = useRouter()
const route = useRoute()
const menuList = $computed<IMenuItem[]>(() => {
const found = menus.find(item => route.fullPath.includes(item.path))
return found?.children || []
})
const defaultActive = computed(() => {
// 扁平菜单
const flatMenuList: IMenuItem[] = menuList.reduce((result: IMenuItem[], item) => {
const flatMenuList: IMenuItem[] = menus.reduce((result: IMenuItem[], item) => {
result.push(item)
if (item.children) {
result = result.concat(item.children)
......@@ -43,10 +39,10 @@ function handleClick(path: string) {
</script>
<template>
<aside class="app-aside" v-if="menuList.length">
<aside class="app-aside">
<nav class="nav">
<el-menu :default-active="defaultActive" class="app-menu">
<template v-for="item in menuList" :key="item.path">
<el-menu collapse :default-active="defaultActive" class="app-menu">
<template v-for="item in menus" :key="item.path">
<el-sub-menu :index="item.path" v-permission="item.tag" v-if="item.children">
<template #title>
<el-icon><component :is="item.icon"></component></el-icon>{{ item.name }}
......@@ -72,62 +68,47 @@ function handleClick(path: string) {
<style lang="scss">
.app-aside {
width: 200px;
background: #2e3036;
border-right: 1px solid rgba(0, 0, 0, 0.12);
width: 90px;
background: #fff;
border-right: 1px solid #e6e6e6;
overflow-x: hidden;
overflow-y: auto;
flex: 0 0 200px;
flex: 0 0 90px;
box-sizing: content-box;
}
.nav {
position: fixed;
width: 200px;
margin: 20px 0;
width: 90px;
--el-menu-item-font-size: 16px;
--el-menu-item-height: 1;
--el-menu-text-color: #cecece;
--el-menu-bg-color: #2e3036;
--el-menu-base-level-padding: 0;
--el-menu-text-color: #707070;
--el-menu-active-color: #fff;
--el-menu-active-bg-color: #4b4c50;
--el-menu-active-color: #ba143e;
--el-menu-active-bg-color: #f7e9ec;
--el-menu-hover-color: #fff;
--el-menu-hover-bg-color: #4b4c50;
--el-menu-hover-color: #ba143e;
--el-menu-hover-bg-color: #f7e9ec;
.el-menu {
border-right: 0;
i {
margin-right: 14px;
font-size: 24px;
}
.el-icon-arrow-down {
margin-right: 0;
font-size: 16px;
}
}
.el-menu-item {
flex-direction: column;
justify-content: center;
width: 66px;
height: 66px;
margin: 50px auto;
border-radius: 14px;
&.is-active {
background: var(--el-menu-active-bg-color);
}
.el-icon {
margin-bottom: 7px;
font-size: 22px;
}
}
// .el-menu-item {
// display: flex;
// align-items: center;
// margin: 0 16px;
// font-size: 16px;
// border-radius: 8px;
// }
// .el-submenu .el-menu-item {
// min-width: auto;
// padding-left: 58px !important;
// }
// .el-submenu__title {
// display: flex;
// align-items: center;
// margin: 0 16px;
// font-size: 16px;
// border-radius: 8px;
// }
}
</style>
<template>
<div class="app-breadcrumb" v-if="routes.length">
<el-breadcrumb>
<el-breadcrumb-item v-for="route in routes" :key="route.path">
<router-link :to="route.path">{{ route.meta.title }}</router-link>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
name: 'AppBreadcrumb',
computed: {
routes() {
return this.$route.matched.filter(route => route.meta.title)
}
}
}
</script>
<style lang="scss">
.app-breadcrumb {
padding: 18px 0 32px;
.el-breadcrumb {
font-size: 20px;
font-weight: 400;
line-height: 1;
}
.el-breadcrumb__inner a {
font-weight: normal;
color: #5b91fd;
}
.router-link-active {
color: #1a1b1c;
}
}
</style>
......@@ -3,48 +3,24 @@ export default { name: 'AppHeader' }
</script>
<script setup lang="ts">
import { menus } from '@/assets/menus'
import { useUserStore } from '@/stores/user'
import type { IMenuItem } from '@/types'
withDefaults(defineProps<{ hasTitle?: boolean }>(), {
hasTitle: true
})
const route = useRoute()
const userStore = useUserStore()
const userInfo = userStore.user
const logout = async () => {
await userStore.logout()
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>
<template>
<header class="app-header">
<div class="app-header-left">
<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>
<h1 class="app-name">产业学院</h1>
</div>
<div class="app-header-nav">
<div
class="app-header-nav-item"
v-for="(item, index) in menus"
:key="index"
:class="genNavClassName(item)"
v-permission="item.tag"
>
<router-link :to="item.path">{{ item.name }}</router-link>
</div>
</div>
<div class="app-header-right">
<el-dropdown v-if="userInfo">
<div class="avatar">
......@@ -80,9 +56,9 @@ function genNavClassName(data: IMenuItem) {
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
background-color: var(--main-color);
color: #fff;
height: 64px;
background-color: #fff;
border-bottom: 1px solid #e6e6e6;
.logo {
width: 120px;
}
......@@ -92,29 +68,12 @@ function genNavClassName(data: IMenuItem) {
align-items: center;
.app-name {
margin-left: 20px;
padding: 0 15px;
line-height: 1;
border-left: 2px solid #fff;
}
}
.app-header-nav {
flex: 1;
display: flex;
align-items: center;
// justify-content: center;
}
.app-header-nav-item {
height: 45px;
a {
display: inline-block;
font-size: 16px;
line-height: 45px;
padding: 0 18px;
}
&:hover,
&.is-active {
background: rgba(144, 6, 44, 0.39);
color: #fff;
font-size: 24px;
font-weight: 400;
line-height: 1;
color: #333;
border-left: 1px solid #707070;
}
}
......
......@@ -2,57 +2,17 @@
export default { name: 'AppMain' }
</script>
<script setup lang="ts">
import AppBreadcrumb from './Breadcrumb.vue'
withDefaults(defineProps<{ hasBreadcrumb?: boolean }>(), {
hasBreadcrumb: true
})
</script>
<template>
<section class="app-main">
<div class="app-main-inner">
<div class="app-main-header">
<app-breadcrumb v-if="hasBreadcrumb"></app-breadcrumb>
</div>
<div class="app-main-container">
<router-view></router-view>
</div>
</div>
<router-view></router-view>
</section>
</template>
<style>
.manual-btn {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 50%;
right: 0;
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 1);
box-shadow: 1px 3px 12px rgba(0, 0, 0, 0.11);
border-radius: 50%;
z-index: 999999;
}
.app-main {
position: relative;
flex: 1;
padding: 20px;
margin: 20px;
overflow: hidden;
}
.app-main-inner {
margin: 0 auto;
}
.app-main-container::after {
content: '';
display: table;
clear: both;
}
.el-form--label-top .el-form-item__label {
padding-bottom: 0;
}
</style>
import httpRequest from '@/utils/axios'
// 获取课程列表
export function getCourseList(params?: { id?: string }) {
return httpRequest.get('/api/saas/api/v1/course/list', { params })
}
// 搜索课程
export function searchCourseList(params?: { id?: string }) {
return httpRequest.get('/api/saas/api/v1/course/search', { params })
}
// 置顶课程
export function topCourse(data: { id: string; status: number }) {
return httpRequest.post('/api/saas/api/v1/course/sticky-top', data)
}
// 获取课程详情信息
export function getCourse(params: { course_id: string; semester_id: string }) {
return httpRequest.get(`/api/saas/api/v1/course/${params.course_id}/detail/${params.semester_id}`, { params })
}
// 获取课程列表
export function getChapterTreeList(data: { course_id: string; semester_id: string }) {
return httpRequest.post('/api/saas/api/v1/chapter/tree', data)
}
<script setup lang="ts"></script>
<script setup lang="ts">
import type { CourseType } from '@/types'
interface Props {
data: CourseType
}
defineProps<Props>()
// 置顶
function handleTop(data) {}
</script>
<template>
<div class="course-item"></div>
<div class="course-item">
<div class="course-item__top" :class="{ 'is-active': !!data.is_top }" @click="handleTop(data)"></div>
<router-link :to="`/course/view?course_id=${data.course_id}&semester_id=${data.semester_id}`">
<div class="course-item-bd">
<div class="course-item-pic"><img :src="data.cover" /><span class="course-item__type">必修课</span></div>
<div class="course-item-main">
<h2 class="course-item__name">{{ data.name }}</h2>
<div class="course-item-progress">总进度<el-progress :percentage="data.watch_video_length" /></div>
</div>
</div>
</router-link>
<div class="course-item-ft">
<div class="course-item-playlist" v-if="data.section">
<p class="t1">{{ data.section.name }}</p>
<p class="t2">已观看{{ data.section.watch_video_length }}%</p>
<el-button type="primary" round>点击观看</el-button>
</div>
<p class="t3" v-else>尚未观看</p>
</div>
</div>
</template>
<style lang="scss" scoped>
.course-item {
position: relative;
padding: 12px;
margin: 20px 0;
border: 1px solid #e6e6e6;
box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.12);
border-radius: 6px;
&:hover {
.course-item__top {
display: block;
}
}
}
.course-item__top {
display: none;
position: absolute;
top: 5px;
right: 5px;
width: 16px;
height: 16px;
background: url('@/assets/images/course_top.png') no-repeat;
background-size: 16px;
z-index: 100;
cursor: pointer;
&.is-active {
display: block;
background: url('@/assets/images/course_top_active.png') no-repeat;
background-size: 16px;
}
}
.course-item-bd {
display: flex;
}
.course-item-pic {
position: relative;
width: 90px;
height: 90px;
border-radius: 4px;
overflow: hidden;
img {
object-fit: cover;
width: 100%;
height: 100%;
}
.course-item__type {
position: absolute;
top: 0;
right: 0;
padding: 0 2px;
font-size: 10px;
line-height: 16px;
color: #dbdbdb;
background: rgba(0, 0, 0, 0.39);
border-radius: 0px 4px 0px 4px;
}
}
.course-item-main {
flex: 1;
margin-left: 6px;
overflow: hidden;
h2 {
height: 60px;
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: #333333;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
}
.course-item-progress {
margin-top: 10px;
display: flex;
align-items: center;
color: #999;
font-size: 12px;
:deep(.el-progress) {
flex: 1;
margin-left: 5px;
}
}
.course-item-ft {
margin-top: 10px;
font-size: 12px;
line-height: 20px;
color: #535353;
.t3 {
color: #999;
}
}
.course-item-playlist {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #f4f4f4;
border-radius: 16px;
.t1 {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding-left: 10px;
}
.t2 {
padding: 0 10px;
white-space: nowrap;
}
.el-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
</style>
......@@ -95,7 +95,7 @@ const playList = [
JobId: 'ba0bb88b427048cab7d4abe8a112833f'
}
]
let src = $ref({ src: '//bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', type: 'application/x-mpegURL' })
let src = $ref({ src: '/trailer.mp4', type: 'video/mp4' })
// 跳过片头
const isSkip = useStorage('isSkip', false)
// 连续播放
......@@ -107,11 +107,10 @@ const onReady = (player: VideoJsPlayer) => {
videoJsPlayer = player
isReady = true
console.log(videoJsPlayer)
// videoJsPlayer.src('/trailer.mp4')
}
function changeSrc(data: any) {
console.log(data)
src = { src: data.PlayURL, type: 'application/x-mpegURL' }
src = { src: data.PlayURL, type: 'video/mp4' }
}
</script>
......
<!-- 学习 -->
<script setup lang="ts"></script>
<template>学习</template>
<script setup lang="ts">
import * as api from '../api'
let chapterList = $ref<CourseType[]>([
{
id: '6952463551923486720',
resource_type: 1,
resource_id: '0',
name: '第一章 测试基础',
lft: 2,
rgt: 19,
depth: 1,
sections: [
{
id: '6952463626879893504',
resource_type: 1,
resource_id: '0',
name: '1.1 软件测试流程',
lft: 3,
rgt: 18,
depth: 2,
resources: [
{
id: '6952463676506898432',
resource_type: 2,
resource_id: '6952457870440923136',
name: '自考和成考毕业证哪一个更好呢? - 知乎',
lft: 4,
rgt: 5,
depth: 3,
collection_count: 0,
info: {
id: '6952457870440923136',
name: '自考和成考毕业证哪一个更好呢? - 知乎',
length: 60,
size: 11522066,
cover: 'https://img1.ezijing.com/curriculum/vods/904f6f495d377aa905e1b20f19c2b1cd.jpg',
pdf: '',
source_id: '8b2c9319016d4e56b13fb629bde66b62'
}
},
{
id: '6952463714540847104',
resource_type: 10,
resource_id: '6952458453805694976',
name: '2022届毕业论文模板',
lft: 6,
rgt: 7,
depth: 3,
collection_count: 0,
info: {}
},
{
id: '6952463732052066304',
resource_type: 11,
resource_id: '6952458579903250432',
name: '葛涵芮(改)',
lft: 8,
rgt: 9,
depth: 3,
collection_count: 0,
info: {}
},
{
id: '6952463756471304192',
resource_type: 4,
resource_id: '6952458799777054720',
name: '阿里云镜像服务私有仓库',
lft: 10,
rgt: 11,
depth: 3,
collection_count: 0,
info: {}
},
{
id: '6952463779271540736',
resource_type: 3,
resource_id: '405319875141836801',
name: '账号006 测试课后作业',
lft: 12,
rgt: 13,
depth: 3,
collection_count: 0,
info: {}
},
{
id: '6952463812154884096',
resource_type: 9,
resource_id: '405320397106192384',
name: '账号006 测试考试-全题型1',
lft: 14,
rgt: 15,
depth: 3,
collection_count: 0,
info: {}
},
{
id: '6952463848183955456',
resource_type: 6,
resource_id: '6951001370124091392',
name: '张传梁测试使用的直播0708',
lft: 16,
rgt: 17,
depth: 3,
collection_count: 0,
info: {}
}
]
}
]
},
{
id: '6952463591937146880',
resource_type: 1,
resource_id: '0',
name: '第二章 专项测试',
lft: 20,
rgt: 27,
depth: 1,
sections: [
{
id: '6952463902990925824',
resource_type: 1,
resource_id: '0',
name: '2.1 移动端测试',
lft: 21,
rgt: 26,
depth: 2,
resources: [
{
id: '6952463957328134144',
resource_type: 2,
resource_id: '6952458166898524160',
name: '转转APP宣传动画_影视_Motion Graphic_CCLab - 原创作品 - 站酷 (ZCOOL)',
lft: 22,
rgt: 23,
depth: 3,
collection_count: 0,
info: {
id: '6952458166898524160',
name: '转转APP宣传动画_影视_Motion Graphic_CCLab - 原创作品 - 站酷 (ZCOOL)',
length: 16,
size: 2624963,
cover: 'https://img1.ezijing.com/curriculum/vods/64f2f365d25e4e3cc9e68e6e72ef3fde.jpg',
pdf: '',
source_id: 'aa0cf2146067476aaf4c2d4e61ae6f96'
}
},
{
id: '6952464226233352192',
resource_type: 4,
resource_id: '6952459029746548736',
name: 'apache-ant-1.10.1',
lft: 24,
rgt: 25,
depth: 3,
collection_count: 0,
info: {}
}
]
}
]
}
])
const { query } = useRoute()
const courseId = $ref<string | null>(query.course_id as string)
const semesterId = $ref<string | null>(query.semester_id as string)
// 获取章节列表
function fetchList() {
if (!courseId || !semesterId) {
return
}
api.getChapterTreeList({ course_id: courseId, semester_id: semesterId }).then(res => {
chapterList = res.data.items
})
}
onMounted(() => {
fetchList()
})
</script>
<template>
<!-- <router-link to="/course/player">学习</router-link> -->
<el-collapse>
<el-collapse-item :title="item.name" :name="item.id" v-for="item in chapterList" :key="item.id">
<el-collapse>
<el-collapse-item :title="section.name" :name="section.id" v-for="section in item.sections" :key="section.id">
<ul>
<li v-for="resource in section.resources" :key="resource.id">
<router-link
:to="`/course/player?course_id=${courseId}&chapter_id=${section.id}&semester_id=${semesterId}&resource_id=${resource.resource_id}`"
>
{{ resource.name }}
</router-link>
</li>
</ul>
</el-collapse-item>
</el-collapse>
</el-collapse-item>
</el-collapse>
</template>
......@@ -9,7 +9,7 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '',
component: () => import('./views/CourseIndex.vue'),
children: [{ path: ':id', component: () => import('./views/CourseView.vue'), props: true }]
children: [{ path: 'view', component: () => import('./views/CourseView.vue'), props: true }]
},
{ path: 'player', component: () => import('./views/CoursePlayer.vue'), props: true },
{ path: 'exam', component: () => import('./views/CourseExam.vue'), props: true }
......
<script setup lang="ts"></script>
<script setup lang="ts">
import { Search, Filter } from '@element-plus/icons-vue'
import CourseListItem from '../components/CourseListItem.vue'
import type { CourseType } from '@/types'
import * as api from '../api'
let courseList = $ref<CourseType[]>([
{
id: '408283947030548481',
course_id: '6952463518419386368',
semester_id: '6954980183342317568',
schedule: '0.00',
watch_video_length: 100,
is_finished: 0,
is_top: 1,
name: '账号006的第一门课程',
cover: 'https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/course-cover.png',
elective_type: 2,
online_type: 2,
section: {
id: '408286099845160961',
name: '1.1 软件测试流程',
schedule: '1.00',
watch_video_length: 100,
is_finished: 0
},
semester: {
id: '6954980183342317568',
name: '第一学期'
}
}
])
function handleSelect() {}
function querySearch() {}
const filter = reactive<Record<string, any>>({
name: [],
type: []
})
const filterList = [
{
model: 'name',
label: '学期',
options: [
{
label: '第一学期',
value: '第一学期'
},
{
label: '第二学期',
value: '第二学期'
}
]
},
{
model: 'type',
label: '课程类型',
options: [
{
label: '必修课',
value: '必修课'
},
{
label: '选修课',
value: '选修课'
},
{
label: '重修课',
value: '重修课'
}
]
}
]
function fetchList() {
api.getCourseList({ id: '' }).then(res => {
courseList = res.data.data
})
}
onMounted(() => {
fetchList()
})
</script>
<template>
<AppCard>
<section class="course">
<div class="course-left">
<router-link to="/course/123">123</router-link>
<section class="course">
<div class="course-left">
<div class="course-search">
<el-autocomplete
:fetch-suggestions="querySearch"
placeholder="搜索"
@select="handleSelect"
:prefix-icon="Search"
class="course-search__input"
/>
<el-popover placement="bottom-end" :width="292" trigger="click">
<template #reference>
<el-button :icon="Filter" class="course-search-filter__button"></el-button>
</template>
<div class="course-search-filter">
<dl class="course-search-filter-item" v-for="item in filterList" :key="item.model">
<dt class="course-search-filter-item__label">{{ item.label }}</dt>
<dd class="course-search-filter-item__main">
<el-checkbox-group v-model="filter[item.model]">
<el-checkbox-button v-for="option in item.options" v-bind="option" :key="option.value" />
</el-checkbox-group>
</dd>
</dl>
</div>
</el-popover>
</div>
<div class="course-right"><router-view :key="$route.fullPath"></router-view></div>
</section>
</AppCard>
<div class="course-list">
<CourseListItem v-for="item in courseList" :data="item" :key="item.id"></CourseListItem>
</div>
</div>
<div class="course-right"><router-view :key="$route.fullPath"></router-view></div>
</section>
</template>
<style lang="scss">
.course {
display: flex;
height: 100%;
background-color: #fff;
border-radius: 6px;
}
// 左侧
.course-left {
width: 400px;
padding: 18px;
width: 354px;
overflow-y: auto;
border-right: 1px solid #e6e6e6;
box-sizing: border-box;
}
.course-search {
display: flex;
align-items: center;
}
.course-search__input {
flex: 1;
}
.course-search-filter__button {
margin-left: 10px;
padding: 8px;
}
.course-search-filter {
padding: 8px;
}
.course-search-filter-item + .course-search-filter-item {
margin-top: 20px;
}
.course-search-filter-item__label {
font-size: 16px;
line-height: 1;
color: #333;
}
.course-search-filter-item__main {
margin-top: 12px;
.el-checkbox-group {
display: flex;
justify-content: space-between;
}
.el-checkbox-button__inner {
padding: 10px 20px;
border: 1px solid #adadad !important;
border-radius: 18px !important;
box-shadow: none !important;
}
}
// 右侧
.course-right {
flex: 1;
}
......
<script setup lang="ts">
import type { CourseType } from '@/types'
import * as api from '../api'
const CourseViewChapter = defineAsyncComponent(() => import('../components/CourseViewChapter.vue'))
const CourseViewBBS = defineAsyncComponent(() => import('../components/CourseViewBBS.vue'))
const CourseViewWork = defineAsyncComponent(() => import('../components/CourseViewWork.vue'))
......@@ -6,36 +8,186 @@ const CourseViewExam = defineAsyncComponent(() => import('../components/CourseVi
const CourseViewAssess = defineAsyncComponent(() => import('../components/CourseViewAssess.vue'))
const CourseViewLive = defineAsyncComponent(() => import('../components/CourseViewLive.vue'))
defineProps<{ id: string }>()
const { query } = useRoute()
const courseId = $ref<string | null>(query.course_id as string)
const semesterId = $ref<string | null>(query.semester_id as string)
const detail = reactive<CourseType>({
id: '6952463518419386368',
name: '账号006的第一门课程',
cover: 'https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/course-cover.png',
online_type: 2,
elective_type: 2,
represent: '',
credit: 5,
previous_preparation: '',
target: '',
semester: {
id: '6954980183342317568',
name: '第一学期',
start_time: '2027-08-31',
end_time: '2023-01-31'
},
lecturers: [
{
id: '6942651161874792448',
name: '凤姐',
avatar: 'https://webapp-pub.ezijing.com/upload/admin/b26d67c22a8380970766e27af3278d6a.png',
summarize:
'<p>真正的产品应该是有一个端到端的一个解决方案。比如说:电子阅读中的从购书,到阅读,再到阅读心得分享,再到推荐,这一整套的解决方案。看看苹果的产品的端到端的解决方案,就知道什么是产品的样子了。</p>\n<p>真正的产品应该是有价值的。这种价值表现在&mdash;&mdash;你可以从中获得有价值的内容,并且你也可以通过他创造对你有价值的东西。比如,像豆瓣,像Stackoverflow,甚至像Twitter和微博这样让信息平等让信息传递更快的社区,或是像AWS或是Apple的开发平台,等等。可见,我们无法通过QQ获得有价值的东西,我们也无法通过QQ创造有价值的东西。</p>'
},
{
id: '6942651161874792449',
name: '凤姐',
avatar: 'https://webapp-pub.ezijing.com/upload/admin/b26d67c22a8380970766e27af3278d6a.png',
summarize:
'<p>真正的产品应该是有一个端到端的一个解决方案。比如说:电子阅读中的从购书,到阅读,再到阅读心得分享,再到推荐,这一整套的解决方案。看看苹果的产品的端到端的解决方案,就知道什么是产品的样子了。</p>\n<p>真正的产品应该是有价值的。这种价值表现在&mdash;&mdash;你可以从中获得有价值的内容,并且你也可以通过他创造对你有价值的东西。比如,像豆瓣,像Stackoverflow,甚至像Twitter和微博这样让信息平等让信息传递更快的社区,或是像AWS或是Apple的开发平台,等等。可见,我们无法通过QQ获得有价值的东西,我们也无法通过QQ创造有价值的东西。</p>'
}
]
})
// 获取课程信息
function fetchInfo() {
if (!courseId || !semesterId) {
return
}
api.getCourse({ course_id: courseId, semester_id: semesterId }).then(res => {
Object.assign(detail, res.data.detail)
})
}
onMounted(() => {
fetchInfo()
})
</script>
<template>
<section class="course-view-header">
<h1>Design Thinking for Managers 设计思维与管理创新</h1>
<ul>
<li>必修课</li>
<li>3学分</li>
<li>第二学期</li>
</ul>
<section class="course-view-hd">
<div class="course-info">
<h1>{{ detail.name }}</h1>
<ul>
<li>{{ detail.elective_type }}</li>
<li>{{ detail.credit }}学分</li>
<li v-if="detail.semester">{{ detail.semester.name }}</li>
</ul>
<el-button round>课程简介</el-button>
<el-button round>预备知识</el-button>
<el-button round>授课目标</el-button>
</div>
<div class="course-lecturers">
<el-carousel :autoplay="false" indicator-position="none" arrow="always" height="126px">
<el-carousel-item v-for="item in detail.lecturers" :key="item.id" class="lecturer-item">
<img :src="item.avatar" class="lecturer-item__pic" />
<div class="lecturer-item__info">
<h2>{{ item.name }}</h2>
<div v-html="item.summarize"></div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</section>
<el-tabs>
<el-tab-pane label="学习">
<CourseViewChapter />
</el-tab-pane>
<el-tab-pane label="论坛" lazy>
<CourseViewBBS />
</el-tab-pane>
<el-tab-pane label="大作业" lazy>
<CourseViewWork />
</el-tab-pane>
<el-tab-pane label="考试" lazy>
<CourseViewExam />
</el-tab-pane>
<el-tab-pane label="课程考核" lazy>
<CourseViewAssess />
</el-tab-pane>
<el-tab-pane label="直播" lazy>
<CourseViewLive />
</el-tab-pane>
</el-tabs>
<div class="course-view-bd">
<el-tabs>
<el-tab-pane label="学习">
<CourseViewChapter />
</el-tab-pane>
<el-tab-pane label="论坛" lazy>
<CourseViewBBS />
</el-tab-pane>
<el-tab-pane label="大作业" lazy>
<CourseViewWork />
</el-tab-pane>
<el-tab-pane label="考试" lazy>
<CourseViewExam />
</el-tab-pane>
<el-tab-pane label="课程考核" lazy>
<CourseViewAssess />
</el-tab-pane>
<el-tab-pane label="直播" lazy>
<CourseViewLive />
</el-tab-pane>
</el-tabs>
</div>
</template>
<style lang="scss" scoped>
.course-view-hd {
display: flex;
padding: 20px;
border-bottom: 1px solid #e6e6e6;
}
.course-info {
flex: 1;
h1 {
font-size: 18px;
font-weight: 500;
line-height: 30px;
color: #333333;
}
ul {
margin-top: 10px;
margin-bottom: 20px;
display: flex;
}
li {
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #bcbcbc;
}
li + li {
padding-left: 10px;
margin-left: 10px;
border-left: 1px solid #bcbcbc;
}
}
.course-lecturers {
width: 444px;
background: #f7f8fa;
border-radius: 10px;
:deep(.el-carousel__arrow) {
background: none;
color: #333;
font-size: 20px;
}
:deep(.el-carousel__arrow--left) {
left: 0;
}
:deep(.el-carousel__arrow--right) {
right: 0;
}
}
.lecturer-item {
width: 364px;
display: flex;
justify-content: center;
height: 106px;
margin: 10px 40px;
overflow: hidden;
}
.lecturer-item__pic {
width: 90px;
height: 106px;
object-fit: cover;
border-radius: 10px;
overflow: hidden;
}
.lecturer-item__info {
flex: 1;
margin-left: 10px;
padding-top: 10px;
height: 100px;
overflow: hidden;
h2 {
font-size: 16px;
font-weight: 500;
line-height: 27px;
color: #333333;
}
}
.course-view-bd {
padding: 10px 20px;
}
</style>
......@@ -16,5 +16,6 @@ const tabs = $ref([
<el-tabs v-model="tabValue">
<el-tab-pane v-for="item in tabs" :label="item.label" :name="item.value" :key="item.value"></el-tab-pane>
</el-tabs>
<el-select></el-select>
</AppCard>
</template>
......@@ -42,7 +42,15 @@ const update = () => {
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk>
<el-form
ref="formRef"
:model="form"
:rules="rules"
hide-required-asterisk
label-width="100px"
label-position="left"
style="width: 360px"
>
<el-form-item label="旧密码" prop="old_password">
<el-input type="password" v-model="form.old_password" />
</el-form-item>
......@@ -53,7 +61,7 @@ const update = () => {
<el-input type="password" v-model="form.password_r" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts"></script>
<template>
<el-form> </el-form>
<el-tabs>
<el-tab-pane label="全部"> </el-tab-pane>
<el-tab-pane label="待处理" lazy></el-tab-pane>
<el-tab-pane label="已处理" lazy></el-tab-pane>
</el-tabs>
<el-button round>创建</el-button>
</template>
<script setup lang="ts"></script>
<template>
<el-tabs>
<el-tab-pane label="全部"> </el-tab-pane>
<el-tab-pane label="待处理" lazy></el-tab-pane>
<el-tab-pane label="已处理" lazy></el-tab-pane>
</el-tabs>
<el-button round>创建</el-button>
</template>
......@@ -28,7 +28,15 @@ const update = () => {
</script>
<template>
<el-form ref="formRef" :model="form" :rules="rules" hide-required-asterisk>
<el-form
ref="formRef"
:model="form"
:rules="rules"
hide-required-asterisk
label-width="60px"
label-position="left"
style="width: 360px"
>
<el-form-item label="昵称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
......@@ -42,7 +50,7 @@ const update = () => {
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
</template>
......@@ -2,20 +2,17 @@
const User = defineAsyncComponent(() => import('../components/User.vue'))
const Password = defineAsyncComponent(() => import('../components/Password.vue'))
const Suggestions = defineAsyncComponent(() => import('../components/Suggestions.vue'))
const currentTab = ref<'User' | 'Password' | 'Suggestions'>('User')
const tabs = { User, Password, Suggestions }
</script>
<template>
<AppCard>
<el-tabs>
<el-tab-pane label="个人资料">
<User />
</el-tab-pane>
<el-tab-pane label="账号密码" lazy>
<Password />
</el-tab-pane>
<el-tab-pane label="投诉建议" lazy>
<Suggestions />
</el-tab-pane>
</el-tabs>
<el-radio-group v-model="currentTab" size="large" style="margin-bottom: 30px">
<el-radio-button label="User">个人资料</el-radio-button>
<el-radio-button label="Password">账号密码</el-radio-button>
<el-radio-button label="Suggestions">投诉建议</el-radio-button>
</el-radio-group>
<component :is="tabs[currentTab]" />
</AppCard>
</template>
......@@ -56,33 +56,46 @@ export interface PermissionType {
// 课程信息
export interface CourseType {
auth_view: boolean
belong_operator: string
belong_operator_name: string
big: string
classification: string
classification_name: string
id: string
course_id: string
semester_id: string
schedule: string
represent: string
watch_video_length: number
is_finished: number
is_top: number
name: string
cover: string
created_operator: string
created_operator_name: string
created_time: string
credit: string
department_public: string
elective_type: string
elective_type_name: string
elective_type: number
online_type: number
section?: ChapterType
semester: SemesterType
credit: number
previous_preparation: string
target: string
lecturers: LecturerType[]
}
// 章节信息
export interface ChapterType {
id: string
name: string
online_type: string
online_type_name: string
organ_id: string
organ_id_name: string
platform_public: string
project_id: string
project_id_name: string
small: string
status: string
status_name: string
updated_operator: string
updated_operator_name: string
updated_time: string
schedule: string
watch_video_length: number
is_finished: number
}
// 学期信息
export interface SemesterType {
id: string
name: string
start_time: string
end_time: string
}
export interface LecturerType {
id: string
name: string
avatar: string
summarize: string
}
......@@ -13,6 +13,19 @@ const httpRequest = axios.create({
// 请求拦截
httpRequest.interceptors.request.use(
function (config) {
// 权限接口单独签名
// https://gitlab.ezijing.com/root/api-documents/-/blob/master/ezijing_permissions/%E7%AD%BE%E5%90%8D%E9%AA%8C%E8%AF%81.md
if (config.url && /^\/api\/saas/.test(config.url)) {
// 默认参数
const defaultHeaders = {
timestamp: Date.now(),
nonce: Math.random().toString(36).slice(-8),
signature: 'UG7wBenexQhiuD2wpCwuxkU0jqcj006d'
}
// config.headers = Object.assign(config.headers, defaultHeaders)
config.params = Object.assign(config.params || {}, defaultHeaders)
}
if (config.headers?.['Content-Type'] === 'application/x-www-form-urlencoded') {
config.data = qs.stringify(config.data, { skipNulls: true })
}
......
......@@ -8,7 +8,7 @@ import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
base: mode === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/center-resource/' : '/',
base: mode === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/saas-learn/' : '/',
plugins: [
vue({ reactivityTransform: true }),
AutoImport({
......@@ -30,10 +30,10 @@ export default defineConfig(({ mode }) => ({
cert: fs.readFileSync(path.join(__dirname, './https/dev.ezijing.com.pem'))
},
proxy: {
'/api/qbs': {
target: 'https://question-api.ezijing.com',
'/api/saas': {
target: 'http://api.ezijing.com:9501',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qbs/, '')
rewrite: path => path.replace(/^\/api\/saas/, '')
},
'/api': 'https://resource-center.ezijing.com'
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论