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

2.0第一版提测

上级 48d63988
差异被折叠。
......@@ -69,19 +69,19 @@
"vconsole-webpack-plugin": "^1.5.2"
},
"dependencies": {
"axios": "^0.20.0",
"cross-env": "^7.0.2",
"element-ui": "^2.14.0",
"axios": "^0.21.1",
"cross-env": "^7.0.3",
"element-ui": "^2.15.0",
"js-cookie": "^2.2.1",
"lodash": "^4.17.15",
"lodash": "^4.17.20",
"vue": "^2.6.12",
"vue-i18n": "^8.16.0",
"vue-loader": "^15.9.3",
"vue-i18n": "^8.22.4",
"vue-loader": "^15.9.6",
"vue-meta-info": "^0.1.7",
"vue-router": "^3.4.3",
"vue-router": "^3.4.9",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.5.1",
"core-js": "^3.6.5"
"vuex": "^3.6.0",
"core-js": "^3.8.3"
},
"engines": {
"node": ">=8.9"
......
......@@ -2,6 +2,9 @@
<div class="app-container">
<div class="app-container-hd" v-if="title">
<div class="app-container-hd__title">{{ title }}</div>
<div class="app-container-hd__right">
<slot name="header-right"></slot>
</div>
</div>
<div class="app-container-bd">
<slot></slot>
......@@ -28,6 +31,9 @@ export default {
box-sizing: border-box;
}
.app-container-hd {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
margin-bottom: 20px;
border-bottom: 1px solid #ccc;
......
<template>
<div class="course-list" element-loading-text="加载中..." v-loading="!loaded">
<template v-if="list.length">
<course-list-item v-for="item in list" :data="item" :key="item.id" v-bind="$attrs" v-on="$listeners" />
<template v-if="currentList.length">
<course-list-item v-for="item in currentList" :data="item" :key="item.id" v-bind="$attrs" v-on="$listeners" />
</template>
<template v-else>
<slot name="empty">
......@@ -19,7 +19,8 @@ export default {
name: 'CourseList',
components: { CourseListItem },
props: {
requestCallback: Function
requestCallback: Function,
searchValue: String
},
data() {
return {
......@@ -27,10 +28,20 @@ export default {
list: []
}
},
computed: {
currentList() {
if (!this.searchValue) {
return this.list
}
return this.list.filter(item => {
return item.course_name.includes(this.searchValue)
})
}
},
methods: {
getCourseList() {
api
.getCourseModule()
.getCourseList()
.then(response => {
this.list = this.requestCallback ? this.requestCallback(response) : response
this.$emit('request-success', response)
......
<template>
<div class="course-item">
<div class="course-item-top">
<img class="course-item-pic" :src="data.photo" v-if="data.photo" />
<div class="course-item-content">
<div class="course-item__title">{{ data.title }}</div>
<div class="course-item__tools">
<div class="course-item__text course-item__text__course">{{ data.course_num }}节课</div>
<div class="course-item__text course-item__text__video">{{ data.video_num }}节视频课</div>
<div class="course-item__text course-item__text__freevideo">{{ data.free_video_num }}个免费视频</div>
<img class="course-item-pic" :src="data.curriculum.curriculum_picture" @click="$emit('on-click', data)" />
<div class="course-item-content">
<div class="course-item__title">{{ data.course_name }}</div>
<div class="course-item__tools">
<div class="course-item__progress">
<span>视频观看进度</span>
<el-progress :percentage="data.video_progress"></el-progress>
</div>
<div class="course-item__buttons">
<el-button type="primary" size="small" round @click="$emit('on-click', data)">查看课程</el-button>
</div>
</div>
</div>
<div class="course-item-bottom">
<div class="course-child" v-for="item in data.child" :key="item.id" @click="$emit('on-click', item)">
<div class="name">{{ item.course_name }}</div>
<div class="progress" v-if="showProgress">{{ item.video_progress | progressText }}</div>
</div>
</div>
</div>
......@@ -47,13 +43,11 @@ export default {
<style lang="scss" scoped>
.course-item {
background: #ffffff;
border-radius: 8px;
padding: 30px;
margin-bottom: 20px;
}
.course-item-top {
display: flex;
padding: 14px 0;
}
.course-item + .course-item {
border-top: 1px solid #ccc;
}
.course-item-pic {
width: 160px;
......@@ -61,6 +55,7 @@ export default {
margin-right: 20px;
border-radius: 2px;
overflow: hidden;
cursor: pointer;
}
.course-item-content {
flex: 1;
......@@ -78,40 +73,16 @@ export default {
-webkit-line-clamp: 2;
overflow: hidden;
}
.course-item__text {
display: inline-block;
font-size: 14px;
}
.course-item__text + .course-item__text {
margin-left: 0.1rem;
}
.course-item__text__course {
color: #88bbff;
}
.course-item__text__video {
color: #5ad0b2;
}
.course-item__text__freevideo {
color: #d05a5a;
}
.course-child {
.course-item__tools {
display: flex;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid #eee;
cursor: pointer;
&:hover {
color: #c01540;
}
.name {
flex: 1;
font-size: 18px;
}
.progress {
margin-left: 20px;
font-size: 12px;
color: #999;
}
.course-item__progress {
display: flex;
flex: 1;
.el-progress {
margin: 0 10px;
width: 50%;
}
}
</style>
......@@ -8,14 +8,24 @@
<span @click="logout">退出登录</span>
</div>
</div>
<ul class="nav">
<li v-for="(item, index) in datalist" :key="index" :class="genClasses(item)">
<router-link :to="item.path">
<i class="iconfont" :class="item.icon" v-if="item.icon"></i>
<span>{{ item.title }}</span>
</router-link>
</li>
</ul>
<el-menu class="nav" :unique-opened="true" :router="true" :default-active="activeLink">
<template v-for="item in datalist">
<el-submenu :index="item.title" :key="item.title" v-if="item.children">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span>{{ item.title }}</span>
</template>
<el-menu-item :index="item.path" :key="item.title" v-for="item in item.children">
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</el-submenu>
<el-menu-item :index="item.path" :key="item.title" v-else>
<i class="iconfont" :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</div>
</template>
......@@ -27,19 +37,49 @@ export default {
name: 'AppAside',
props: {
menus: { type: Array, default: () => [] },
showUser: { type: Boolean, default: true }
showUser: { type: Boolean, default: false }
},
data() {
return {
activeLink: '/course/learn',
defaultMenus: [
{ title: '课程学习', icon: 'icon-bianzu6-hong', path: '/course/learn' },
{ title: '考前摸底', icon: 'icon-bianzuhong', path: '/testExam' },
// { title: '真题实战', icon: 'icon-kaoshihong', path: '/mock' },
{ title: '错题集合', icon: 'icon-guanlizhongxinbeifen-hong', path: '/my/questions/wrong' },
{ title: '收藏试题', icon: 'icon-shoucang-hong', path: '/my/questions/collection' },
// { title: '必考考点', icon: 'icon-kaozheng-hong', path: '/course/test' },
{ title: '意见反馈', icon: 'icon-fankui-hong', path: '/feedback' },
{ title: '联系客服', icon: 'icon-bianzu8-hong', path: '/contact' }
{
title: '我的课程',
icon: 'icon-bianzu6-hong',
children: [{ title: '课程学习', path: '/course/learn' }]
},
{
title: '我的考试',
icon: 'icon-bianzuhong',
children: [
{ title: '考前摸底', path: '/testExam' },
{ title: '错题集合', path: '/my/questions/wrong' },
{ title: '收藏试题', path: '/my/questions/collection' }
]
},
{
title: '实训练习',
icon: 'icon-kaoshihong',
children: [{ title: '实训案例练习', path: '/xxxx' }]
},
{
title: '个人中心',
icon: 'icon-guanlizhongxinbeifen-hong',
children: [
{ title: '个人信息', path: '/account' },
{ title: '修改密码', path: '/account/password' },
{ title: '安全设置', path: '/account/safe' }
]
},
{
title: '帮助与反馈',
icon: 'icon-bianzu8-hong',
children: [
{ title: '服务专区', path: '/contact' },
{ title: '系统说明', path: '/doc' },
{ title: '常见问题', path: '/help' }
]
}
]
}
},
......@@ -57,6 +97,14 @@ export default {
return this.user.avatar || defaultAvatar
}
},
watch: {
$route: {
immediate: true,
handler(to, from) {
this.activeLink = to.path
}
}
},
methods: {
genClasses(data) {
const isActive = this.$route.fullPath.includes(data.path)
......@@ -110,28 +158,27 @@ export default {
}
.nav {
border: 0;
padding: 30px 0;
li {
text-align: center;
color: #ccc;
.iconfont {
font-size: 16px;
font-weight: 600;
color: #666;
a {
display: block;
padding: 16px 0;
}
&:hover,
&.is-active {
color: #c01540;
background-color: #fff4f7;
.iconfont {
color: #c01540;
}
}
.iconfont {
margin-right: 4px;
color: #ccc;
}
color: currentColor;
}
.el-submenu__title:hover {
color: #c01540;
}
.is-active {
color: #c01540;
}
.is-active .el-submenu__title {
background: #fff4f7;
color: #c01540;
}
.el-menu-item:hover,
.el-menu-item:focus {
color: #c01540;
background: transparent;
}
}
}
......
......@@ -5,17 +5,28 @@
</div>
<div class="tool">
<!-- <app-search-bar :value="$route.query.keywords" @search="handleSearch" /> -->
<nav class="nav">
<!-- <nav class="nav">
<router-link to="/">首页</router-link>
<router-link to="/my">我的</router-link>
<!-- <router-link to="/messages">通知</router-link> -->
</nav>
<router-link to="/messages">通知</router-link>
</nav> -->
<el-dropdown>
<div class="user">
<img :src="avatar" class="user-avatar" />
<span class="user-name">{{ user.realname }}</span>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item icon="el-icon-user" @click.native="$router.push('/account')">个人中心</el-dropdown-item>
<el-dropdown-item icon="el-icon-switch-button" @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import AppSearchBar from '@/components/AppSearchBar'
import defaultAvatar from '@/assets/images/avatar.png'
export default {
name: 'AppHeader',
components: { AppSearchBar },
......@@ -24,15 +35,29 @@ export default {
title: '金融产品数字化营销职业技能等级证书'
}
},
computed: {
user() {
return this.$store.state.user
},
avatar() {
return this.user.avatar || defaultAvatar
}
},
methods: {
handleSearch(value) {
this.$router.replace({ path: '/search', query: { keywords: value } })
},
// 退出登录
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = webConf.others.loginUrl
})
}
}
}
</script>
<style lang="scss" >
<style lang="scss">
.app-header {
display: flex;
align-items: center;
......@@ -60,5 +85,30 @@ export default {
padding: 0 20px;
}
}
.user {
height: 80px;
padding: 0 10px;
display: flex;
align-items: center;
cursor: pointer;
&:hover {
background-color: #fff4f7;
}
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.user-name {
padding: 0 10px;
}
}
</style>
......@@ -13,11 +13,11 @@
id="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no, viewport-fit=cover"
/>
<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.1/skins/default/aliplayer-min.css" />
<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.3/skins/default/aliplayer-min.css" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2173492_ctgt96uojqw.css" />
</head>
<body>
<div id="app"></div>
<script src="https://g.alicdn.com/de/prismplayer/2.9.1/aliplayer-min.js"></script>
<script src="https://g.alicdn.com/de/prismplayer/2.9.3/aliplayer-min.js"></script>
</body>
</html>
......@@ -6,7 +6,7 @@
<aside-chapter :data="data" :chapters="chapters" :active="active"></aside-chapter>
</div>
</el-tab-pane>
<el-tab-pane label="学习资料" name="1" v-if="active && active.type === 2">
<el-tab-pane label="讲义" name="1" v-if="active && active.type === 2">
<div class="tab-pane">
<aside-lecture :ppts="ppts" :pptIndex="pptIndex" v-on="$listeners"></aside-lecture>
</div>
......
<template>
<app-container title="个人信息"> </app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
export default {
components: { AppContainer }
}
</script>
......@@ -10,10 +10,12 @@
<el-form-item label="重复新密码" prop="passwordR">
<el-input type="password" v-model="ruleForm.passwordR" placeholder="请重复新密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
</el-form-item>
</el-form>
<template #footer>
<div class="app-container-ft">
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
</div>
</template>
</app-container>
</template>
......
<template>
<app-container title="安全设置">
<el-form :model="ruleForm" :rules="rules" label-width="100px" ref="ruleForm" class="form">
<el-form-item label="手机号码" prop="phone">
<el-input type="password" v-model="ruleForm.phone"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="app-container-ft">
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
</div>
</template>
</app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
import * as api from '@/api/account'
export default {
components: { AppContainer },
data() {
return {
ruleForm: {
phone: ''
},
rules: {
phone: [{ required: true, message: '请输入手机号码', trigger: 'blur' }]
},
submitLoading: false
}
},
methods: {
handleSubmit() {
this.$refs.ruleForm.validate().then(this.handleSubmitRequest)
},
handleSubmitRequest() {
this.submitLoading = true
api
.updatePassword(this.ruleForm)
.then(response => {
this.$message({ message: '密码修改成功', type: 'success' })
// 重置表单
this.$refs.ruleForm.resetFields()
})
.finally(() => {
this.submitLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.form {
max-width: 340px;
}
</style>
......@@ -4,6 +4,7 @@
<ul>
<li v-for="subItem in item.children" :key="subItem.id" @click="handleClick(subItem)">
<div class="name">{{ subItem.name }}</div>
<div class="duration">{{ subItem.duration }}</div>
<div class="progress" v-if="showProgress">{{ progressText(subItem.video_progress) }}</div>
</li>
</ul>
......
<template>
<div class="teacher">
<div class="teacher-item" v-for="item in data" :key="item.id">
<img :src="item.lecturer_avatar" class="teacher-item-pic" />
<div class="teacher-item-content">
<p class="t1">{{ item.lecturer_name }}</p>
<p class="t2">{{ item.lecturer_education }}</p>
<p class="t2">{{ item.lecturer_title }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
data: { type: Array, default: () => [] }
}
}
</script>
<style lang="scss" scoped>
.teacher-item {
display: flex;
margin-bottom: 10px;
}
.teacher-item-pic {
width: 90px;
height: 110px;
object-fit: cover;
margin-right: 10px;
}
.teacher-item-content {
flex: 1;
.t1 {
font-weight: bold;
}
.t2 {
margin-top: 5px;
color: #707070;
}
}
</style>
<template>
<course-list @on-click="handleClick" />
<app-container title="课程学习">
<template #header-right>
<div class="search">
<el-input prefix-icon="el-icon-search" clearable v-model="searchValue" @keyup.enter.native="handleSearch" />
<el-button type="text" @click="handleSearch">搜索</el-button>
</div>
</template>
<course-list @on-click="handleClick" :searchValue="searchValue" />
</app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
import CourseList from '@/components/CourseList.vue'
export default {
components: { CourseList },
components: { AppContainer, CourseList },
data() {
return {
searchValue: ''
}
},
methods: {
handleClick(data) {
this.$router.push({ name: 'courseLearnItem', params: { id: data.id } })
}
this.$router.push({ name: 'courseLearnItem', params: { id: data.course_id } })
},
// 搜索
handleSearch() {}
}
}
</script>
<style lang="scss" scoped>
.search {
display: flex;
width: 300px;
.el-button {
margin-left: 10px;
}
}
</style>
<template>
<div class="main-container" element-loading-text="加载中..." v-loading="!loaded">
<div class="course-top" v-if="detail.curriculum">
<app-container title="课程详情" element-loading-text="加载中..." v-loading="!loaded">
<div class="course-top" v-if="loaded">
<div class="course-top-hd">
<div class="course-top__title">{{ detail.curriculum.curriculum_name }}</div>
<div class="course-top-hd-left">
<div class="course-top__title">{{ detail.curriculum.curriculum_name }}</div>
<div class="course-top__progress">
视频观看进度 <el-progress :percentage="detail.video_progress"></el-progress>
</div>
</div>
<div class="course-top-hd-right">
<el-button type="primary" size="small" @click="onChapterClick(latestVideo)" v-if="latestVideo">
{{ buttonText }}
</el-button>
</div>
</div>
<div class="course-top-bd">
<div class="course-top__pic"><img :src="detail.curriculum.curriculum_picture" /></div>
<div class="course-top__content" v-html="detail.curriculum.curriculum_represent"></div>
</div>
</div>
<el-tabs v-model="tabActive">
<el-tab-pane lazy label="按章节学习">
<course-chapter :courseId="courseId" :data="detail.chapters"></course-chapter>
</el-tab-pane>
<!-- <el-tab-pane lazy label="按考点学习">
<div class="course-bottom">
<div class="course-bottom-left">
<el-tabs v-model="tabActive">
<el-tab-pane lazy label="课程内容">
<course-chapter :courseId="courseId" :showProgress="true" :data="detail.chapters"></course-chapter>
</el-tab-pane>
<!-- <el-tab-pane lazy label="按考点学习">
<course-tag :courseId="courseId" @on-click="onTagClick"></course-tag>
</el-tab-pane> -->
</el-tabs>
</div>
</el-tabs>
</div>
<div class="course-bottom-right">
<el-tabs>
<el-tab-pane label="课程讲师">
<course-teacher :data="detail.lecturers"></course-teacher>
</el-tab-pane>
</el-tabs>
</div>
</div>
</app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
import CourseChapter from './components/CourseChapter'
import CourseTeacher from './components/CourseTeacher'
import CourseTag from '@/components/CourseTag'
import * as api from '@/api/course.js'
export default {
components: { CourseChapter, CourseTag },
components: { AppContainer, CourseChapter, CourseTag, CourseTeacher },
metaInfo() {
return {
title: this.detail.course_name || ''
......@@ -41,6 +64,23 @@ export default {
computed: {
courseId() {
return this.$route.params.id
},
buttonText() {
return this.detail.latest_play ? '继续学习' : '开始学习'
},
// 扁平化章节数据
flatChapters() {
return this.detail.chapters.reduce((result, item) => {
return result.concat(item.children)
}, [])
},
// 最新的视频
latestVideo() {
if (this.detail.latest_play && this.flatChapters.length) {
return this.flatChapters.find(item => item.resource_id === this.detail.latest_play)
} else {
return this.flatChapters.length ? this.flatChapters[0] : null
}
}
},
methods: {
......@@ -59,6 +99,9 @@ export default {
// 考点点击
onTagClick(data) {
this.$router.push({ name: 'courseTagItem', params: { courseId: this.courseId, id: data.id } })
},
onChapterClick(data) {
this.$router.push({ name: 'viewerCourseChapter', params: { cid: this.courseId, id: data.id } })
}
},
beforeMount() {
......@@ -79,15 +122,27 @@ export default {
padding-bottom: 20px;
}
.course-top-hd {
padding-bottom: 8px;
margin-bottom: 10px;
border-bottom: 1px solid #ccc;
display: flex;
justify-content: space-between;
padding-bottom: 10px;
}
.course-top-hd-left {
flex: 1;
}
.course-top__title {
font-size: 18px;
font-weight: bold;
line-height: 1;
}
.course-top__progress {
margin-top: 10px;
display: flex;
align-items: center;
.el-progress {
width: 50%;
margin: 0 10px;
}
}
.course-top-bd {
display: flex;
}
......@@ -108,4 +163,14 @@ export default {
line-height: 24px;
overflow: hidden;
}
.course-bottom {
display: flex;
}
.course-bottom-left {
flex: 1;
}
.course-bottom-right {
margin-left: 20px;
width: 300px;
}
</style>
<template>
<app-container title="系统说明"> </app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
export default {
components: { AppContainer }
}
</script>
<template>
<app-container title="常见问题">
<dl v-for="(item, index) in list" :key="index">
<dt>{{ item.title }}</dt>
<dd>{{ item.content }}</dd>
</dl>
</app-container>
</template>
<script>
import AppContainer from '@/components/AppContainer'
export default {
components: { AppContainer },
data() {
return {
list: [
{
title: '给组件绑定的事件为什么无法触发?',
content: '在 Vue 2.0 中,为自定义组件绑定原生事件必须使用 .native 修饰符'
},
{
title: '给组件绑定的事件为什么无法触发?',
content: '在 Vue 2.0 中,为自定义组件绑定原生事件必须使用 .native 修饰符'
},
{
title: '给组件绑定的事件为什么无法触发?',
content: '在 Vue 2.0 中,为自定义组件绑定原生事件必须使用 .native 修饰符'
}
]
}
}
}
</script>
<style lang="scss" scoped>
dl {
padding: 10px 0;
}
dt {
font-weight: bold;
color: #262626;
}
dd {
padding-top: 10px;
color: #666;
}
dl + dl {
border-top: 1px solid #eee;
}
</style>
......@@ -69,11 +69,23 @@ export default [
{ path: '/contact', component: () => import(/* webpackChunkName: "contact" */ '@/pages/contact') },
/* 通知 */
{ path: '/messages', component: () => import(/* webpackChunkName: "messages" */ '@/pages/messages') },
/* 通知 */
{ path: '/doc', component: () => import(/* webpackChunkName: "help" */ '@/pages/doc') },
/* 通知 */
{ path: '/help', component: () => import(/* webpackChunkName: "help" */ '@/pages/help') },
{
path: '/account',
component: () => import(/* webpackChunkName: "account" */ '@/pages/account/index')
},
/* 修改密码 */
{
path: '/account/password',
component: () => import(/* webpackChunkName: "account" */ '@/pages/account/password')
},
{
path: '/account/safe',
component: () => import(/* webpackChunkName: "account" */ '@/pages/account/safe')
},
{
path: '/my/questions/wrong',
component: () => import(/* webpackChunkName: "course-learn" */ '@/pages/my/questions/questionWrong')
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论