optimize+注释

This commit is contained in:
stardrophere
2026-03-13 23:48:49 +08:00
parent 6aee65af6c
commit da00ebb8f2
41 changed files with 874 additions and 174 deletions
+1
View File
@@ -1,3 +1,4 @@
<!-- 品牌 Logo雷达扫描风格 SVG带呼吸灯与旋转动画 -->
<template>
<div class="brand-logo-container">
<svg class="insight-logo" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+201
View File
@@ -0,0 +1,201 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const props = defineProps<{
modelValue: string | number
options: { label: string; value: string | number }[]
icon: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: string | number): void
}>()
const isOpen = ref(false)
const selectRef = ref<HTMLElement | null>(null)
const toggle = () => {
isOpen.value = !isOpen.value
}
const selectOption = (value: string | number) => {
emit('update:modelValue', value)
isOpen.value = false
}
const handleClickOutside = (event: MouseEvent) => {
if (selectRef.value && !selectRef.value.contains(event.target as Node)) {
isOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<template>
<div class="custom-select-wrapper" ref="selectRef">
<i :class="['select-icon', icon]"></i>
<div class="select-trigger" :class="{ 'is-open': isOpen }" @click="toggle">
<span class="selected-label">
{{ options.find(o => o.value === modelValue)?.label }}
</span>
<i class="fa-solid fa-chevron-down select-arrow"></i>
</div>
<transition name="dropdown">
<ul v-if="isOpen" class="select-dropdown">
<li
v-for="option in options"
:key="option.value"
class="select-option"
:class="{ 'is-active': option.value === modelValue }"
@click="selectOption(option.value)"
>
{{ option.label }}
</li>
</ul>
</transition>
</div>
</template>
<style scoped>
.custom-select-wrapper {
position: relative;
display: flex;
align-items: center;
min-width: 140px;
}
.select-icon {
position: absolute;
left: 14px;
color: var(--text-placeholder);
font-size: 14px;
pointer-events: none;
z-index: 2;
transition: color 0.2s;
}
.select-arrow {
position: absolute;
right: 12px;
color: var(--text-placeholder);
font-size: 11px;
pointer-events: none;
z-index: 2;
transition: color 0.2s, transform 0.2s;
}
.select-trigger {
width: 100%;
padding: 12px 34px 12px 38px;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
background-color: var(--bg-input);
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
user-select: none;
white-space: nowrap;
}
.select-trigger:hover {
border-color: var(--border-strong);
background-color: var(--bg-surface);
}
.select-trigger.is-open {
border-color: var(--brand-primary);
box-shadow: 0 0 0 3px var(--brand-primary-alpha);
background-color: var(--bg-surface);
}
.select-trigger.is-open .select-arrow {
transform: rotate(180deg);
color: var(--brand-primary);
}
.custom-select-wrapper:hover .select-icon,
.custom-select-wrapper:hover .select-arrow {
color: var(--brand-primary);
}
.select-dropdown {
position: absolute;
top: calc(100% + 8px);
left: 0;
width: 100%;
min-width: 100%;
margin: 0;
padding: 6px;
list-style: none;
background: var(--bg-surface);
backdrop-filter: var(--backdrop-blur, blur(12px));
-webkit-backdrop-filter: var(--backdrop-blur, blur(12px));
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
z-index: 100;
max-height: 250px;
overflow-y: auto;
}
.select-option {
padding: 10px 12px;
border-radius: var(--radius-md);
font-size: 14px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s;
margin-bottom: 2px;
white-space: nowrap;
}
.select-option:last-child {
margin-bottom: 0;
}
.select-option:hover {
background-color: var(--bg-input);
color: var(--text-primary);
}
.select-option.is-active {
background-color: var(--brand-primary-alpha);
color: var(--brand-primary);
font-weight: 600;
}
/* 滚动条美化 */
.select-dropdown::-webkit-scrollbar {
width: 4px;
}
.select-dropdown::-webkit-scrollbar-track {
background: transparent;
}
.select-dropdown::-webkit-scrollbar-thumb {
background-color: var(--border-strong);
border-radius: 4px;
}
/* 过渡动画 */
.dropdown-enter-active,
.dropdown-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.dropdown-enter-from,
.dropdown-leave-to {
opacity: 0;
transform: translateY(-8px);
}
</style>
+4 -4
View File
@@ -4,17 +4,17 @@ import { useThemeStore } from '@/stores/theme'
const themeStore = useThemeStore()
/**
* 切换主题,使用 View Transitions API 实现高级扩散动画(如果浏览器支持)
* 这种动画比之前像玩具一样的开关要高级得多,提供原生级的丝滑过渡
* 切换主题:支持 View Transitions API 时使用点击位置扩散动画,
* 否则直接切换
*/
function handleToggle(event: MouseEvent) {
// 检浏览器是否支持 document.startViewTransition 且用户没有开启减弱动画
// 检浏览器是否支持 View Transitions 且用户开启减弱动画
const isAppearanceTransition = typeof document !== 'undefined' &&
'startViewTransition' in document &&
!window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (!isAppearanceTransition) {
// 降级处理:直接切换
// 不支持时直接切换,无动画
themeStore.toggleTheme()
return
}
@@ -1,3 +1,4 @@
<!-- 统一事件卡片展示标题摘要平台来源排名轨迹悬停展开图表 -->
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'