mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 08:54:45 +00:00
Improved Client implementation
This commit is contained in:
@@ -7,4 +7,8 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class CourseEnrollment(Document):
|
class CourseEnrollment(Document):
|
||||||
pass
|
|
||||||
|
def get_all_activity(self):
|
||||||
|
course_activity_list = frappe.get_all("Course Activity", filters={'enrollment':self.name}, fields=['content', 'content_type', 'modified'], order_by='modified')
|
||||||
|
quiz_activity_list = frappe.get_all("Quiz Activity", filters={'enrollment':self.name}, fields=['quiz', 'status', 'modified'], order_by='modified')
|
||||||
|
return course_activity_list, quiz_activity_list
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"stock/dashboard/item_dashboard_list.html",
|
"stock/dashboard/item_dashboard_list.html",
|
||||||
"stock/dashboard/item_dashboard.js"
|
"stock/dashboard/item_dashboard.js"
|
||||||
],
|
],
|
||||||
"js/web-academy.min.js": [
|
"js/academy.min.js": [
|
||||||
"public/js/education/web-academy.js"
|
"public/js/education/academy/academy.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import Vue from 'vue/dist/vue.js';
|
import Vue from 'vue/dist/vue.js';
|
||||||
import VueRouter from 'vue-router/dist/vue-router.js'
|
import VueRouter from 'vue-router/dist/vue-router.js'
|
||||||
|
|
||||||
import AcademyRoot from "./web-academy/AcademyRoot.vue";
|
import AcademyRoot from "./AcademyRoot.vue";
|
||||||
import AcademyHome from "./web-academy/pages/AcademyHome.vue";
|
import routes from './routes';
|
||||||
import AcademyProgramPage from "./web-academy/pages/AcademyProgramPage.vue";
|
import './call';
|
||||||
import AcademyCoursePage from "./web-academy/pages/AcademyCoursePage.vue";
|
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{name: 'home', path: '', component: AcademyHome},
|
|
||||||
{name: 'program', path: '/Program/:code', component: AcademyProgramPage, props: true},
|
|
||||||
{name: 'content', path: '/Program/:code/:course/:type/:content', component: AcademyCoursePage, props: true},
|
|
||||||
];
|
|
||||||
|
|
||||||
var store = {
|
|
||||||
|
frappe.provide('academy')
|
||||||
|
|
||||||
|
frappe.utils.make_event_emitter(academy);
|
||||||
|
|
||||||
|
academy.store = {
|
||||||
debug: true,
|
debug: true,
|
||||||
isLogin: false,
|
isLogin: false,
|
||||||
completedCourses: new Set(),
|
completedCourses: new Set(),
|
||||||
@@ -104,19 +103,13 @@ var store = {
|
|||||||
this.updateEnrolledPrograms()
|
this.updateEnrolledPrograms()
|
||||||
this.updateEnrolledCourses()
|
this.updateEnrolledCourses()
|
||||||
this.checkLogin()
|
this.checkLogin()
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = new VueRouter({
|
|
||||||
routes: routes,
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
window.v = new Vue({
|
window.v = new Vue({
|
||||||
el: "#academy",
|
el: "#academy",
|
||||||
router: router,
|
router: new VueRouter({ routes }),
|
||||||
data: store,
|
|
||||||
template: "<academy-root/>",
|
template: "<academy-root/>",
|
||||||
components: { AcademyRoot },
|
components: { AcademyRoot },
|
||||||
created: function() {
|
created: function() {
|
||||||
@@ -125,4 +118,20 @@ frappe.ready(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
academy.store = new Vue({
|
||||||
|
data: store,
|
||||||
|
methods: {
|
||||||
|
checkLogin (){
|
||||||
|
if(frappe.session.user === "Guest"){
|
||||||
|
if (this.debug) console.log('No Session')
|
||||||
|
this.isLogin = false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.debug) console.log('Current User: ', frappe.session.user)
|
||||||
|
this.isLogin = true
|
||||||
|
}
|
||||||
|
return this.isLogin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
13
erpnext/public/js/education/academy/call.js
Normal file
13
erpnext/public/js/education/academy/call.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
frappe.provide('academy');
|
||||||
|
|
||||||
|
academy.call = (method, args) => {
|
||||||
|
const method_path = 'erpnext.www.academy.' + method;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return frappe.call({
|
||||||
|
method: method_path,
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
.then(r => resolve(r.message))
|
||||||
|
.fail(reject)
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -12,7 +12,29 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$root.$data.isLogin" class='course-buttons text-center col-xs-4 col-sm-3 col-md-2'>
|
<div v-if="$root.$data.isLogin" class='course-buttons text-center col-xs-4 col-sm-3 col-md-2'>
|
||||||
<AcademyCourseCardButton v-if="this.$root.$data.checkProgramEnrollment(this.$route.params.code)" :course="course.name" :nextContent="nextContent" :nextContentType="nextContentType"/>
|
<!-- <AcademyCourseCardButton
|
||||||
|
v-if="this.$root.$data.checkProgramEnrollment(this.$route.params.code)"
|
||||||
|
:course="course.name"
|
||||||
|
:nextContent="nextContent"
|
||||||
|
:nextContentType="nextContentType"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
v-if="showCompleted"
|
||||||
|
type="success"
|
||||||
|
size="sm"
|
||||||
|
:route="firstContentRoute"
|
||||||
|
>
|
||||||
|
Completed
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-if="showStart"
|
||||||
|
type="primary"
|
||||||
|
size="sm"
|
||||||
|
:route="firstContentRoute"
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,6 +42,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import AButton from './Button';
|
||||||
import AcademyCourseCardButton from './AcademyCourseCardButton.vue'
|
import AcademyCourseCardButton from './AcademyCourseCardButton.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -32,7 +55,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if(this.$root.$data.checkLogin()){
|
if(this.$root.$data.checkLogin()) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.www.academy.get_starting_content",
|
method: "erpnext.www.academy.get_starting_content",
|
||||||
args: {
|
args: {
|
||||||
@@ -45,7 +68,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AcademyCourseCardButton
|
AcademyCourseCardButton,
|
||||||
|
AButton
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showStart() {
|
||||||
|
return academy.loggedIn && !this.course.completed;
|
||||||
|
},
|
||||||
|
showCompleted() {
|
||||||
|
return academy.loggedIn && this.course.completed;
|
||||||
|
},
|
||||||
|
firstContentRoute() {
|
||||||
|
return `${course.name}/${course.content_type}/${data.content}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -38,14 +38,14 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
primaryAction(){
|
primaryAction(){
|
||||||
if(this.$root.$data.isLogin){
|
if(this.$root.$data.isLogin){
|
||||||
if(this.$root.$data.checkProgramEnrollment(program_code)){
|
if(this.$root.$data.checkProgramEnrollment(this.program_code)){
|
||||||
this.$router.push('/Program/' + program.name)
|
this.$router.push('/Program/' + program.name)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.enroll()
|
this.enroll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
enroll() {
|
enroll() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.www.academy.enroll_in_program",
|
method: "erpnext.www.academy.enroll_in_program",
|
||||||
@@ -57,11 +57,11 @@ export default {
|
|||||||
this.$root.$data.enrolledPrograms.add(this.program_code)
|
this.$root.$data.enrolledPrograms.add(this.program_code)
|
||||||
this.$root.$data.updateEnrolledPrograms()
|
this.$root.$data.updateEnrolledPrograms()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
computed: {
|
computed: {
|
||||||
buttonName() {
|
buttonName() {
|
||||||
if(this.$root.$data.isLogin){
|
if(this.$root.$data.isLogin){
|
||||||
if(this.$root.$data.checkProgramEnrollment(program_code)){
|
if(this.$root.$data.checkProgramEnrollment(this.program_code)){
|
||||||
return "Start Course"
|
return "Start Course"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
25
erpnext/public/js/education/academy/components/Button.vue
Normal file
25
erpnext/public/js/education/academy/components/Button.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<button :class="classList" v-on="$listeners" v-bind="$attrs" @click="goToRoute">
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AButton',
|
||||||
|
props: ['type', 'size', 'route'],
|
||||||
|
computed: {
|
||||||
|
classList() {
|
||||||
|
return [
|
||||||
|
'btn',
|
||||||
|
'btn-' + this.type,
|
||||||
|
'btn-' + this.size
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToRoute() {
|
||||||
|
this.$router.push(this.route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -41,9 +41,12 @@ export default {
|
|||||||
enrollment: this.$root.$data.enrolledCourses[this.$route.params.course]
|
enrollment: this.$root.$data.enrolledCourses[this.$route.params.course]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.$root.$data.addCompletedCourses(this.$route.params.course)
|
// this.$root.$data.addCompletedCourses(this.$route.params.course)
|
||||||
this.$root.$data.updateCompletedCourses()
|
this.$root.$data.updateCompletedCourses()
|
||||||
this.$router.push({ name: 'program', params: { code: this.$route.params.code}})
|
this.$router.push({ name: 'program', params: { code: this.$route.params.code}})
|
||||||
|
|
||||||
|
//
|
||||||
|
academy.trigger('course-completed', course_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AcademyTopSection v-bind:title="program.program_name" v-bind:description="program.description">
|
||||||
|
<!-- <AcademyTopSectionButton/> -->
|
||||||
|
<a-button @click="startCourse">Start Course</a-button>
|
||||||
|
<a-button @click="continueCourse">Continue Course</a-button>
|
||||||
|
</AcademyTopSection>
|
||||||
|
<AcademyList :title="'Courses'" :description="''">
|
||||||
|
<AcademyCourseCard v-for="course in course_list" :course="course" :key="course.name"/>
|
||||||
|
</AcademyList>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Button from '../components/Button.vue';
|
||||||
|
import AcademyTopSection from "../components/AcademyTopSection.vue"
|
||||||
|
import AcademyList from "../components/AcademyList.vue"
|
||||||
|
import AcademyCourseCard from "../components/AcademyCourseCard.vue"
|
||||||
|
import AcademyTopSectionButton from "../components/AcademyTopSectionButton.vue";
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['program_name'],
|
||||||
|
name: "AcademyProgramPage",
|
||||||
|
components: {
|
||||||
|
AButton: Button,
|
||||||
|
AcademyTopSection,
|
||||||
|
AcademyList,
|
||||||
|
AcademyCourseCard,
|
||||||
|
AcademyTopSectionButton
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
program: {},
|
||||||
|
course_list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
if(this.$root.$data.isLogin) this.$root.$data.updateCompletedCourses()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getProgramDetails().then(data => this.program = data);
|
||||||
|
this.getCourses().then(data => this.course_list = data);
|
||||||
|
|
||||||
|
academy.on(`course-completed`, (course_name) => {
|
||||||
|
const course = this.course_list.findIndex(c => c.name === course_name);
|
||||||
|
this.course_list[course].completed = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
startCourse() {
|
||||||
|
this.getContentForNextCourse()
|
||||||
|
.then((data) =>
|
||||||
|
this.$router.push(`/Program/${this.program_name}/${data.course}/${data.content_type}/${data.content}`)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
getContentForNextCourse() {
|
||||||
|
return academy.call('get_continue_data', {
|
||||||
|
program_name: this.program_name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getProgramDetails() {
|
||||||
|
return academy.call('get_program_details', {
|
||||||
|
program_name: this.program_name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCourses() {
|
||||||
|
return academy.call('get_courses', {
|
||||||
|
program_name: this.program_name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
11
erpnext/public/js/education/academy/routes.js
Normal file
11
erpnext/public/js/education/academy/routes.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import AcademyHome from "./academy/pages/AcademyHome.vue";
|
||||||
|
import AcademyProgramPage from "./academy/pages/AcademyProgramPage.vue";
|
||||||
|
import AcademyCoursePage from "./academy/pages/AcademyCoursePage.vue";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{name: 'home', path: '', component: AcademyHome},
|
||||||
|
{name: 'program', path: '/Program/:program_name', component: AcademyProgramPage, props: true},
|
||||||
|
{name: 'content', path: '/Program/:code/:course/:type/:content', component: AcademyCoursePage, props: true},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<AcademyTopSection v-bind:title="program.program_name" v-bind:description="program.description">
|
|
||||||
<AcademyTopSectionButton/>
|
|
||||||
</AcademyTopSection>
|
|
||||||
<AcademyList :title="'Courses'" :description="''">
|
|
||||||
<AcademyCourseCard v-for="course in course_list" :course="course" :key="course.name"/>
|
|
||||||
</AcademyList>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import AcademyTopSection from "../components/AcademyTopSection.vue"
|
|
||||||
import AcademyList from "../components/AcademyList.vue"
|
|
||||||
import AcademyCourseCard from "../components/AcademyCourseCard.vue"
|
|
||||||
import AcademyTopSectionButton from "../components/AcademyTopSectionButton.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['code'],
|
|
||||||
name: "AcademyProgramPage",
|
|
||||||
components: {
|
|
||||||
AcademyTopSection,
|
|
||||||
AcademyList,
|
|
||||||
AcademyCourseCard,
|
|
||||||
AcademyTopSectionButton
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
program: '',
|
|
||||||
course_list: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount(){
|
|
||||||
if(this.$root.$data.isLogin) this.$root.$data.updateCompletedCourses()
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.www.academy.get_program_details",
|
|
||||||
args: {
|
|
||||||
program_name: this.code
|
|
||||||
}
|
|
||||||
}).then(r => {
|
|
||||||
this.program = r.message
|
|
||||||
});
|
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.www.academy.get_courses",
|
|
||||||
args: {
|
|
||||||
program_name: this.code
|
|
||||||
}
|
|
||||||
}).then(r => {
|
|
||||||
this.course_list = r.message
|
|
||||||
})
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div id="academy"></div>
|
<div id="academy"></div>
|
||||||
<script type="text/javascript" src="/assets/js/web-academy.min.js"></script>
|
<script type="text/javascript" src="/assets/js/academy.min.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user