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

updates

上级 6731bd7b
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
"scripts": { "scripts": {
"dev": "vite --mode dev", "dev": "vite --mode dev",
"build": "vue-tsc --noEmit && vite build --mode prod && npm run deploy", "build": "vue-tsc --noEmit && vite build --mode prod && npm run deploy",
"build:test": "vue-tsc --noEmit && vite build --test prod", "build:test": "vue-tsc --noEmit && vite build --mode test",
"build:pre": "vue-tsc --noEmit && vite build --pre prod", "build:pre": "vue-tsc --noEmit && vite build --mode pre",
"preview": "vite preview --port 5050", "preview": "vite preview --port 5050",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.13", "pinia": "^2.0.13",
"qs": "^6.10.3", "qs": "^6.10.3",
"sass": "^1.50.0", "sass": "^1.50.1",
"swiper": "^8.1.0", "swiper": "^8.1.1",
"vant": "^3.4.7", "vant": "^3.4.8",
"vue": "^3.2.33", "vue": "^3.2.33",
"vue-router": "^4.0.14" "vue-router": "^4.0.14"
}, },
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
"@rushstack/eslint-patch": "^1.1.3", "@rushstack/eslint-patch": "^1.1.3",
"@types/blueimp-md5": "^2.18.0", "@types/blueimp-md5": "^2.18.0",
"@types/lodash-es": "^4.17.6", "@types/lodash-es": "^4.17.6",
"@types/node": "^17.0.24", "@types/node": "^17.0.25",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
...@@ -41,6 +41,6 @@ ...@@ -41,6 +41,6 @@
"typescript": "~4.6.3", "typescript": "~4.6.3",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-checker": "^0.4.6", "vite-plugin-checker": "^0.4.6",
"vue-tsc": "^0.34.6" "vue-tsc": "^0.34.7"
} }
} }
差异被折叠。
...@@ -27,3 +27,8 @@ export function uploadFile(data: object) { ...@@ -27,3 +27,8 @@ export function uploadFile(data: object) {
headers: { 'Content-Type': 'multipart/form-data' } headers: { 'Content-Type': 'multipart/form-data' }
}) })
} }
// 获取导学视频详情
export function getVideoView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/learning/video-view', { params })
}
<script setup lang="ts">
import type { ICourseItem } from '@/types'
defineProps<{ data: ICourseItem }>()
const studyStatus = (progress: number) => {
if (progress === 100) {
return '已完成'
} else if (progress > 0) {
return '已学习'
} else {
return '未学习'
}
}
</script>
<template>
<div class="course-item">
<router-link :to="{ path: `/course/view/${data.id}` }" class="course-item-inner">
<img :src="data.course_picture" class="course-item-pic" />
<div class="course-item-content">
<h5>{{ data.course_name }}</h5>
<p class="t1">
<span v-for="(lecturer, index) in data.course_lectures" :key="index">{{ lecturer.lecturer_name }}</span>
</p>
<p class="t2">
<span>{{ data.course_chapters.big_total }}章节</span>
<span>{{ data.course_chapters.small_total }}课时</span>
</p>
<p class="t2">
<span>{{ data.pv }}人看过</span>
<span>{{ data.records_total }}人评论</span>
</p>
<p class="t3">{{ studyStatus(data.progress) }}</p>
</div>
</router-link>
</div>
</template>
<style lang="scss">
.course-item {
padding: 0.22rem;
background: #fff;
border-radius: 0.2rem;
}
.course-item-inner {
display: flex;
}
.course-item-pic {
width: 1.6rem;
height: 1.9rem;
border-radius: 0.1rem;
overflow: hidden;
object-fit: cover;
}
.course-item-content {
flex: 1;
margin-left: 0.26rem;
line-height: 0.33rem;
overflow: hidden;
h5 {
font-size: 0.24rem;
font-weight: 500;
line-height: 0.33rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t1 {
margin-top: 0.05rem;
font-size: 0.24rem;
font-weight: 400;
line-height: 0.33rem;
color: #666;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t2 {
margin-top: 0.05rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
line-height: 0.28rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
span + span {
margin-left: 0.2rem;
}
}
.t3 {
margin-top: 0.23rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
text-align: right;
}
}
</style>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { getVideoView } from '@/api/base'
import type { IVideoItem } from '@/types'
defineProps<{ data: IVideoItem }>()
// 查看详情
const dialogVisible = ref<boolean>(false)
const videoInfo = ref<IVideoItem>()
const videoUrl = computed(() => {
if (videoInfo.value) {
// 优先取mp4
const [first = {}] = videoInfo.value.play_info.filter(item => item.Format === 'mp4')
return first.PlayURL
}
return ''
})
const fetchVideoView = (id: string) => {
getVideoView({ id }).then(res => {
videoInfo.value = res.data
})
}
const onClick = (data: IVideoItem) => {
fetchVideoView(data.id)
dialogVisible.value = true
}
</script>
<template>
<div class="video-item" @click="onClick(data)">
<img :src="data.cover_page" />
<h5>{{ data.course_name }}</h5>
<p>{{ data.pv }}播放</p>
</div>
<van-popup v-model:show="dialogVisible" round teleport="body" v-if="dialogVisible">
<div class="video-wrap" v-if="dialogVisible">
<video controls autoplay :src="videoUrl"></video>
</div>
</van-popup>
</template>
<style lang="scss">
.video-item {
width: 2rem;
cursor: pointer;
img {
width: 2rem;
height: 1.9rem;
border-radius: 0.1rem;
overflow: hidden;
object-fit: cover;
}
h5 {
margin-top: 0.09rem;
font-size: 0.24rem;
font-weight: 500;
line-height: 0.33rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
p {
margin-top: 0.04rem;
font-size: 0.1rem;
line-height: 0.28rem;
color: #999999;
}
}
.video-wrap {
width: 80vw;
margin: 0.2rem;
video {
width: 100%;
max-height: 80vh;
}
}
</style>
...@@ -5,6 +5,6 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -5,6 +5,6 @@ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/message', path: '/message',
component: AppLayout, component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }] children: [{ path: '', component: () => import('./views/View.vue') }]
} }
] ]
<script setup lang="ts"> <script setup lang="ts">
import { Notify } from 'vant' import { Notify } from 'vant'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import VideoItem from '@/components/VideoItem.vue'
import type { IDocItem, IVideoItem } from '../types' import type { IDocItem, IVideoItem } from '../types'
defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>() defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>()
function showTips() { function showTips() {
...@@ -36,10 +41,18 @@ function showTips() { ...@@ -36,10 +41,18 @@ function showTips() {
</div> </div>
</div> </div>
</div> </div>
<div class="admission-box" v-if="videos.length">
<h2>免费视频观看</h2>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in videos" :key="item.id" class="video-swiper-slide">
<VideoItem :data="item"></VideoItem>
</swiper-slide>
</swiper>
</div>
</AppCard> </AppCard>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.admission { .admission {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -126,4 +139,21 @@ function showTips() { ...@@ -126,4 +139,21 @@ function showTips() {
background-size: 50%; background-size: 50%;
} }
} }
.admission-box {
margin-top: 0.28rem;
padding: 0.2rem;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
h2 {
margin-bottom: 0.15rem;
font-size: 26px;
font-weight: 600;
color: #333333;
}
}
.video-swiper-slide {
width: 2rem;
}
</style> </style>
...@@ -76,6 +76,29 @@ function showTips() { ...@@ -76,6 +76,29 @@ function showTips() {
.learn-docs { .learn-docs {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_bg.png) no-repeat; background: url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_bg.png) no-repeat;
background-size: contain; background-size: contain;
ul {
overflow: hidden;
}
li {
margin: 0.3rem 0.14rem 0;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
}
p {
flex: 1;
font-size: 0.24rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
} }
.learn-banner { .learn-banner {
margin-top: 0.2rem; margin-top: 0.2rem;
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue' import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css' import 'swiper/css'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '../types' import type { ICourseItem } from '../types'
import { getCourseList } from '../api' import { getCourseList } from '../api'
...@@ -12,15 +14,7 @@ const fetchCourseList = () => { ...@@ -12,15 +14,7 @@ const fetchCourseList = () => {
dataset.value = res.data dataset.value = res.data
}) })
} }
const studyStatus = (progress: number) => {
if (progress === 100) {
return '已完成'
} else if (progress > 0) {
return '已学习'
} else {
return '未学习'
}
}
onMounted(() => { onMounted(() => {
fetchCourseList() fetchCourseList()
}) })
...@@ -28,89 +22,14 @@ onMounted(() => { ...@@ -28,89 +22,14 @@ onMounted(() => {
<template> <template>
<swiper slides-per-view="auto" :space-between="10"> <swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="(item, index) in dataset.list" :key="index" class="course-item"> <swiper-slide v-for="item in dataset.list" :key="item.id" class="course-swiper-slide">
<router-link :to="{ path: `/course/view/${item.id}` }" class="course-item-inner"> <CourseItem :data="item"></CourseItem>
<img :src="item.course_picture" class="course-item-pic" />
<div class="course-item-content">
<h5>{{ item.course_name }}</h5>
<p class="t1">
<span v-for="(lecturer, index) in item.course_lectures" :key="index">{{ lecturer.lecturer_name }}</span>
</p>
<p class="t2">
<span>{{ item.course_chapters.big_total }}章节</span>
<span>{{ item.course_chapters.small_total }}课时</span>
</p>
<p class="t2">
<span>{{ item.pv }}人看过</span>
<span>{{ item.records_total }}人评论</span>
</p>
<p class="t3">{{ studyStatus(item.progress) }}</p>
</div>
</router-link>
</swiper-slide> </swiper-slide>
</swiper> </swiper>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.course-item { .course-swiper-slide {
width: 4.7rem; width: 4.7rem;
padding: 0.22rem;
background: #fff;
border-radius: 0.2rem;
}
.course-item-inner {
display: flex;
}
.course-item-pic {
width: 1.6rem;
height: 1.9rem;
border-radius: 0.1rem;
overflow: hidden;
object-fit: cover;
}
.course-item-content {
flex: 1;
margin-left: 0.26rem;
line-height: 0.33rem;
overflow: hidden;
h5 {
font-size: 0.24rem;
font-weight: 500;
line-height: 0.33rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t1 {
margin-top: 0.05rem;
font-size: 0.24rem;
font-weight: 400;
line-height: 0.33rem;
color: #666;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t2 {
margin-top: 0.05rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
line-height: 0.28rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
span + span {
margin-left: 0.2rem;
}
}
.t3 {
margin-top: 0.23rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
text-align: right;
}
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue' import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css' import 'swiper/css'
import VideoItem from '@/components/VideoItem.vue'
import type { IVideoItem } from '../types' import type { IVideoItem } from '../types'
import { getVideoList, getVideoView } from '../api' import { getVideoList } from '../api'
// 课程导学 // 课程导学
const dataset = ref<{ total: number; list: IVideoItem[] }>({ total: 0, list: [] }) const dataset = ref<{ total: number; list: IVideoItem[] }>({ total: 0, list: [] })
...@@ -15,77 +16,18 @@ const fetchVideoList = () => { ...@@ -15,77 +16,18 @@ const fetchVideoList = () => {
onMounted(() => { onMounted(() => {
fetchVideoList() fetchVideoList()
}) })
// 查看详情
const dialogVisible = ref<boolean>(false)
const videoInfo = ref<IVideoItem>()
const videoUrl = computed(() => {
if (videoInfo.value) {
// 优先取mp4
const [first = {}] = videoInfo.value.play_info.filter(item => item.Format === 'mp4')
return first.PlayURL
}
return ''
})
const fetchVideoView = (id: string) => {
getVideoView({ id }).then(res => {
videoInfo.value = res.data
})
}
const onClick = (data: IVideoItem) => {
fetchVideoView(data.id)
dialogVisible.value = true
}
</script> </script>
<template> <template>
<swiper slides-per-view="auto" :space-between="10"> <swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="(item, index) in dataset.list" :key="index" class="video-item" @click="onClick(item)"> <swiper-slide v-for="item in dataset.list" :key="item.id" class="video-swiper-slide">
<img :src="item.cover_page" /> <VideoItem :data="item"></VideoItem>
<h5>{{ item.course_name }}</h5>
<p>{{ item.pv }}播放</p>
</swiper-slide> </swiper-slide>
</swiper> </swiper>
<van-popup v-model:show="dialogVisible" round>
<div class="video-wrap" v-if="dialogVisible">
<video controls autoplay :src="videoUrl"></video>
</div>
</van-popup>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.video-item { .video-swiper-slide {
width: 2rem; width: 2rem;
img {
width: 2rem;
height: 1.9rem;
border-radius: 0.1rem;
overflow: hidden;
object-fit: cover;
}
h5 {
margin-top: 0.09rem;
font-size: 0.24rem;
font-weight: 500;
line-height: 0.33rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
p {
margin-top: 0.04rem;
font-size: 0.1rem;
line-height: 0.28rem;
color: #999999;
}
}
.video-wrap {
width: 80vw;
margin: 0.2rem;
video {
width: 100%;
max-height: 80vh;
}
} }
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '@/types' import type { ICourseItem } from '@/types'
import { getCourseList } from '../api' import { getCourseList } from '../api'
...@@ -10,43 +11,18 @@ const fetchCourseList = () => { ...@@ -10,43 +11,18 @@ const fetchCourseList = () => {
dataset.value = res.data dataset.value = res.data
}) })
} }
const studyStatus = (progress: number) => {
if (progress === 100) {
return '已完成'
} else if (progress > 0) {
return '已学习'
} else {
return '未学习'
}
}
onMounted(() => { onMounted(() => {
fetchCourseList() fetchCourseList()
}) })
</script> </script>
<template> <template>
<img src="https://webapp-pub.ezijing.com/project/prp-h5/banner_my_course.png" style="width: 100%" /> <a href="https://mp.weixin.qq.com/s/fDf4NpPZH4BNI_KEopiSwQ" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/banner_my_course.png" style="width: 100%" />
</a>
<p class="tips">如果你也是知识获得者,点击课程,晒出你的海报、说出你的感想,得到你的星星。</p> <p class="tips">如果你也是知识获得者,点击课程,晒出你的海报、说出你的感想,得到你的星星。</p>
<div v-for="(item, index) in dataset.list" :key="index" class="course-item"> <CourseItem v-for="item in dataset.list" :data="item" :key="item.id"></CourseItem>
<router-link :to="{ path: `/course/view/${item.id}` }" class="course-item-inner">
<img :src="item.course_picture" class="course-item-pic" />
<div class="course-item-content">
<h5>{{ item.course_name }}</h5>
<p class="t1">
<span v-for="(lecturer, index) in item.course_lectures" :key="index">{{ lecturer.lecturer_name }}</span>
</p>
<p class="t2">
<span>{{ item.course_chapters.big_total }}章节</span>
<span>{{ item.course_chapters.small_total }}课时</span>
</p>
<p class="t2">
<span>{{ item.pv }}人看过</span>
<span>{{ item.records_total }}人评论</span>
</p>
<p class="t3">{{ studyStatus(item.progress) }}</p>
</div>
</router-link>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -58,64 +34,6 @@ onMounted(() => { ...@@ -58,64 +34,6 @@ onMounted(() => {
line-height: 0.36rem; line-height: 0.36rem;
} }
.course-item { .course-item {
padding: 0.22rem;
background: #fff;
border-radius: 0.2rem;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
} }
.course-item-inner {
display: flex;
}
.course-item-pic {
width: 1.6rem;
height: 1.9rem;
border-radius: 0.1rem;
overflow: hidden;
object-fit: cover;
}
.course-item-content {
flex: 1;
margin-left: 0.26rem;
line-height: 0.33rem;
overflow: hidden;
h5 {
font-size: 0.24rem;
font-weight: 500;
line-height: 0.33rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t1 {
margin-top: 0.05rem;
font-size: 0.24rem;
font-weight: 400;
line-height: 0.33rem;
color: #666;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.t2 {
margin-top: 0.05rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
line-height: 0.28rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
span + span {
margin-left: 0.2rem;
}
}
.t3 {
margin-top: 0.23rem;
font-size: 0.2rem;
font-weight: 400;
color: #999;
text-align: right;
}
}
</style> </style>
...@@ -23,32 +23,32 @@ const menus: Array<{ ...@@ -23,32 +23,32 @@ const menus: Array<{
path: '/my/course', path: '/my/course',
name: '我的课程', name: '我的课程',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png' icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png'
},
{
path: '/',
name: '我的团队',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png'
},
{
path: '/',
name: '我的问答',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_3.png'
},
{
path: '/',
name: '申请导师',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_4.png'
},
{
path: '/',
name: '申请紫荆奖',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_5.png'
},
{
path: '/',
name: '参加PRP大会',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_6.png'
} }
// {
// path: '/',
// name: '我的团队',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png'
// },
// {
// path: '/',
// name: '我的问答',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_3.png'
// },
// {
// path: '/',
// name: '申请导师',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_4.png'
// },
// {
// path: '/',
// name: '申请紫荆奖',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_5.png'
// },
// {
// path: '/',
// name: '参加PRP大会',
// icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_6.png'
// }
] ]
// 退出登录 // 退出登录
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论