Files
huike-front/src/store/daka.ts
T
weli 19fbb53060 fix(front): 打卡/计划提交不再强制课程含图片;选课不限媒体;曲谱文案改为图片
- daka/jihua prepare 去掉仅 Picture+CourseSection 的前端校验
- pick 列表始终可选课,去掉仅按图片判断的警告
- 课程与练习相关界面 ref_name 与提示由「曲谱」改为「图片」等

Made-with: Cursor
2026-04-26 21:38:34 +08:00

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
};
}