feat: 课程模块更名为 clazz(路由 /clazz、store、通知与选型)

对接 AuthCoreJS 通知枚举与 clazz_id;打卡与通知页使用 clazz 字段;补充 package-lock。

Made-with: Cursor
This commit is contained in:
2026-04-24 07:43:06 +08:00
parent a7d09b4f1c
commit b7fa0a3d7e
19 changed files with 16878 additions and 239 deletions
+16529
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,7 +12,7 @@
"typecheck": "vuedx-typecheck ."
},
"dependencies": {
"@authcore/commons": "github:alchemy-studio/AuthCoreJS",
"@authcore/commons": "file:../AuthCoreJS",
"@fullcalendar/bootstrap5": "^6.1.10",
"@fullcalendar/core": "^6.1.10",
"@fullcalendar/daygrid": "^6.1.10",
+2 -2
View File
@@ -3,12 +3,12 @@
<van-tabbar route v-if="!hide_bottom_tab && user.enabled && user.is_registered && current_role && user.real_name" :before-change="tab_change_check">
<van-tabbar-item name="首页" replace to="/" icon="home-o">首页</van-tabbar-item>
<template v-if="is_student">
<van-tabbar-item name="课程" replace to="/kecheng" icon="calendar-o">课程</van-tabbar-item>
<van-tabbar-item name="课程" replace to="/clazz" icon="calendar-o">课程</van-tabbar-item>
<van-tabbar-item name="打卡" replace to="/daka" icon="completed-o">打卡</van-tabbar-item>
</template>
<template v-if="is_teacher">
<van-tabbar-item name="教学资源库" replace to="/course/summary" icon="music-o">教学资源库</van-tabbar-item>
<van-tabbar-item name="课程" replace to="/kecheng" icon="calendar-o">课程</van-tabbar-item>
<van-tabbar-item name="课程" replace to="/clazz" icon="calendar-o">课程</van-tabbar-item>
<van-tabbar-item name="打卡" replace to="/daka" icon="completed-o">打卡</van-tabbar-item>
</template>
<template v-if="is_admin">
@@ -14,8 +14,8 @@
</div>
<van-form @submit="save">
<div class="form-content">
<van-field label="课程名称" required v-model="store.current.kecheng_name" :readonly="state.readonly"/>
<van-field v-model="store.current.kecheng_desc" label="课程描述" autosize rows="2" type="textarea" :readonly="state.readonly"/>
<van-field label="课程名称" required v-model="store.current.clazz_name" :readonly="state.readonly"/>
<van-field v-model="store.current.clazz_desc" label="课程描述" autosize rows="2" type="textarea" :readonly="state.readonly"/>
<template v-if="state.readonly">
<van-field :model-value="dateTimeFormat(store.current.start_from)" readonly label="上课时间" required />
<van-field label="下课时间" readonly required :model-value="dateTimeFormat(store.current.end_by)" />
@@ -101,6 +101,7 @@
</div>
<div class="footer" v-if="is_creator || !store.current.id ">
<template v-if="state.readonly">
<van-cell v-if="is_creator && store.current.id && !store.current.is_repeat" title="点名(消课)" is-link @click="openAttendance"/>
<van-button size="mini" type="primary" @click="edit(true)" v-if="is_creator && (store.current.instance || store.current.has_root)">编辑重复课程</van-button>
<van-button size="mini" type="primary" @click="edit(false)">编辑本次课程</van-button>
<van-button size="mini" type="danger" @click="remove(true)" v-if="is_creator && (store.current.instance || store.current.has_root)">取消重复课程</van-button>
@@ -119,6 +120,17 @@
:columns-type="['hour', 'minute']" :filter="timePickerFilter" />
</van-action-sheet>
<van-popup v-model:show="state.showAttendance" position="bottom" round :style="{ padding: '1rem' }">
<h3 style="margin: 0 0 0.75rem;">点名 勾选已到学员</h3>
<div v-for="u in attendanceStudents" :key="u.user_id || ''" style="margin: 0.5rem 0;">
<van-checkbox v-model="attendanceMap[u.user_id || '']" shape="square">{{ u.real_name || u.user_id }}</van-checkbox>
</div>
<div style="margin-top: 1rem; display: flex; gap: 0.5rem; justify-content: flex-end;">
<van-button size="small" @click="state.showAttendance = false">取消</van-button>
<van-button size="small" type="primary" @click="submitAttendance">保存</van-button>
</div>
</van-popup>
<van-action-sheet :lock-scroll="false" v-model:show="replying" title="留言回复" @close="removeComment" :close-on-click-overlay="false">
<comments-sheet :disabled="!is_teacher" :ref_id="store.current.id" :comment="commenting" @reply="replyComment" @submit="saveComment" @remove="removeComment"></comments-sheet>
</van-action-sheet>
@@ -135,9 +147,9 @@ import zhLocale from '@fullcalendar/core/locales/zh-cn'
import bootstrap5Plugin from '@fullcalendar/bootstrap5'
import CourseGroup from "~/components/qumu-section-group.vue";
import CommentsSheet from '~/components/comments-sheet.vue'
import {Field, Cell, Checkbox, Calendar, showConfirmDialog, TimePicker, Button, Tag, Badge, ActionSheet, Overlay, Form, Icon, showFailToast} from 'vant'
import {Field, Cell, Checkbox, Calendar, showConfirmDialog, TimePicker, Button, Tag, Badge, ActionSheet, Overlay, Form, Icon, showFailToast, Popup, showSuccessToast} from 'vant'
import {DateFormatter, formatDate, hexToRgb} from "~/utils";
import useKecheng from "../../store/kecheng";
import useClazz from "../../store/clazz";
import {useRouter} from "vue-router";
import {
Comment,
@@ -146,7 +158,7 @@ import {
GroupUser,
HtyBaseRoles,
HtySuperRoles,
Kecheng,
Clazz,
NotifyParam,
NotifyTypes,
PickTargets,
@@ -158,7 +170,7 @@ import useComment from "~/store/comment";
import useSupervisor from "~/store/supervisor";
export default defineComponent({
name: "kecheng",
name: "clazz",
components: {
FullCalendar,
[Icon.name]: Icon,
@@ -174,14 +186,15 @@ export default defineComponent({
[Button.name]: Button,
[Overlay.name]: Overlay,
[Form.name]: Form,
[CourseGroup.name]: CourseGroup
[CourseGroup.name]: CourseGroup,
[Popup.name]: Popup,
},
props: {
params: {}
},
setup({params}: any) {
const router = useRouter();
const {store, query_repeats, query, query_for_subsidiaries, query_repeats_for_subsidiaries, createOrUpdate, createInstance, removeCurrent, notify} = useKecheng();
const {store, query_repeats, query, query_for_subsidiaries, query_repeats_for_subsidiaries, createOrUpdate, createInstance, removeCurrent, notify, find_clazz_attendance_by_clazz_id, batch_save_clazz_attendance} = useClazz();
const {set: setPool, setKey, getKey} = usePool();
const usingSupervisor = useSupervisor();
const usingUser = useUser();
@@ -196,9 +209,14 @@ export default defineComponent({
readonly: false,
showCalendar: false,
showTimePicker: false,
show_courses: false
show_courses: false,
showAttendance: false,
})
const attendanceMap = reactive<Record<string, boolean>>({})
const attendanceStudents = computed(() => store.current.students?.val?.users?.vals || [])
const calendar = ref(null)
const footer = ref(null)
@@ -218,7 +236,7 @@ export default defineComponent({
if (item.is_delete) return;
api.addEvent({
id: item.id,
title: item.kecheng_name,
title: item.clazz_name,
start: item.start_from,
end: item.end_by,
})
@@ -228,7 +246,7 @@ export default defineComponent({
if (!item.instance.id) {
api.addEvent({
id: item.id,
title: item.instance.kecheng_name,
title: item.instance.clazz_name,
start: item.instance.start_from,
end: item.instance.end_by,
color: "rgba(55, 136, 216, 0.6)",
@@ -237,7 +255,7 @@ export default defineComponent({
}
})
Object.entries(store.subsidiaryKCs).forEach(([key, {list, repeatList}]) => {
Object.entries(store.subsidiaryClazzByHtyId).forEach(([key, {list, repeatList}]) => {
let color = Colors[subsidiaries.value.findIndex(x => x.to_user_id === key)];
let {r, g, b} = hexToRgb(color);
let hidden = !viewingSubsidiaries.value.includes(key)
@@ -245,7 +263,7 @@ export default defineComponent({
if (item.is_delete) return;
api.addEvent({
id: item.id,
title: item.kecheng_name,
title: item.clazz_name,
start: item.start_from,
end: item.end_by,
subsidiary_id: key,
@@ -257,7 +275,7 @@ export default defineComponent({
if (!item.instance.id) {
api.addEvent({
id: item.id,
title: item.instance.kecheng_name,
title: item.instance.clazz_name,
start: item.instance.start_from,
end: item.instance.end_by,
color: `rgba(${r}, ${g}, ${b}, 0.6)`,
@@ -295,12 +313,12 @@ export default defineComponent({
const viewingSubsidiaries = ref([])
const notify_comment = (comment: Comment) => {
let {kecheng_name, start_from, end_by} = store.current;
let comment_msg = "新的课程留言(" + kecheng_name + " " + usingUser.store.current.real_name + ""
let {clazz_name, start_from, end_by} = store.current;
let comment_msg = "新的课程留言(" + clazz_name + " " + usingUser.store.current.real_name + ""
let payload: NotifyParam = {
kecheng_name, start_from, end_by,
notify_type: NotifyTypes.TeacherCommentKecheng,
kecheng_id: store.current.id, comment_id: comment.id,
clazz_name: clazz_name, start_from, end_by,
notify_type: NotifyTypes.TeacherCommentClazz,
clazz_id: store.current.id, comment_id: comment.id,
comment_time: formatDate(new Date(), DateFormatter.DateTime),
comment_msg, teacher_name: usingUser.store.current.real_name
}
@@ -316,7 +334,7 @@ export default defineComponent({
}
const replyComment = (parent_id?: string) => {
commenting.value = { parent_id, ref_type: CommentRefTypes.Kecheng, content: {} as CommentContent} as Comment
commenting.value = { parent_id, ref_type: CommentRefTypes.Clazz, content: {} as CommentContent} as Comment
}
const saveComment = async () => {
@@ -337,7 +355,7 @@ export default defineComponent({
const comments_count = computed(() => count_comments(store.current.id))
const searchForSubsidiaries = async () => {
// query kechengs of subsidiary teachers under current user if user is supervisor
// query clazz rows of subsidiary teachers under current user if user is supervisor
if (usingUser.store.currentRole === HtyBaseRoles.TEACHER && usingUser.store.current.roles.some(r => r.role_key === HtySuperRoles.SUPERVISOR)) {
subsidiaries.value = usingSupervisor.store.subsidiaries;
if (!subsidiaries.value.length) {
@@ -378,7 +396,7 @@ export default defineComponent({
return root.instance;
}
// find in subsidiaries' list
let subsidiaries = Object.values(store.subsidiaryKCs);
let subsidiaries = Object.values(store.subsidiaryClazzByHtyId);
for (let i = 0; i < subsidiaries.length; i++) {
let {list, repeatList} = subsidiaries[i]
data = list.find(x => x.id === id)
@@ -412,7 +430,7 @@ export default defineComponent({
initialDate = new Date(params.start_from)
}
if (store.hanging) {
let { date } = getKey('kecheng_state')
let { date } = getKey('clazz_state')
initialDate = date;
}
@@ -472,7 +490,7 @@ export default defineComponent({
datesSet: function({start, end}) {
if (store.hanging) {
store.hanging = false;
let {state: cachedState} = getKey('kecheng_state');
let {state: cachedState} = getKey('clazz_state');
Object.entries(cachedState || {}).forEach(([k, v]) => {
state[k] = v;
})
@@ -484,7 +502,7 @@ export default defineComponent({
}
},
eventDidMount({event}) {
if (!store.hanging && state.initial && event.id === params.kecheng_id) {
if (!store.hanging && state.initial && event.id === params.clazz_id) {
view(event.id)
if (params.comment_id) {
replying.value = true;
@@ -515,7 +533,7 @@ export default defineComponent({
if (store.current.instance) {
if (await createInstance(store.current.instance)) {
state.editing = false;
store.current = {} as Kecheng;
store.current = {} as Clazz;
await search()
return;
}
@@ -523,7 +541,7 @@ export default defineComponent({
}
if (await createOrUpdate()) {
state.editing = false;
store.current = {} as Kecheng;
store.current = {} as Clazz;
await search()
}
}
@@ -531,23 +549,23 @@ export default defineComponent({
const freeze = () => {
store.hanging = true;
let date = calendar.value.getApi().getDate();
setKey('kecheng_state', {state, date})
setKey('clazz_state', {state, date})
}
const pickUserGroup = () => {
freeze();
router.push('/user-group/pick?target=' + PickTargets.KECHENG)
router.push('/user-group/pick?target=' + PickTargets.CLAZZ)
}
const pickStudent = () => {
store.hanging = true;
freeze();
router.push('/student?multiple=1&target=' + PickTargets.KECHENG)
router.push('/student?multiple=1&target=' + PickTargets.CLAZZ)
}
const pickTeacher = () => {
freeze();
router.push("/teacher/subsidiaries?multiple=1&target=" + PickTargets.KECHENG)
router.push("/teacher/subsidiaries?multiple=1&target=" + PickTargets.CLAZZ)
}
const createDaka = async () => {
@@ -557,15 +575,15 @@ export default defineComponent({
teachers: store.current.teachers,
students: store.current.students,
})
// create kecheng if it's root
// create clazz-linked daka if it's root
if (!store.current.id) {
if (await createInstance(store.current)) {
await router.push('/daka/add?kecheng_id=' + store.current.id + '&kecheng_name=' + store.current.kecheng_name)
await router.push('/daka/add?clazz_id=' + store.current.id + '&clazz_name=' + encodeURIComponent(store.current.clazz_name))
} else {
showFailToast("课程创建失败!")
}
} else {
await router.push('/daka/add?kecheng_id=' + store.current.id + '&kecheng_name=' + store.current.kecheng_name)
await router.push('/daka/add?clazz_id=' + store.current.id + '&clazz_name=' + encodeURIComponent(store.current.clazz_name))
}
}
@@ -578,7 +596,7 @@ export default defineComponent({
const pickQS = () => {
freeze();
router.push('/course/mix-pick?target=' + PickTargets.KECHENG)
router.push('/course/mix-pick?target=' + PickTargets.CLAZZ)
}
const removeTeacher = (user: GroupUser) => {
@@ -680,12 +698,47 @@ export default defineComponent({
const isTagPlain = (item) => !viewingSubsidiaries.value.includes(item.to_user_id)
const openAttendance = async () => {
if (!store.current.id) {
showFailToast('课程 ID 不存在');
return;
}
state.showAttendance = true;
const list = await find_clazz_attendance_by_clazz_id(store.current.id);
attendanceStudents.value.forEach((u) => {
const uid = u.user_id || '';
const row = list?.find((a) => a.student_id === uid);
attendanceMap[uid] = row ? row.status === 'NORMAL' : false;
});
};
const submitAttendance = async () => {
if (!store.current.id) return;
const hours = (store.current.duration || 60) / 60;
const items = attendanceStudents.value.map((u) => {
const uid = u.user_id || '';
const present = !!attendanceMap[uid];
return {
student_id: uid,
status: present ? 'NORMAL' : 'ABSENT',
deducted_hours: present ? hours : 0,
sign_method: 'TEACHER_MANUAL',
};
});
const ok = await batch_save_clazz_attendance({ clazz_id: store.current.id, items });
if (ok) {
showSuccessToast('已保存点名');
state.showAttendance = false;
}
};
return {
options, cancel, save, state, store, removeQS, gotoDaka, subsidiaries, toggleTeacherView,
pickTeacher, removeTeacher, calendar, edit, remove, dateFormat, pickQS, timePickerFilter,
onChangeDate, pickUserGroup, pickStudent, onChangeTime, createDaka, repeatMinDate, isTagPlain,
removeStudent, dateTimeFormat, is_teacher, is_creator, repeatDefaultDate, footer, footerHeight,
replyComment, removeComment, saveComment, replying, commenting, comments_count, viewingSubsidiaries
replyComment, removeComment, saveComment, replying, commenting, comments_count, viewingSubsidiaries,
openAttendance, submitAttendance, attendanceMap, attendanceStudents
}
}
})
+4 -4
View File
@@ -51,7 +51,7 @@ import {Button, Calendar, Cell, Field, Form, Tag} from 'vant';
import {DateFormatter, formatDate} from "~/utils";
import {useRouter} from 'vue-router';
import useDaka from "~/store/daka";
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import useUser from '~/store/user';
import useLoading from "~/store/loading";
import {GroupUser, HtySuperRoles, PickTargets} from "~/types";
@@ -75,7 +75,7 @@ export default defineComponent({
const {store:{current}} = useUser()
const {loading} = useLoading();
const { get: getPool, remove: removePool, set: setPool } = usePool()
const {setDakasForKC} = useKecheng()
const {setDakasForClazz} = useClazz()
const genName = () => {
let name = "打卡";
@@ -102,9 +102,9 @@ export default defineComponent({
const onSubmit = async () => {
let id = await create();
if (id) {
if (params.kecheng_id) {
if (params.clazz_id) {
let { name, start_date, duration_days } = store.current;
await setDakasForKC({id, name, start_date, duration_days});
await setDakasForClazz({id, name, start_date, duration_days});
reset();
await router.back();
} else {
+2 -2
View File
@@ -23,8 +23,8 @@
<div class="title" v-if="item.group_name">
<span class="label">学生小组:</span> {{ item.group_name }}
</div>
<div class="title" v-if="item.kecheng">
<span class="label">课程名称:</span> {{ item.kecheng.kecheng_name }}
<div class="title" v-if="item.clazz">
<span class="label">课程名称:</span> {{ item.clazz.clazz_name }}
</div>
<div class="line">
<label>打卡时限</label><span :class="{expired: expired(item)}">{{ weekShow(item) }}</span><van-tag :show="item.is_yanqi" style="margin-left: 5px" type="warning">已延期</van-tag>
+3 -3
View File
@@ -73,7 +73,7 @@ import useDaka from "~/store/daka";
import useUser from '~/store/user';
import {Daka, HtyBaseRoles} from "~/types";
import dayjs from "dayjs";
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import {useRouter} from "vue-router";
@@ -92,7 +92,7 @@ export default defineComponent({
const usingUser = useUser();
const router = useRouter();
const usingKecheng = useKecheng()
const usingClazz = useClazz()
const {store, query, reset} = useDaka();
const state = reactive({
@@ -157,7 +157,7 @@ export default defineComponent({
});
const onClick = (item: Daka) => {
usingKecheng.store.current.daka = item;
usingClazz.store.current.daka = item;
router.back();
}
+4 -4
View File
@@ -60,7 +60,7 @@ import {
} from 'vant';
import useJihua from "~/store/jihua";
import useDaka from '~/store/daka';
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import {useRouter} from "vue-router";
import {CourseGroup, PickTargets} from '~/types';
import {DateFormatter, formatDate} from "~/utils";
@@ -96,7 +96,7 @@ export default defineComponent({
setup({params}) {
const usingJihua = useJihua();
const usingDaka = useDaka();
const usingKecheng = useKecheng();
const usingClazz = useClazz();
const {store:{current:{hty_id}}} = useUser()
const state = reactive<CourseState>({
loading: false,
@@ -208,8 +208,8 @@ export default defineComponent({
case PickTargets.DAKA:
usingDaka.store.current.course_sections = course_sections;
break;
case PickTargets.KECHENG:
usingKecheng.store.current.course_sections = {vals: course_sections};
case PickTargets.CLAZZ:
usingClazz.store.current.course_sections = {vals: course_sections};
break;
}
router.back();
+4 -4
View File
@@ -86,7 +86,7 @@ import useJihua from "~/store/jihua";
import useDaka from '~/store/daka';
import useCourseGroup from "~/store/qumu-section-group";
import useComment from "~/store/comment";
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import CommentsSheet from "~/components/comments-sheet.vue";
import QupuMap from "~/components/qupu-map.vue";
import HtyImage from "~/components/hty-image.vue";
@@ -137,7 +137,7 @@ export default defineComponent({
const usingJihua = useJihua();
const usingDaka = useDaka();
const usingCourseGroup = useCourseGroup();
const usingKecheng = useKecheng();
const usingClazz = useClazz();
const state = reactive<CourseState>({
loading: false,
finished: false,
@@ -263,8 +263,8 @@ export default defineComponent({
case PickTargets.COURSE_GROUP:
usingCourseGroup.store.current.course_sections = state.checked;
break;
case PickTargets.KECHENG:
usingKecheng.store.current.course_sections = {vals: state.checked}
case PickTargets.CLAZZ:
usingClazz.store.current.course_sections = {vals: state.checked}
break;
}
router.back();
+9 -9
View File
@@ -39,7 +39,7 @@ import { useRouter } from 'vue-router';
import useJihua from "~/store/jihua";
import useUser from "~/store/user";
import useUserGroup from '~/store/user-group';
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import useDaka from "~/store/daka";
import {PickTargets, TeacherStudentStates, User} from '~/types'
@@ -62,7 +62,7 @@ export default defineComponent({
setup({params}) {
const usingGroup = useUserGroup();
const usingKecheng = useKecheng();
const usingClazz = useClazz();
const usingDaka = useDaka();
const state = reactive({
@@ -123,11 +123,11 @@ export default defineComponent({
case PickTargets.USER_GROUP:
state.checked = [...(usingGroup.store.current.users.vals || [])]
break;
case PickTargets.KECHENG:
state.checked = [...(usingKecheng.store.current.students?.val?.users?.vals || [])]
case PickTargets.CLAZZ:
state.checked = [...(usingClazz.store.current.students?.val?.users?.vals || [])]
break;
case PickTargets.DAKA:
state.checked = [...(usingKecheng.store.current.students?.val?.users?.vals || [])]
state.checked = [...(usingClazz.store.current.students?.val?.users?.vals || [])]
break;
}
})
@@ -152,12 +152,12 @@ export default defineComponent({
case PickTargets.USER_GROUP:
usingGroup.store.current.users.vals = [...state.checked];
break;
case PickTargets.KECHENG:
ids = new Set((usingKecheng.store.current.students?.val.users.vals || []).map(x => x.user_id));
case PickTargets.CLAZZ:
ids = new Set((usingClazz.store.current.students?.val.users.vals || []).map(x => x.user_id));
if (ids.size > 0) {
usingKecheng.store.current.students.val.users.vals.push(...state.checked.filter(x => !ids.has(x.user_id)))
usingClazz.store.current.students.val.users.vals.push(...state.checked.filter(x => !ids.has(x.user_id)))
} else {
usingKecheng.store.current.students = {val: {users: {vals: [...state.checked]}}}
usingClazz.store.current.students = {val: {users: {vals: [...state.checked]}}}
}
break;
case PickTargets.DAKA:
+6 -6
View File
@@ -39,7 +39,7 @@ import useUserGroup from "~/store/user-group";
import useCourseSection from "~/store/qumu-section";
import useCourseGroup from "~/store/qumu-section-group";
import useUser from "~/store/user";
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
export default defineComponent({
name: "teacher-index",
@@ -64,7 +64,7 @@ export default defineComponent({
const usingUserGroup = useUserGroup();
const usingQS = useCourseSection();
const usingQSGroup = useCourseGroup()
const usingKecheng = useKecheng()
const usingClazz = useClazz()
const {store:{current:{supervisor}}} = useUser()
const { store, getSubsidiaries } = useSupervisor();
@@ -144,8 +144,8 @@ export default defineComponent({
case PickTargets.COURSE_GROUP:
state.checked = [...(usingQSGroup.store.current.teachers?.vals || [])].filter(t => dataset.value.some(x => x.teacher_id === t.teacher_id))
break;
case PickTargets.KECHENG:
state.checked = [...(usingKecheng.store.current.teachers?.val?.users?.vals || [])].filter(t => dataset.value.some(x => x.teacher_id === t.user_id))
case PickTargets.CLAZZ:
state.checked = [...(usingClazz.store.current.teachers?.val?.users?.vals || [])].filter(t => dataset.value.some(x => x.teacher_id === t.user_id))
break;
}
})
@@ -178,8 +178,8 @@ export default defineComponent({
case PickTargets.COURSE_GROUP:
usingQSGroup.store.current.teachers = {vals: teachers}
break;
case PickTargets.KECHENG:
usingKecheng.store.current.teachers = {val: {users: {vals: teachers.map(t => ({user_id: t.teacher_id, real_name: t.teacher_name}))}}}
case PickTargets.CLAZZ:
usingClazz.store.current.teachers = {val: {users: {vals: teachers.map(t => ({user_id: t.teacher_id, real_name: t.teacher_name}))}}}
break;
}
router.back();
+22 -22
View File
@@ -3,14 +3,14 @@
<div class="title" v-if="entity.content?.content">
{{ entity.content.content }}
</div>
<div class="title" v-if="entity.push_info?.kecheng_id">
<div class="title" v-if="entity.push_info?.clazz_id">
{{ getTongzhiTitle(entity.tongzhi_type) }}
</div>
<div class="van-cell" @click="onClick">
<template v-if="entity.push_info?.kecheng_id">
<template v-if="entity.push_info?.clazz_id">
<div class="line">
<span>课程名称</span>
<span>{{ entity.push_info.kecheng_name }}</span>
<span>{{ entity.push_info.clazz_name }}</span>
</div>
<div class="line">
<span>上课时间</span>
@@ -116,7 +116,7 @@ onBeforeUnmount(() => {
const entity = computed(() => store.current);
const onClick = () => {
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, kecheng_id } =
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, clazz_id } =
getIds(entity.value);
let comment_param = comment_id ? "&comment_id=" + comment_id : "";
@@ -224,29 +224,29 @@ const onClick = () => {
case NotifyTypes.TeacherRegisterApproved:
router.back();
break;
case NotifyTypes.KechengCreateOrUpdate:
case NotifyTypes.TeacherCommentKecheng:
case NotifyTypes.ClazzCreateOrUpdate:
case NotifyTypes.TeacherCommentClazz:
let {
push_info: { start_from, comment_id },
} = tongzhi;
} = entity.value;
let url =
"/kecheng?kecheng_id=" + kecheng_id + "&start_from=" + start_from;
"/clazz?clazz_id=" + clazz_id + "&start_from=" + start_from;
if (comment_id) {
url += "&comment_id=" + comment_id;
}
router.push(url);
break;
case NotifyTypes.KechengDelete:
router.push("/kecheng");
case NotifyTypes.ClazzDelete:
router.push("/clazz");
break;
}
};
const getTongzhiTitle = (tongzhi_type) => {
return {
[NotifyTypes.KechengCreateOrUpdate]: "上课时间确定通知",
[NotifyTypes.KechengDelete]: "课程预约取消通知",
[NotifyTypes.TeacherCommentKecheng]: "课程留言通知",
[NotifyTypes.ClazzCreateOrUpdate]: "上课时间确定通知",
[NotifyTypes.ClazzDelete]: "课程预约取消通知",
[NotifyTypes.TeacherCommentClazz]: "课程留言通知",
}[tongzhi_type];
};
@@ -264,13 +264,13 @@ const formatDateTime = (dt: Date | string) =>
const getIds = ({ content, push_info }: Tongzhi) => {
let { ref_id, serial, comment_id } = push_info || {};
let { jihua_id, lianxi_id, daka_id, kecheng_id } = content;
let { jihua_id, lianxi_id, daka_id, clazz_id } = content;
if (!(jihua_id || lianxi_id || daka_id)) {
if (push_info) {
jihua_id = push_info.jihua_id;
lianxi_id = push_info.lianxi_id;
daka_id = push_info.daka_id;
kecheng_id = push_info.kecheng_id;
clazz_id = push_info.clazz_id;
}
}
return {
@@ -280,14 +280,14 @@ const getIds = ({ content, push_info }: Tongzhi) => {
ref_id,
serial,
comment_id,
kecheng_id,
clazz_id,
};
};
const gotoDetail = () => {
let tongzhi = store.current;
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, kecheng_id } =
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, clazz_id } =
getIds(tongzhi);
let comment_param = comment_id ? "&comment_id=" + comment_id : "";
@@ -395,20 +395,20 @@ const gotoDetail = () => {
case NotifyTypes.TeacherRegisterApproved:
router.back();
break;
case NotifyTypes.KechengCreateOrUpdate:
case NotifyTypes.TeacherCommentKecheng:
case NotifyTypes.ClazzCreateOrUpdate:
case NotifyTypes.TeacherCommentClazz:
let {
push_info: { start_from, comment_id },
} = tongzhi;
let url =
"/kecheng?kecheng_id=" + kecheng_id + "&start_from=" + start_from;
"/clazz?clazz_id=" + clazz_id + "&start_from=" + start_from;
if (comment_id) {
url += "&comment_id=" + comment_id;
}
router.push(url);
break;
case NotifyTypes.KechengDelete:
router.push("/kecheng");
case NotifyTypes.ClazzDelete:
router.push("/clazz");
break;
}
};
+18 -18
View File
@@ -16,12 +16,12 @@
<van-swipe-cell>
<div class="row van-cell" @click="() => onClick(item)" >
<div v-if="item.content?.content">{{item.content.content}}</div>
<div v-if="item.push_info?.kecheng_id">{{getTongzhiTitle(item.tongzhi_type)}}<small>({{formatDateTime(item.content.created_at)}})</small></div>
<div v-if="item.push_info?.clazz_id">{{getTongzhiTitle(item.tongzhi_type)}}<small>({{formatDateTime(item.content.created_at)}})</small></div>
<div class="van-hairline--bottom"></div>
<template v-if="item.push_info?.kecheng_id">
<template v-if="item.push_info?.clazz_id">
<div class="line">
<span>课程名称</span>
<span>{{item.push_info.kecheng_name}}</span>
<span>{{item.push_info.clazz_name}}</span>
</div>
<div class="line">
<span>上课时间</span>
@@ -75,12 +75,12 @@
<van-swipe-cell>
<div class="row van-cell read" @click="() => onClick(item)" >
<div v-if="item.content?.content">{{item.content.content}}</div>
<div v-if="item.push_info?.kecheng_id">{{getTongzhiTitle(item.tongzhi_type)}}<small>({{formatDateTime(item.content.created_at)}})</small></div>
<div v-if="item.push_info?.clazz_id">{{getTongzhiTitle(item.tongzhi_type)}}<small>({{formatDateTime(item.content.created_at)}})</small></div>
<div class="van-hairline--bottom"></div>
<template v-if="item.push_info?.kecheng_id">
<template v-if="item.push_info?.clazz_id">
<div class="line">
<span>课程名称</span>
<span>{{item.push_info.kecheng_name}}</span>
<span>{{item.push_info.clazz_name}}</span>
</div>
<div class="line">
<span>上课时间</span>
@@ -213,17 +213,17 @@ export default defineComponent({
const getIds = ({content, push_info}: Tongzhi) => {
let { ref_id, serial, comment_id } = push_info || {};
let { jihua_id, lianxi_id, daka_id, kecheng_id } = content;
let { jihua_id, lianxi_id, daka_id, clazz_id } = content;
if (!(jihua_id || lianxi_id || daka_id)) {
if (push_info) {
jihua_id = push_info.jihua_id;
lianxi_id = push_info.lianxi_id;
daka_id = push_info.daka_id;
kecheng_id = push_info.kecheng_id;
clazz_id = push_info.clazz_id;
}
}
return {
jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, kecheng_id
jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, clazz_id
}
}
@@ -233,7 +233,7 @@ export default defineComponent({
await setRead(tongzhi.tongzhi_id);
}
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, kecheng_id } = getIds(tongzhi);
let { jihua_id, lianxi_id, daka_id, ref_id, serial, comment_id, clazz_id } = getIds(tongzhi);
let comment_param = comment_id ? "&comment_id=" + comment_id : "";
let serial_param = serial ? '&serial=' + serial: '';
@@ -313,17 +313,17 @@ export default defineComponent({
case NotifyTypes.TeacherRegisterApproved:
router.back();
break;
case NotifyTypes.KechengCreateOrUpdate:
case NotifyTypes.TeacherCommentKecheng:
case NotifyTypes.ClazzCreateOrUpdate:
case NotifyTypes.TeacherCommentClazz:
let { push_info: {start_from, comment_id} } = tongzhi;
let url = "/kecheng?kecheng_id=" + kecheng_id + "&start_from=" + start_from;
let url = "/clazz?clazz_id=" + clazz_id + "&start_from=" + start_from;
if (comment_id) {
url += "&comment_id=" + comment_id
}
router.push(url)
break;
case NotifyTypes.KechengDelete:
router.push("/kecheng")
case NotifyTypes.ClazzDelete:
router.push("/clazz")
break;
}
}
@@ -354,9 +354,9 @@ export default defineComponent({
const getTongzhiTitle = (tongzhi_type) => {
return {
[NotifyTypes.KechengCreateOrUpdate]: "上课时间确定通知",
[NotifyTypes.KechengDelete]: "课程预约取消通知",
[NotifyTypes.TeacherCommentKecheng]: "课程留言通知"
[NotifyTypes.ClazzCreateOrUpdate]: "上课时间确定通知",
[NotifyTypes.ClazzDelete]: "课程预约取消通知",
[NotifyTypes.TeacherCommentClazz]: "课程留言通知"
}[tongzhi_type]
}
+4 -4
View File
@@ -38,7 +38,7 @@ import {useRouter} from "vue-router";
import useUserGroup from '~/store/user-group';
import useUser from '~/store/user';
import useDaka from '~/store/daka';
import useKecheng from "~/store/kecheng";
import useClazz from "~/store/clazz";
import {UserGroup, PickTargets} from '~/types';
export interface GroupState {
@@ -70,7 +70,7 @@ export default defineComponent({
setup({params}) {
const router = useRouter();
const usingDaka = useDaka();
const usingKecheng = useKecheng();
const usingClazz = useClazz();
const {store:{current:{hty_id}}} = useUser();
const state = reactive<GroupState>({
@@ -135,8 +135,8 @@ export default defineComponent({
usingDaka.store.current.teachers?.vals.push({teacher_id: created_by, teacher_name: creator_name})
}
break;
case PickTargets.KECHENG:
usingKecheng.store.current.students = {val: {users}}
case PickTargets.CLAZZ:
usingClazz.store.current.students = {val: {users}}
}
router.go(-1);
}
+2 -2
View File
@@ -67,7 +67,7 @@ import DakaDetail from '~/pages/daka/detail.vue';
import DakaPiyue from '~/pages/daka/piyue.vue';
import SupervisorProfile from "~/pages/supervisor/profile.vue"
import SupervisorSubsidiaries from "~/pages/supervisor/subsidiaries.vue"
import Kecheng from "~/pages/kecheng/index.vue"
import ClazzPage from "~/pages/clazz/index.vue"
import UserSettings from "~/pages/user-settings.vue";
import {RouteQueryAndHash} from "vue-router";
@@ -245,7 +245,7 @@ export default [
meta: {title: "打卡批阅"}, props: ({query}: RouteQueryAndHash) => ({params: query})
},
{
path: '/kecheng', component: Kecheng, meta: {title: "课程"},
path: '/clazz', component: ClazzPage, meta: {title: "课程"},
props: ({query}: RouteQueryAndHash) => ({params: query})
},
{path: '/user-settings', component: UserSettings, meta: {title: "个性化设置"}},
+94 -65
View File
@@ -1,16 +1,19 @@
import {reactive} from "vue";
import {
Clazz,
ClazzAttendance,
ClazzRepeat,
ClazzRepeatStatuses,
ClazzStatuses,
CourseSection,
Daka,
HtyBaseRoles,
HtyRoles,
Kecheng,
KechengRepeatStatuses,
KechengStatuses,
MultiVals,
NotifyParam,
NotifyTypes,
CourseSection,
RepeatKecheng
ClazzRepeatRow,
ReqBatchClazzAttendance,
} from "~/types";
import useUser from "~/store/user";
import useLoading from "~/store/loading";
@@ -20,26 +23,26 @@ import {showFailToast} from "vant";
import useNotify from "~/store/notify";
import {DateFormatter, formatDate} from "~/utils";
interface KechengStore {
interface ClazzStore {
hanging: boolean,
current: Kecheng,
list: Kecheng[],
subsidiaryKCs: {
[key: string]: {list: Kecheng[], repeatList: RepeatKecheng[]}
current: Clazz,
list: Clazz[],
subsidiaryClazzByHtyId: {
[key: string]: {list: Clazz[], repeatList: ClazzRepeatRow[]}
}
repeatList: RepeatKecheng[]
repeatList: ClazzRepeatRow[]
}
const store = reactive<KechengStore>({
const store = reactive<ClazzStore>({
hanging: false,
current: {} as Kecheng,
current: {} as Clazz,
list: [],
subsidiaryKCs: {},
subsidiaryClazzByHtyId: {},
repeatList: [],
})
export default function useKecheng() {
export default function useClazz() {
const {load_start, load_done} = useLoading()
const usingUser = useUser();
@@ -56,7 +59,7 @@ export default function useKecheng() {
let {currentRole, current:{hty_id}} = usingUser.store;
load_start()
const {r, d, e} = await request({
url: "/api/v1/kc/find_all_non_repeatable_kechengs_within_date_range_by_hty_id",
url: "/api/v1/clazz/find_all_non_repeatable_within_date_range_by_hty_id",
method: "GET", data: {start_from, end_by, hty_id}
})
load_done()
@@ -81,11 +84,11 @@ export default function useKecheng() {
await Promise.all(hty_ids.map(async hty_id => {
const {r, d, e} = await request({
url: "/api/v1/kc/find_all_non_repeatable_kechengs_within_date_range_by_hty_id",
url: "/api/v1/clazz/find_all_non_repeatable_within_date_range_by_hty_id",
method: "GET", data: {start_from, end_by, hty_id}
})
if (r) {
store.subsidiaryKCs[hty_id] = {
store.subsidiaryClazzByHtyId[hty_id] = {
list: d.filter(x => x.teachers?.val?.users?.vals?.some(u => u.user_id === hty_id)),
repeatList: []
}
@@ -98,24 +101,23 @@ export default function useKecheng() {
function repeat_prepare(start_from: string, end_by: string, repeatList, nonRepeatList) {
return repeatList.map(item => {
let kecheng: Kecheng = { kecheng_repeat: {} };
let row: Clazz = { clazz_repeat: {} as ClazzRepeat };
Object.entries(item).forEach(([k, v]) => {
if (k.startsWith("kc_")) {
kecheng[k.replace("kc_", "")] = v;
if (k.startsWith("clz_")) {
(row as Record<string, unknown>)[k.replace("clz_", "")] = v;
}
if (k.startsWith("re_")) {
kecheng.kecheng_repeat[k.replace("re_", "")] = v;
const cr = row.clazz_repeat ?? (row.clazz_repeat = {} as ClazzRepeat);
(cr as Record<string, unknown>)[k.replace("re_", "")] = v;
}
})
if (kecheng.is_delete) return null;
kecheng.repeat_start = formatDate(kecheng.kecheng_repeat?.repeat_start, DateFormatter.Date)
kecheng.repeat_end = formatDate(kecheng.kecheng_repeat?.repeat_end, DateFormatter.Date)
// check instanced kechengs
if (row.is_delete) return null;
row.repeat_start = formatDate(row.clazz_repeat?.repeat_start, DateFormatter.Date)
row.repeat_end = formatDate(row.clazz_repeat?.repeat_end, DateFormatter.Date)
let start = new Date(start_from).valueOf(), end = new Date(end_by).valueOf()
let instance = nonRepeatList.find(x => x.root_id === kecheng.id);
// move start_from and end_by to current week
let kc_start_from = new Date(kecheng.start_from);
let kc_end_by = new Date(kecheng.end_by);
let instance = nonRepeatList.find(x => x.root_id === row.id);
let kc_start_from = new Date(row.start_from);
let kc_end_by = new Date(row.end_by);
if (!instance) {
while (kc_end_by.valueOf() < start) {
kc_start_from.setDate(kc_start_from.getDate() + 7)
@@ -125,15 +127,15 @@ export default function useKecheng() {
kc_start_from.setDate(kc_start_from.getDate() - 7)
kc_end_by.setDate(kc_end_by.getDate() - 7)
}
let {kecheng_repeat, id, is_repeat, ...rest} = kecheng
let {clazz_repeat, id, is_repeat, ...rest} = row
instance = {
...rest,
start_from: formatDate(kc_start_from, DateFormatter.DateTimeSave),
end_by: formatDate(kc_end_by, DateFormatter.DateTimeSave),
}
}
instance.has_root = true;
return {...kecheng, instance};
instance!.has_root = true;
return {...row, instance};
}).filter(x => !!x);
}
@@ -141,7 +143,7 @@ export default function useKecheng() {
let {current:{hty_id}} = usingUser.store;
load_start()
const {r, d, e} = await request({
url: "/api/v1/kc/find_all_repeatable_kechengs_within_date_range_by_hty_id",
url: "/api/v1/clazz/find_all_repeatable_within_date_range_by_hty_id",
method: "GET", data: {start_from, end_by, hty_id}
})
load_done()
@@ -158,12 +160,12 @@ export default function useKecheng() {
load_start()
await Promise.all(hty_ids.map(async hty_id => {
const {r, d, e} = await request({
url: "/api/v1/kc/find_all_repeatable_kechengs_within_date_range_by_hty_id",
url: "/api/v1/clazz/find_all_repeatable_within_date_range_by_hty_id",
method: "GET", data: {start_from, end_by, hty_id}
})
if (r) {
store.subsidiaryKCs[hty_id] = store.subsidiaryKCs[hty_id] || {list: [], repeatList: []}
store.subsidiaryKCs[hty_id].repeatList = repeat_prepare(start_from, end_by, d, store.subsidiaryKCs[hty_id].list);
store.subsidiaryClazzByHtyId[hty_id] = store.subsidiaryClazzByHtyId[hty_id] || {list: [], repeatList: []}
store.subsidiaryClazzByHtyId[hty_id].repeatList = repeat_prepare(start_from, end_by, d, store.subsidiaryClazzByHtyId[hty_id].list);
} else {
console.error("query failed...", e)
}
@@ -183,8 +185,8 @@ export default function useKecheng() {
}
async function createOrUpdate() {
let { kecheng_name, students, course_sections, kecheng_repeat, start_from, end_by, is_repeat, instance, has_root, repeat_start, repeat_end, teachers, ...rest} = store.current;
if (!kecheng_name) {
let { clazz_name, students, course_sections, clazz_repeat, start_from, end_by, is_repeat, instance, has_root, repeat_start, repeat_end, teachers, ...rest} = store.current;
if (!clazz_name) {
showFailToast("请输入课程名称!")
return false
}
@@ -202,9 +204,9 @@ export default function useKecheng() {
showFailToast("请为重复课程选择重复时间!")
return false;
}
kecheng_repeat = {
...kecheng_repeat,
start_from, end_by, repeat_status: KechengRepeatStatuses.OnGoing,
clazz_repeat = {
...clazz_repeat,
start_from, end_by, repeat_status: ClazzRepeatStatuses.OnGoing,
repeat_start: formatDate(repeat_start, DateFormatter.DateTimeSave),
repeat_end: formatDate(repeat_end, DateFormatter.DateTimeSave),
repeat_cycle_days: (new Date(repeat_end).valueOf() - new Date(repeat_start).valueOf()) / (24 * 3600 * 1000)
@@ -231,17 +233,17 @@ export default function useKecheng() {
});
load_start()
let data = {...rest, kecheng_name, students, start_from, end_by, is_repeat, kecheng_repeat, course_sections, duration, teachers, kecheng_status: KechengStatuses.Open};
let data = {...rest, clazz_name, students, start_from, end_by, is_repeat, clazz_repeat, course_sections, duration, teachers, clazz_status: ClazzStatuses.Open};
let res: any;
if (rest.id) {
res = await request({url: "/api/v1/kc/update_kecheng", method: "POST", data})
res = await request({url: "/api/v1/clazz/update", method: "POST", data})
if (res.r && is_repeat) {
res = await request({url: "/api/v1/kc/update_kecheng_repeat", method: "POST", data: kecheng_repeat})
res = await request({url: "/api/v1/clazz/repeat/update", method: "POST", data: clazz_repeat})
}
} else {
// set created by
data.created_by = hty_id;
res = await request({url: "/api/v1/kc/create_kecheng_with_kecheng_repeat", method: "POST", data})
res = await request({url: "/api/v1/clazz/create_with_repeat", method: "POST", data})
}
const {r, d, e} = res
load_done()
@@ -254,7 +256,7 @@ export default function useKecheng() {
let teacher_name = teachers?.val?.users?.vals.map(t => t.real_name).filter(x => x).join(", ")
let payload: NotifyParam = {
kecheng_id: store.current.id, kecheng_name: store.current.kecheng_name, teacher_name,
clazz_id: store.current.id, clazz_name: store.current.clazz_name, teacher_name,
start_from: start_from.replace("T", " ").replace(/:00$/, ""), end_by: end_by.split("T")[1].replace(/:00$/, ""),
}
// notify assist teacher
@@ -262,17 +264,17 @@ export default function useKecheng() {
teachers?.val?.users?.vals.forEach(t => {
// not notify creator
if (t.user_id !== (store.current.created_by || hty_id)) {
notify({ ...payload, hty_id: t.user_id, student_name, notify_type: NotifyTypes.KechengCreateOrUpdate, } as NotifyParam, HtyBaseRoles.TEACHER)
notify({ ...payload, hty_id: t.user_id, student_name, notify_type: NotifyTypes.ClazzCreateOrUpdate, } as NotifyParam, HtyBaseRoles.TEACHER)
}
})
students?.val?.users?.vals.forEach(s => {
notify({ ...payload, hty_id: s.user_id, student_name: s.real_name, notify_type: NotifyTypes.KechengCreateOrUpdate } as NotifyParam, HtyBaseRoles.STUDENT)
notify({ ...payload, hty_id: s.user_id, student_name: s.real_name, notify_type: NotifyTypes.ClazzCreateOrUpdate } as NotifyParam, HtyBaseRoles.STUDENT)
})
}
return r;
}
async function setDakasForKC(daka: Daka) {
async function setDakasForClazz(daka: Daka) {
try {
let {dakas, ...rest} = store.current;
if (dakas?.vals.length && !dakas.vals.some(x => x.id === daka.id)) {
@@ -281,21 +283,21 @@ export default function useKecheng() {
dakas = {vals: [daka]}
}
load_start()
const { r, d, e} = await request({url: "/api/v1/kc/update_kecheng", method: "POST", data: {...rest, dakas}})
const { r, d, e} = await request({url: "/api/v1/clazz/update", method: "POST", data: {...rest, dakas}})
load_done()
if (r) {
// reset hanging to force data refresh
store.hanging = false;
}
} catch (e) {
console.log('update kecheng failed...', e)
console.log('update clazz failed...', e)
}
}
async function createInstance(instance: Kecheng, is_delete?: boolean) {
let { kecheng_name, students, start_from, end_by, has_root, teachers, ...rest} = instance;
if (!kecheng_name) {
async function createInstance(instance: Clazz, is_delete?: boolean) {
let { clazz_name, students, start_from, end_by, has_root, teachers, ...rest} = instance;
if (!clazz_name) {
showFailToast("请输入课程名称!")
return false
}
@@ -317,8 +319,8 @@ export default function useKecheng() {
teachers.val.users.vals.push({user_id: hty_id, real_name})
}
load_start()
let data = {...rest, kecheng_name, is_delete, students, start_from, end_by, duration, created_by: hty_id, teachers, kecheng_status: KechengStatuses.Open};
const {r, d, e} = await request({url: "/api/v1/kc/create_kecheng_with_kecheng_repeat", method: "POST", data})
let data = {...rest, clazz_name, is_delete, students, start_from, end_by, duration, created_by: hty_id, teachers, clazz_status: ClazzStatuses.Open};
const {r, d, e} = await request({url: "/api/v1/clazz/create_with_repeat", method: "POST", data})
load_done()
if (!r) {
showFailToast(e);
@@ -331,10 +333,10 @@ export default function useKecheng() {
let teacher_name = teachers?.val?.users?.vals.map(t => t.real_name).join(", ")
let payload: NotifyParam = {
kecheng_id: d.id, kecheng_name, teacher_name,
clazz_id: d.id, clazz_name: clazz_name, teacher_name,
start_from: start_from.replace("T", " ").replace(/:00$/, ""), end_by: end_by.split("T")[1].replace(/:00$/, ""),
}
let notify_type = is_delete ? NotifyTypes.KechengDelete : NotifyTypes.KechengCreateOrUpdate;
let notify_type = is_delete ? NotifyTypes.ClazzDelete : NotifyTypes.ClazzCreateOrUpdate;
// notify assist teacher
let student_name = students?.val?.users?.vals.map(x => x.real_name).join(", ")
teachers?.val?.users?.vals.forEach(t => {
@@ -368,16 +370,16 @@ export default function useKecheng() {
let {has_root, repeat_start, repeat_end, instance, ...rest} = store.current;
load_start()
const {r, d, e} = await request({
url: "/api/v1/kc/update_kecheng", method: "POST", data: {...rest, is_delete: true}
url: "/api/v1/clazz/update", method: "POST", data: {...rest, is_delete: true}
})
load_done()
if (!r) {
showFailToast(e)
} else {
let { id, teachers, students, kecheng_name, start_from, end_by, created_by} = store.current;
let { id, teachers, students, clazz_name, start_from, end_by, created_by} = store.current;
let teacher_name = teachers?.val?.users?.vals.map(t => t.real_name).join(", ")
let payload: NotifyParam = {
kecheng_id: id, kecheng_name, teacher_name,
clazz_id: id, clazz_name: clazz_name, teacher_name,
start_from: start_from.replace("T", " ").replace(/:00$/, ""),
end_by: end_by.split("T")[1].replace(/:00$/, ""),
}
@@ -386,18 +388,45 @@ export default function useKecheng() {
teachers?.val?.users?.vals.forEach(t => {
// not notify creator
if (t.user_id !== created_by) {
notify({ ...payload, hty_id: t.user_id, student_name, notify_type: NotifyTypes.KechengDelete, } as NotifyParam, HtyBaseRoles.TEACHER)
notify({ ...payload, hty_id: t.user_id, student_name, notify_type: NotifyTypes.ClazzDelete, } as NotifyParam, HtyBaseRoles.TEACHER)
}
})
students?.val?.users?.vals.forEach(s => {
notify({ ...payload, hty_id: s.user_id, student_name: s.real_name, notify_type: NotifyTypes.KechengDelete } as NotifyParam, HtyBaseRoles.STUDENT)
notify({ ...payload, hty_id: s.user_id, student_name: s.real_name, notify_type: NotifyTypes.ClazzDelete } as NotifyParam, HtyBaseRoles.STUDENT)
})
}
return Promise.resolve(r)
}
async function find_clazz_attendance_by_clazz_id(clazz_id: string): Promise<ClazzAttendance[] | null> {
const { r, d, e } = await request({
url: `/api/v1/clazz/attendance/${encodeURIComponent(clazz_id)}`,
method: "GET",
});
if (r) {
return d as ClazzAttendance[];
}
showFailToast(e);
return null;
}
async function batch_save_clazz_attendance(payload: ReqBatchClazzAttendance): Promise<boolean> {
load_start();
const { r, e } = await request({
url: "/api/v1/clazz/batch_save_attendance",
method: "POST",
data: payload,
});
load_done();
if (!r) {
showFailToast(e);
}
return !!r;
}
return {
store, query, query_repeats, createOrUpdate, removeCurrent, createInstance, notify, setDakasForKC, query_for_subsidiaries, query_repeats_for_subsidiaries
store, query, query_repeats, createOrUpdate, removeCurrent, createInstance, notify, setDakasForClazz, query_for_subsidiaries, query_repeats_for_subsidiaries,
find_clazz_attendance_by_clazz_id, batch_save_clazz_attendance,
}
}
+8 -7
View File
@@ -9,7 +9,8 @@ import {
DakaScope,
HtyBaseRoles,
HtyRoles,
JihuaQueryParam, Kecheng,
Clazz,
JihuaQueryParam,
Lianxi, MultiVals,
NotifyParam,
NotifyTypes,
@@ -95,14 +96,14 @@ export default function useDaka() {
store.list = [];
}
let [list, pages, total] = d;
let kcs = await queryKechengs(list.map((x: Daka) => x.id))
let dict: {[key: string]: Kecheng} = {};
let kcs = await queryClazzByDakaIds(list.map((x: Daka) => x.id))
let dict: {[key: string]: Clazz} = {};
kcs.forEach(kc => {
kc.dakas?.vals.forEach(x => {
dict[x.id as string] = kc;
})
})
store.list = [...store.list, ...list.map((x: Daka) => ({...convert(x), kecheng: dict[x.id], end_date: getEndDate(x)}))];
store.list = [...store.list, ...list.map((x: Daka) => ({...convert(x), clazz: dict[x.id], end_date: getEndDate(x)}))];
store.total = total;
store.pages = pages;
} else {
@@ -112,10 +113,10 @@ export default function useDaka() {
}
async function queryKechengs(daka_ids: string[]): Promise<Kecheng[]> {
async function queryClazzByDakaIds(daka_ids: string[]): Promise<Clazz[]> {
load_start()
const {r, d, e} = await request({
url: '/api/v1/kc/find_kechengs_by_daka_ids', data: daka_ids, method: 'POST'
url: '/api/v1/clazz/find_by_daka_ids', data: daka_ids, method: 'POST'
});
load_done()
if (r) {
@@ -290,7 +291,7 @@ export default function useDaka() {
load_done();
return 0;
}
let { end_date, teachers, students, kecheng, ...rest } = data;
let { end_date, teachers, students, clazz, ...rest } = data;
clean_teachers(teachers)
const {r, e} = await request({
url: `/api/v1/ws/update_daka`,
+76 -49
View File
@@ -198,7 +198,7 @@ export enum CommentRefTypes {
Jihua = 'JIHUA',
Daka = 'DAKA',
ResourceNote = 'RESOURCE_NOTE',
Kecheng = 'KECHENG',
Clazz = 'CLAZZ',
}
export interface Comment {
@@ -466,7 +466,7 @@ export interface Daka {
groups?: import('@authcore/commons').UserGroup[];
teachers?: import('@authcore/commons').MultiVals<TeacherSummary>;
students?: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
kecheng?: Kecheng;
clazz?: Clazz;
is_yanqi?: boolean;
}
@@ -497,7 +497,7 @@ export enum PickTargets {
USER_GROUP = 'USER_GROUP',
COURSE_SECTION = 'COURSE_SECTION',
COURSE_GROUP = 'COURSE_GROUP',
KECHENG = 'KECHENG',
CLAZZ = 'CLAZZ',
}
export interface LatitudePos {
@@ -563,7 +563,7 @@ export interface PoolData {
current_role?: string;
hide_bottom_tab?: boolean;
scoring_audio?: RefResource;
kecheng_state?: unknown;
clazz_state?: unknown;
course_sections?: CourseSection[];
teachers?: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
students?: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
@@ -576,31 +576,43 @@ export enum TimeUnits {
SECOND = 'SECOND',
}
export enum KechengStatuses {
export enum ClazzStatuses {
Open = 'OPEN',
}
export enum KechengRepeatStatuses {
export enum ClazzRepeatStatuses {
OnGoing = 'ON_GOING',
}
export interface KechengUser {
export interface ClazzRepeat {
id: string;
clazz_id: string;
start_from?: string | Date;
end_by?: string | Date;
repeat_start: string | Date;
repeat_end?: string | Date;
repeat_cycle_days: number;
repeat_status: ClazzRepeatStatuses;
latest_clazz_created_at?: string | Date;
}
export interface ClazzUser {
id: string;
user_id: string;
kecheng_id: string;
kecheng_status: KechengStatuses;
clazz_id: string;
clazz_status: ClazzStatuses;
user_type: import('@authcore/commons').HtyBaseRoles;
}
export interface Kecheng {
export interface Clazz {
id: string;
kecheng_name: string;
kecheng_desc: string;
kecheng_status: KechengStatuses;
clazz_name: string;
clazz_desc: string;
clazz_status: ClazzStatuses;
start_from: string | Date;
end_by: string | Date;
duration: number;
kecheng_type: string;
clazz_type: string;
root_id: string;
parent_id: string;
teachers: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
@@ -610,62 +622,77 @@ export interface Kecheng {
dakas?: import('@authcore/commons').MultiVals<Daka>;
jihuas?: import('@authcore/commons').MultiVals<Jihua>;
is_delete?: boolean;
kecheng_repeat?: KechengRepeat;
clazz_repeat?: ClazzRepeat;
is_repeat: boolean;
repeat_start?: string | Date;
repeat_end?: string | Date;
instance?: Kecheng;
instance?: Clazz;
has_root?: boolean;
is_notified?: boolean;
course_sections?: import('@authcore/commons').MultiVals<CourseSection>;
}
export interface KechengWithRepeat {
kc_id: string;
kc_kecheng_name: string;
kc_kecheng_desc: string;
kc_kecheng_status: KechengStatuses;
kc_start_from: string | Date;
kc_end_by: string | Date;
kc_duration: number;
kc_kecheng_type: string;
kc_root_id: string;
kc_parent_id: string;
kc_teachers: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
kc_students: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
kc_created_by: string;
kc_created_at: string | Date;
kc_dakas?: import('@authcore/commons').MultiVals<Daka>;
kc_jihuas?: import('@authcore/commons').MultiVals<Jihua>;
kc_is_delete?: boolean;
kc_is_repeat: boolean;
/** 与后端 ReqClazzWithRepeat(扁平 join)字段一致:clz_* 主表,re_* 重复规则 */
export interface ClazzWithRepeat {
clz_id: string;
clz_clazz_name: string;
clz_clazz_desc: string;
clz_clazz_status: ClazzStatuses;
clz_start_from: string | Date;
clz_end_by: string | Date;
clz_duration: number;
clz_clazz_type: string;
clz_root_id: string;
clz_parent_id: string;
clz_teachers: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
clz_students: import('@authcore/commons').SingleVal<import('@authcore/commons').UserGroup>;
clz_created_by: string;
clz_created_at: string | Date;
clz_dakas?: import('@authcore/commons').MultiVals<Daka>;
clz_jihuas?: import('@authcore/commons').MultiVals<Jihua>;
clz_is_delete?: boolean;
clz_is_repeat: boolean;
re_id: string;
re_kecheng_id: string;
re_clazz_id: string;
re_start_from?: string | Date;
re_end_by?: string | Date;
re_repeat_start: string | Date;
re_repeat_end?: string | Date;
re_repeat_cycle_days: number;
re_repeat_status: KechengRepeatStatuses;
re_repeat_status: ClazzRepeatStatuses;
}
export interface KechengRepeat {
/** 排课消课记录(对应 htykc.clazz_attendance */
export interface ClazzAttendance {
id: string;
kecheng_id: string;
start_from?: string | Date;
end_by?: string | Date;
repeat_start: string | Date;
repeat_end?: string | Date;
repeat_cycle_days: number;
repeat_status: KechengRepeatStatuses;
latest_kc_created_at?: string | Date;
clazz_id: string;
student_id: string;
course_hour_package_id?: string | null;
status: string;
deducted_hours: number;
sign_time: string;
sign_method?: string | null;
created_at: string;
created_by?: string | null;
is_delete?: boolean | null;
}
export interface RepeatKecheng {
rootKecheng: Kecheng;
rpeat: KechengRepeat;
export interface ReqClazzAttendanceItem {
student_id: string;
status: string;
deducted_hours: number;
sign_method?: string | null;
course_hour_package_id?: string | null;
}
export interface ReqBatchClazzAttendance {
clazz_id: string;
items: ReqClazzAttendanceItem[];
}
/** 日历重复行:扁平 clazz + 当周实例 */
export type ClazzRepeatRow = Clazz & { instance?: Clazz };
export interface ScoreShifan {
shifan_url?: string;
audio_type?: string;
+1 -1
View File
@@ -71,7 +71,7 @@ axiosInstance.interceptors.request.use((options) => {
const cozePath = '/api/v1/coze' + url.slice(5);
// @ts-ignore
url = import.meta.env.DEV ? cozePath : (AI_SERVER + cozePath);
} else if (url?.startsWith('/api/v1/kc')) {
} else if (url?.startsWith('/api/v1/clazz')) {
// @ts-ignore
url = KC_SERVER + url;
}