Version: 0.9.39.dev.260423
后端: 1. 记忆系统移除 todo_hint 类型——随口记已由 Task 系统承接,todo_hint 语义重叠且无完成追踪 - 全链路清理:常量、校验、默认重要度、30 天 TTL、读取预算、LLM 抽取提示词枚举 - 总预算从四类收缩为三类(preference / constraint / fact) 2. 记忆抽取触发点从 chat-persist 移至 graph-completion——避免随口记消息被误提取为 constraint/preference - chat-persist consumer 不再自动入队 memory.extract.requested,仅负责聊天历史落库 - graph 完成后新增条件发布:检测 UsedQuickNote 标记,调用过 quick_note_create 则跳过记忆抽取 - ResetForNextRun 重置 UsedQuickNote,防止跨轮残留导致后续正常消息记忆抽取被误跳过 3. 任务类查询接口返回 items 补充数据库主键 ID(前端拖拽编排依赖此字段) 前端: 4. 排程视图新增手动编排模式——侧边栏任务块拖拽入周课表 + 悬浮删除热区 + 建议块虚线标识 - TaskClassSidebar 拖拽发起 + 预览态嵌入时间格式化(含周次/星期) - WeekPlanningBoard 外部拖入 / 内部移动 / 悬浮删除区交互 - ScheduleView 手动编排状态机(进入/退出/取消/覆盖确认)+ apply 时同步处理新增与删除
This commit is contained in:
@@ -11,6 +11,7 @@ const props = defineProps<{
|
||||
expandedTaskClassDetail: TaskClassDetail | null
|
||||
selectedTaskClassIds: number[]
|
||||
taskClassMultiSelectMode: boolean
|
||||
manualEditMode: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -35,14 +36,45 @@ function isSelected(taskClassId: number) {
|
||||
}
|
||||
|
||||
function formatEmbeddedTime(value: TaskClassDetail['items'][number]['embedded_time']) {
|
||||
if (!value?.date) {
|
||||
if (!value && !(value as any)?._preview_week) {
|
||||
return '未安排'
|
||||
}
|
||||
|
||||
const weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
const weekNum = (value as any)?._preview_week
|
||||
const dayNum = (value as any)?._day_of_week
|
||||
|
||||
if (weekNum && dayNum) {
|
||||
return `第${weekNum}周 ${weekDays[dayNum - 1]} ${value?.section_from || 0}-${value?.section_to || 0}节`
|
||||
}
|
||||
|
||||
if (!value?.date) return '未安排'
|
||||
|
||||
const date = new Date(value.date)
|
||||
// getDay() 返回 0 (周日) 到 6 (周六)。 转换成我们的 1-7。
|
||||
const rawDay = date.getDay()
|
||||
const displayDay = rawDay === 0 ? 6 : rawDay - 1 // 对应 weekDays 索引
|
||||
|
||||
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
||||
const day = `${date.getDate()}`.padStart(2, '0')
|
||||
return `${month}.${day} ${value.section_from}-${value.section_to}节`
|
||||
return `${month}.${day} ${weekDays[displayDay]} ${value.section_from}-${value.section_to}节`
|
||||
}
|
||||
|
||||
function handleDragStart(item: TaskClassDetail['items'][number], dragEvent: DragEvent) {
|
||||
if (!props.manualEditMode) return
|
||||
|
||||
dragEvent.dataTransfer?.setData(
|
||||
'application/task-item',
|
||||
JSON.stringify({
|
||||
id: item.id,
|
||||
content: item.content,
|
||||
taskClassId: props.expandedTaskClassId,
|
||||
}),
|
||||
)
|
||||
if (dragEvent.dataTransfer) {
|
||||
dragEvent.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
function syncViewportHeight() {
|
||||
@@ -181,6 +213,9 @@ watch(
|
||||
v-for="item in expandedTaskClassDetail.items"
|
||||
:key="item.order"
|
||||
class="task-class-card__detail-item"
|
||||
:class="{ 'task-class-card__detail-item--draggable': manualEditMode }"
|
||||
:draggable="manualEditMode"
|
||||
@dragstart="handleDragStart(item, $event)"
|
||||
>
|
||||
<span class="task-class-card__detail-order">{{ item.order }}</span>
|
||||
<span class="task-class-card__detail-text">{{ item.content }}</span>
|
||||
@@ -471,6 +506,17 @@ watch(
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-class-card__detail-item--draggable {
|
||||
cursor: grab;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.task-class-card__detail-item--draggable:hover {
|
||||
border-color: #3b82f6;
|
||||
background: #f1f5f9;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.task-class-card__detail-order {
|
||||
color: #17253d;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -30,15 +30,20 @@ const props = defineProps<{
|
||||
scheduleSelectionMode: boolean
|
||||
selectedScheduleEventIds: number[]
|
||||
previewDragEnabled: boolean
|
||||
manualEditMode: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleScheduleEvent: [eventId: number]
|
||||
movePreviewEvent: [payload: PreviewMovePayload]
|
||||
dropTaskItem: [payload: { id: number; content: string; taskClassId: number; week: number; dayOfWeek: number; order: number }]
|
||||
removeEvent: [payload: { id: number; type: string; status?: string; week: number; dayOfWeek: number; order: number }]
|
||||
}>()
|
||||
|
||||
const draggingCellKey = ref<string | null>(null)
|
||||
const dragOverCellKey = ref<string | null>(null)
|
||||
const isDraggingOverDeleteZone = ref(false)
|
||||
const isExternalDragging = ref(false)
|
||||
|
||||
const sectionSlots: SectionSlot[] = [
|
||||
{ order: 1, title: '1-2', timeRange: '08:00\n09:40' },
|
||||
@@ -68,11 +73,12 @@ function isSelected(eventId: number) {
|
||||
}
|
||||
|
||||
function hasEmbeddedTask(event?: ScheduleWeekEvent) {
|
||||
const taskId = Number(event?.embedded_task_info?.id)
|
||||
return Boolean(
|
||||
event &&
|
||||
event.type === 'course' &&
|
||||
event.embedded_task_info &&
|
||||
event.embedded_task_info.id > 0,
|
||||
!isNaN(taskId) &&
|
||||
taskId > 0
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,7 +136,7 @@ function resolveEmbeddedTaskName(event?: ScheduleWeekEvent) {
|
||||
// 2. 只有 preview 模式下的 suggested 条目才允许拖拽,正式课表与普通课程保持只读。
|
||||
function isSuggestedPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
return Boolean(
|
||||
props.previewDragEnabled &&
|
||||
(props.previewDragEnabled || props.manualEditMode) &&
|
||||
!props.scheduleSelectionMode &&
|
||||
event &&
|
||||
event.status === 'suggested',
|
||||
@@ -147,7 +153,15 @@ function isEmbeddedSuggestedPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
}
|
||||
|
||||
function isWholeCellDraggable(event?: ScheduleWeekEvent) {
|
||||
return Boolean(isSuggestedPreviewEvent(event) && !isEmbeddedSuggestedPreviewEvent(event))
|
||||
if (props.scheduleSelectionMode || !event) return false
|
||||
|
||||
// 1. 建议块可拖拽
|
||||
if (event.status === 'suggested' && event.type !== 'course') return true
|
||||
|
||||
// 2. 已安排的任务块,仅在手动编辑模式下可拖拽(用于删除/移动)
|
||||
if (props.manualEditMode && event.type === 'task') return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// canDropPreviewEvent 负责判断当前格子是否允许作为“拖拽目标”。
|
||||
@@ -157,7 +171,11 @@ function isWholeCellDraggable(event?: ScheduleWeekEvent) {
|
||||
// 2. 课程格允许接收 suggested 任务,父组件会把它转换成“嵌入课程”的预览结构。
|
||||
// 3. suggested 格本身也允许作为目标,用于交换两个建议任务的位置。
|
||||
function canDropPreviewEvent(event?: ScheduleWeekEvent) {
|
||||
if (!props.previewDragEnabled || props.scheduleSelectionMode) {
|
||||
if (!props.manualEditMode && !props.previewDragEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (props.scheduleSelectionMode) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -178,18 +196,19 @@ function buildCellKey(dayOfWeek: number, order: number) {
|
||||
|
||||
function handlePreviewDragStart(dayOfWeek: number, order: number, dragEvent: DragEvent) {
|
||||
const event = resolveEvent(dayOfWeek, order)
|
||||
if (!isSuggestedPreviewEvent(event) || !props.weekData) {
|
||||
if (!isWholeCellDraggable(event) && !isEmbeddedSuggestedPreviewEvent(event)) {
|
||||
dragEvent.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
draggingCellKey.value = buildCellKey(dayOfWeek, order)
|
||||
dragOverCellKey.value = null
|
||||
isExternalDragging.value = false
|
||||
|
||||
dragEvent.dataTransfer?.setData(
|
||||
'application/json',
|
||||
JSON.stringify({
|
||||
week: props.weekData.week,
|
||||
week: props.weekData?.week ?? 0,
|
||||
sourceDayOfWeek: dayOfWeek,
|
||||
sourceOrder: order,
|
||||
}),
|
||||
@@ -200,10 +219,6 @@ function handlePreviewDragStart(dayOfWeek: number, order: number, dragEvent: Dra
|
||||
}
|
||||
|
||||
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
|
||||
@@ -211,17 +226,46 @@ function handlePreviewDragOver(dayOfWeek: number, order: number, dragEvent: Drag
|
||||
|
||||
dragEvent.preventDefault()
|
||||
dragOverCellKey.value = cellKey
|
||||
isDraggingOverDeleteZone.value = false
|
||||
if (dragEvent.dataTransfer) {
|
||||
dragEvent.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
function handleExternalDragOver(dragEvent: DragEvent) {
|
||||
if (dragEvent.dataTransfer?.types.includes('application/task-item')) {
|
||||
dragEvent.preventDefault()
|
||||
isExternalDragging.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewDrop(dayOfWeek: number, order: number, dragEvent: DragEvent) {
|
||||
if (!draggingCellKey.value) {
|
||||
const cellKey = buildCellKey(dayOfWeek, order)
|
||||
|
||||
// 1. 处理从侧边栏拖入的任务块
|
||||
const taskItemData = dragEvent.dataTransfer?.getData('application/task-item')
|
||||
if (taskItemData) {
|
||||
try {
|
||||
const payload = JSON.parse(taskItemData)
|
||||
// 强制转换 ID 为数字,确保后续匹配逻辑一致
|
||||
if (payload.id) payload.id = Number(payload.id)
|
||||
|
||||
dragEvent.preventDefault()
|
||||
emit('dropTaskItem', {
|
||||
...payload,
|
||||
week: props.weekData?.week ?? 0,
|
||||
dayOfWeek,
|
||||
order,
|
||||
})
|
||||
} finally {
|
||||
draggingCellKey.value = null
|
||||
dragOverCellKey.value = null
|
||||
isExternalDragging.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const cellKey = buildCellKey(dayOfWeek, order)
|
||||
// 2. 处理内部拖拽移动
|
||||
const payloadText = dragEvent.dataTransfer?.getData('application/json')
|
||||
if (!payloadText || cellKey === draggingCellKey.value || !canDropPreviewEvent(resolveEvent(dayOfWeek, order))) {
|
||||
draggingCellKey.value = null
|
||||
@@ -253,9 +297,45 @@ function handlePreviewDrop(dayOfWeek: number, order: number, dragEvent: DragEven
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragOverDeleteZone(dragEvent: DragEvent) {
|
||||
if (draggingCellKey.value) {
|
||||
dragEvent.preventDefault()
|
||||
isDraggingOverDeleteZone.value = true
|
||||
dragOverCellKey.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropOnDeleteZone(dragEvent: DragEvent) {
|
||||
if (!draggingCellKey.value) return
|
||||
|
||||
const payloadText = dragEvent.dataTransfer?.getData('application/json')
|
||||
if (!payloadText) return
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(payloadText)
|
||||
const event = resolveEvent(payload.sourceDayOfWeek, payload.sourceOrder)
|
||||
if (event) {
|
||||
dragEvent.preventDefault()
|
||||
emit('removeEvent', {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
status: event.status,
|
||||
week: payload.week,
|
||||
dayOfWeek: payload.sourceDayOfWeek,
|
||||
order: payload.sourceOrder,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
draggingCellKey.value = null
|
||||
isDraggingOverDeleteZone.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePreviewDragEnd() {
|
||||
draggingCellKey.value = null
|
||||
dragOverCellKey.value = null
|
||||
isDraggingOverDeleteZone.value = false
|
||||
isExternalDragging.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -265,7 +345,7 @@ function handlePreviewDragEnd() {
|
||||
<strong>{{ weekLabel }}</strong>
|
||||
</header>
|
||||
|
||||
<div class="planning-board__grid">
|
||||
<div class="planning-board__grid" @dragover="handleExternalDragOver">
|
||||
<div class="planning-board__corner" />
|
||||
|
||||
<div v-for="header in weekHeaders" :key="header.dayOfWeek" class="planning-board__day-head">
|
||||
@@ -290,6 +370,7 @@ function handlePreviewDragEnd() {
|
||||
'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--suggested': isSuggestedPreviewEvent(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),
|
||||
},
|
||||
@@ -302,10 +383,13 @@ function handlePreviewDragEnd() {
|
||||
@dragend="handlePreviewDragEnd"
|
||||
>
|
||||
<button
|
||||
v-if="scheduleSelectionMode && resolveEvent(header.dayOfWeek, slot.order)?.type !== 'empty'"
|
||||
v-if="(scheduleSelectionMode || manualEditMode) && resolveEvent(header.dayOfWeek, slot.order)?.type !== 'empty'"
|
||||
type="button"
|
||||
class="planning-board__checkbox"
|
||||
:class="{ 'planning-board__checkbox--active': isSelected(resolveEvent(header.dayOfWeek, slot.order)!.id) }"
|
||||
:class="{
|
||||
'planning-board__checkbox--active': isSelected(resolveEvent(header.dayOfWeek, slot.order)!.id),
|
||||
'planning-board__checkbox--hidden': manualEditMode && resolveEvent(header.dayOfWeek, slot.order)?.status !== 'suggested'
|
||||
}"
|
||||
@click="emit('toggleScheduleEvent', resolveEvent(header.dayOfWeek, slot.order)!.id)"
|
||||
/>
|
||||
|
||||
@@ -333,6 +417,14 @@ function handlePreviewDragEnd() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="resolveEvent(header.dayOfWeek, slot.order)?.type === 'task' || resolveEvent(header.dayOfWeek, slot.order)?.status === 'suggested'"
|
||||
class="planning-board__cell-main"
|
||||
>
|
||||
<strong>{{ resolveCellTitle(resolveEvent(header.dayOfWeek, slot.order)) }}</strong>
|
||||
<span>{{ resolveCellMeta(resolveEvent(header.dayOfWeek, slot.order)) }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="planning-board__cell-main"
|
||||
@@ -344,6 +436,25 @@ function handlePreviewDragEnd() {
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 悬浮删除热区 -->
|
||||
<transition name="delete-zone">
|
||||
<div
|
||||
v-if="draggingCellKey && manualEditMode"
|
||||
class="planning-board__delete-zone"
|
||||
:class="{ 'planning-board__delete-zone--active': isDraggingOverDeleteZone }"
|
||||
@dragover="handleDragOverDeleteZone"
|
||||
@dragleave="isDraggingOverDeleteZone = false"
|
||||
@drop="handleDropOnDeleteZone"
|
||||
>
|
||||
<span class="delete-zone-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 7L18.1327 19.1425C18.0579 20.1891 17.187 21 16.1378 21H7.86224C6.81296 21 5.94208 20.1891 5.86732 19.1425L5 7M10 11V17M14 11V17M15 7V4C15 3.44772 14.5523 3 14 3H10C9.44772 3 9 3.44772 9 4V7M4 7H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<strong>在此处松开以解除安排</strong>
|
||||
</div>
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -465,81 +576,89 @@ function handlePreviewDragEnd() {
|
||||
}
|
||||
|
||||
.planning-board__cell--course {
|
||||
background: #e0f2fe;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.planning-board__cell--course-embedded {
|
||||
background: #b9e6fe;
|
||||
background: #f0f7ff;
|
||||
align-items: stretch;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.planning-board__cell--suggested {
|
||||
outline: 2px dashed #3b82f6;
|
||||
outline-offset: -2px;
|
||||
background: #ffffff !important;
|
||||
box-shadow: inset 0 0 0 100px #eff6ffaa;
|
||||
}
|
||||
|
||||
.planning-board__cell--course-embedded.planning-board__cell--suggested {
|
||||
outline-color: #0284c7;
|
||||
background: #f0f9ff !important;
|
||||
}
|
||||
|
||||
.planning-board__cell--course .planning-board__cell-main strong,
|
||||
.planning-board__cell--course .planning-board__cell-main span {
|
||||
color: #0284c7;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.planning-board__embedded-shell {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
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;
|
||||
padding: 2px 4px;
|
||||
font-size: 13px;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.planning-board__embedded-course strong,
|
||||
.planning-board__embedded-task strong {
|
||||
min-width: 0;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-overflow: ellipsis;
|
||||
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: 10px;
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
|
||||
border: 1px solid rgba(15, 23, 42, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px;
|
||||
min-height: 0;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.planning-board__embedded-task strong {
|
||||
color: #0369a1;
|
||||
font-size: 11px;
|
||||
line-height: 1.24;
|
||||
font-weight: 800;
|
||||
-webkit-line-clamp: 2;
|
||||
.planning-board__embedded-task:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.12);
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task-dragger {
|
||||
font-size: 12px;
|
||||
color: #334155;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
padding: 2px 4px;
|
||||
cursor: grab;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task-dragger--active {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.planning-board__embedded-task-dragger--active {
|
||||
@@ -646,6 +765,60 @@ function handlePreviewDragEnd() {
|
||||
box-shadow: inset 0 0 0 3px #ffffff;
|
||||
}
|
||||
|
||||
.planning-board__checkbox--hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 悬浮删除区样式 */
|
||||
.planning-board__delete-zone {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 80px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
width: 280px;
|
||||
height: 64px;
|
||||
border-radius: 32px;
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 2px dashed rgba(255, 255, 255, 0.4);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
box-shadow: 0 12px 32px rgba(239, 68, 68, 0.3);
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.planning-board__delete-zone--active {
|
||||
background: #ef4444;
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
box-shadow: 0 16px 48px rgba(239, 68, 68, 0.45);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.delete-zone-icon {
|
||||
animation: delete-icon-shake 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes delete-icon-shake {
|
||||
0%, 100% { transform: rotate(0deg); }
|
||||
25% { transform: rotate(-10deg); }
|
||||
75% { transform: rotate(10deg); }
|
||||
}
|
||||
|
||||
.delete-zone-enter-active,
|
||||
.delete-zone-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.delete-zone-enter-from,
|
||||
.delete-zone-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(40px) scale(0.8);
|
||||
}
|
||||
|
||||
@keyframes board-item-spring {
|
||||
0% { opacity: 0; transform: scale(0.6) translateY(20px); }
|
||||
60% { opacity: 1; transform: scale(1.05) translateY(-2px); }
|
||||
|
||||
Reference in New Issue
Block a user