feat: 重做灵动岛编辑器与课表档案时间表编辑
灵动岛 - 重构自定义方案编辑器:组件库常驻、拖拽添加与排序、方案切换/新建/复制/删除、编辑模式预览与缩放,容器在编辑时展开全部子组件。 - 新增天气简报组件:主进程固定请求 Open-Meteo,只接受数值经纬度,失败退避并保留本次运行缓存;冒烟模式不访问外网。 - 倒计时支持目标名称与日期;课程表组件新增「今日时间点」,按时间点类型折叠/展开,课中不再常驻倒计时。 - preload 暴露 getIslandWeather 与时间点订阅,契约同步 desktop.d.ts。 课表档案 - 时间点默认课程:布局返回 defaultCourse,日课表没有条目时回退;新增 apply-default 覆盖所有引用课表;全校时间表不携带默认课程。 - 新增 ClassIsland 式时间表编辑器(列表/时间轴视图、拖动把柄、按类型添加、默认时长与缩放)与「课表信息」表单。 - 课表页默认进入单表编辑,底部保留列表/周/月视图;时间表支持新路由删除。 - 「课间」统一改称「课间休息」。 验证 - pnpm typecheck、pnpm lint 通过。 - verify:e2e 隔离验收 248/248 项通过(含默认课程回退、覆盖写入与时间表删除)。 - ui-smoke 覆盖单表编辑、课表信息与时间轴视图,并更新回归截图。
This commit is contained in:
1 parent
938285bb9c
commit
682818dd4a
41 files changed
+4349
-1095
No files matched your search
@@ -23,6 +23,14 @@ const paths: Record<string, string> = {
|
||||
shield: 'M12 3l8 3v6c0 5-8 9-8 9s-8-4-8-9V6z M8 12l3 3 5-6',
|
||||
back: 'M11 5l-7 7 7 7 M4 12h13a3 3 0 0 1 3 3v3',
|
||||
chevron: 'M6 9l6 6 6-6',
|
||||
lesson: 'M4 5h16v12H4z M8 21h8 M12 17v4 M7 9h6 M7 12h4',
|
||||
break: 'M6 8h9v4a4 4 0 0 1-4 4H10a4 4 0 0 1-4-4V8z M15 9h2a2 2 0 0 1 0 4h-2 M4 20h13',
|
||||
divider: 'M4 12h16',
|
||||
action: 'M13 3 6 13h5l-1 8 8-11h-5l1-7z',
|
||||
timeline: 'M7 3v18 M4 6h6 M4 12h6 M4 18h6 M14 8h6v4h-6z M14 15h4v4h-4z',
|
||||
trash: 'M4 7h16 M9 7V4h6v3 M6 7l1 14h10l1-14 M10 11v6 M14 11v6',
|
||||
'zoom-in': 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14 M20 20l-4-4 M8 11h6 M11 8v6',
|
||||
'zoom-out': 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14 M20 20l-4-4 M8 11h6',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const CLASS_PLAN_SAMPLE = `{
|
||||
"Name": "周一作息",
|
||||
"Layouts": [
|
||||
{ "StartTime": "08:00:00", "EndTime": "08:45:00", "TimeType": 0, "BreakName": "" },
|
||||
{ "StartTime": "08:45:00", "EndTime": "08:55:00", "TimeType": 1, "BreakName": "课间" },
|
||||
{ "StartTime": "08:45:00", "EndTime": "08:55:00", "TimeType": 1, "BreakName": "课间休息" },
|
||||
{ "StartTime": "08:55:00", "EndTime": "09:40:00", "TimeType": 0, "BreakName": "" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import type { ClassPlanDto, TimeLayoutDetailDto } from '@classhelper/shared';
|
||||
import ArchiveIcon from './ArchiveIcon.vue';
|
||||
|
||||
/**
|
||||
* 日课表的触发规则与基本信息。
|
||||
*
|
||||
* 同一份表单同时用于课表页右侧「课表信息」标签和周视图的详细信息入口,
|
||||
* 直接修改父组件传入的 `plan`,保留 PlanEditorView 的脏数据比较。
|
||||
*/
|
||||
const plan = defineModel<ClassPlanDto>('plan', { required: true });
|
||||
const props = defineProps<{
|
||||
layouts: TimeLayoutDetailDto[];
|
||||
groups: { id: string; name: string; isGlobal: boolean; isDefault: boolean }[];
|
||||
maxWeek: number;
|
||||
disabled: boolean;
|
||||
saving: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
'change-layout': [value: string];
|
||||
'edit-layout': [];
|
||||
save: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form :disabled="disabled" label-position="top" class="plan-details" @submit.prevent>
|
||||
<label class="detail-field">
|
||||
<span>课表名称</span>
|
||||
<el-input v-model="plan.name" maxlength="64" placeholder="例如:星期一课表" />
|
||||
</label>
|
||||
<label class="detail-field">
|
||||
<span>启用星期</span>
|
||||
<el-select v-model="plan.weekday">
|
||||
<el-option label="任意星期" :value="0" />
|
||||
<el-option v-for="day in 7" :key="day" :value="day" :label="`星期${'一二三四五六日'[day - 1]}`" />
|
||||
</el-select>
|
||||
</label>
|
||||
<div class="detail-field">
|
||||
<span>轮周设定</span>
|
||||
<div class="inline-fields">
|
||||
<el-input-number v-model="plan.weekCountDivTotal" :min="1" :max="9" />
|
||||
<span>周一轮,使用第</span>
|
||||
<el-input-number v-model="plan.weekCountDiv" :min="0" :max="plan.weekCountDivTotal" />
|
||||
<span>周</span>
|
||||
</div>
|
||||
<small>轮换周期第 0 周表示每周使用,支持 1–9 周轮换。</small>
|
||||
</div>
|
||||
<div class="detail-field">
|
||||
<span>生效周次</span>
|
||||
<div class="inline-fields">
|
||||
<el-input-number v-model="plan.weekStart" :min="1" :max="plan.weekEnd" />
|
||||
<span>至</span>
|
||||
<el-input-number v-model="plan.weekEnd" :min="plan.weekStart" :max="props.maxWeek" />
|
||||
<span>周</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-field">
|
||||
<span>时间表模板</span>
|
||||
<el-select
|
||||
:model-value="plan.timeLayoutId"
|
||||
placeholder="选择时间表"
|
||||
@change="emit('change-layout', $event)"
|
||||
>
|
||||
<el-option v-for="item in props.layouts" :key="item.id" :value="item.id" :label="item.name" />
|
||||
</el-select>
|
||||
<button class="text-command" type="button" @click="emit('edit-layout')">
|
||||
<ArchiveIcon name="clock" />
|
||||
编辑时间表
|
||||
</button>
|
||||
</div>
|
||||
<div class="detail-field">
|
||||
<span>课表群</span>
|
||||
<el-select v-model="plan.associatedGroup" clearable placeholder="未分组">
|
||||
<el-option v-for="group in props.groups" :key="group.id" :value="group.id" :label="group.name" />
|
||||
</el-select>
|
||||
</div>
|
||||
<label class="detail-field inline">
|
||||
<span>启用课表</span>
|
||||
<el-switch v-model="plan.isEnabled" />
|
||||
</label>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="save-plan"
|
||||
:disabled="props.disabled"
|
||||
:loading="props.saving"
|
||||
@click="emit('save')"
|
||||
>
|
||||
保存课表
|
||||
</el-button>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plan-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.detail-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
color: var(--ch-text-secondary);
|
||||
font-size: var(--el-font-size-small);
|
||||
}
|
||||
.detail-field :deep(.el-select),
|
||||
.detail-field :deep(.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
.detail-field small {
|
||||
color: var(--ch-text-muted);
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.detail-field.inline {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.inline-fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.inline-fields :deep(.el-input-number) {
|
||||
width: 74px;
|
||||
}
|
||||
.inline-fields span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.text-command {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: flex-start;
|
||||
gap: 5px;
|
||||
padding: 4px 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--el-color-primary);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.text-command .archive-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.save-plan {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -243,7 +243,7 @@ function fillSample(): void {
|
||||
<el-table-column prop="endTime" label="结束" width="90" />
|
||||
<el-table-column label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.type === 'break' ? '课间' : row.type === 'divider' ? '分割线' : '上课' }}
|
||||
{{ row.type === 'break' ? '课间休息' : row.type === 'divider' ? '分割线' : '上课' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="跳过" width="80">
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user