19fbb53060
- daka/jihua prepare 去掉仅 Picture+CourseSection 的前端校验 - pick 列表始终可选课,去掉仅按图片判断的警告 - 课程与练习相关界面 ref_name 与提示由「曲谱」改为「图片」等 Made-with: Cursor
466 lines
17 KiB
TypeScript
466 lines
17 KiB
TypeScript
import {reactive} from 'vue';
|
|
import request from "../utils/request";
|
|
import {showFailToast, showSuccessToast} from "vant";
|
|
import dayjs from "dayjs";
|
|
import {
|
|
Comment,
|
|
CommentRefTypes,
|
|
Daka,
|
|
DakaScope,
|
|
HtyBaseRoles,
|
|
HtyRoles,
|
|
Clazz,
|
|
JihuaQueryParam,
|
|
Lianxi, MultiVals,
|
|
NotifyParam,
|
|
NotifyTypes,
|
|
Piyue,
|
|
CourseSection, TeacherSummary
|
|
} from '~/types'
|
|
import useUser from "~/store/user";
|
|
import useRefResource from "~/store/ref-resource";
|
|
import {formatDate} from "~/utils";
|
|
import useLoading from "~/store/loading";
|
|
import useLianxi from "~/store/lianxi";
|
|
import useComment from "~/store/comment";
|
|
import usePiyue from "~/store/piyue";
|
|
import useNotify from "~/store/notify";
|
|
import useCourseCategory from "~/store/qumu-category";
|
|
|
|
const initialDaka = () => ({
|
|
start_date: dayjs().startOf('day').toDate(),
|
|
end_date: dayjs().add(6, 'day').startOf('day').toDate(),
|
|
duration_days: 7,
|
|
course_sections: [] as CourseSection[]
|
|
} as Daka);
|
|
|
|
export interface DakaState {
|
|
current: Daka,
|
|
list: Daka[],
|
|
total: number,
|
|
pages: number,
|
|
hanging: boolean,
|
|
checking: boolean,
|
|
current_lianxi?: Lianxi,
|
|
query_cache?: JihuaQueryParam,
|
|
}
|
|
|
|
const store = reactive<DakaState>({
|
|
current: initialDaka(),
|
|
list: [],
|
|
total: 0,
|
|
pages: 0,
|
|
hanging: false,
|
|
checking: false
|
|
});
|
|
|
|
|
|
export default function useDaka() {
|
|
|
|
const usingUser = useUser();
|
|
const {create_lianxi, remove_lianxi, check_lianxi_tasks} = useLianxi();
|
|
const {create_piyue} = usePiyue();
|
|
const {create_comment, load_comments, check_comments_task, get_resource_note_group, get_resource_note_group_by_ref_id, check_resource_notes_task} = useComment()
|
|
const {notify_ws} = useNotify();
|
|
const {get_hty_resource_by_id} = useRefResource();
|
|
const usingQC = useCourseCategory()
|
|
usingQC.query();
|
|
const notify = (params: NotifyParam, role_key: HtyRoles) => notify_ws(params, role_key);
|
|
const {load_start, load_done} = useLoading()
|
|
|
|
function getEndDate({start_date, duration_days}: Daka) {
|
|
let end = dayjs(start_date).add(duration_days, 'day');
|
|
return formatDate(end);
|
|
}
|
|
function reset() {
|
|
if (!store.hanging) {
|
|
store.current = initialDaka();
|
|
}
|
|
store.hanging = false;
|
|
}
|
|
|
|
async function query(params: JihuaQueryParam): Promise<boolean> {
|
|
let {current: {hty_id}, currentRole} = usingUser.store;
|
|
if (currentRole === HtyBaseRoles.TEACHER) {
|
|
params.teacher_id = hty_id;
|
|
params.scope = DakaScope.ALL;
|
|
} else if (currentRole === HtyBaseRoles.STUDENT) {
|
|
params.student_id = hty_id;
|
|
}
|
|
store.query_cache = params;
|
|
load_start()
|
|
const {r, d, e} = await request({url: '/api/v1/ws/find_dakas_with_sections_by_user_id', params});
|
|
load_done()
|
|
if (r) {
|
|
if (params.page === 1) {
|
|
store.list = [];
|
|
}
|
|
let [list, pages, total] = d;
|
|
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), clazz: dict[x.id], end_date: getEndDate(x)}))];
|
|
store.total = total;
|
|
store.pages = pages;
|
|
} else {
|
|
showFailToast(e);
|
|
}
|
|
return Promise.resolve(r);
|
|
}
|
|
|
|
|
|
async function queryClazzByDakaIds(daka_ids: string[]): Promise<Clazz[]> {
|
|
load_start()
|
|
const {r, d, e} = await request({
|
|
url: '/api/v1/clazz/find_by_daka_ids', data: daka_ids, method: 'POST'
|
|
});
|
|
load_done()
|
|
if (r) {
|
|
return d || [];
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
|
|
async function get_qs_resource_note_groups() {
|
|
for (let i = 0; i < store.current.course_sections.length; i++) {
|
|
let qs = store.current.course_sections[i]
|
|
if (!qs.resource_note_group) {
|
|
await get_resource_note_group(qs)
|
|
} else {
|
|
// we need to set comments to global state by check_comments_tasks function
|
|
await check_resource_notes_task(qs.resource_note_group)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function check_tasks() {
|
|
if (store.checking) return;
|
|
store.checking = true;
|
|
await check_comments_task(store.current.id)
|
|
await check_lianxi_tasks(store.current.course_sections);
|
|
store.checking = false;
|
|
}
|
|
|
|
|
|
const convert = (daka: Daka) => {
|
|
let { course_sections, course_sections2 } = daka;
|
|
let lianxiDict: { [key: string]: CourseSection } = {};
|
|
course_sections.forEach(qs => {
|
|
lianxiDict[qs.id as string] = qs
|
|
})
|
|
if (course_sections2?.vals?.length) {
|
|
daka.course_sections = course_sections2.vals
|
|
daka.course_sections.forEach(qs => {
|
|
let tmp = lianxiDict[qs.id as string];
|
|
qs.lianxis = tmp?.lianxis;
|
|
qs.lianxi_count = tmp?.lianxi_count;
|
|
qs.not_piyue_count = tmp?.not_piyue_count
|
|
})
|
|
}
|
|
return daka;
|
|
}
|
|
|
|
async function read(id: string, forceReload = false) {
|
|
if (!id) return;
|
|
if (store.current.id === id && !forceReload) return;
|
|
load_start()
|
|
const {r, d, e} = await request({url: `/api/v1/ws/find_daka_by_id2/${id}`});
|
|
if (r) {
|
|
convert(d);
|
|
store.current = {...d, end_date: getEndDate(d)};
|
|
await load_comments(id);
|
|
await get_qs_resource_note_groups();
|
|
} else {
|
|
reset()
|
|
showFailToast(e);
|
|
}
|
|
load_done()
|
|
}
|
|
|
|
function valid() {
|
|
let { name, start_date, students, course_sections } = store.current;
|
|
if (!name) {
|
|
showFailToast("请输入打卡名称!")
|
|
return false;
|
|
}
|
|
if (!start_date) {
|
|
showFailToast("请选择打卡起始日期!")
|
|
return false;
|
|
}
|
|
if (!students?.val.users.vals.length) {
|
|
showFailToast("请选择学生!")
|
|
return false;
|
|
}
|
|
if (!course_sections?.length) {
|
|
showFailToast('请添加课程!')
|
|
return false;
|
|
}
|
|
return true
|
|
}
|
|
|
|
async function prepare(qs?: CourseSection) {
|
|
let { course_sections, course_sections2, ...rest } = store.current
|
|
|
|
console.log(rest)
|
|
console.log(qs)
|
|
let qsData: {vals: CourseSection[]} = {vals: []}
|
|
for (let i = 0; i < course_sections.length; i++) {
|
|
if (qs?.id === course_sections[i].id) {
|
|
course_sections[i] = qs as CourseSection;
|
|
store.current.course_sections[i] = qs as CourseSection;
|
|
}
|
|
let { resources, ref_resources, id, resource_note_group, ...rest } = course_sections[i];
|
|
ref_resources = ref_resources || resources;
|
|
if (!resource_note_group) {
|
|
resource_note_group = await get_resource_note_group_by_ref_id(id as string)
|
|
}
|
|
qsData.vals.push({id, ref_resources, ...rest, resource_note_group})
|
|
}
|
|
|
|
return {...rest, course_sections: qsData, course_section_ids: course_sections.map(x => x.id)} as unknown as Daka
|
|
}
|
|
|
|
async function create(): Promise<string | undefined> {
|
|
if (!valid()) return;
|
|
load_start()
|
|
let data = await prepare();
|
|
if (!data) {
|
|
load_done();
|
|
return;
|
|
}
|
|
let { end_date, teachers, students, ...rest } = data;
|
|
let teacher_id = usingUser.store.current.hty_id;
|
|
let teacher_name = usingUser.store.current.real_name;
|
|
let created_by = usingUser.store.current.hty_id;
|
|
clean_teachers(teachers)
|
|
let payload = {teacher_id, teacher_name, created_by, students, teachers, ...rest};
|
|
const {r, d, e} = await request({
|
|
url: `/api/v1/ws/create_daka`,
|
|
data: {...payload},
|
|
method: 'POST'
|
|
})
|
|
if (r) {
|
|
showSuccessToast("打卡创建成功!");
|
|
// notify students
|
|
students?.val.users.vals.forEach(u => {
|
|
notify({ hty_id: u.user_id, daka_id: d, notify_type: NotifyTypes.DakaCreate, remark: "请认真完成练习,按时提交" } as NotifyParam, HtyBaseRoles.STUDENT)
|
|
})
|
|
// notify piyue teacher
|
|
if (teachers?.vals?.length) {
|
|
teachers?.vals.forEach(t => {
|
|
notify({ hty_id: t.teacher_id, daka_id: d, notify_type: NotifyTypes.DakaCreate, remark: "请注意查阅学生练习" } as NotifyParam, HtyBaseRoles.TEACHER)
|
|
})
|
|
}
|
|
} else {
|
|
showFailToast(e);
|
|
}
|
|
load_done()
|
|
return d;
|
|
}
|
|
|
|
function clean_teachers(teachers?: MultiVals<TeacherSummary>) {
|
|
if (!teachers) return;
|
|
// clean repeat teachers
|
|
let teacher_ids = new Set(usingUser.store.current.hty_id), i = 0;
|
|
while (i < teachers?.vals?.length) {
|
|
let {teacher_id} = teachers.vals[i]
|
|
if (teacher_ids.has(teacher_id)) {
|
|
teachers.vals.splice(i, 1);
|
|
} else {
|
|
teacher_ids.add(teacher_id)
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
async function update(qs? :CourseSection) {
|
|
if (!valid()) return Promise.reject()
|
|
load_start()
|
|
let data = await prepare(qs);
|
|
if (!data) {
|
|
load_done();
|
|
return 0;
|
|
}
|
|
let { end_date, teachers, students, clazz, ...rest } = data;
|
|
clean_teachers(teachers)
|
|
const {r, e} = await request({
|
|
url: `/api/v1/ws/update_daka`,
|
|
data: {
|
|
teachers, students, ...rest,
|
|
updated_by: usingUser.store.current.hty_id,
|
|
updated_at: new Date()
|
|
},
|
|
method: 'POST'
|
|
});
|
|
load_done()
|
|
if (r) {
|
|
showSuccessToast('保存成功');
|
|
reset()
|
|
// notify students
|
|
students?.val.users.vals.forEach(u => {
|
|
notify({ hty_id: u.user_id, daka_id: rest.id, notify_type: NotifyTypes.DakaUpdate } as NotifyParam, HtyBaseRoles.STUDENT)
|
|
})
|
|
// notify all piyue teachers
|
|
if (teachers?.vals?.length) {
|
|
teachers?.vals.forEach(t => {
|
|
notify({ hty_id: t.teacher_id, daka_id: rest.id, notify_type: NotifyTypes.DakaUpdate } as NotifyParam, HtyBaseRoles.TEACHER)
|
|
})
|
|
}
|
|
} else {
|
|
load_done()
|
|
showFailToast(e);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
async function remove() {
|
|
load_start()
|
|
let { id, students } = store.current
|
|
const {r, e} = await request({
|
|
url: `/api/v1/ws/delete_daka_by_id/${id}`,
|
|
method: 'POST'
|
|
});
|
|
load_done()
|
|
students?.val.users.vals.forEach(u => {
|
|
notify({ hty_id: u.user_id, daka_id: id, notify_type: NotifyTypes.DakaDelete } as NotifyParam, HtyBaseRoles.STUDENT)
|
|
})
|
|
|
|
if (!r) {
|
|
showFailToast(e);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
async function get_shifan(daka_course_section_id?: string) {
|
|
let relation = store.current.relations?.find(r => r.id === daka_course_section_id);
|
|
let course_section = store.current.course_sections.find(qs => qs.id === relation?.course_section_id);
|
|
let audio_type = course_section?.course_category_key || usingQC.getDefault()?.category_key;
|
|
let shifan = (course_section?.ref_resources || course_section?.resources)?.find(r => r.is_shifan);
|
|
if (shifan) {
|
|
let url = shifan.resource_url;
|
|
if (shifan.hty_resource_id) {
|
|
let resource = await get_hty_resource_by_id(shifan.hty_resource_id);
|
|
if (resource) {
|
|
url = resource.url;
|
|
}
|
|
}
|
|
return { shifan_url: url, audio_type }
|
|
}
|
|
return;
|
|
}
|
|
|
|
async function create_daka_lianxi(data: Lianxi) {
|
|
load_start()
|
|
let shifan = await get_shifan(data.daka_course_section_id);
|
|
let lianxi_id = await create_lianxi(data, shifan)
|
|
if (lianxi_id) {
|
|
await read(store.current.id, true);
|
|
}
|
|
load_done()
|
|
return lianxi_id;
|
|
}
|
|
|
|
async function delete_daka_lianxi(data: Lianxi) {
|
|
let relation = store.current.relations?.find(r => r.id === data.daka_course_section_id);
|
|
let course_section = store.current.course_sections.find(qs => qs.id === relation?.course_section_id);
|
|
const r = await remove_lianxi(data.id);
|
|
notify({ hty_id: store.current.teacher_id, lianxi_id: data.id, course_name: course_section?.course_name, course_section_name: course_section?.section_name, notify_type: NotifyTypes.LianxiDelete } as NotifyParam, HtyBaseRoles.TEACHER)
|
|
await read(store.current.id, true);
|
|
return r;
|
|
}
|
|
|
|
async function create_daka_comment(comment: Comment) {
|
|
// @ts-ignore
|
|
let qs = store.current.course_sections.find(x => x.lianxis?.includes(store.current_lianxi));
|
|
console.log('qs...', qs)
|
|
let shifan_url = qs?.ref_resources?.find(r => r.is_shifan)?.resource_url;
|
|
let audio_type = qs?.course_category_key;
|
|
console.log('shifan url...', shifan_url, audio_type)
|
|
const created = await create_comment(comment, {shifan_url, audio_type});
|
|
if (created) {
|
|
let piyues = store.current_lianxi?.piyues;
|
|
if (comment.ref_type === CommentRefTypes.Piyue) {
|
|
piyues?.forEach(x => {
|
|
if (x.id === comment.ref_id) {
|
|
if (!x.comments.includes(created)) {
|
|
x.comments.push(created)
|
|
}
|
|
}
|
|
})
|
|
} else if (comment.ref_type === CommentRefTypes.ResourceNote) {
|
|
piyues?.forEach(x => {
|
|
x.resource_note_group?.resource_notes?.forEach(y => {
|
|
if (y.id === comment.ref_id) {
|
|
if (!y.comments?.includes(created)) {
|
|
y.comments?.push(created)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
return created
|
|
}
|
|
}
|
|
|
|
async function create_daka_piyue(piyue: Piyue) {
|
|
const piyue_id = await create_piyue(piyue);
|
|
if (piyue_id) {
|
|
await read(store.current.id, true);
|
|
notify({
|
|
first: "同学,您提交的练习老师批阅啦",
|
|
hty_id: store.current_lianxi?.created_by,
|
|
lianxi_id: piyue.lianxi_id, piyue_id,
|
|
notify_type: NotifyTypes.PiyueCreate
|
|
} as NotifyParam, HtyBaseRoles.STUDENT)
|
|
|
|
let params = {
|
|
first: `学生【${store.current_lianxi?.creator_name}】的打卡练习已被【${usingUser.store.current.real_name}】老师批阅`,
|
|
hty_id: store.current.teacher_id,
|
|
lianxi_id: piyue.lianxi_id, piyue_id,
|
|
notify_type: NotifyTypes.PiyueCreate
|
|
}
|
|
if (usingUser.store.current.hty_id === store.current.teacher_id) {
|
|
// send notification to all piyue teachers if supervisor piyued
|
|
store.current.teachers?.vals?.forEach(t => {
|
|
notify({...params, hty_id: t.teacher_id} as NotifyParam, HtyBaseRoles.TEACHER)
|
|
})
|
|
} else {
|
|
// send notification to supervisor teacher and all other piyue teachers if subsidiary teacher piyued
|
|
notify({...params} as NotifyParam, HtyBaseRoles.TEACHER)
|
|
store.current.teachers?.vals?.forEach(t => {
|
|
if (t.teacher_id === usingUser.store.current.hty_id) return;
|
|
notify({...params, hty_id: t.teacher_id} as NotifyParam, HtyBaseRoles.TEACHER)
|
|
})
|
|
}
|
|
|
|
}
|
|
return piyue_id;
|
|
}
|
|
|
|
function get_lianxi_by_id(lianxi_id: string) {
|
|
let lianxi;
|
|
store.current.course_sections.forEach(qs => {
|
|
qs.lianxis?.forEach(lx => {
|
|
if (lx.id === lianxi_id) {
|
|
lianxi =lx;
|
|
return;
|
|
}
|
|
})
|
|
})
|
|
return lianxi;
|
|
}
|
|
|
|
return {
|
|
store, query, read, create, update, remove, reset, create_daka_lianxi, notify, get_shifan, prepare,
|
|
delete_daka_lianxi, create_daka_piyue, check_tasks, create_daka_comment, load_comments, get_lianxi_by_id
|
|
};
|
|
|
|
|
|
}
|