Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cccf7a37be
commit
f6f7a0c3af
3 files changed
+37
-3
No files matched your search
@@ -1,11 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AchievementIcon from 'shared/components/AchievementIcon.vue'
|
import AchievementIcon from 'shared/components/AchievementIcon.vue'
|
||||||
|
import { mascotBox } from 'shared/mascot/guide'
|
||||||
import { useAchievementStore } from 'shared/store/achievement'
|
import { useAchievementStore } from 'shared/store/achievement'
|
||||||
import { RARITY_COLOR } from 'utils/constants'
|
import { RARITY_COLOR } from 'utils/constants'
|
||||||
|
|
||||||
const store = useAchievementStore()
|
const store = useAchievementStore()
|
||||||
const { current, queue } = storeToRefs(store)
|
const { current, queue } = storeToRefs(store)
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
const toastEl = ref<HTMLElement>()
|
||||||
|
|
||||||
|
const RIGHT = 24
|
||||||
|
const TOP = 72
|
||||||
|
const GAP = 12
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认在右上角。看板娘被拖到和它重叠的地方(多半是右上角)时,挪到她左边 ——
|
||||||
|
* 补发成就时一次会连着弹好几个,一直压在她头上不好看。尺寸按弹出来以后量到的算,
|
||||||
|
* 还没量到时按最小宽度和大致高度估。
|
||||||
|
*/
|
||||||
|
const placement = computed(() => {
|
||||||
|
if (!mascotBox.shown || !visible.value) return {}
|
||||||
|
const vw = document.documentElement.clientWidth
|
||||||
|
const width = toastEl.value?.offsetWidth ?? 300
|
||||||
|
const height = toastEl.value?.offsetHeight ?? 90
|
||||||
|
const left = vw - RIGHT - width
|
||||||
|
const overlaps = mascotBox.x < vw - RIGHT && mascotBox.x + mascotBox.width > left && mascotBox.y < TOP + height && mascotBox.y + mascotBox.height > TOP
|
||||||
|
return overlaps ? { right: `${vw - mascotBox.x + GAP}px` } : {}
|
||||||
|
})
|
||||||
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null
|
let timer: ReturnType<typeof setTimeout> | null = null
|
||||||
let gapTimer: ReturnType<typeof setTimeout> | null = null
|
let gapTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
@@ -37,7 +58,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Transition name="slide">
|
<Transition name="slide">
|
||||||
<div v-if="visible && current" class="toast" :style="{ borderColor: RARITY_COLOR[current.rarity] }">
|
<div v-if="visible && current" ref="toastEl" class="toast" :style="{ borderColor: RARITY_COLOR[current.rarity], ...placement }">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<AchievementIcon :icon="current.icon" :size="34" />
|
<AchievementIcon :icon="current.icon" :size="34" />
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +77,7 @@ onUnmounted(() => {
|
|||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
/* 右上角、顶栏下面。原来在右下角,题目页上正好和结果、点评弹窗叠在一起 */
|
/* 右上角、顶栏下面。原来在右下角,题目页上正好和结果、点评弹窗叠在一起 */
|
||||||
right: 24px;
|
right: 24px; /* 和脚本里的 RIGHT / TOP 一致 */
|
||||||
top: 72px;
|
top: 72px;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { TIME_ZONE_OFFSET_MINUTES } from '@oj2/contract'
|
|||||||
import imageUrl from 'shared/assets/mascot/whale.webp'
|
import imageUrl from 'shared/assets/mascot/whale.webp'
|
||||||
import patUrl from 'shared/assets/mascot/whale-pat.gif'
|
import patUrl from 'shared/assets/mascot/whale-pat.gif'
|
||||||
import { MASCOT_LINES, type MascotSituation } from 'shared/mascot/lines'
|
import { MASCOT_LINES, type MascotSituation } from 'shared/mascot/lines'
|
||||||
import { currentMascotGuide, fillLine, type MascotAction, type MascotGuide } from 'shared/mascot/guide'
|
import { currentMascotGuide, fillLine, mascotBox, type MascotAction, type MascotGuide } from 'shared/mascot/guide'
|
||||||
import { logMascotEvent, notePoke, personalize, refreshMascotProfile, speechRequest } from 'shared/mascot/behavior'
|
import { logMascotEvent, notePoke, personalize, refreshMascotProfile, speechRequest } from 'shared/mascot/behavior'
|
||||||
import { useAuthModalStore } from 'shared/store/authModal'
|
import { useAuthModalStore } from 'shared/store/authModal'
|
||||||
import { useCollabStore } from 'shared/store/collab'
|
import { useCollabStore } from 'shared/store/collab'
|
||||||
@@ -352,10 +352,15 @@ watch(silent, (quiet) => quiet && close())
|
|||||||
/** 她靠上时气泡往下冒,免得顶出屏幕;在左半边时气泡贴左对齐 */
|
/** 她靠上时气泡往下冒,免得顶出屏幕;在左半边时气泡贴左对齐 */
|
||||||
const bubbleBelow = computed(() => pos.y < 220)
|
const bubbleBelow = computed(() => pos.y < 220)
|
||||||
|
|
||||||
|
// 告诉别的浮层她在哪(成就弹框会让开)
|
||||||
|
watchEffect(() => Object.assign(mascotBox, { x: pos.x, y: pos.y, width: WIDTH, height: HEIGHT }))
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', settle, { passive: true })
|
window.addEventListener('resize', settle, { passive: true })
|
||||||
|
mascotBox.shown = true
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
mascotBox.shown = false
|
||||||
window.removeEventListener('resize', settle)
|
window.removeEventListener('resize', settle)
|
||||||
clearTimeout(patTimer)
|
clearTimeout(patTimer)
|
||||||
clearTimeout(quietTimer)
|
clearTimeout(quietTimer)
|
||||||
|
|||||||
@@ -81,3 +81,11 @@ export function randomProblemAction(router: Router): MascotAction {
|
|||||||
export function goAction(router: Router, label: string, to: string): MascotAction {
|
export function goAction(router: Router, label: string, to: string): MascotAction {
|
||||||
return { label, run: () => router.push(to) }
|
return { label, run: () => router.push(to) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 别的浮层要让开她 ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 看板娘现在占的地方(视口坐标,拖动中也跟着变)。不在页面上(手机、没挂载)时 shown 为 false。
|
||||||
|
* 成就弹框靠它让开:学生把她拖进右上角时,弹框正好压在她头上。
|
||||||
|
*/
|
||||||
|
export const mascotBox = reactive({ shown: false, x: 0, y: 0, width: 0, height: 0 })
|
||||||
Reference in new issue
Block a user