Version: 0.8.3.dev.260328
后端: 1.彻底删除原agent文件夹,并将现agent2文件夹全量重命名为agent(包括全部涉及到的文件以及文档、注释),迁移工作完美结束 2.修复了重试消息的相关逻辑问题 前端: 1.改善了一些交互体验,修复了一些bug,现在只剩少的功能了,现存的bug基本都修复完毕 全仓库: 1.更新了决策记录和README文档
This commit is contained in:
@@ -677,6 +677,27 @@ function resolveVisibleUserMessageBeforeAssistant(messageId: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
function findMessageIndexInList(messages: AssistantMessage[], messageId: string) {
|
||||
return messages.findIndex((message) => message.id === messageId)
|
||||
}
|
||||
|
||||
function resolveUserMessageBeforeAssistantInBucket(conversationId: string, assistantMessageId: string) {
|
||||
const bucket = conversationMessagesMap[conversationId] ?? []
|
||||
const index = findMessageIndexInList(bucket, assistantMessageId)
|
||||
if (index <= 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let current = index - 1; current >= 0; current -= 1) {
|
||||
const candidate = bucket[current]
|
||||
if (candidate?.role === 'user') {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function isLocalEphemeralMessageId(id: string) {
|
||||
return /^(user|assistant|system)-\d{13}-[a-z0-9]+$/i.test(id)
|
||||
}
|
||||
@@ -697,6 +718,81 @@ function resolvePersistedMessageId(message: AssistantMessage | null) {
|
||||
return message.id
|
||||
}
|
||||
|
||||
function resolveBestMatchedMessageFromBucket(conversationId: string, targetMessage: AssistantMessage) {
|
||||
const bucket = conversationMessagesMap[conversationId] ?? []
|
||||
const directMatchedMessage = bucket.find((message) => message.id === targetMessage.id)
|
||||
if (directMatchedMessage) {
|
||||
return directMatchedMessage
|
||||
}
|
||||
|
||||
const targetTimestamp = resolveMessageTimestamp(targetMessage)
|
||||
const logicalMatchedMessages = bucket
|
||||
.filter((message) => isSameLogicalMessage(message, targetMessage))
|
||||
.sort((left, right) => {
|
||||
// 1. 优先命中已经拿到后端稳定主键的消息,避免继续引用本地占位态。
|
||||
// 2. 若候选状态一致,则优先选择时间更接近原消息的那条。
|
||||
// 3. 时间也一致时再按较新的记录兜底,降低重复文案时误命中旧消息的概率。
|
||||
const persistedScoreDiff =
|
||||
Number(!isLocalEphemeralMessageId(right.id)) - Number(!isLocalEphemeralMessageId(left.id))
|
||||
if (persistedScoreDiff !== 0) {
|
||||
return persistedScoreDiff
|
||||
}
|
||||
|
||||
const leftGap = Math.abs(resolveMessageTimestamp(left) - targetTimestamp)
|
||||
const rightGap = Math.abs(resolveMessageTimestamp(right) - targetTimestamp)
|
||||
if (leftGap !== rightGap) {
|
||||
return leftGap - rightGap
|
||||
}
|
||||
|
||||
return resolveMessageTimestamp(right) - resolveMessageTimestamp(left)
|
||||
})
|
||||
|
||||
return logicalMatchedMessages[0] ?? null
|
||||
}
|
||||
|
||||
async function resolveRetrySourceMessages(
|
||||
conversationId: string,
|
||||
sourceUserMessage: AssistantMessage,
|
||||
sourceAssistantMessage: AssistantMessage,
|
||||
) {
|
||||
let resolvedUserMessage: AssistantMessage | null = sourceUserMessage
|
||||
let resolvedAssistantMessage: AssistantMessage | null = sourceAssistantMessage
|
||||
|
||||
let persistedUserMessageId = resolvePersistedMessageId(resolvedUserMessage)
|
||||
let persistedAssistantMessageId = resolvePersistedMessageId(resolvedAssistantMessage)
|
||||
|
||||
if (persistedUserMessageId && persistedAssistantMessageId) {
|
||||
return {
|
||||
sourceUserMessage: resolvedUserMessage,
|
||||
sourceAssistantMessage: resolvedAssistantMessage,
|
||||
persistedUserMessageId,
|
||||
persistedAssistantMessageId,
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 若当前点击时仍是本地占位消息,先静默拉一次权威历史,尽量把真实 ID 补回来。
|
||||
// 2. 这里复用现有 history 接口即可,避免为了一次重试再新增额外查询接口。
|
||||
// 3. 若静默刷新后依然拿不到稳定 ID,则说明消息大概率仍处于异步持久化窗口期。
|
||||
await loadConversationMessages(conversationId, true)
|
||||
|
||||
resolvedAssistantMessage =
|
||||
resolveBestMatchedMessageFromBucket(conversationId, sourceAssistantMessage) ?? sourceAssistantMessage
|
||||
resolvedUserMessage =
|
||||
resolveUserMessageBeforeAssistantInBucket(conversationId, resolvedAssistantMessage.id) ??
|
||||
resolveBestMatchedMessageFromBucket(conversationId, sourceUserMessage) ??
|
||||
sourceUserMessage
|
||||
|
||||
persistedUserMessageId = resolvePersistedMessageId(resolvedUserMessage)
|
||||
persistedAssistantMessageId = resolvePersistedMessageId(resolvedAssistantMessage)
|
||||
|
||||
return {
|
||||
sourceUserMessage: resolvedUserMessage,
|
||||
sourceAssistantMessage: resolvedAssistantMessage,
|
||||
persistedUserMessageId,
|
||||
persistedAssistantMessageId,
|
||||
}
|
||||
}
|
||||
|
||||
function createRetryGroupId() {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return `retry-${crypto.randomUUID()}`
|
||||
@@ -1407,32 +1503,36 @@ async function regenerateAssistantMessage(message: AssistantMessage) {
|
||||
}
|
||||
|
||||
const sourceUserMessage = resolveVisibleUserMessageBeforeAssistant(message.id)
|
||||
const text = sourceUserMessage?.content.trim() || ''
|
||||
const conversationId = selectedConversationId.value
|
||||
const persistedUserMessageId = resolvePersistedMessageId(sourceUserMessage)
|
||||
const persistedAssistantMessageId = resolvePersistedMessageId(message)
|
||||
if (!text || !conversationId || !sourceUserMessage) {
|
||||
if (!conversationId || !sourceUserMessage) {
|
||||
ElMessage.warning('没有找到可用于重试的用户消息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!persistedUserMessageId) {
|
||||
ElMessage.info('当前消息仍在本地态,稍后刷新完成后再试重试')
|
||||
const retrySource = await resolveRetrySourceMessages(conversationId, sourceUserMessage, message)
|
||||
const text = retrySource.sourceUserMessage?.content.trim() || sourceUserMessage.content.trim()
|
||||
if (!text) {
|
||||
ElMessage.warning('没有找到可用于重试的用户消息')
|
||||
return
|
||||
}
|
||||
|
||||
if (!persistedAssistantMessageId) {
|
||||
ElMessage.info('当前回复仍在本地态,稍后刷新完成后再试重试')
|
||||
if (!retrySource.persistedUserMessageId || !retrySource.persistedAssistantMessageId) {
|
||||
ElMessage.info('消息正在处理,请稍后再重试,或者直接复制消息重新发送')
|
||||
return
|
||||
}
|
||||
|
||||
chatLoading.value = true
|
||||
cancelEditUserMessage()
|
||||
|
||||
const retryGroup = resolveRetryPageGroup(message)
|
||||
const retryGroup = resolveRetryPageGroup(retrySource.sourceAssistantMessage)
|
||||
const retryGroupId = retryGroup?.groupId || createRetryGroupId()
|
||||
const nextRetryIndex = (retryGroup?.total ?? 1) + 1
|
||||
applyRetryGroupToExistingMessages(retryGroupId, nextRetryIndex, sourceUserMessage.id, message.id)
|
||||
applyRetryGroupToExistingMessages(
|
||||
retryGroupId,
|
||||
nextRetryIndex,
|
||||
retrySource.sourceUserMessage.id,
|
||||
retrySource.sourceAssistantMessage.id,
|
||||
)
|
||||
|
||||
const now = new Date().toISOString()
|
||||
appendConversationMessage(conversationId, {
|
||||
@@ -1464,8 +1564,8 @@ async function regenerateAssistantMessage(message: AssistantMessage) {
|
||||
try {
|
||||
const actualConversationId = await streamAssistantReply(conversationId, text, retryAssistantMessage, now, true, {
|
||||
retryGroupId,
|
||||
retryFromUserMessageId: persistedUserMessageId,
|
||||
retryFromAssistantMessageId: persistedAssistantMessageId,
|
||||
retryFromUserMessageId: retrySource.persistedUserMessageId,
|
||||
retryFromAssistantMessageId: retrySource.persistedAssistantMessageId,
|
||||
})
|
||||
await loadConversationMessages(actualConversationId, true)
|
||||
} catch (error) {
|
||||
@@ -3169,4 +3269,3 @@ onBeforeUnmount(() => {
|
||||
background: rgba(51, 95, 194, 0.16);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ function resolveDetailPanelStyle(items: TaskClassDetail['items']) {
|
||||
// 2. 条目超过“当前屏幕可安全展示的最大条数”后,立即锁住高度并进入内部滚动。
|
||||
// 3. 这样像 8 条 task_item 这类中等长度列表会稳定触发滚动,不会再因为估算过大而失效。
|
||||
return {
|
||||
height: `${finalHeight}px`,
|
||||
maxHeight: `${finalHeight}px`,
|
||||
}
|
||||
}
|
||||
@@ -299,13 +298,15 @@ watch(
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 14px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.task-class-sidebar__skeleton-item {
|
||||
flex: 0 0 auto;
|
||||
height: 120px;
|
||||
border-radius: 24px;
|
||||
background: linear-gradient(90deg, rgba(234, 239, 246, 0.9), rgba(248, 251, 255, 1), rgba(234, 239, 246, 0.9));
|
||||
@@ -314,6 +315,7 @@ watch(
|
||||
}
|
||||
|
||||
.task-class-card {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(216, 225, 238, 0.9);
|
||||
@@ -330,6 +332,7 @@ watch(
|
||||
.task-class-card__summary {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 92px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 18px 20px 18px 18px;
|
||||
@@ -394,7 +397,9 @@ watch(
|
||||
}
|
||||
|
||||
.task-class-card__detail {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
padding: 0 14px 14px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -490,6 +495,7 @@ watch(
|
||||
}
|
||||
|
||||
.task-class-sidebar__create {
|
||||
flex: 0 0 auto;
|
||||
min-width: 0;
|
||||
min-height: 108px;
|
||||
border: 1px dashed rgba(204, 216, 232, 0.92);
|
||||
@@ -542,6 +548,7 @@ watch(
|
||||
|
||||
.task-class-card__summary {
|
||||
padding: 16px 16px 16px 15px;
|
||||
min-height: 84px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +597,7 @@ watch(
|
||||
|
||||
.task-class-card__summary {
|
||||
padding: 14px 14px 14px 13px;
|
||||
min-height: 76px;
|
||||
}
|
||||
|
||||
.task-class-card__content {
|
||||
@@ -616,6 +624,7 @@ watch(
|
||||
|
||||
.task-class-card__summary {
|
||||
padding: 12px;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.task-class-card__corner {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import type { ScheduleWeekData, ScheduleWeekEvent } from '@/types/schedule'
|
||||
|
||||
@@ -15,18 +15,31 @@ interface SectionSlot {
|
||||
timeRange: string
|
||||
}
|
||||
|
||||
interface PreviewMovePayload {
|
||||
week: number
|
||||
sourceDayOfWeek: number
|
||||
sourceOrder: number
|
||||
targetDayOfWeek: number
|
||||
targetOrder: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
weekLabel: string
|
||||
weekHeaders: WeekDayHeader[]
|
||||
weekData: ScheduleWeekData | null
|
||||
scheduleSelectionMode: boolean
|
||||
selectedScheduleEventIds: number[]
|
||||
previewDragEnabled: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleScheduleEvent: [eventId: number]
|
||||
movePreviewEvent: [payload: PreviewMovePayload]
|
||||
}>()
|
||||
|
||||
const draggingCellKey = ref<string | null>(null)
|
||||
const dragOverCellKey = ref<string | null>(null)
|
||||
|
||||
const sectionSlots: SectionSlot[] = [
|
||||
{ order: 1, title: '1-2', timeRange: '08:00\n09:40' },
|
||||
{ order: 2, title: '3-4', timeRange: '10:15\n11:55' },
|
||||
@@ -54,11 +67,24 @@ function isSelected(eventId: number) {
|
||||
return props.selectedScheduleEventIds.includes(eventId)
|
||||
}
|
||||
|
||||
function hasEmbeddedTask(event?: ScheduleWeekEvent) {
|
||||
return Boolean(
|
||||
event &&
|
||||
event.type === 'course' &&
|
||||
event.embedded_task_info &&
|
||||
event.embedded_task_info.id > 0,
|
||||
)
|
||||
}
|
||||
|
||||
function resolveEventTone(event?: ScheduleWeekEvent) {
|
||||
if (!event || event.type === 'empty') {
|
||||
return 'empty'
|
||||
}
|
||||
|
||||
if (hasEmbeddedTask(event)) {
|
||||
return 'course-embedded'
|
||||
}
|
||||
|
||||
if (event.type === 'course') {
|
||||
return 'course'
|
||||
}
|
||||
@@ -88,6 +114,149 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
}
|
||||
return event.location || '未定'
|
||||
}
|
||||
|
||||
function resolveEmbeddedTaskName(event?: ScheduleWeekEvent) {
|
||||
if (!hasEmbeddedTask(event)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return event!.embedded_task_info.name
|
||||
}
|
||||
|
||||
// isSuggestedPreviewEvent 负责判断当前格子是否允许作为“拖拽源”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 这里只判断前端交互条件,不负责真正改写 preview JSON。
|
||||
// 2. 只有 preview 模式下的 suggested 条目才允许拖拽,正式课表与普通课程保持只读。
|
||||
function isSuggestedPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
return Boolean(
|
||||
props.previewDragEnabled &&
|
||||
!props.scheduleSelectionMode &&
|
||||
event &&
|
||||
event.status === 'suggested',
|
||||
)
|
||||
}
|
||||
|
||||
function isEmbeddedSuggestedPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
return Boolean(
|
||||
isSuggestedPreviewEvent(event) &&
|
||||
event &&
|
||||
event.type === 'course' &&
|
||||
hasEmbeddedTask(event),
|
||||
)
|
||||
}
|
||||
|
||||
function isWholeCellDraggable(event?: ScheduleWeekEvent) {
|
||||
return Boolean(isSuggestedPreviewEvent(event) && !isEmbeddedSuggestedPreviewEvent(event))
|
||||
}
|
||||
|
||||
// canDropPreviewEvent 负责判断当前格子是否允许作为“拖拽目标”。
|
||||
//
|
||||
// 设计说明:
|
||||
// 1. 空白格允许放置 suggested 任务。
|
||||
// 2. 课程格允许接收 suggested 任务,父组件会把它转换成“嵌入课程”的预览结构。
|
||||
// 3. suggested 格本身也允许作为目标,用于交换两个建议任务的位置。
|
||||
function canDropPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
if (!props.previewDragEnabled || props.scheduleSelectionMode) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!event || event.type === 'empty') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.status === 'suggested') {
|
||||
return true
|
||||
}
|
||||
|
||||
return event.type === 'course'
|
||||
}
|
||||
|
||||
function buildCellKey(dayOfWeek: number, order: number) {
|
||||
return `${dayOfWeek}-${order}`
|
||||
}
|
||||
|
||||
function handlePreviewDragStart(dayOfWeek: number, order: number, dragEvent: DragEvent) {
|
||||
const event = resolveEvent(dayOfWeek, order)
|
||||
if (!isSuggestedPreviewEvent(event) || !props.weekData) {
|
||||
dragEvent.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
draggingCellKey.value = buildCellKey(dayOfWeek, order)
|
||||
dragOverCellKey.value = null
|
||||
|
||||
dragEvent.dataTransfer?.setData(
|
||||
'application/json',
|
||||
JSON.stringify({
|
||||
week: props.weekData.week,
|
||||
sourceDayOfWeek: dayOfWeek,
|
||||
sourceOrder: order,
|
||||
}),
|
||||
)
|
||||
if (dragEvent.dataTransfer) {
|
||||
dragEvent.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewDragOver(dayOfWeek: number, order: number, dragEvent: DragEvent) {
|
||||
if (!draggingCellKey.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const cellKey = buildCellKey(dayOfWeek, order)
|
||||
if (cellKey === draggingCellKey.value || !canDropPreviewEvent(resolveEvent(dayOfWeek, order))) {
|
||||
return
|
||||
}
|
||||
|
||||
dragEvent.preventDefault()
|
||||
dragOverCellKey.value = cellKey
|
||||
if (dragEvent.dataTransfer) {
|
||||
dragEvent.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewDrop(dayOfWeek: number, order: number, dragEvent: DragEvent) {
|
||||
if (!draggingCellKey.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const cellKey = buildCellKey(dayOfWeek, order)
|
||||
const payloadText = dragEvent.dataTransfer?.getData('application/json')
|
||||
if (!payloadText || cellKey === draggingCellKey.value || !canDropPreviewEvent(resolveEvent(dayOfWeek, order))) {
|
||||
draggingCellKey.value = null
|
||||
dragOverCellKey.value = null
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(payloadText) as Partial<PreviewMovePayload>
|
||||
if (
|
||||
typeof payload.week !== 'number' ||
|
||||
typeof payload.sourceDayOfWeek !== 'number' ||
|
||||
typeof payload.sourceOrder !== 'number'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
dragEvent.preventDefault()
|
||||
emit('movePreviewEvent', {
|
||||
week: payload.week,
|
||||
sourceDayOfWeek: payload.sourceDayOfWeek,
|
||||
sourceOrder: payload.sourceOrder,
|
||||
targetDayOfWeek: dayOfWeek,
|
||||
targetOrder: order,
|
||||
})
|
||||
} finally {
|
||||
draggingCellKey.value = null
|
||||
dragOverCellKey.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewDragEnd() {
|
||||
draggingCellKey.value = null
|
||||
dragOverCellKey.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -119,8 +288,16 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
{
|
||||
'planning-board__cell--selectable': scheduleSelectionMode && resolveEvent(header.dayOfWeek, slot.order)?.type !== 'empty',
|
||||
'planning-board__cell--selected': resolveEvent(header.dayOfWeek, slot.order) && isSelected(resolveEvent(header.dayOfWeek, slot.order)!.id),
|
||||
'planning-board__cell--draggable': isWholeCellDraggable(resolveEvent(header.dayOfWeek, slot.order)),
|
||||
'planning-board__cell--dragging': draggingCellKey === buildCellKey(header.dayOfWeek, slot.order),
|
||||
'planning-board__cell--dragover': dragOverCellKey === buildCellKey(header.dayOfWeek, slot.order),
|
||||
},
|
||||
]"
|
||||
:draggable="isWholeCellDraggable(resolveEvent(header.dayOfWeek, slot.order))"
|
||||
@dragstart="handlePreviewDragStart(header.dayOfWeek, slot.order, $event)"
|
||||
@dragover="handlePreviewDragOver(header.dayOfWeek, slot.order, $event)"
|
||||
@drop="handlePreviewDrop(header.dayOfWeek, slot.order, $event)"
|
||||
@dragend="handlePreviewDragEnd"
|
||||
>
|
||||
<button
|
||||
v-if="scheduleSelectionMode && resolveEvent(header.dayOfWeek, slot.order)?.type !== 'empty'"
|
||||
@@ -131,7 +308,33 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
/>
|
||||
|
||||
<template v-if="resolveEvent(header.dayOfWeek, slot.order)">
|
||||
<div class="planning-board__cell-main">
|
||||
<div
|
||||
v-if="hasEmbeddedTask(resolveEvent(header.dayOfWeek, slot.order))"
|
||||
class="planning-board__embedded-shell"
|
||||
>
|
||||
<div class="planning-board__embedded-course">
|
||||
<strong>{{ resolveCellTitle(resolveEvent(header.dayOfWeek, slot.order)) }}</strong>
|
||||
</div>
|
||||
|
||||
<div class="planning-board__embedded-task">
|
||||
<strong
|
||||
class="planning-board__embedded-task-dragger"
|
||||
:class="{
|
||||
'planning-board__embedded-task-dragger--active': isEmbeddedSuggestedPreviewEvent(resolveEvent(header.dayOfWeek, slot.order)),
|
||||
}"
|
||||
:draggable="isEmbeddedSuggestedPreviewEvent(resolveEvent(header.dayOfWeek, slot.order))"
|
||||
@dragstart.stop="handlePreviewDragStart(header.dayOfWeek, slot.order, $event)"
|
||||
@dragend.stop="handlePreviewDragEnd"
|
||||
>
|
||||
{{ resolveEmbeddedTaskName(resolveEvent(header.dayOfWeek, slot.order)) }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="planning-board__cell-main"
|
||||
>
|
||||
<strong>{{ resolveCellTitle(resolveEvent(header.dayOfWeek, slot.order)) }}</strong>
|
||||
<span>{{ resolveCellMeta(resolveEvent(header.dayOfWeek, slot.order)) }}</span>
|
||||
</div>
|
||||
@@ -261,11 +464,85 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
background: #acd6f4;
|
||||
}
|
||||
|
||||
.planning-board__cell--course-embedded {
|
||||
background: linear-gradient(180deg, rgba(121, 187, 239, 0.96) 0%, rgba(88, 161, 225, 0.96) 100%);
|
||||
align-items: stretch;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.planning-board__cell--course .planning-board__cell-main strong,
|
||||
.planning-board__cell--course .planning-board__cell-main span {
|
||||
color: #2576cc;
|
||||
}
|
||||
|
||||
.planning-board__embedded-shell {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course,
|
||||
.planning-board__embedded-task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course {
|
||||
padding: 6px 4px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course strong,
|
||||
.planning-board__embedded-task strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course strong {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
line-height: 1.28;
|
||||
font-weight: 800;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task {
|
||||
padding: 6px 8px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 10px 18px rgba(31, 82, 145, 0.14);
|
||||
}
|
||||
|
||||
.planning-board__embedded-task strong {
|
||||
color: #1f5db3;
|
||||
font-size: 11px;
|
||||
line-height: 1.24;
|
||||
font-weight: 800;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task-dragger--active {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.planning-board__cell--amber {
|
||||
background: #ffe58b;
|
||||
}
|
||||
@@ -319,6 +596,20 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
box-shadow: inset 0 0 0 2px rgba(32, 102, 212, 0.52);
|
||||
}
|
||||
|
||||
.planning-board__cell--draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.planning-board__cell--dragging {
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.planning-board__cell--dragover {
|
||||
box-shadow:
|
||||
inset 0 0 0 2px rgba(20, 92, 192, 0.58),
|
||||
0 0 0 4px rgba(33, 109, 215, 0.1);
|
||||
}
|
||||
|
||||
.planning-board__checkbox {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
@@ -378,6 +669,14 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
.planning-board__cell-main strong {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course strong {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.planning-board__cell--course-embedded {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
@@ -433,5 +732,23 @@ function resolveCellMeta(event?: ScheduleWeekEvent) {
|
||||
.planning-board__cell {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task {
|
||||
padding: 5px 7px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course strong,
|
||||
.planning-board__embedded-task strong {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.planning-board__cell--course-embedded {
|
||||
padding: 7px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user