fix: supervisor role check for subsidiary data + color passthrough in matrix view

Subsidiary courses now only appear in SUPERVISOR mode, not TEACHER mode.
Matrix view shows each subsidiary teacher's courses with a distinct color.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-02 10:22:22 +08:00
parent ba05fc1b3a
commit bb19834725
3 changed files with 38 additions and 11 deletions
+25 -5
View File
@@ -29,6 +29,7 @@
v-for="slot in timeSlots" v-for="slot in timeSlots"
:key="slot.key" :key="slot.key"
class="header-cell sr-cell" class="header-cell sr-cell"
:style="{ width: columnWidths[slot.key] }"
> >
<div class="header-label">{{ slot.label }}</div> <div class="header-label">{{ slot.label }}</div>
<div class="header-time">{{ slot.time }}</div> <div class="header-time">{{ slot.time }}</div>
@@ -46,6 +47,7 @@
v-for="slot in timeSlots" v-for="slot in timeSlots"
:key="`${day.dateKey}_${slot.key}`" :key="`${day.dateKey}_${slot.key}`"
class="event-cell sr-cell" class="event-cell sr-cell"
:style="{ width: columnWidths[slot.key] }"
> >
<template v-if="eventMap[day.dateKey]?.[slot.key]?.length"> <template v-if="eventMap[day.dateKey]?.[slot.key]?.length">
<div <div
@@ -154,6 +156,21 @@ export default defineComponent({
return map; return map;
}); });
/** 每列宽度 = 按该列最多课块数量计算,课块横向排列时宽度自适应 */
const columnWidths = computed(() => {
const widths: Record<string, string> = {};
for (const slot of timeSlots.value) {
let maxN = 1;
for (const day of weekDays.value) {
const evts = eventMap.value[day.dateKey]?.[slot.key] || [];
if (evts.length > maxN) maxN = evts.length;
}
// 每个课块至少 1.4rem,列宽 = 数量 × 1.4rem + 间距
widths[slot.key] = `${Math.max(1.8, maxN * 1.4 + (maxN - 1) * 0.03)}rem`;
}
return widths;
});
function eventStyle(ev: NormalizedClazzEvent) { function eventStyle(ev: NormalizedClazzEvent) {
return { return {
backgroundColor: ev.color || 'rgba(55, 136, 216, 0.12)', backgroundColor: ev.color || 'rgba(55, 136, 216, 0.12)',
@@ -169,7 +186,7 @@ export default defineComponent({
emit('event-click', ev); emit('event-click', ev);
} }
return { timeSlots, weekDays, eventMap, fmtTime, eventStyle, onCellClick, onEventClick }; return { timeSlots, weekDays, eventMap, columnWidths, fmtTime, eventStyle, onCellClick, onEventClick };
}, },
}); });
</script> </script>
@@ -306,8 +323,12 @@ export default defineComponent({
} }
} }
/* ─── 课块容器 ─── */ /* ─── 课块容器(横向排列) ─── */
.event-cell { .event-cell {
display: flex;
flex-direction: row;
gap: 0.03rem;
align-items: stretch;
padding: 0.03rem; padding: 0.03rem;
border-bottom: 1px solid #f5f5f5; border-bottom: 1px solid #f5f5f5;
border-left: 1px solid #f5f5f5; border-left: 1px solid #f5f5f5;
@@ -315,13 +336,12 @@ export default defineComponent({
} }
.event-block { .event-block {
flex: 1;
min-width: 0;
padding: 0.04rem 0.06rem; padding: 0.04rem 0.06rem;
border-radius: 0.04rem; border-radius: 0.04rem;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
margin-bottom: 0.02rem;
&:last-child { margin-bottom: 0; }
.ev-title { .ev-title {
font-weight: 600; font-weight: 600;
+6 -2
View File
@@ -307,6 +307,10 @@ export default defineComponent({
repeatList: computed(() => store.repeatList), repeatList: computed(() => store.repeatList),
subsidiaryClazzByHtyId: computed(() => store.subsidiaryClazzByHtyId), subsidiaryClazzByHtyId: computed(() => store.subsidiaryClazzByHtyId),
getViewingSubsidiaries: () => viewingSubsidiaries.value, getViewingSubsidiaries: () => viewingSubsidiaries.value,
getSubsidiaryColor: (htyId) => {
const idx = subsidiaries.value.findIndex(x => x.to_user_id === htyId);
return idx >= 0 ? Colors[idx] : undefined;
},
}); });
// 切换视图时缓存 // 切换视图时缓存
@@ -493,8 +497,8 @@ export default defineComponent({
const comments_count = computed(() => count_comments(store.current.id)) const comments_count = computed(() => count_comments(store.current.id))
const searchForSubsidiaries = async () => { const searchForSubsidiaries = async () => {
// 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)) { if (usingUser.store.currentRole !== HtyBaseRoles.TEACHER && usingUser.store.current.roles.some(r => r.role_key === HtySuperRoles.SUPERVISOR)) {
subsidiaries.value = usingSupervisor.store.subsidiaries; subsidiaries.value = usingSupervisor.store.subsidiaries;
if (!subsidiaries.value.length) { if (!subsidiaries.value.length) {
await usingSupervisor.getSubsidiaries(); await usingSupervisor.getSubsidiaries();
+7 -4
View File
@@ -98,8 +98,10 @@ export function useClazzViewModel(params: {
subsidiaryClazzByHtyId: ComputedRef<Record<string, { list: Clazz[]; repeatList: ClazzRepeatRow[] }>>; subsidiaryClazzByHtyId: ComputedRef<Record<string, { list: Clazz[]; repeatList: ClazzRepeatRow[] }>>;
/** Getter 避免 TDZ hoisting 问题 */ /** Getter 避免 TDZ hoisting 问题 */
getViewingSubsidiaries: () => string[]; getViewingSubsidiaries: () => string[];
/** 下属老师颜色映射,htyId → 颜色 */
getSubsidiaryColor?: (htyId: string) => string | undefined;
}) { }) {
const { list, repeatList, subsidiaryClazzByHtyId, getViewingSubsidiaries } = params; const { list, repeatList, subsidiaryClazzByHtyId, getViewingSubsidiaries, getSubsidiaryColor } = params;
/** 扁平化的标准化事件列表 */ /** 扁平化的标准化事件列表 */
const normalizedEvents = computed<NormalizedClazzEvent[]>(() => { const normalizedEvents = computed<NormalizedClazzEvent[]>(() => {
@@ -125,15 +127,16 @@ export function useClazzViewModel(params: {
// 下属老师的排课 // 下属老师的排课
for (const [htyId, data] of Object.entries(subsidiaryClazzByHtyId.value)) { for (const [htyId, data] of Object.entries(subsidiaryClazzByHtyId.value)) {
const hidden = !getViewingSubsidiaries().includes(htyId); const hidden = !getViewingSubsidiaries().includes(htyId);
const color = getSubsidiaryColor?.(htyId);
for (const item of data.list) { for (const item of data.list) {
if (item.is_delete) continue; if (item.is_delete) continue;
out.push(makeEvent(item, false, true, { subsidiaryTeacherId: htyId, visible: !hidden })); out.push(makeEvent(item, false, true, { subsidiaryTeacherId: htyId, visible: !hidden, color }));
} }
for (const row of data.repeatList) { for (const row of data.repeatList) {
if (row.is_delete) continue; if (row.is_delete) continue;
out.push(makeEvent(row, true, true, { subsidiaryTeacherId: htyId, visible: !hidden })); out.push(makeEvent(row, true, true, { subsidiaryTeacherId: htyId, visible: !hidden, color }));
if (row.instance && row.instance.id) { if (row.instance && row.instance.id) {
out.push(makeEvent(row.instance, false, true, { subsidiaryTeacherId: htyId, visible: !hidden })); out.push(makeEvent(row.instance, false, true, { subsidiaryTeacherId: htyId, visible: !hidden, color }));
} }
} }
} }