提交 587f5246 authored 作者: lhh's avatar lhh

wmpc开发

上级 016c9a42
差异被折叠。
...@@ -27,7 +27,9 @@ ...@@ -27,7 +27,9 @@
"vant": "^3.5.0", "vant": "^3.5.0",
"vue": "^3.2.37", "vue": "^3.2.37",
"vue-infinite-scroll": "^2.0.2", "vue-infinite-scroll": "^2.0.2",
"vue-router": "^4.0.15" "vue-router": "^4.0.15",
"vue3-video-play": "^1.3.1-beta.6",
"xgplayer": "^3.0.19"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.1.3", "@rushstack/eslint-patch": "^1.1.3",
......
...@@ -33,8 +33,8 @@ export function getVideoView(params: { id: string }) { ...@@ -33,8 +33,8 @@ export function getVideoView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/admission/video-view', { params }) return httpRequest.get('/api/psp/v1/admission/video-view', { params })
} }
// 获取推荐课程列表 // 获取推荐课程列表
export function getRecommendCourse(params?: { page_size?: string; page?: string }) { export function getRecommendCourse(params?: { page_size?: string; page?: string; category:string }) {
return httpRequest.get('/api/psp/v1/recommend-course/list', { params }) return httpRequest.get('/api/psp/v2/learning/course-list', { params })
} }
// 获取导师列表 // 获取导师列表
export function getTeacherList(params?: { page_size?: string; page?: string; type?: string }) { export function getTeacherList(params?: { page_size?: string; page?: string; type?: string }) {
......
...@@ -31,6 +31,8 @@ const files = computed<{ name: string; url: string }[]>(() => { ...@@ -31,6 +31,8 @@ const files = computed<{ name: string; url: string }[]>(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.doc-hd { .doc-hd {
// margin-top: 1rem;
padding: 0.1rem 0.2rem;
h1 { h1 {
font-size: 0.32rem; font-size: 0.32rem;
font-weight: 500; font-weight: 500;
...@@ -48,7 +50,7 @@ const files = computed<{ name: string; url: string }[]>(() => { ...@@ -48,7 +50,7 @@ const files = computed<{ name: string; url: string }[]>(() => {
} }
} }
.doc-bd { .doc-bd {
padding: 0.5rem 0; padding: 0.5rem 0.2rem;
font-size: 0.24rem; font-size: 0.24rem;
font-weight: 400; font-weight: 400;
color: #666666; color: #666666;
......
...@@ -6,6 +6,7 @@ interface Props { ...@@ -6,6 +6,7 @@ interface Props {
title?: string title?: string
headerAlign?: string headerAlign?: string
backgroundColor?: string backgroundColor?: string
type?: string
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
headerAlign: 'left', headerAlign: 'left',
...@@ -15,10 +16,12 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -15,10 +16,12 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['back']) const emit = defineEmits(['back'])
const router = useRouter() const router = useRouter()
function handleBack() { function handleBack() {
try { if (!props.type) {
router.back() try {
} catch (e) { router.back()
router.replace('/') } catch (e) {
router.replace('/')
}
} }
emit('back') emit('back')
} }
...@@ -46,16 +49,27 @@ const containerPaddingValue = computed(() => { ...@@ -46,16 +49,27 @@ const containerPaddingValue = computed(() => {
<style lang="scss"> <style lang="scss">
.app-container { .app-container {
width: 100%;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
padding: v-bind(containerPaddingValue); // padding: 0 0.3rem;
border-radius: 0.2rem; border-radius: 0.2rem;
background: v-bind(backgroundColor); background: v-bind(backgroundColor);
} }
.app-container-hd { .app-container-hd {
padding: 0 0.3rem;
width: 100%;
position: fixed;
top: 0;
left: 0;
background-color: #fff;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 0.32rem; font-size: 0.32rem;
padding: 0.24rem 0; // padding: 0.24rem 0;
height: 0.9rem;
box-sizing: border-box;
z-index: 9999999;
.van-icon-arrow-left { .van-icon-arrow-left {
margin-left: -2px; margin-left: -2px;
} }
...@@ -68,4 +82,10 @@ const containerPaddingValue = computed(() => { ...@@ -68,4 +82,10 @@ const containerPaddingValue = computed(() => {
color: #333333; color: #333333;
text-align: v-bind(headerAlign); text-align: v-bind(headerAlign);
} }
.app-container-hd-aside {
margin-left: auto;
}
.app-container-bd {
padding-top: 0.9rem;
}
</style> </style>
<script setup lang="ts"></script>
<template>
<div class="foot">
<div class="foot-t">
<div class="foot-t_l">
<div class="title">联系我们</div>
<div class="info">
紫荆小秘书:13263110169(同微信)<br />
地址:北京市海淀区中关村东路1号院清华科技园7号楼5层<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;深圳市福田区博今商务广场A座22层
<br />邮箱:WMC@ezijing.com
</div>
</div>
<div class="foot-t_r">
<img class="code" src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/er-code.png" />
<div class="wx">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/wx.png" class="icon" />
<div class="txt">微信公众号</div>
</div>
</div>
</div>
<div class="foot-b">
Copyright @ 2013 Zijing Education.All rights reserved.京ICP证150431号
<div class="c">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/wmpc-h5/jinghui.png" class="jh" />
<p>京公网安备 11010802023681号</p>
</div>
清控紫荆(北京)教育科技股份有限公司
</div>
</div>
</template>
<style lang="scss" scoped>
.foot {
background-color: #242424;
padding: 0.46rem 0.3rem;
box-sizing: border-box;
.foot-t {
display: flex;
justify-content: space-between;
.foot-t_l {
.title {
font-weight: bold;
font-size: 0.22rem;
color: #ffffff;
line-height: 100%;
letter-spacing: 0.03rem;
}
.info {
font-size: 0.2rem;
color: #ffffff;
line-height: 0.36rem;
text-align: left;
margin-top: 0.2rem;
}
}
.foot-t_r {
width: 1.26rem;
.code {
width: 100%;
}
.wx {
display: flex;
justify-content: space-between;
margin-top: 0.08rem;
.icon {
width: 0.21rem;
}
.txt {
font-size: 0.2rem;
color: #ffffff;
line-height: 100%;
}
}
}
}
.foot-b {
text-align: center;
font-size: 0.2rem;
color: #999999;
line-height: 100%;
margin-top: 0.5rem;
.c {
display: flex;
align-items: center;
justify-content: center;
margin: 0.24rem 0;
img {
height: 0.26rem;
margin-right: 0.16rem;
}
}
}
}
</style>
...@@ -3,7 +3,11 @@ ...@@ -3,7 +3,11 @@
<template> <template>
<header class="app-header"> <header class="app-header">
<div class="app-header__logo"> <div class="app-header__logo">
<router-link to="/"><img src="https://webapp-pub.ezijing.com/project/prp-h5/logo1.png" /></router-link> <router-link to="/"><img src="https://webapp-pub.ezijing.com/project/wmpc-h5/ezijing-logo.png" /></router-link>
<div class="app-header_text">
由清华大学五道口金融学院<br />
相关知识产权创设而成
</div>
</div> </div>
<ul class="app-header-right"> <ul class="app-header-right">
<li> <li>
...@@ -25,10 +29,19 @@ ...@@ -25,10 +29,19 @@
padding: 0.44rem 0 0.4rem; padding: 0.44rem 0 0.4rem;
} }
.app-header__logo { .app-header__logo {
height: 0.51rem; display: flex;
height: 0.5rem;
img { img {
height: 100%; height: 100%;
} }
.app-header_text {
border-left: 1px solid #707070;
padding-left: 0.15rem;
margin-left: 0.15rem;
font-size: 0.18rem;
color: #000000;
line-height: 0.22rem;
}
} }
.app-header-right { .app-header-right {
display: flex; display: flex;
......
<script setup lang="ts"> <script setup lang="ts">
import AppHeader from './Header.vue' import AppHeader from './Header.vue'
import Footer from './Footer.vue'
const route = useRoute()
console.log(route.path, 'route')
</script> </script>
<template> <template>
<div class="app-layout"> <div class="app-layout">
<AppHeader /> <AppHeader v-if="route.path === '/'" />
<RouterView /> <RouterView />
</div> </div>
<Footer v-if="route.path === '/'" />
</template> </template>
<style lang="scss"> <style lang="scss">
...@@ -15,7 +20,7 @@ import AppHeader from './Header.vue' ...@@ -15,7 +20,7 @@ import AppHeader from './Header.vue'
max-width: 750px; max-width: 750px;
margin: 0 auto; margin: 0 auto;
// background: #f3f4f8 url(https://webapp-pub.ezijing.com/project/prp-h5/bg.png) no-repeat; // background: #f3f4f8 url(https://webapp-pub.ezijing.com/project/prp-h5/bg.png) no-repeat;
background: #f7f7f7; background: #f8f8f8;
background-size: 100% auto; background-size: 100% auto;
padding: 0 0.3rem; padding: 0 0.3rem;
box-sizing: border-box; box-sizing: border-box;
......
...@@ -4,6 +4,7 @@ import App from './App.vue' ...@@ -4,6 +4,7 @@ import App from './App.vue'
import router from './router' import router from './router'
import Vant from 'vant' import Vant from 'vant'
import 'vant/lib/index.css' import 'vant/lib/index.css'
// 公共组件 // 公共组件
import AppCard from '@/components/base/AppCard.vue' import AppCard from '@/components/base/AppCard.vue'
import AppContainer from '@/components/base/AppContainer.vue' import AppContainer from '@/components/base/AppContainer.vue'
......
import httpRequest from '@/utils/axios'
// 获取文档数据
export function getDocView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/admission/doc-view', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admission',
component: AppLayout,
children: [{ path: 'doc/:id', component: () => import('./views/DocView.vue'), props: true }]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const route = useRoute()
const data = ref()
function fetchDocView() {
getDocView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
console.log(route.query, '111')
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
import httpRequest from '@/utils/axios'
// 获取消息列表
export function getCourseDetail(params: { id: string }) {
return httpRequest.get('/api/psp/v2/learning/course-view', { params })
}
// 获取视频
export function getVideo(params: { resource_id: string, course_id: string, chapter_id: string }) {
return httpRequest.get('/api/psp/v2/learning/video-streaming', { params })
}
// 上传视频进度
export function uploadVideo(params: any) {
return httpRequest.get('/api/psp/v2/learning/upload-video', { params })
}
<script setup lang="ts">
const props = defineProps<{ data: any; isFree: string }>()
const router = useRouter()
const route = useRoute()
const handleClickItem = function (item: any) {
item.show = !item.show
}
const secondsToHms = function (d: any) {
d = Number(d)
const h = Math.floor(d / 3600)
const m = Math.floor((d % 3600) / 60)
const s = Math.floor((d % 3600) % 60)
let t = ''
if (h <= 0) {
t = `${m}${s}秒`
} else {
t = `${h}${m}${s}秒`
}
return t
}
const handleChapter = function (item: any, cItem: any) {
if (route.path === '/course/detail') {
router.push(`/course/chapter?id=${route.query?.id}&chapterId=${item.id}`)
} else {
if (props.isFree !== '收费') {
if (parseInt(cItem.type) === 2) {
router.push(
`/course/player?id=${route.query?.id}&chapterId=${cItem.id}&rId=${cItem.resource_id}&&zId=${item.id}`
)
} else {
if (cItem.status === 1) return
router.push(`/exam?id=${route.query?.id}&cId=${cItem.id}`)
}
}
}
}
</script>
<template>
<div class="chapter-box" v-if="props.data?.length">
<div class="collapse" v-for="item in props.data" :key="item.id">
<div class="h2" @click="handleClickItem(item)">
<div class="t">{{ item.name }}</div>
<van-icon name="arrow" v-if="!item.show" />
<van-icon name="arrow-down" v-else />
</div>
<div class="sections" v-if="item.show">
<div class="item" v-for="cItem in item.children" @click="handleChapter(item, cItem)" :key="cItem.id">
<div class="t">{{ cItem.name }}</div>
<div class="learn-info">
<div
class="v-time"
v-if="
parseInt(cItem.type) === 2 && ($route.path === '/course/chapter' || $route.path === '/course/player')
"
>
{{ secondsToHms(cItem.video_length) }}
</div>
<template
v-if="props.isFree !== '收费' && ($route.path === '/course/chapter' || $route.path === '/course/player')"
>
<template v-if="parseInt(cItem.type) === 2">
<div class="btn btn-s1" v-if="parseInt(cItem.progress) === 0">未开始</div>
<div class="btn btn-s2" v-if="parseInt(cItem.progress) !== 0 && parseInt(cItem.progress) !== 100">
进行中
</div>
<div class="btn btn-s3" v-if="parseInt(cItem.progress) === 100">已完成</div>
</template>
<template v-else>
<div class="btn btn-s1" v-if="parseInt(cItem.status) === 0">未开始</div>
<div class="btn btn-s2" v-if="parseInt(cItem.status) === 1">已提交</div>
<div class="btn btn-s3" v-if="parseInt(cItem.status) === 2">已完成</div>
</template>
</template>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.chapter-box {
border-radius: 0.15rem;
padding: 0.25rem 0.2rem;
border-top: 0.06rem solid #fff5e1;
.collapse {
margin-bottom: 0.2rem;
.h2 {
background: #f5f8fb;
border-radius: 0.1rem;
padding: 0.17rem 0.07rem 0.17rem 0.18rem;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.t {
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 6rem;
}
}
.sections {
.item {
padding: 0 0.3rem;
border-bottom: 0.01rem solid #f4f4f4;
.t {
font-size: 0.22rem;
color: #666666;
line-height: 0.6rem;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 5.5rem;
}
.learn-info {
display: flex;
justify-content: right;
align-items: center;
margin-bottom: 0.15rem;
.v-time {
font-size: 0.2rem;
color: #acacac;
margin-right: 0.1rem;
}
.btn {
font-size: 0.18rem;
line-height: 100%;
text-align: center;
font-style: normal;
text-transform: none;
padding: 0.03rem 0.13rem;
border-radius: 44px 44px 44px 44px;
}
.btn-s1 {
color: #acacac;
border: 0.01rem solid #acacac;
}
.btn-s2 {
color: #1847a0;
border: 0.01rem solid #1847a0;
}
.btn-s3 {
color: #aa1941;
border: 0.01rem solid #aa1941;
}
}
}
}
}
}
</style>
...@@ -5,6 +5,11 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -5,6 +5,11 @@ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/course', path: '/course',
component: AppLayout, component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }] children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'detail', component: () => import('./views/Detail.vue') },
{ path: 'chapter', component: () => import('./views/Chapter.vue') },
{ path: 'player', component: () => import('./views/Player.vue') }
]
} }
] ]
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail } from '../api'
import CourseCatalog from '../components/CourseCatalog.vue'
const route = useRoute()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
if (item.id === route.query.chapterId) {
item.show = true
} else {
item.show = false
}
return item
})
}
data.course = d
})
</script>
<template>
<AppContainer :title="data.course.category_name" headerAlign="center"></AppContainer>
<img :src="data.course.course_picture" class="banner" />
<div class="content">
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free_name"
></CourseCatalog>
<div class="all-btn">
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail } from '../api'
import CourseCatalog from '../components/CourseCatalog.vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
item.show = false
return item
})
}
data.course = d
})
const handleStudy = function () {
const videoId = data.course.last_play_video
let query = {
cId: '',
rId: '',
zId: ''
}
data.course.chapters.forEach((item: any) => {
const findItem = item.children.find((cItem: any) => cItem.resource_id === videoId)
query = {
cId: findItem?.id,
rId: findItem?.resource_id,
zId: item.id
}
})
if (videoId === '') {
router.push(`/course/chapter?id=${route.query?.id}&chapterId=${data.course?.id}`)
} else {
router.push(`/course/player?id=${route.query?.id}&chapterId=${query.cId}&rId=${query.rId}&&zId=${query.zId}`)
}
}
</script>
<template>
<AppContainer :title="data.course.category_name" headerAlign="center"></AppContainer>
<img :src="data.course.course_picture" class="banner" />
<div class="content">
<h2>课程推荐</h2>
<div class="course-tj" v-html="data.course?.course_represent"></div>
<h2>讲师介绍</h2>
<div class="lecturer">
<div class="lecturer-li" v-for="item in data.course?.course_lectures" :key="item.id">
<div class="info">
<img :src="item.lecturer_avatar" />
<div class="name">{{ item.lecturer_name }}</div>
</div>
<div class="li" v-if="item.lecturer_office !== ''">{{ item.lecturer_office }}</div>
<div class="li" v-if="item.lecturer_title !== ''">{{ item.lecturer_title }}</div>
</div>
</div>
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free"
:isBtn="false"
></CourseCatalog>
<div class="all-btn">
<!-- 没购买 -->
<div class="btn-s1" v-if="data.course?.is_free_name === '收费'">
<div class="price">
课程价格<span></span>
<p>1999.00</p>
</div>
<div class="btn">立即购买</div>
</div>
<!-- 已购买没考试 -->
<div class="btn-s2" v-else @click="handleStudy">立即学习</div>
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getRecommendCourse } from '@/api/base' import { getRecommendCourse } from '@/api/base'
import type { IRecommendCourse } from '@/types'
import num from '@/utils/numTo' const router = useRouter()
interface IRecommendCourseAllList { interface IRecommendCourseAllList {
loading: boolean loading: boolean
page: number page: number
total: number total: number
list: IRecommendCourse[] list: any[]
} }
const categoryType = ref('')
const hasMore = ref(false) const hasMore = ref(false)
const courseList = reactive<IRecommendCourseAllList>({ loading: false, page: 1, total: 0, list: [] }) const courseList = reactive<IRecommendCourseAllList>({ loading: false, page: 1, total: 0, list: [] })
const handleGetCourseList = () => { const handleGetCourseList = () => {
const params: any = { page_size: 20, page: courseList.page } courseList.list = []
const params: any = { page_size: 100, page: courseList.page, category: categoryType.value }
courseList.loading = true courseList.loading = true
getRecommendCourse(params) getRecommendCourse(params)
.then(res => { .then(res => {
...@@ -47,32 +52,44 @@ onMounted(() => { ...@@ -47,32 +52,44 @@ onMounted(() => {
handleGetCourseList() handleGetCourseList()
}) })
const handleClickItem = (item: any) => { const handleClickItem = (item: any) => {
if (item.url) { router.push(`/course/detail?id=${item.id}`)
location.href = item.url }
}
const handleCategory = function (n: string) {
categoryType.value = n
handleGetCourseList()
} }
</script> </script>
<template> <template>
<a href="https://fi.ezijing.com/shop" target="_blank"> <AppContainer title="课程列表" headerAlign="center"></AppContainer>
<img src="https://webapp-pub.ezijing.com/project/prp-h5/course_banner.png" style="width: 100%" /> <div class="screen">
</a> <div :class="categoryType === '' ? 'tag active' : 'tag'" @click="handleCategory('')">全部</div>
<AppCard title="推荐课程"> <div class="tag-group">
<div ref="el"> <div :class="categoryType === '1' ? 'tag active' : 'tag'" @click="handleCategory('1')">财富管理基础知识</div>
<div class="list_item" v-for="(item, index) in courseList.list" :key="index" @click="handleClickItem(item)"> <div :class="categoryType === '2' ? 'tag active' : 'tag'" @click="handleCategory('2')">金融投资工具</div>
<img :src="item.cover" class="item_img" /> <div :class="categoryType === '3' ? 'tag active' : 'tag'" @click="handleCategory('3')">财富管理实务应用</div>
<div class="item_right"> </div>
<div class="right_tit">{{ item.name }}</div> </div>
<div class="right_bottom"> <div ref="el" v-if="courseList.list?.length">
<div class="views">{{ num(item.pv) }}播放</div> <div class="list_item" v-for="(item, index) in courseList.list" :key="index" @click="handleClickItem(item)">
<div class="is_free" :class="item.is_free === '1' ? 'free' : 'unfree'"> <img :src="item.course_picture" class="item_img" />
{{ item.is_free_name }} <div class="item_right">
</div> <div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom">
<div class="views">已学习{{ item.times }}</div>
<div
class="is_free"
:class="item.is_free_name === '免费' ? 'free' : item.is_free_name === '已购买' ? 'c' : 'unfree'"
>
{{ item.is_free_name }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</AppCard> </div>
<van-empty description="无数据" image-size="100" v-else />
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -95,7 +112,7 @@ img { ...@@ -95,7 +112,7 @@ img {
} }
.item_right { .item_right {
margin-left: 0.21rem; margin-left: 0.21rem;
padding-top: 0.17rem; // padding-top: 0.17rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
...@@ -107,7 +124,7 @@ img { ...@@ -107,7 +124,7 @@ img {
color: #333333; color: #333333;
} }
.right_bottom { .right_bottom {
margin-top: 0.21rem; // margin-top: 0.21rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
...@@ -130,7 +147,54 @@ img { ...@@ -130,7 +147,54 @@ img {
.unfree { .unfree {
color: #e9a724; color: #e9a724;
} }
.c {
color: #1847a0;
}
}
}
}
.el {
background-color: #fff;
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
.screen {
margin-bottom: 0.4rem;
.tag {
font-size: 0.24rem;
color: #000000;
line-height: 100%;
padding: 0.08rem 0.2rem;
background: #ffffff;
border-radius: 0.24rem;
border: 0.01rem solid #acacac;
width: fit-content;
&.active {
color: #aa1941;
border: 0.01rem solid #aa1941;
} }
} }
.tag-group {
display: flex;
justify-content: space-between;
margin-top: 0.2rem;
}
} }
</style> </style>
<script setup lang="ts">
import AppContainer from '@/components/base/AppContainer.vue'
import { getCourseDetail, getVideo, uploadVideo } from '../api'
import { getUser } from '@/api/base'
import CourseCatalog from '../components/CourseCatalog.vue'
import Player, { Events } from 'xgplayer'
import 'xgplayer/dist/index.min.css'
const route = useRoute()
const router = useRouter()
let data: any = reactive({
course: {}
})
getCourseDetail({ id: route.query?.id as string }).then((res: any) => {
const d = res.data.course
if (d.chapters?.length) {
d.chapters = d.chapters.map((item: any) => {
console.log(item.id, route.query.chapterId, 'item.id === route.query.chapterId')
if (item.id === route.query.zId) {
item.show = true
} else {
item.show = false
}
return item
})
}
data.course = d
})
// 提交视频
const params = reactive({
c: route.query.id,
v: route.query.rId,
uid: '',
_m: 0,
_c: 0,
_p: 0,
ps: ''
})
// 播放器
let m = 0
let player = null
let ps = [0, 0]
onMounted(() => {
getVideo({
resource_id: route.query?.rId as string,
course_id: route.query?.id as string,
chapter_id: route.query?.chapterId as string
}).then((res: any) => {
// videoSrc.value = res.data.info?.FD
player = new Player({
id: 'mse',
url: res.data.info?.FD,
height: '200px',
width: '100%',
startTime: res.data.cache?.cpt || 0
})
player.on(Events.TIME_UPDATE, ev => {
m++
// 最大学习时刻
params._m = Math.max(ev.currentTime, params._m)
params._c = ev.currentTime
params._p = ev.currentTime
if (!ps.includes(parseInt(ev.currentTime))) {
ps.push(parseInt(ev.currentTime))
}
if (m % 20 === 0) {
// console.log('-播放开始-', ev)
params.ps = ps.join(',')
uploadVideo(params)
// console.log(params)
ps = []
}
})
})
})
// 缓存
function getUserInfo() {
getUser().then((res: any) => {
params.uid = res.data.info?.sso_id
})
}
getUserInfo()
watch(
() => route.query.chapterId,
newVal => {
router.go(0)
}
)
</script>
<template>
<AppContainer title="课程列表" headerAlign="center"></AppContainer>
<div id="mse"></div>
<!-- <img :src="data.course.course_picture" class="banner" /> -->
<div class="content">
<h2>课程目录</h2>
<CourseCatalog
v-if="data.course?.chapters && data.course?.chapters.length"
:data="data.course.chapters"
:isFree="data.course.is_free_name"
></CourseCatalog>
<div class="all-btn">
<!-- 已考试 -->
<!-- <div class="btn-s2">查看课程成绩</div> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.banner {
width: 100%;
display: block;
}
.content {
background: #ffffff;
border-radius: 0.12rem;
margin-top: 0.54rem;
padding: 0 0.2rem;
box-sizing: border-box;
margin-bottom: 1.7rem;
h2 {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 0.78rem;
}
.course-tj {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.3rem 0.2rem;
box-sizing: border-box;
font-size: 0.24rem;
color: #666666;
line-height: 0.4rem;
text-align: left;
font-style: normal;
text-transform: none;
}
.lecturer {
background: #f4f8fb;
border-radius: 0.16rem;
padding: 0.27rem 0.2rem 0.37rem;
box-sizing: border-box;
.lecturer-li {
margin-top: 0.5rem;
&:nth-child(1) {
margin-top: 0;
}
}
.info {
display: flex;
align-items: center;
img {
width: 0.86rem;
height: 0.86rem;
border-radius: 50%;
object-fit: cover;
}
.name {
font-weight: bold;
font-size: 0.28rem;
color: #333333;
line-height: 100%;
margin-left: 0.36rem;
}
}
.li {
font-size: 0.24rem;
color: #666666;
// line-height: 100%;
padding-left: 0.2rem;
position: relative;
margin-top: 0.18rem;
&::after {
content: '';
position: absolute;
top: 50%;
left: 0;
margin-top: -0.04rem;
width: 0.08rem;
height: 0.08rem;
background-color: #c1ab85;
border-radius: 50%;
}
}
}
}
.all-btn {
.btn-s1 {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 1.2rem;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 0.3rem;
box-sizing: border-box;
align-items: center;
box-shadow: 0 10px 10px 5px #000;
.price {
display: flex;
font-size: 0.24rem;
color: #333333;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
align-items: center;
span {
font-weight: 500;
font-size: 0.24rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
p {
font-size: 0.4rem;
color: #aa1941;
line-height: 100%;
text-align: left;
font-style: normal;
text-transform: none;
}
}
.btn {
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
}
}
.btn-s2 {
position: fixed;
left: 50%;
bottom: 0.46rem;
transform: translateX(-50%);
width: 4rem;
line-height: 0.8rem;
background: #aa1941;
border-radius: 0.4rem;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
text-align: center;
box-shadow: 0 0 5px 1px rgba(170, 25, 65, 0.5);
}
}
</style>
import httpRequest from '@/utils/axios' import httpRequest from '@/utils/axios'
// 获取消息列表 // 获取考卷
export function getExamList(params?: { type: number }) { export function getExam(params: any) {
return httpRequest.get('/api/psp/v1/exam/list', { params }) return httpRequest.get('/api/psp/v2/learning/get-question', { params })
} }
// 获取文档数据···· // 提交
export function chooseExam(data: { export function submitQuestion(data: { id: string; type: string; answers: string; status: string }) {
exam_id: string return httpRequest.post('/api/psp/v2/learning/submit-question', data)
id_number: string
address: string
company: string
position: string
need_receive: string
}) {
return httpRequest.post('/api/psp/v1/exam/choose', data)
} }
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import { getExam, submitQuestion } from '../api'
import { getExamList } from '../api' import { Dialog } from 'vant'
import type { ExamType } from '../types'
import FormDialog from '../components/FormDialog.vue'
interface Info {
address: string
can_choose: boolean
id_number: string
list: ExamType[]
}
const route = useRoute()
const router = useRouter() const router = useRouter()
const tabActive = $ref<number>(2) // 当前题号
let currentIndex = $ref(0)
// 请求全部数据
let data: any = $ref()
// 题目数据
let questions: any = $ref()
getExam({ type: 'chapter', course_id: route.query?.id, chapter_id: route.query?.cId }).then((res: any) => {
data = res.data
if (res.data?.questions || res.data?.questions !== '') {
questions = changeData(JSON.parse(res.data.questions).question_items)
}
})
const dataset = reactive<Info>({ address: '', can_choose: true, id_number: '', list: [] }) // 重构题目数据
// 日期转换 const changeData = function (data: any) {
function formatDate(startTime: string, endTime: string) { const qTypeCnName: any = {
return dayjs(startTime).format('YYYY年MM月DD日 HH:mm') + '-' + dayjs(endTime).format('HH:mm') '1': '单选',
} '2': '多选',
// 获取列表 '6': '判断'
const fetchList = () => { }
getExamList({ type: tabActive }).then(res => { return data.map((item: any) => {
Object.assign(dataset, res.data) item.question_item_typeName = qTypeCnName[item.question_item_type]
if (item.question_list?.length) {
item.question_list = item.question_list.map((qList: any) => {
if (qList?.options || qList.options !== '') {
// 答案转成对象
qList.options = JSON.parse(qList.options)
// 单选v-m和多选不同
const t = parseInt(item.question_item_type)
qList.answer = t === 1 || t === 6 ? '' : []
}
return qList
})
}
return item
}) })
} }
onMounted(() => {
fetchList() // 选出当前题
const currentQ = $computed(() => {
return questions?.length ? questions[currentIndex] : []
}) })
let dialogShow = $ref<boolean>(false) // 切换题
let activeExam = $ref<ExamType>() const changeQ = function (n: string) {
function handleClick(data: ExamType) { if (n === 'prev') {
if (data.choose) { currentIndex !== 0 && currentIndex--
router.push(`/exam/${data.exam_id}`) } else {
currentIndex + 2 <= questions.length && currentIndex++
} }
if (!dataset.can_choose) return }
activeExam = data
dialogShow = true // 进页面的提示
Dialog.alert({
title: '重要提示',
message:
'一旦开始考试,您将不能再次进行考试,请确保考试期间不要关闭或退出本页面,否则将会导致本次考试异常结束,影响您的课程成绩!'
}).then(() => {
// on close
})
// 左上角返回按钮
const handleBack = function () {
Dialog.confirm({
title: '重要提示',
message: '您尚未完成本次考试,回退操作将会导致本次考试异常结束,影响您的课程成绩!',
confirmButtonText: '结束考试'
})
.then(() => {
// end
router.go(-1)
})
.catch(() => {
// on cancel
})
}
// 倒计时结束
const finish = function () {
Dialog.alert({
title: '重要提示',
message: '考试结束,已自动提交试卷!'
}).then(() => {
// end
router.go(-1)
})
}
// 提交
const submit = function () {
const params: any = {}
questions.forEach((item: any) => {
item.question_list.forEach((cItem: any) => {
params[item.question_item_id] = {
[cItem.id]: {
answer: Array.isArray(cItem.answer) ? cItem.answer : [cItem.answer]
}
}
})
})
submitQuestion({ id: data.id, type: 'chapter', answers: JSON.stringify(params), status: '1' }).then(res => {
router.go(-1)
})
} }
</script> </script>
<template> <template>
<AppContainer title="考试系统"> <AppContainer title="考试系统" headerAlign="center" @back="handleBack" type="1">
<van-tabs <div class="exam-t">
v-model:active="tabActive" <van-count-down @finish="finish" :time="data?.end_time * 1000" />
shrink <div class="count">{{ currentIndex + 1 }}/{{ questions?.length }}</div>
background="transparent" </div>
title-active-color="#033974" <div class="q" v-for="item in currentQ?.question_list" :key="item.id">
title-inactive-color="#4E4E4E" <!-- <van-radio-group v-model="checked"> -->
line-height="0" <div class="question">
@change="fetchList" <van-tag type="primary">{{ currentQ?.question_item_typeName }}</van-tag>
> <div class="t">{{ item?.content }}</div>
<van-tab title="已选" :name="2"></van-tab> </div>
<van-tab title="未选" :name="1"> <!-- 单选 -->
<p class="tips">请选择以下一场考试。每人一次正式考试机会,一次补考机会。</p></van-tab <van-radio-group
v-model="item.answer"
v-if="currentQ.question_item_type === '1' || currentQ.question_item_type === '6'"
> >
</van-tabs> <van-cell-group inset>
<div class="exam-list" v-if="dataset.list.length"> <van-cell :title="opt?.option" clickable v-for="opt in item?.options" :key="opt.id">
<div class="exam-item" v-for="item in dataset.list" :key="item.exam_id" @click="handleClick(item)"> <template #right-icon>
<h3 class="exam-item__name">{{ item.name }}</h3> <van-radio :name="opt?.id" />
<p class="exam-item__time">考试时间:{{ formatDate(item.start_time, item.end_time) }}</p> </template>
</van-cell>
</van-cell-group>
</van-radio-group>
<!-- 多选 -->
<van-checkbox-group v-model="item.answer" v-else>
<van-cell-group inset>
<van-cell v-for="opt in item?.options" clickable :key="opt.id" :title="opt?.option">
<template #right-icon>
<van-checkbox checked-color="#aa1941" :name="opt.id" @click.stop shape="square" />
</template>
</van-cell>
</van-cell-group>
</van-checkbox-group>
<div class="btn-box">
<van-button type="primary" @click="changeQ('prev')">上一题</van-button>
<van-button type="primary" style="margin-left: 0.1rem" @click="changeQ('next')">下一题</van-button>
</div> </div>
</div> </div>
<van-empty description="暂无数据" v-else />
</AppContainer> </AppContainer>
<FormDialog :data="activeExam" v-model:show="dialogShow" @update="fetchList"></FormDialog> <van-button type="primary" class="submit" @click="submit">交卷</van-button>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.van-tabs__nav) { .btn-box {
padding: 0 !important; padding-top: 1rem;
} display: flex;
:deep(.van-tab) { justify-content: space-around;
padding: 0 15px; flex: 1;
} button {
:deep(.van-tab + .van-tab) { width: 100%;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
width: 1px;
height: 12px;
background-color: #fff;
} }
} }
.tips { .submit {
color: #033974; position: fixed;
font-size: 0.24rem; bottom: 0;
font-weight: 400; left: 0;
padding: 0.2rem 0; width: 100%;
text-align: center; background-color: #aa1941;
} border-color: #aa1941;
.exam-item {
padding: 0.3rem 0.45rem;
background-color: #fff;
border-radius: 0.2rem;
margin-bottom: 0.2rem;
cursor: pointer;
} }
.exam-item-hd { .exam-t {
height: 0.7rem;
display: flex; display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 0.1rem; align-items: center;
line-height: 0.4rem; padding-top: 0.2rem;
border-bottom: 1px solid #ccc;
} }
.exam-item__name { :deep(.van-cell-group) {
font-size: 0.28rem; margin: 0;
font-weight: 400; background: none;
color: #333;
} }
.exam-item__time { :deep(.van-cell) {
margin-top: 0.07rem; background: none;
font-size: 0.24rem; }
color: #999; :deep(.van-tag) {
height: fit-content;
}
:deep(.van-radio__icon--checked .van-icon) {
background-color: #aa1941;
border-color: #aa1941;
}
:deep(.van-tag--primary) {
background: #aa1941;
}
:deep(.van-button--primary) {
background: #aa1941;
border-color: #aa1941;
}
.question {
display: flex;
margin: 0.3rem 0;
.t {
font-size: 0.3rem;
color: #000;
width: 5.8rem;
margin-left: 0.2rem;
line-height: 0.36rem;
}
} }
</style> </style>
...@@ -2,7 +2,7 @@ import httpRequest from '@/utils/axios' ...@@ -2,7 +2,7 @@ import httpRequest from '@/utils/axios'
// 获取首页数据 // 获取首页数据
export function getHomeData() { export function getHomeData() {
return httpRequest.get('/api/psp/v1/index/index') return httpRequest.get('/api/psp/v2/index/index')
} }
// 获取导学视频列表 // 获取导学视频列表
export function getVideoList(params?: { page_size?: number; page?: number }) { export function getVideoList(params?: { page_size?: number; page?: number }) {
......
<script setup lang="ts">
import { useRouter } from 'vue-router'
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'
defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>()
const router = useRouter()
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/admission/doc/' + data.id)
}
}
function showTips() {
Notify({ type: 'primary', message: '尚未开放' })
}
</script>
<template>
<AppCard title="入学指南" id="admission">
<div class="admission">
<div class="admission-left">
<h2>解释文档</h2>
<div class="box">
<ul>
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
</div>
</div>
<div class="admission-right">
<div class="box box-test">
<h2>入学评测</h2>
<p class="t1">系统专业知识</p>
<p class="t2"><a @click="showTips">去看看 ></a></p>
</div>
<div class="box box-notice">
<h2>入学通知书</h2>
<p class="t1">报名之后即可查询</p>
<p class="t2"><a href="https://prp.ezijing.com/personal">去看看 ></a></p>
</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>
</template>
<style lang="scss" scoped>
.admission {
display: flex;
justify-content: space-between;
}
.admission-left {
padding: 0.1rem;
width: 3.33rem;
margin-right: 0.2rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/admission_bg.png) no-repeat;
background-size: contain;
box-sizing: border-box;
h2 {
margin: 0.1rem 0.1rem 0.14rem;
font-size: 0.26rem;
font-weight: 500;
line-height: 1.4;
color: #fff;
}
.box {
width: 3.13rem;
height: 3.43rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
background-size: 100% auto;
border-radius: 0.1rem;
ul {
overflow: hidden;
}
li {
margin: 0.3rem 0.14rem 0;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
}
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;
}
}
}
.admission-right {
flex: 1;
.box {
padding: 0.22rem 0.2rem;
// width: 3.33rem;
height: 2.01rem;
background: #fff;
border-radius: 0.2rem;
box-sizing: border-box;
h2 {
font-size: 0.26rem;
line-height: 1.4;
font-weight: 600;
color: #333;
}
.t1 {
margin-top: 0.14rem;
font-size: 0.22rem;
font-weight: 400;
color: #666666;
}
.t2 {
margin-top: 0.35rem;
font-size: 0.22rem;
font-weight: 400;
color: #033974;
}
}
.box + .box {
margin-top: 0.2rem;
}
.box-test {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/admission_1.png) no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
.box-notice {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/admission_2.png) no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
}
.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: 0.26rem;
font-weight: 600;
color: #333333;
}
}
.video-swiper-slide {
width: 2rem;
}
</style>
...@@ -10,7 +10,7 @@ defineProps<{ list: IBanner[] }>() ...@@ -10,7 +10,7 @@ defineProps<{ list: IBanner[] }>()
function handleClick(item: IBanner) { function handleClick(item: IBanner) {
// 外链 // 外链
if (item.type === '2') { if (item.type === '2') {
location.href = item.url // location.href = item.url
} }
} }
</script> </script>
......
<script setup lang="ts">
// import { useRouter } from 'vue-router'
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'
// defineProps<{ docs: IDocItem[]; videos: IVideoItem[] }>()
// const router = useRouter()
const docs = [
{
title: 'PRP认证培训',
url: 'https://prp.ezijing.com/'
},
{
title: 'PAA系列认证',
url: 'https://paa.ezijing.com/'
},
{
title: '新型人才培育',
url: ''
},
{
title: '更多FI项目',
url: 'https://fi.ezijing.com/'
}
]
function handleViewDoc(data: any) {
// window.open(data.url)
if (data.url) {
location.href = data.url
} else {
Notify({ type: 'primary', message: '尚未开放' })
}
}
// function showTips() {
// Notify({ type: 'primary', message: '尚未开放' })
// }
</script>
<template>
<AppCard id="admission">
<div class="admission">
<div class="admission-right">
<div class="box box-test">
<h2>免费课程</h2>
<p class="t1">实战精华等你来看</p>
<p class="t2"><a href="https://fi.ezijing.com/shop/?activeIndex=2&type=free_course">去看看 ></a></p>
</div>
<div class="box box-notice">
<h2>系统小课</h2>
<p class="t1">专业思维 赢战未来</p>
<p class="t2"><a href="https://fi.ezijing.com/shop/?activeIndex=3&type=system_course">去看看 ></a></p>
</div>
</div>
<div class="admission-left">
<h2>认证课程包</h2>
<!-- <div class="box"> -->
<ul>
<li v-for="(item, index) in docs" :key="index" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<!-- <span>{{ item.pv }}</span> -->
<van-icon name="arrow" />
</li>
</ul>
<!-- </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>
</template>
<style lang="scss" scoped>
.admission {
display: flex;
justify-content: space-between;
}
.admission-left {
padding: 0.1rem;
width: 3.33rem;
margin-left: 0.24rem;
background: url(https://webapp-pub.ezijing.com/project/prp-h5/certifation_course_bg.png) no-repeat;
background-size: contain;
box-sizing: border-box;
h2 {
margin: 0.1rem 0.1rem 0.14rem;
font-size: 0.28rem;
font-weight: 500;
line-height: 1.5;
color: #fff;
}
// .box {
// width: 3.13rem;
// height: 3.43rem;
// background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
// background-size: 100% auto;
// border-radius: 0.1rem;
ul {
overflow: hidden;
}
li {
margin: 0.2rem 0.14rem 0;
display: flex;
align-items: center;
font-size: 0.24rem;
color: #333333;
cursor: pointer;
background: #ffffff;
padding: 0.14rem 0.2rem;
border-radius: 0.3rem;
cursor: pointer;
}
p {
flex: 1;
font-size: 0.24rem;
color: #333333;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
// }
.admission-right {
flex: 1;
.box {
padding: 0.22rem 0.2rem;
// width: 3.33rem;
height: 2.01rem;
background: #fff;
border-radius: 0.2rem;
box-sizing: border-box;
cursor: pointer;
h2 {
font-size: 0.28rem;
line-height: 1.5;
font-weight: 500;
color: #333;
}
.t1 {
margin-top: 0.14rem;
font-size: 0.24rem;
font-weight: 400;
color: #666666;
}
.t2 {
margin-top: 0.35rem;
font-size: 0.24rem;
font-weight: 400;
color: #e6a522;
}
}
.box + .box {
margin-top: 0.2rem;
}
.box-test {
background: #ffffff url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/free_course_logo.png)
no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
.box-notice {
background: #fff5e3 url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/system_course_logo.png)
no-repeat right bottom;
background-size: 1.7rem 1.36rem;
}
}
</style>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import type { IDocItem } from '../types'
defineProps<{ docs: IDocItem[] }>()
const router = useRouter()
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/learn/doc/' + data.id)
}
}
</script>
<template>
<AppCard title="考试攻略" id="exam">
<div class="exam">
<div class="box box-doc">
<h2>解释文档</h2>
<ul v-if="docs.length">
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
<van-empty description="暂无内容" v-else />
</div>
<div class="box box-exam">
<h2>考试系统</h2>
<router-link to="/exam" class="box-exam-content">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/exam_bg.png" />
<p class="t2">去看看</p>
</router-link>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.exam {
display: flex;
justify-content: space-between;
}
.box {
padding: 0.2rem;
width: 3.33rem;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
h2 {
margin-bottom: 0.15rem;
font-size: 0.26rem;
font-weight: 600;
color: #4e4e4e;
}
}
.box-doc {
height: 3.43rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat center bottom;
background-size: 100% auto;
ul {
overflow: hidden;
}
li {
margin: 0.3rem 0 0;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
}
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;
}
}
.box-exam {
.t2 {
display: inline-block;
padding: 0 0.2rem;
margin-top: 0.35rem;
font-size: 0.22rem;
line-height: 0.4rem;
color: #ffffff;
background-color: #033974;
}
}
.box-exam-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 1.61rem;
}
}
</style>
<script setup lang="ts"></script>
<template>
<div class="output_main">
<AppCard title="知识输出者" id="team">
<template #header-aside>
<div class="more">
<router-link to="/qa">知识IP践行 <van-icon name="arrow" /></router-link>
</div>
</template>
<div class="output_banner">
<a href="https://mp.weixin.qq.com/s/wUOtrmOttyNCyIecswqC4w" target="_blank">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/output_banner.png" alt="" />
</a>
</div>
</AppCard>
</div>
<div class="input_main">
<AppCard title="知识输入者" id="team">
<template #header-aside>
<div class="more">
<router-link to="/learn/course">如何成为知识获得者 <van-icon name="arrow" /></router-link>
</div>
</template>
<div class="output_banner">
<a href="https://mp.weixin.qq.com/s/fDf4NpPZH4BNI_KEopiSwQ" target="_blank">
<img src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/input_banner.png" alt="" />
</a>
</div>
</AppCard>
</div>
</template>
<style lang="scss" scoped>
.output_main {
.app-card {
background: url('https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/output_bg.png') no-repeat;
background-size: 100% 100%;
}
.app-card-hd {
margin-top: 0.2rem !important;
}
.more {
font-size: 0.24rem;
font-weight: 500;
color: #f39929;
}
.output_banner {
padding: 0.06rem;
img {
width: 100%;
-webkit-filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1)); /*考虑浏览器兼容性:兼容 Chrome, Safari, Opera */
filter: drop-shadow(0px 5px 2px rgba(0, 0, 0, 0.1));
}
}
}
.input_main {
.app-card {
background: url('https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/input_bg.png') no-repeat;
background-size: 100% 100%;
}
.more {
font-size: 0.24rem;
font-weight: 500;
color: #bc2d29;
}
.output_banner {
padding: 0.06rem;
img {
width: 100%;
-webkit-filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1)); /*考虑浏览器兼容性:兼容 Chrome, Safari, Opera */
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.1));
}
}
}
</style>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { Notify } from 'vant'
import type { IDocItem } from '../types'
import LearningMapVideo from './LearningMapVideo.vue'
import LearningMapCourse from './LearningMapCourse.vue'
defineProps<{ docs: IDocItem[] }>()
const router = useRouter()
const active = ref<number>(0)
function handleViewDoc(data: IDocItem) {
if (data.desc_type === '2') {
location.href = data.url
} else {
router.push('/learn/doc/' + data.id)
}
}
function showTips() {
Notify({ type: 'primary', message: '尚未开放' })
}
</script>
<template>
<AppCard title="学习地图" id="learning">
<!-- <template #header-aside>
<div class="button"><a href="https://mp.weixin.qq.com/s/EdS6wpcdL0IEMK11WQ1Oyg" target="_blank">去学习</a></div>
</template> -->
<van-tabs
v-model:active="active"
shrink
background="transparent"
title-active-color="#E9A724"
title-inactive-color="#4E4E4E"
>
<van-tab title="课程导学">
<div class="learn-box">
<LearningMapVideo></LearningMapVideo>
</div>
</van-tab>
<van-tab title="学习进度">
<div class="learn-box learn-course">
<LearningMapCourse></LearningMapCourse>
</div>
</van-tab>
<van-tab title="解释文档" v-if="false">
<div class="learn-box learn-docs">
<ul>
<li v-for="item in docs" :key="item.id" @click="handleViewDoc(item)">
<p>{{ item.title }}</p>
<span>{{ item.pv }}</span>
<van-icon name="arrow" />
</li>
</ul>
</div>
</van-tab>
<van-tab title="学前测评">
<div class="learn-box learn-test">
<h2>查漏补缺 建立系统概念</h2>
<p>了解PRP学习前系统专业知识的情况</p>
<a class="button" @click="showTips">去测评</a>
</div>
</van-tab>
</van-tabs>
<!-- <div class="learn-banner">
<router-link to="/learn/course">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/learning_map_banner.png" />
</router-link>
</div> -->
</AppCard>
</template>
<style lang="scss" scoped>
.learn-box {
height: 3.1rem;
padding: 0.3rem 0;
background: #fff;
border-radius: 0.2rem;
overflow: hidden;
box-sizing: border-box;
}
// 解释文档
.learn-docs {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_bg.png) no-repeat;
background-size: contain;
ul {
overflow: hidden;
}
li {
position: relative;
display: flex;
align-items: center;
font-size: 0.2rem;
color: #adadad;
cursor: pointer;
&::before {
content: '';
width: 0.08rem;
height: 0.08rem;
background: #033974;
border-radius: 50%;
}
}
p {
margin-left: 0.1rem;
flex: 1;
font-size: 0.24rem;
color: #4e4e4e;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
span {
margin: 0 0.1rem;
min-width: 0.44rem;
font-size: 0.2rem;
color: #c6c6c6;
}
}
.learn-banner {
margin-top: 0.2rem;
margin-left: -0.3rem;
margin-right: -0.3rem;
img {
width: 100%;
}
}
// 学前测评
.learn-test {
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/learning_map_test.png) no-repeat right 0.2rem
bottom 0.5rem;
background-size: 2.08rem;
h2 {
margin-top: 0.2rem;
margin-left: 0.3rem;
font-size: 0.32rem;
font-weight: 500;
line-height: 1.4;
color: #4e4e4e;
}
p {
margin-top: 0.3rem;
margin-left: 0.3rem;
font-size: 0.28rem;
font-weight: 400;
line-height: 1.4;
color: #666666;
}
.button {
margin-top: 0.4rem;
display: block;
height: 0.8rem;
font-size: 0.28rem;
line-height: 0.8rem;
color: #fff;
text-align: center;
background: linear-gradient(90deg, #f7c988 0%, #e5a448 100%);
border-radius: 0.4rem;
}
}
.learn-course {
background-color: #f4e6d3;
padding: 0.3rem 0 0 0.3rem;
margin-top: 0.2rem;
}
:deep(.van-tabs__line) {
background: #e9a724;
width: 55px;
}
:deep(.van-tabs) {
margin-left: -0.05rem;
}
:deep(.van-tabs__wrap) {
margin-left: -0.2rem;
}
.app-card-hd {
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
:deep(.van-tabs__content) {
margin-right: -0.2rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '../types'
import { getCourseList } from '../api'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
})
</script>
<template>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in dataset.list" :key="item.id" class="course-swiper-slide">
<CourseItem :data="item"></CourseItem>
</swiper-slide>
</swiper>
</template>
<style lang="scss" scoped>
.course-swiper-slide {
width: 4.7rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
import VideoItem from '@/components/VideoItem.vue'
import type { IVideoItem } from '../types'
import { getVideoList } from '../api'
// 课程导学
const dataset = ref<{ total: number; list: IVideoItem[] }>({ total: 0, list: [] })
const fetchVideoList = () => {
getVideoList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchVideoList()
})
</script>
<template>
<swiper slides-per-view="auto" :space-between="10">
<swiper-slide v-for="item in dataset.list" :key="item.id" class="video-swiper-slide">
<VideoItem :data="item"></VideoItem>
</swiper-slide>
</swiper>
</template>
<style lang="scss" scoped>
.video-swiper-slide {
width: 2.2rem;
}
</style>
<script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css'
const menus: Array<{
name: string
path: string
icon: string
}> = [
{
path: '#admission',
name: '入学指南',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_1.png'
},
{
path: '#learning',
name: '学习地图',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_2.png'
},
{
path: '#query',
name: '权益查看',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_3.png'
},
{
path: '#team',
name: '荣誉总榜',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_4.png'
},
{
path: '#exam',
name: '考试攻略',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_6.png'
},
{
path: '#qa',
name: '陪伴问答',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/home_menu_5.png'
}
]
</script>
<template>
<nav class="home-nav">
<swiper slides-per-view="auto" :space-between="12">
<swiper-slide v-for="(item, index) in menus" :key="index" class="nav-item">
<a :href="item.path">
<img :src="item.icon" />
<p>{{ item.name }}</p>
</a>
</swiper-slide>
</swiper>
</nav>
</template>
<style lang="scss">
.home-nav {
margin-top: 0.3rem;
text-align: center;
.nav-item {
width: 1.2rem;
}
img {
width: 1.2rem;
height: 1.2rem;
}
p {
font-size: 0.24rem;
}
}
</style>
<script setup lang="ts">
import type { INews } from '../types'
const router = useRouter()
defineProps<{ docs: INews[] }>()
const handleViewNews = (item: INews) => {
if (item.desc_type === '2') {
location.href = item.url
} else {
router.push('/news/doc/' + item.id)
}
}
</script>
<template>
<div>
<van-swipe vertical :autoplay="3000">
<van-swipe-item v-for="(item, index) in docs" :key="index" @click="handleViewNews(item)">
<div class="item_news">
<div class="news_tips"></div>
<div class="news_tit">{{ item.title }}</div>
<div class="news_arrow">
<van-icon name="arrow" />
</div>
</div>
</van-swipe-item>
</van-swipe>
</div>
</template>
<style lang="scss" scoped>
:deep(.van-swipe) {
width: 6.9rem;
height: 0.84rem;
background: #ffffff;
border-radius: 0.16rem;
}
:deep(.van-swipe-item) {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 0.2rem;
cursor: pointer;
box-sizing: border-box;
}
:deep(.van-swipe__indicator) {
display: none;
}
.item_news {
display: flex;
align-items: center;
width: 100%;
.news_tips {
width: 0.33rem;
height: 0.33rem;
background: linear-gradient(313deg, #ef9446 0%, #de3a39 100%);
border-radius: 0.06rem;
font-size: 0.22rem;
font-weight: 400;
line-height: 0.33rem;
color: #ffffff;
text-align: center;
}
.news_tit {
width: 5rem;
margin-left: 0.12rem;
font-size: 0.24rem;
font-weight: 400;
color: #4e4e4e;
line-height: 0.33rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.news_arrow {
margin-left: auto;
padding-right: 0.3rem;
}
}
</style>
<template>
<AppCard title="权益查看" id="query">
<div class="query">
<div class="query-left">
<div class="box">
<router-link to="/query?active=1">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_5.png" />证书查询</h2>
<p class="t1">考试通过后可查看</p>
</router-link>
</div>
<div class="box box1">
<router-link to="/query?active=0">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_6.png" />名片展示</h2>
<p class="t1">持证人专属</p>
</router-link>
</div>
</div>
<div class="query-right">
<div class="box">
<router-link to="/query?active=2">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_7.png" />持证人展示</h2>
<p class="t1">考试通过后可查看</p>
<!-- <p class="t2">去看看</p> -->
</router-link>
</div>
<div class="box box1">
<router-link to="/exam">
<h2><img src="https://webapp-pub.ezijing.com/project/prp-h5/query_icon_8.png" />考试系统</h2>
<p class="t1">持证人专属</p>
</router-link>
</div>
</div>
</div>
</AppCard>
</template>
<style lang="scss" scoped>
.query {
display: flex;
justify-content: space-between;
.box {
padding: 0.2rem;
width: 3.15rem;
height: 1.42rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_2.png) no-repeat;
background-size: contain;
border-radius: 0.2rem;
box-sizing: border-box;
h2 {
display: flex;
align-items: center;
font-size: 0.3rem;
font-weight: 500;
color: #4e4e4e;
}
img {
margin-right: 0.1rem;
width: 0.5rem;
}
.t1 {
font-size: 0.26rem;
color: #666666;
margin-top: 0.12rem;
}
.t2 {
display: inline-block;
padding: 0 0.2rem;
margin-top: 0.35rem;
font-size: 0.22rem;
line-height: 0.4rem;
color: #ffffff;
background-color: #033974;
}
}
.box1 {
margin-top: 0.2rem;
}
.box-avatar {
height: 3.36rem;
background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/query_bg_1.png) no-repeat;
background-size: contain;
}
}
</style>
<script setup lang="ts">
import { Toast } from 'vant'
import PublishItem from '@/components/PublishItem.vue'
import { createQuestionComment } from '../api'
const props = defineProps<{ data: { total: number; list: Record<string, any>[] } }>()
const isMoreClick = ref(false)
const commentList: any = ref([])
// 评论
const onSubmitComment = (data: any, action: string) => {
if (action === 'comment') {
// 评论
createQuestionComment({
question_id: data.id,
content: data.comment
}).then(() => {
Toast.success('评论成功')
})
} else {
// 回复
createQuestionComment({
question_id: data.entity_id,
content: data.comment,
to_comment_id: data.id
}).then(() => {
Toast.success('回复成功')
})
}
}
watchEffect(() => {
if (isMoreClick.value === false) {
commentList.value = props.data.list.slice(0, 2)
} else {
commentList.value = props.data.list
}
})
const handleViewMore = () => {
isMoreClick.value = true
commentList.value = props.data.list
}
</script>
<template>
<AppCard title="陪伴问答" id="qa">
<template #header-aside>
<div class="button"><router-link to="/qa/publish">发表问答</router-link></div>
</template>
<template v-if="data.list?.length">
<PublishItem
v-for="(item, index) in commentList"
:data="item"
:key="index"
@submitComment="onSubmitComment"
></PublishItem>
<div class="line"></div>
<div class="btn" color="#FCEDD0" round size="large" @click="handleViewMore" v-if="data.list.length > 2">
查看更多问答
</div>
</template>
<van-empty description="暂无内容" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
:deep(.publish-item) {
padding: 0.24rem;
margin-bottom: 0.2rem;
background: #fff;
border-radius: 0.2rem;
}
.btn {
margin: 0.2rem auto;
width: 5.78rem;
height: 0.69rem;
background: #fcedd0;
text-align: center;
line-height: 0.69rem;
color: #e2a022;
font-size: 0.24rem;
border-radius: 0.35rem;
cursor: pointer;
}
.line {
width: 5.97rem;
height: 0px;
border-top: 0.01rem solid #d3d3d3;
margin: auto;
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { getRecommendCourse } from '@/api/base' const router = useRouter()
import type { IRecommendCourse } from '@/types'
import num from '@/utils/numTo' const props = defineProps<{ list: any }>()
const courseList = ref<{ total: number; list: IRecommendCourse[] }>({ total: 0, list: [] })
getRecommendCourse().then(res => {
courseList.value = res.data
if (res.data.length > 3) {
courseList.value = res.data.slice(0, 3)
}
})
const handleClickItem = (item: any) => { const handleClickItem = (item: any) => {
if (item.url) { router.push(`/course/detail?id=${item.id}`)
location.href = item.url
}
} }
</script> </script>
<template> <template>
<a href="https://fi.ezijing.com/shop" target="_blank"> <a href="#" target="_blank">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/course_banner.png" class="exam_banner" /> <img src="https://webapp-pub.ezijing.com/project/prp-h5/course_banner.png" class="exam_banner" />
</a> </a>
<AppCard title="推荐课程" id="team"> <AppCard title="推荐课程" id="team">
...@@ -26,25 +18,25 @@ const handleClickItem = (item: any) => { ...@@ -26,25 +18,25 @@ const handleClickItem = (item: any) => {
<router-link to="/course">查看更多 <van-icon name="arrow" /></router-link> <router-link to="/course">查看更多 <van-icon name="arrow" /></router-link>
</div> </div>
</template> </template>
<div class="course_list"> <div class="course_list" v-if="props.list?.length">
<div <div class="list_item" v-for="(item, index) in props.list" :key="index" @click="handleClickItem(item)">
class="list_item" <img :src="item.course_picture" class="item_img" />
v-for="(item, index) in courseList.list.slice(0, 3)"
:key="index"
@click="handleClickItem(item)"
>
<img :src="item.cover" class="item_img" />
<div class="item_right"> <div class="item_right">
<div class="right_tit">{{ item.name }}</div> <div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom"> <div class="right_bottom">
<div class="views">{{ num(item.pv) }}播放</div> <div class="views">已学习{{ item.times }}人</div>
<div class="is_free" :class="item.is_free === '1' ? 'free' : 'unfree'"> <div
class="is_free"
:class="item.is_free_name === '免费' ? 'free' : item.is_free_name === '已购买' ? 'c' : 'unfree'"
>
{{ item.is_free_name }} {{ item.is_free_name }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard> </AppCard>
</template> </template>
...@@ -68,7 +60,7 @@ const handleClickItem = (item: any) => { ...@@ -68,7 +60,7 @@ const handleClickItem = (item: any) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding-top: 0.17rem; // padding-top: 0.17rem;
box-sizing: border-box; box-sizing: border-box;
.right_tit { .right_tit {
font-size: 0.28rem; font-size: 0.28rem;
...@@ -90,7 +82,7 @@ const handleClickItem = (item: any) => { ...@@ -90,7 +82,7 @@ const handleClickItem = (item: any) => {
.is_free { .is_free {
font-size: 0.22rem; font-size: 0.22rem;
font-weight: 400; font-weight: 400;
line-height: 30px; // line-height: 30px;
padding-right: 0.3rem; padding-right: 0.3rem;
} }
.free { .free {
...@@ -99,8 +91,30 @@ const handleClickItem = (item: any) => { ...@@ -99,8 +91,30 @@ const handleClickItem = (item: any) => {
.unfree { .unfree {
color: #e9a724; color: #e9a724;
} }
.c {
color: #1847a0;
}
} }
} }
} }
} }
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style> </style>
<script setup lang="ts"> <script setup lang="ts">
import { Swiper, SwiperSlide } from 'swiper/vue' import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/css' import 'swiper/css'
import { getTeacherList } from '@/api/base' // import { Empty } from 'vant'
import type { ITeacherList } from '@/types' // import { getTeacherList } from '@/api/base'
// import type { ITeacherList } from '@/types'
const teacherList = ref<{ total: number; list: ITeacherList[] }>({ total: 0, list: [] }) // const teacherList = ref<{ total: number; list: ITeacherList[] }>({ total: 0, list: [] })
getTeacherList().then(res => { // getTeacherList().then(res => {
teacherList.value = res.data // teacherList.value = res.data
}) // })
const props = defineProps<{ list: any }>()
</script> </script>
<template> <template>
...@@ -20,12 +22,13 @@ getTeacherList().then(res => { ...@@ -20,12 +22,13 @@ getTeacherList().then(res => {
</div> </div>
</div> </div>
<div class="output_banner"> <div class="output_banner">
<swiper slides-per-view="auto" :space-between="10"> <swiper slides-per-view="auto" :space-between="10" v-if="props.list?.length">
<swiper-slide v-for="(item, index) in teacherList.list" :key="index" class="video-swiper-slide"> <swiper-slide v-for="(item, index) in props.list" :key="index" class="video-swiper-slide">
<img :src="item.avatar" class="img" /> <img :src="item.avatar" class="img" />
<div class="name">{{ item.name }}</div> <div class="name">{{ item.name }}</div>
</swiper-slide> </swiper-slide>
</swiper> </swiper>
<van-empty description="无数据" image-size="100" v-else />
</div> </div>
</div> </div>
</template> </template>
......
<script setup lang="ts">
import type { ITeam } from '../types'
defineProps<{ teams: ITeam[] }>()
</script>
<template>
<div id="team">
<div class="team_header">
<div class="title">团队荣誉总榜</div>
<div class="more">
<router-link to="/team">查看更多</router-link>
</div>
</div>
<div class="team-ranking">
<!-- <h2>团队荣誉总榜</h2> -->
<ul>
<li v-for="item in teams.slice(0, 3)" :key="item.id">
<router-link :to="{ name: 'teamView', params: { id: item.id } }">
<h4>{{ item.name }}</h4>
<p>{{ item.slogan }}<em>|</em>{{ item.members_count }}</p>
</router-link>
</li>
</ul>
</div>
</div>
</template>
<style lang="scss" scoped>
#team {
border-radius: 0.2rem;
margin-bottom: 0.29rem;
background: #ffffff;
.team_header {
background: #fff url(https://webapp-pub.oss-cn-beijing.aliyuncs.com/project/prp-h5/ranking_bg.png) no-repeat;
background-size: 100% 100%;
padding: 0.4rem 0.37rem 0.47rem 0.37rem;
height: 1.37rem;
box-sizing: border-box;
display: flex;
justify-content: space-between;
.title {
color: #955801;
font-size: 0.36rem;
font-weight: 600;
}
.more {
color: #955815;
color: 0.22rem;
}
}
.team-ranking {
// background: #fff url(https://webapp-pub.ezijing.com/project/prp-h5/team_bg.png) no-repeat;
background-size: 100%;
border-radius: 0.2rem;
li {
padding: 0.3rem 0.3rem 0.3rem 1.88rem;
h4 {
font-size: 0.3rem;
color: #4e4e4e;
line-height: 0.42rem;
}
p {
margin-top: 0.08rem;
font-size: 0.26rem;
color: #999999;
line-height: 0.38rem;
em {
padding: 0 0.2rem;
}
}
&:nth-child(1) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_1.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
&:nth-child(2) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_2.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
&:nth-child(3) {
background: url(https://webapp-pub.ezijing.com/project/prp-h5/team_3.png) no-repeat 0.68rem center;
background-size: 0.54rem;
}
}
li + li {
border-top: 0.01rem solid #e4e4e4;
}
}
}
</style>
...@@ -2,13 +2,32 @@ import type { IVideoItem, ICourseItem, ITeam } from '@/types' ...@@ -2,13 +2,32 @@ import type { IVideoItem, ICourseItem, ITeam } from '@/types'
export interface HomeInfo { export interface HomeInfo {
banner: IBanner[] banner: IBanner[]
admission_guide_docs: IDocItem[] course: ICourse[]
admission_guide_videos: IVideoItem[] lecture: ILecture[]
learning_map_docs: IDocItem[] // admission_guide_docs: IDocItem[]
exam_strategy_docs: IDocItem[] // admission_guide_videos: IVideoItem[]
questions: { total: number; list: Record<string, any>[] } // learning_map_docs: IDocItem[]
ranking: ITeam[] // exam_strategy_docs: IDocItem[]
hot_message_docs: INews[] // questions: { total: number; list: Record<string, any>[] }
// ranking: ITeam[]
// hot_message_docs: INews[]
}
export interface ILecture {
id: string
lecturer_name: string
lecturer_title: string
lecturer_office: string
}
export interface ICourse {
id: string
category_name: string
category: string
is_free_name: string
is_free: string
prices: string
times: string
} }
export interface IBanner { export interface IBanner {
...@@ -41,4 +60,4 @@ export interface INews { ...@@ -41,4 +60,4 @@ export interface INews {
url: string url: string
} }
export { IVideoItem, ICourseItem, ITeam } export { IVideoItem, ICourseItem, ITeam }
\ No newline at end of file
...@@ -3,30 +3,21 @@ import { ref, onMounted } from 'vue' ...@@ -3,30 +3,21 @@ import { ref, onMounted } from 'vue'
import type { HomeInfo } from '../types' import type { HomeInfo } from '../types'
import * as api from '../api' import * as api from '../api'
import Banner from '../components/Banner.vue' import Banner from '../components/Banner.vue'
// import Menu from '../components/Menu.vue'
// import AdmissionGuide from '../components/AdmissionGuide.vue'
import CourseCard from '../components/CourseCard.vue'
import News from '../components/News.vue'
import RecommendCourse from '../components/RecommendCourse.vue' import RecommendCourse from '../components/RecommendCourse.vue'
import KnowledgeOut from '../components/KnowledgeOut.vue'
import Teacher from '../components/Teacher.vue' import Teacher from '../components/Teacher.vue'
import LearningMap from '../components/LearningMap.vue'
import QueryView from '../components/QueryView.vue'
// import ExamStrategy from '../components/ExamStrategy.vue'
import TeamRanking from '../components/TeamRanking.vue'
import Questions from '../components/Questions.vue'
import useWXShare from '@/utils/wx' import useWXShare from '@/utils/wx'
const data = ref<HomeInfo>({ const data = ref<HomeInfo>({
banner: [], banner: [],
admission_guide_docs: [], course: [],
admission_guide_videos: [], lecture: []
learning_map_docs: [], // admission_guide_docs: [],
exam_strategy_docs: [], // admission_guide_videos: [],
questions: { total: 0, list: [] }, // learning_map_docs: [],
ranking: [], // exam_strategy_docs: [],
hot_message_docs: [] // questions: { total: 0, list: [] },
// ranking: [],
// hot_message_docs: []
}) })
// 获取首页数据 // 获取首页数据
const fetchHomeData = () => { const fetchHomeData = () => {
...@@ -41,31 +32,11 @@ onMounted(() => { ...@@ -41,31 +32,11 @@ onMounted(() => {
</script> </script>
<template> <template>
<Banner :list="data.banner"></Banner> <div style="padding-bottom: 0.6rem">
<!-- <Menu></Menu> --> <Banner :list="data.banner" />
<!-- 入学指南 --> <!-- 推荐课程 -->
<!-- <AdmissionGuide :docs="data.admission_guide_docs" :videos="data.admission_guide_videos"></AdmissionGuide> --> <RecommendCourse :list="data.course" />
<CourseCard :docs="data.admission_guide_docs" :videos="data.admission_guide_videos" /> <!-- 讲师团 -->
<!-- 新闻轮播 --> <Teacher :list="data.lecture" />
<News :docs="data.hot_message_docs" /> </div>
<!-- 推荐课程 -->
<RecommendCourse />
<!-- 知识输出者 -->
<KnowledgeOut />
<!-- 讲师团 -->
<Teacher />
<!-- 学习地图 -->
<LearningMap :docs="data.learning_map_docs"></LearningMap>
<!-- 权益查看 -->
<QueryView></QueryView>
<!-- <RouterLink to="/qa">
<img src="https://webapp-pub.ezijing.com/project/prp-h5/qa_banner.png" style="width: 100%" />
</RouterLink> -->
<!-- 陪伴问答 -->
<Questions :data="data.questions"></Questions>
<!-- 荣誉总榜 -->
<TeamRanking :teams="data.ranking"></TeamRanking>
<!-- 考试攻略 -->
<!-- <ExamStrategy :docs="data.exam_strategy_docs"></ExamStrategy> -->
<img src="https://webapp-pub.ezijing.com/project/prp-h5/exam_banner.png" style="width: 100%; margin-bottom: 0.5rem" />
</template> </template>
import httpRequest from '@/utils/axios'
// 获取课程列表
export function getCourseList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/course-list', { params })
}
// 获取课程列表
export function getCourseView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/learning/course-view', { params })
}
// 获取课程列表
export function getChapterView(params: { chapter_id: string; page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/chapter-view', { params })
}
// 课程章节打卡
export function createCourseRecord(data: {
chapter_id: string
course_id: string
content: string
picture?: string
file?: string
}) {
return httpRequest.post('/api/psp/v1/learning/upload', data)
}
// 打卡记录评论
export function createCourseComment(data: { entity_id: string; to_comment_id?: string; content: string }) {
return httpRequest.post('/api/psp/v1/learning/comment', data)
}
// 获取打卡记录评论
export function getRecordComment(params: { id: string; page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/record-comments', { params })
}
// 获取文档数据
export function getDocView(params: { id: string }) {
return httpRequest.get('/api/psp/v1/learning/doc-view', { params })
}
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useInfiniteScroll } from '@vueuse/core'
import ChapterItemRecord from './ChapterItemRecord.vue'
import { getChapterView } from '../api'
interface Info {
loading: boolean
page: number
total: number
list: any[]
}
const props = defineProps<{ courseId: string; data: any }>()
// 章节详情
const chapterVisible = ref<boolean>(false)
const dataset = reactive<Info>({ loading: false, page: 1, total: 0, list: [] })
// 查看本章所有打卡记录
const showChapterRecord = () => {
chapterVisible.value = true
dataset.page = 1
dataset.list = []
getChapterRecord()
}
// 获取章节打卡记录
const getChapterRecord = () => {
dataset.loading = true
getChapterView({ chapter_id: props.data.id, page: dataset.page, page_size: 10 })
.then(res => {
const { total, list } = res.data
dataset.total = total
dataset.list = dataset.list.concat(list)
if (dataset.list.length <= total) {
dataset.page++
}
})
.finally(() => {
dataset.loading = false
})
}
// 滚动加载
const el = ref<HTMLElement>()
useInfiniteScroll(
el,
() => {
!dataset.loading && getChapterRecord()
},
{ distance: 50 }
)
</script>
<template>
<div class="course-chapter-item">
<h2 class="chapter-title">{{ data.chapter_name }}</h2>
<ChapterItemRecord v-for="publish in data.records.list" :data="publish" :key="publish.id"></ChapterItemRecord>
<div class="chapter-view" @click="showChapterRecord">查看本章所有打卡记录</div>
</div>
<van-popup v-model:show="chapterVisible" round position="bottom" :style="{ height: '80%' }">
<div class="course-chapter" ref="el">
<ChapterItemRecord v-for="publish in dataset.list" :data="publish" :key="publish.id"></ChapterItemRecord>
<van-button
block
round
class="my-button button-fixed"
:to="{ path: '/learn/publish', query: { course_id: courseId, chapter_id: data.id } }"
>我也要拍照得星星</van-button
>
</div>
</van-popup>
</template>
<style lang="scss">
.chapter-title {
margin-bottom: 0.22rem;
font-size: 0.28rem;
font-weight: bold;
color: #333333;
line-height: 0.28rem;
}
.chapter-view {
padding: 0.36rem 0;
font-size: 0.24rem;
color: #033974;
line-height: 0.36rem;
text-align: center;
border-top: 0.01rem solid #d3d3d3;
cursor: pointer;
}
.course-chapter {
height: 100%;
overflow-y: auto;
padding: 0.38rem 0.24rem 1rem;
box-sizing: border-box;
}
.button-fixed {
position: fixed;
bottom: 0.2rem;
left: 0.2rem;
right: 0.2rem;
width: auto;
}
</style>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { uniqWith } from 'lodash-es'
import PublishItem from '@/components/PublishItem.vue'
import { getRecordComment, createCourseComment } from '../api'
const props = defineProps<{ data: any }>()
const page = ref<number>(1)
const dataset = reactive(props.data)
// 数据合并
const mergeList = (arr: any[], arr2: any[]) => {
return uniqWith(arr.concat(arr2), (a, b) => a.id === b.id)
}
// 获取打卡评论
const getRecordCommentList = () => {
getRecordComment({ id: props.data.id, page: page.value, page_size: 5 }).then(res => {
const { total, list } = res.data.comments
dataset.comments.total = total
dataset.comments.list = page.value === 1 ? list : mergeList(dataset.comments.list, list)
})
}
// 评论
const onSubmitComment = (data: any, action: string) => {
const params =
action === 'comment'
? { entity_id: data.id, content: data.comment } // 评论
: { entity_id: data.entity_id, content: data.comment, to_comment_id: data.id } // 回复
createCourseComment(params).then(() => {
page.value = 1
getRecordCommentList()
})
}
const onLoadMore = () => {
page.value++
getRecordCommentList()
}
</script>
<template>
<PublishItem :data="dataset" :key="dataset.id" @submitComment="onSubmitComment" @load="onLoadMore"></PublishItem>
</template>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/learn',
component: AppLayout,
children: [
{ name: 'learnCourse', path: 'course', component: () => import('./views/Course.vue') },
{ name: 'learnCourseView', path: 'course/:id', component: () => import('./views/CourseView.vue'), props: true },
{ path: 'doc/:id', component: () => import('./views/DocView.vue'), props: true },
{ path: 'publish', component: () => import('./views/Publish.vue'), meta: { requireLogin: true } }
]
}
]
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '@/types'
import { getCourseList } from '../api'
import useWXShare from '@/utils/wx'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
useWXShare({ desc: '夯实【系统新知】与顶级高校专家同创【知识获得者】' })
})
</script>
<template>
<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>
<CourseItem v-for="item in dataset.list" :data="item" :key="item.id"></CourseItem>
</template>
<style lang="scss" scoped>
.tips {
padding: 0.1rem 0 0.2rem;
font-size: 0.24rem;
font-weight: 400;
color: #033974;
line-height: 0.36rem;
}
.course-item {
margin-bottom: 0.3rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Toast } from 'vant'
import ChapterItem from '../components/ChapterItem.vue'
import { getCourseView } from '../api'
const props = defineProps<{ id: string }>()
// 课程
const data = ref()
const getCourse = () => {
const toast = Toast.loading({ message: '加载中...', forbidClick: true })
getCourseView({ id: props.id }).then(res => {
data.value = res.data.course
toast.clear()
})
}
onMounted(() => {
getCourse()
})
</script>
<template>
<div class="course" v-if="data">
<div class="course-top">
<div class="course-info">
<img :src="data.course_picture" class="course-info-pic" />
<div class="course-info-content">
<h1>{{ data.course_name }}</h1>
<ul>
<li class="l1">
<span>{{ data.course_chapters.big_total }}章节</span>
<span>{{ data.course_chapters.small_total }}小节</span>
</li>
<li class="l2">
<span>{{ data.pv }}人看过</span>
<span>{{ data.records_total }}人评论</span>
</li>
</ul>
<div class="star">
<p>
共计可得<b>{{ data.star_total }}</b
>个星星
</p>
</div>
<div class="lecturer" v-for="(lecturer, index) in data.course_lectures" :key="index">
<h4>{{ lecturer.lecturer_name }}</h4>
<p>{{ lecturer.lecturer_title }}</p>
<!-- <p>{{ lecturer.lecturer_office }}</p> -->
</div>
</div>
</div>
<div class="course-desc">
<div v-html="data.course_represent"></div>
</div>
</div>
<div class="course-bottom">
<div class="course-tips">如果你也是知识获得者,请晒出你的海报、说出你的感想,得到你的星星。</div>
<div class="course-chapters">
<ChapterItem v-for="item in data.course_chapters.list" :courseId="id" :data="item" :key="item.id"></ChapterItem>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.course-info {
display: flex;
}
.course-info-pic {
width: 2.5rem;
height: 2rem;
background-color: #fff;
border-radius: 0.2rem;
overflow: hidden;
object-fit: cover;
}
.course-info-content {
flex: 1;
margin-left: 0.16rem;
h1 {
font-size: 0.36rem;
line-height: 0.5rem;
font-weight: 500;
color: #333333;
}
ul {
display: flex;
justify-content: space-between;
li {
font-size: 0.2rem;
font-weight: 300;
line-height: 0.28rem;
span + span {
margin-left: 0.1rem;
}
}
.l1 {
color: #033974;
}
.l2 {
color: #999;
}
}
.star {
margin: 0.07rem 0;
p {
display: inline-block;
padding: 0 0.15rem 0 0.38rem;
height: 0.3rem;
font-size: 0.2rem;
line-height: 0.3rem;
border-radius: 0.15rem;
border: 0.01rem solid #80b0e5;
background: url('https://webapp-pub.ezijing.com/project/prp-h5/icon_star.png') no-repeat 0.1rem center;
background-size: 0.22rem;
b {
color: #033974;
}
}
}
.lecturer {
h4 {
margin-bottom: 0.05rem;
font-size: 0.24rem;
line-height: 0.33rem;
font-weight: 500;
color: #333333;
}
p {
font-size: 0.22rem;
font-weight: 400;
line-height: 0.3rem;
color: #666666;
}
}
}
.course-desc {
margin-top: 0.15rem;
font-size: 0.24rem;
line-height: 0.3rem;
color: #333;
}
.course-bottom {
margin-top: 0.28rem;
margin-bottom: 0.28rem;
background-color: #033974;
border-radius: 0.2rem;
overflow: hidden;
}
.course-tips {
padding: 0.3rem;
font-size: 0.24rem;
color: #ffffff;
line-height: 0.36rem;
}
.course-chapters {
padding: 0.38rem 0.24rem;
background-color: #fff;
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getDocView } from '../api'
import DocView from '@/components/DocView.vue'
const props = defineProps<{ id: string }>()
const data = ref()
function fetchDocView() {
getDocView({ id: props.id }).then(res => {
data.value = res.data
})
}
onMounted(() => {
fetchDocView()
})
</script>
<template>
<DocView :data="data"></DocView>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { createCourseRecord } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
import { Toast } from 'vant'
const router = useRouter()
const route = useRoute()
const form = reactive({ ...{ chapter_id: '', course_id: '', content: '', picture: [] }, ...route.query })
const pictureValidator = () => !!form.picture.length
function onSubmit() {
const params = Object.assign({}, form, { picture: JSON.stringify(form.picture) })
createCourseRecord(params).then(() => {
Toast.success('发布成功')
router.push({ name: 'learnCourseView', params: { id: form.course_id } })
})
}
</script>
<template>
<AppContainer title="打卡" backgroundColor="#fff" headerAlign="center">
<van-form @submit="onSubmit">
<van-field
v-model="form.content"
type="textarea"
placeholder="请输入打卡内容"
:autosize="{ minHeight: 200 }"
:rules="[{ required: true, message: '请输入打卡内容' }]"
/>
<van-field :rules="[{ validator: pictureValidator, message: '请上传图片' }]">
<template #input>
<AppUpload v-model="form.picture"></AppUpload>
</template>
</van-field>
<van-button block round native-type="submit" class="my-button">发表</van-button>
</van-form>
</AppContainer>
</template>
<style lang="scss" scoped>
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
&::after {
left: 0;
right: 0;
}
}
.my-button {
margin: 1rem 0;
}
</style>
...@@ -99,6 +99,9 @@ onMounted(() => { ...@@ -99,6 +99,9 @@ onMounted(() => {
</template> </template>
<style lang="scss"> <style lang="scss">
.message-list{
padding-top: .2rem;
}
.message-item { .message-item {
padding: 0.24rem 0.3rem; padding: 0.24rem 0.3rem;
background-color: #fff; background-color: #fff;
......
...@@ -5,6 +5,11 @@ export function getMyInfo() { ...@@ -5,6 +5,11 @@ export function getMyInfo() {
return httpRequest.get('/api/psp/v1/my/info') return httpRequest.get('/api/psp/v1/my/info')
} }
// 获取学习
export function getStudyInfo() {
return httpRequest.get('/api/psp/v2/my/info')
}
// 获取课程列表 // 获取课程列表
export function getCourseList(params?: { page_size?: number; page?: number }) { export function getCourseList(params?: { page_size?: number; page?: number }) {
return httpRequest.get('/api/psp/v1/learning/course-list', { params }) return httpRequest.get('/api/psp/v1/learning/course-list', { params })
......
<script setup lang="ts">
const props = defineProps<{ data: any }>()
const router = useRouter()
const handleClickItem = (item: any) => {
router.push(`/my/cert?c=${item.certificate}`)
}
const isEmpty = $computed(() => {
if (props?.data || props.data?.length === 0) return false
if (props.data?.findIndex((item: any) => item.certificate === '') !== -1) {
return false
} else {
return true
}
})
console.log(isEmpty, 'isEmpty')
</script>
<template>
<AppCard title="我的电子学习证明" id="team">
<template #header-aside> </template>
<div class="course_list" v-if="isEmpty">
<template v-for="item in data" :key="item.id">
<div class="list_item" v-if="item.certificate !== ''" @click="handleClickItem(item)">
<img :src="item.certificate" class="item_img" />
<div class="item_right">
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
</div>
</div>
</template>
</div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
.exam_banner {
width: 6.9rem;
margin-top: 0.3rem;
}
.course_list {
.list_item {
display: flex;
margin-bottom: 0.2rem;
.item_img {
width: 2.2rem;
height: 1.4rem;
object-fit: cover;
// border-radius: 0.1rem;
}
.item_right {
margin-left: 0.21rem;
// // display: flex;
// flex-direction: column;
// justify-content: space-between;
// padding-top: 0.17rem;
box-sizing: border-box;
.right_tit {
font-size: 0.28rem;
font-weight: 500;
line-height: 0.36rem;
color: #333333;
}
.right_bottom {
width: 4.09rem;
margin-top: 0.21rem;
display: flex;
justify-content: space-between;
align-items: center;
.views {
font-size: 0.22rem;
font-weight: 400;
color: #999999;
}
.is_free {
font-size: 0.22rem;
font-weight: 400;
// line-height: 30px;
padding-right: 0.3rem;
}
.free {
color: #4ad1a3;
}
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style>
<script setup lang="ts">
defineProps<{ data: any }>()
const router = useRouter()
const handleClickItem = (item: any) => {
router.push(`/course/detail?id=${item.id}`)
}
</script>
<template>
<AppCard title="我的课程" id="team">
<template #header-aside> </template>
<div class="course_list" v-if="data?.length">
<div class="list_item" v-for="item in data" :key="item.id" @click="handleClickItem(item)">
<img :src="item.course_picture" class="item_img" />
<div class="item_right">
<div :class="`right_tag c${item.category}`">{{ item.category_name }}</div>
<div class="right_tit">{{ item.course_name }}</div>
<div class="right_bottom">
<div class="views">已学习{{ item.times }}人</div>
</div>
</div>
</div>
</div>
<van-empty description="无数据" image-size="100" v-else />
</AppCard>
</template>
<style lang="scss" scoped>
.exam_banner {
width: 6.9rem;
margin-top: 0.3rem;
}
.course_list {
.list_item {
display: flex;
margin-bottom: 0.2rem;
.item_img {
width: 2.2rem;
height: 1.4rem;
object-fit: cover;
border-radius: 0.1rem;
}
.item_right {
margin-left: 0.21rem;
display: flex;
flex-direction: column;
justify-content: space-between;
// padding-top: 0.17rem;
box-sizing: border-box;
.right_tit {
font-size: 0.28rem;
font-weight: 500;
line-height: 0.36rem;
color: #333333;
}
.right_bottom {
width: 4.09rem;
margin-top: 0.21rem;
display: flex;
justify-content: space-between;
align-items: center;
.views {
font-size: 0.22rem;
font-weight: 400;
color: #999999;
}
.is_free {
font-size: 0.22rem;
font-weight: 400;
// line-height: 30px;
padding-right: 0.3rem;
}
.free {
color: #4ad1a3;
}
.unfree {
color: #e9a724;
}
.c {
color: #1847a0;
}
}
}
}
}
.right_tag {
font-weight: bold;
font-size: 0.24rem;
color: #ffffff;
line-height: 100%;
border-radius: 0.24rem;
padding: 0.08rem 0.19rem;
width: fit-content;
margin-bottom: 0.15rem;
&.c1 {
background: #b51e4d;
}
&.c2 {
background: #1847a0;
}
&.c3 {
background: #bc9854;
}
}
</style>
...@@ -8,7 +8,8 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -8,7 +8,8 @@ export const routes: Array<RouteRecordRaw> = [
meta: { requireLogin: true }, meta: { requireLogin: true },
children: [ children: [
{ path: '', component: () => import('./views/Index.vue') }, { path: '', component: () => import('./views/Index.vue') },
{ path: 'course', component: () => import('./views/Course.vue') } { path: 'cert', component: () => import('./views/Cert.vue') },
{ path: 'data', component: () => import('./views/Data.vue') }
] ]
} }
] ]
<script setup lang="ts"></script>
<template>
<AppContainer title="我的电子学习证明" headerAlign="center"></AppContainer>
<AppCard>
<img :src="$route.query.c" />
</AppCard>
<a href="#" :download="$route.query.c" class="btn">
下载电子学习证明
<!-- <div class="btn">下载电子学习证明</div> -->
</a>
</template>
<style lang="scss" scoped>
img {
width: 100%;
}
.btn {
position: absolute;
bottom: 3rem;
left: 50%;
transform: translateX(-50%);
border-radius: 0.4rem;
background: #aa1941;
font-weight: 500;
font-size: 0.28rem;
color: #ffffff;
line-height: 100%;
padding: 0.26rem 0.43rem;
text-align: center;
width: 4rem;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import CourseItem from '@/components/CourseItem.vue'
import type { ICourseItem } from '@/types'
import { getCourseList } from '../api'
// 学习进度
const dataset = ref<{ total: number; list: ICourseItem[] }>({ total: 0, list: [] })
const fetchCourseList = () => {
getCourseList({ page_size: 100 }).then(res => {
dataset.value = res.data
})
}
onMounted(() => {
fetchCourseList()
})
</script>
<template>
<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>
<CourseItem v-for="item in dataset.list" :data="item" :key="item.id"></CourseItem>
</template>
<style lang="scss" scoped>
.tips {
padding: 0.1rem 0 0.2rem;
font-size: 0.24rem;
font-weight: 400;
color: #033974;
line-height: 0.36rem;
}
.course-item {
margin-bottom: 0.3rem;
}
</style>
<script setup lang="ts">
import { getStudyInfo } from '../api'
let studyInfo: any = $ref()
getStudyInfo().then(res => {
studyInfo = res.data
})
const lastStudy = $computed(() => {
return studyInfo?.courses[0] || []
})
</script>
<template>
<AppContainer title="我的学习数据" headerAlign="center"></AppContainer>
<div class="data-t">
<div class="data-t_l">
<div class="t">我的学习总时长</div>
<div class="time" v-if="studyInfo?.learn_duration">
<span>{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}</span
>分钟
</div>
<div class="time" v-else><span>0</span>分钟</div>
</div>
<div class="data-t_r">
<div class="top">
<div class="t-tit">我的学习总次数</div>
<div class="t-time">
<span>{{ studyInfo?.learn_times }}</span
>
</div>
</div>
<div class="b">
<div class="b-item">
<div class="t">报名课程数</div>
<div class="time">
<span>{{ studyInfo?.course_count }}</span
>
</div>
</div>
<div class="b-item">
<div class="t">学习课程数</div>
<div class="time">
<span>{{ studyInfo?.learn_count }}</span
>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="tit">最近一次学习</div>
<div class="course-name" v-if="lastStudy?.course_name">{{ lastStudy?.course_name }}</div>
<div class="chapter-name" v-if="lastStudy?.last_chapter">
{{ lastStudy?.last_chapter ? lastStudy?.last_chapter : '您还没有学习,赶紧去报名课程学习吧!' }}
</div>
<div class="time" v-if="studyInfo?.learn_duration">
学习时长: <span>{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}</span
>分钟
</div>
<div class="time" v-else>学习时长:<span>0</span>分钟</div>
<!-- <div class="time" v-if="lastStudy?.learn_duration">{{ lastStudy?.learn_duration }}</div> -->
</div>
<div class="course-list" v-if="studyInfo?.courses && studyInfo?.courses.length !== 0">
<div class="item" v-for="item in studyInfo?.courses" :key="item.id">
<div class="name">{{ item?.course_name }}</div>
<div class="pro">
学习进度:<span>{{ item?.last_chapter }}</span>
</div>
<div class="scu">
完成百分比:<span>{{ item?.percent }}%</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.data-t {
display: flex;
justify-content: space-between;
.data-t_l {
width: 3.35rem;
height: 3.35rem;
background: linear-gradient(180deg, #ffffff 0%, #f8ecef 100%);
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.7rem;
}
.time {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.7rem;
span {
font-size: 0.6rem;
color: #aa1941;
}
}
}
.data-t_r {
.top {
width: 3.35rem;
height: 1.58rem;
background: #ffffff;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t-tit {
font-size: 0.28rem;
color: #656565;
line-height: 100%;
padding-top: 0.2rem;
}
.t-time {
font-size: 0.28rem;
color: #000000;
line-height: 100%;
padding-top: 0.4rem;
span {
font-size: 0.6rem;
color: #aa1941;
}
}
}
.b {
display: flex;
justify-content: space-between;
margin-top: 0.2rem;
.b-item {
width: 1.58rem;
height: 1.58rem;
background: linear-gradient(180deg, #ffffff 0%, #eaeef7 100%);
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
text-align: center;
.t {
font-size: 0.24rem;
color: #656565;
line-height: 100%;
padding-top: 0.2rem;
}
.time {
font-size: 0.24rem;
color: #656565;
line-height: 100%;
padding-top: 0.4rem;
span {
font-size: 0.48rem;
color: #1847a0;
}
}
}
}
}
}
.card {
background: #ffffff;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
border-radius: 0.2rem;
overflow: hidden;
text-align: center;
padding-bottom: 0.2rem;
margin-top: 0.5rem;
.tit {
background: #aa1941;
line-height: 0.6rem;
text-align: center;
}
.tit {
font-size: 0.26rem;
color: #ffffff;
}
.course-name {
font-weight: 500;
font-size: 0.36rem;
color: #aa1941;
text-align: center;
margin-top: 0.3rem;
}
.chapter-name {
font-size: 0.22rem;
color: #656565;
margin-top: 0.2rem;
}
.time {
margin-top: 0.1rem;
font-size: 0.22rem;
color: #656565;
}
}
.course-list {
margin-top: 0.5rem;
.item {
margin-bottom: 0.2rem;
background: #ffffff;
border-radius: 0.2rem;
padding: 0.2rem 0.25rem;
.name {
font-weight: 500;
font-size: 0.3rem;
color: #000000;
}
.pro,
.scu {
margin-top: 0.2rem;
font-size: 0.22rem;
line-height: 0.36rem;
color: #000000;
span {
color: #656565;
}
}
.scu {
margin-top: 0;
}
}
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'
import AppUploadSignImage from '@/components/base/AppUploadSignImage.vue' import AppUploadSignImage from '@/components/base/AppUploadSignImage.vue'
import { getMyInfo, updateAvatar } from '../api' import AppContainer from '@/components/base/AppContainer.vue'
import { getMyInfo, updateAvatar, getStudyInfo } from '../api'
import { logout } from '@/api/base' import { logout } from '@/api/base'
import { Toast } from 'vant' import { Toast } from 'vant'
import Course from '../components/Course.vue'
import Cert from '../components/Cert.vue'
let info = $ref<{ avatar: string; name: string; star: string }>() const router = useRouter()
let teamInfo = $ref<{ star: number; name?: string; team_id?: string }>({ star: 0 })
let info = $ref<{ avatar: string; name: string }>()
let studyInfo: any = $ref()
function fetchMyInfo() { function fetchMyInfo() {
getMyInfo().then(res => { getMyInfo().then(res => {
info = res.data.info || {} info = res.data.info || {}
teamInfo = res.data.team_info || { star: 0 } })
getStudyInfo().then(res => {
studyInfo = res.data
}) })
} }
onMounted(() => { onMounted(() => {
fetchMyInfo() fetchMyInfo()
}) })
const menus = computed<
Array<{
name: string
path?: string
icon: string
href?: string
}>
>(() => {
return [
{
path: '/learn/course',
name: '我的课程',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_1.png'
},
{
path: teamInfo.team_id ? `/team/view/${teamInfo.team_id}` : '/team',
name: '我的团队',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_2.png'
},
{
href: 'https://account-show.ezijing.com/h5/payment',
name: '我的发票',
icon: 'https://webapp-pub.ezijing.com/project/prp-h5/my_menu_7.png'
},
{
path: '/qa',
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'
// }
]
})
// 退出登录 // 退出登录
const onLogout = () => { const onLogout = () => {
logout().then(() => { logout().then(() => {
...@@ -76,8 +37,19 @@ function onUploadSuccess(url: string) { ...@@ -76,8 +37,19 @@ function onUploadSuccess(url: string) {
Toast.success('上传成功') Toast.success('上传成功')
}) })
} }
const viewData = function () {
router.push('/my/data')
}
const editPassword = function () {
location.href = `https://login.ezijing.com/auth/password?rd=${encodeURIComponent(location.href)}`
}
</script> </script>
<template> <template>
<AppContainer>
<template #header-aside><div @click="editPassword">修改密码</div></template>
</AppContainer>
<div class="my" v-if="info"> <div class="my" v-if="info">
<div class="user"> <div class="user">
<div class="user-avatar"> <div class="user-avatar">
...@@ -90,30 +62,26 @@ function onUploadSuccess(url: string) { ...@@ -90,30 +62,26 @@ function onUploadSuccess(url: string) {
</div> </div>
<div class="quantity"> <div class="quantity">
<dl> <dl>
<dt>{{ info.star }}</dt> <dt>{{ studyInfo?.course_count }}<span></span></dt>
<dd>我的星星</dd> <dd>已报名课程</dd>
</dl> </dl>
<dl> <dl>
<dt>{{ teamInfo.star }}</dt> <dt>{{ studyInfo?.learn_count }}<span></span></dt>
<dd>我的积分</dd> <dd>已学习课程</dd>
</dl>
<dl>
<dt v-if="studyInfo?.learn_duration">
{{ (parseInt(studyInfo?.learn_duration) / 60).toFixed(1) }}<span>分钟</span>
</dt>
<dt v-else>0<span>分钟</span></dt>
<dd>已学习时长</dd>
</dl> </dl>
</div> </div>
<div class="box"> <div class="view-data" @click="viewData">查看我的学习数据</div>
<nav class="menus"> <!-- 我的课程 -->
<ul> <Course :data="studyInfo?.courses" />
<li v-for="(item, index) in menus" :key="index"> <!-- 电子证书 -->
<a :href="item.href" target="_blank" v-if="item.href"> <Cert :data="studyInfo?.courses" />
<img :src="item.icon" />
<p>{{ item.name }}</p>
</a>
<router-link :to="item.path" v-if="item.path">
<img :src="item.icon" />
<p>{{ item.name }}</p>
</router-link>
</li>
</ul>
</nav>
</div>
<div class="logout" @click="onLogout">退出登录</div> <div class="logout" @click="onLogout">退出登录</div>
</div> </div>
</template> </template>
...@@ -162,6 +130,10 @@ function onUploadSuccess(url: string) { ...@@ -162,6 +130,10 @@ function onUploadSuccess(url: string) {
.quantity { .quantity {
display: flex; display: flex;
margin-top: 0.4rem; margin-top: 0.4rem;
background-color: #fff;
padding: 0.52rem 0;
border-radius: 0.2rem;
box-shadow: 0px 2px 8px 1px rgba(0, 0, 0, 0.16);
dl { dl {
flex: 1; flex: 1;
text-align: center; text-align: center;
...@@ -173,11 +145,14 @@ function onUploadSuccess(url: string) { ...@@ -173,11 +145,14 @@ function onUploadSuccess(url: string) {
font-size: 0.42rem; font-size: 0.42rem;
font-weight: 500; font-weight: 500;
line-height: 0.6rem; line-height: 0.6rem;
color: #033974; color: #aa1941;
span {
font-size: 0.28rem;
}
} }
dd { dd {
font-size: 0.3rem; font-size: 0.28rem;
color: #666666; color: #000;
line-height: 0.42rem; line-height: 0.42rem;
} }
} }
...@@ -212,11 +187,25 @@ function onUploadSuccess(url: string) { ...@@ -212,11 +187,25 @@ function onUploadSuccess(url: string) {
height: 0.8rem; height: 0.8rem;
font-size: 0.28rem; font-size: 0.28rem;
line-height: 0.8rem; line-height: 0.8rem;
color: #666666; color: #fff;
text-align: center; text-align: center;
border-radius: 0.4rem; border-radius: 0.4rem;
border: 0.01rem solid #999999; background-color: #aa1941;
cursor: pointer; cursor: pointer;
margin-bottom: 1rem;
} }
} }
.view-data {
margin-top: 0.6rem;
margin-bottom: 0.6rem;
height: 0.8rem;
font-size: 0.28rem;
line-height: 0.8rem;
color: #fff;
text-align: center;
border-radius: 0.4rem;
// border: 0.01rem solid #999999;
background-color: #aa1941;
cursor: pointer;
}
</style> </style>
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论