feat(homeworks,island,classisland,desktop,web,server): 作业按天查看与教室机器录入 + 提醒上课时段暂存规则 + 灵动岛边距/跟随鼠标屏幕
- 作业:新增 Homework.assignDate(本地日期 YYYY-MM-DD;UTC 切片在晚上会把今天的作业算成明天),列表按天过滤与排序,新增 GET /homeworks?date= 与 GET /homeworks/days(日期高亮)供客户端与 Web 共用;客户端作业页改为"看作业 + 录作业"(不再显示完成状态),支持快捷短语(存本机配置,「设置 → 作业录入」可增删/恢复默认) - 教室机器录入:POST /homeworks 去掉 requireRole,权限下沉到 homeworks.service(staff 或本班班级账号);班级账号的 sub 是班级 id,写 createdBy 前换成该班班主任,避免撞 User 外键 - 提醒类型:ClassIslandPush.kind(notification / call)。通知类上课时段暂存、下课补弹,叫人/紧急立即弹;客户端灵动岛与 ClassIsland 插件共用同一套规则 - 灵动岛:新增 marginX / marginY(默认仍 8px,不动老外观)与「跟随鼠标屏幕」;文案统一成"灵动岛 / ClassIsland" - 设置页:新增「ClassIsland 联动」(联动状态 + 通知模式)与「作业录入」分区 - 校验:verify:e2e 增补 assignDate 默认当天 / ?date= 过滤 / days / 非法日期 422 / 班级账号录入 201 与跨班 403;verify:classisland 与插件静态契约增补上课时段暂存断言(57/57) - 文档:README 与 AGENTS.md 同步(作业按天、上课时段策略、两个方向的开关表、新增踩坑) 门禁:pnpm lint 0 error、pnpm typecheck 四包通过、verify:classisland-plugin 57/57 通过。 未跑:verify:e2e / verify:classisland(需后端启动)、verify:web / verify:desktop(需先构建产物)。
This commit is contained in:
1 parent
d0830f6830
commit
7536abfc95
33 files changed
+1236
-162
No files matched your search
@@ -17,6 +17,7 @@ import {
|
||||
type DashboardSummary,
|
||||
type GradeDto,
|
||||
type GradeStats,
|
||||
type HomeworkDaysDto,
|
||||
type HomeworkDto,
|
||||
type HomeworkSubmissionsDto,
|
||||
type IntegrationDeviceDto,
|
||||
@@ -116,7 +117,16 @@ export const homeworkApi = {
|
||||
courseId?: string;
|
||||
pendingOnly?: boolean;
|
||||
keyword?: string;
|
||||
/** 只看某一天(YYYY-MM-DD,按作业所属日期) */
|
||||
date?: string;
|
||||
}): Promise<HomeworkDto[]> => api.get(API_PATHS.homeworks, params),
|
||||
/** 哪些天有作业(日期选择器高亮) */
|
||||
days: (params: {
|
||||
classId?: string;
|
||||
from?: string;
|
||||
to?: string;
|
||||
days?: number;
|
||||
}): Promise<HomeworkDaysDto> => api.get(`${API_PATHS.homeworks}/days`, params),
|
||||
detail: (id: string): Promise<HomeworkDto> => api.get(`${API_PATHS.homeworks}/${id}`),
|
||||
create: (payload: CreateHomeworkRequest): Promise<HomeworkDto> => api.post(API_PATHS.homeworks, payload),
|
||||
update: (id: string, payload: Partial<CreateHomeworkRequest>): Promise<HomeworkDto> =>
|
||||
|
||||
@@ -4,7 +4,9 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem
|
||||
import {
|
||||
SOCKET_EVENTS,
|
||||
SUBJECT_CATALOG,
|
||||
dayKeyLocal,
|
||||
formatDate,
|
||||
shiftDayKey,
|
||||
truncate,
|
||||
type ClassDto,
|
||||
type CourseDto,
|
||||
@@ -25,7 +27,9 @@ const loading = ref(false);
|
||||
const classes = ref<ClassDto[]>([]);
|
||||
const courses = ref<CourseDto[]>([]);
|
||||
const homeworks = ref<HomeworkDto[]>([]);
|
||||
const filter = reactive({ classId: '', courseId: '', keyword: '' });
|
||||
const filter = reactive({ classId: '', courseId: '', keyword: '', date: '' });
|
||||
/** 有作业的日期(日期选择器高亮),来自 GET /homeworks/days */
|
||||
const dayMarks = ref<Map<string, number>>(new Map());
|
||||
const detailVisible = ref(false);
|
||||
const current = ref<HomeworkDto | null>(null);
|
||||
|
||||
@@ -108,10 +112,12 @@ async function loadCourses(): Promise<void> {
|
||||
async function loadHomeworks(): Promise<void> {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: { classId?: string; courseId?: string; keyword?: string } = {};
|
||||
const params: { classId?: string; courseId?: string; keyword?: string; date?: string } = {};
|
||||
if (filter.classId) params.classId = filter.classId;
|
||||
if (filter.courseId) params.courseId = filter.courseId;
|
||||
if (filter.keyword.trim()) params.keyword = filter.keyword.trim();
|
||||
// 按天查看:选了日期就只列那一天的作业
|
||||
if (filter.date) params.date = filter.date;
|
||||
homeworks.value = await homeworkApi.list(params);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -121,9 +127,30 @@ async function loadHomeworks(): Promise<void> {
|
||||
async function onClassChange(): Promise<void> {
|
||||
filter.courseId = '';
|
||||
await loadCourses();
|
||||
await loadDayMarks();
|
||||
await loadHomeworks();
|
||||
}
|
||||
|
||||
/** 拉取"哪些天有作业"(日期选择器高亮);范围取今天前后各 45 天 */
|
||||
async function loadDayMarks(): Promise<void> {
|
||||
try {
|
||||
const today = dayKeyLocal(new Date());
|
||||
const result = await homeworkApi.days({
|
||||
classId: filter.classId || undefined,
|
||||
from: shiftDayKey(today, -45),
|
||||
to: shiftDayKey(today, 45),
|
||||
});
|
||||
dayMarks.value = new Map(result.days.map((item) => [item.date, item.count]));
|
||||
} catch {
|
||||
dayMarks.value = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
/** 日期选择器:有作业的日期加高亮类 */
|
||||
function dayCellClass(date: Date): string {
|
||||
return dayMarks.value.has(dayKeyLocal(date)) ? 'day-has-homework' : '';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ 发布 / 编辑 */
|
||||
|
||||
const formVisible = ref(false);
|
||||
@@ -135,6 +162,8 @@ const form = reactive({
|
||||
title: '',
|
||||
content: '',
|
||||
attachmentUrl: '',
|
||||
/** 作业所属日期(YYYY-MM-DD):决定它出现在哪一天的「按天查看」里 */
|
||||
assignDate: dayKeyLocal(new Date()),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -182,6 +211,8 @@ function openCreate(): void {
|
||||
form.title = '';
|
||||
form.content = '';
|
||||
form.attachmentUrl = '';
|
||||
// 默认跟当前「按天查看」的日期一致:老师多半就是在给这一天留作业
|
||||
form.assignDate = filter.date || dayKeyLocal(new Date());
|
||||
formVisible.value = true;
|
||||
void loadFormCourses();
|
||||
}
|
||||
@@ -193,6 +224,7 @@ function openEdit(row: HomeworkDto): void {
|
||||
form.title = row.title;
|
||||
form.content = row.content;
|
||||
form.attachmentUrl = row.attachmentUrl ?? '';
|
||||
form.assignDate = row.assignDate;
|
||||
formVisible.value = true;
|
||||
void loadFormCourses();
|
||||
}
|
||||
@@ -253,6 +285,7 @@ async function submitForm(): Promise<void> {
|
||||
title: form.title.trim(),
|
||||
content: form.content,
|
||||
attachmentUrl: form.attachmentUrl.trim() || null,
|
||||
assignDate: form.assignDate || dayKeyLocal(new Date()),
|
||||
};
|
||||
|
||||
if (editingId.value) {
|
||||
@@ -325,6 +358,16 @@ onUnmounted(() => {
|
||||
>
|
||||
<el-option v-for="item in courses" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-model="filter.date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="按天查看"
|
||||
clearable
|
||||
class="day-picker"
|
||||
:cell-class-name="dayCellClass"
|
||||
@change="loadHomeworks"
|
||||
/>
|
||||
<el-input
|
||||
v-model="filter.keyword"
|
||||
placeholder="搜索标题/内容"
|
||||
@@ -400,6 +443,16 @@ onUnmounted(() => {
|
||||
}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属日期">
|
||||
<el-date-picker
|
||||
v-model="form.assignDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:cell-class-name="dayCellClass"
|
||||
style="width: 200px"
|
||||
/>
|
||||
<span class="form-hint">作业会出现在这一天(按天查看与学生端都按它归类)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" maxlength="120" show-word-limit />
|
||||
</el-form-item>
|
||||
@@ -502,6 +555,29 @@ onUnmounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 日期选择器:有作业的日子加一个圆点,翻月份时一眼看出哪天有作业 */
|
||||
:deep(.day-has-homework .el-date-table-cell__text)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 2px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
margin-left: -2px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.day-picker {
|
||||
width: 168px;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
margin-left: 10px;
|
||||
opacity: 0.65;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user