Compare commits

...

5 Commits

Author SHA1 Message Date
xiangshu233
b47617226e fix(navbar): 🐛 fix(navbar): 调整底部导航选中框尺寸以避免与分割线重叠 2026-03-24 22:12:04 +08:00
xiangshu233
b01e46b64f fix(ui,router): 🐛 修复移动端底部遮挡并消除路由导航警告
调整登录页滚动容器与底部安全区留白,避免小屏下底部 UI 被遮挡

为“我的”页面增加底部留白,防止“退出登录”被悬浮导航覆盖

将全局路由守卫改为 Vue Router 返回值写法,移除 next() 过时用法

在悬浮导航中避免重复跳转当前路由,消除 redundant navigation 告警
2026-03-24 21:43:05 +08:00
xiangshu233
1019242a0c fix(view): 🐛 修复小屏幕下登录页底部内容被遮挡问题 2026-03-24 21:10:08 +08:00
乡树
23e3c9d369
Update README.md 2026-03-24 20:41:30 +08:00
xiangshu233
fbc12f02f9 refactor: ♻️ 升级依赖并重构导航与主题交互
升级核心依赖与构建链路(Vite/Vue/Pinia/VueUse/Vant 等)并完成兼容调整

Vibe Coding 了一个导航组件 FloatingNavBar

修复暗黑模式 switch 动画异常:统一 useDark 行为并设置 disableTransition: false,恢复 Vant Switch 过渡动画

调整亮色模式下页面背景与卡片层次(以边框+轻阴影为主),降低过强对比并提升一致性

我的页面入口 icon 统一为线性风格
2026-03-24 20:36:10 +08:00
28 changed files with 3328 additions and 2146 deletions

3
.gitignore vendored
View File

@ -23,4 +23,5 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
/components.d.ts /components.d.ts
/components.d.ts .agents/
skills-lock.json

View File

@ -20,7 +20,7 @@
## 介绍 ## 介绍
👋👋👋 Vue3 Vant4 Mobile 使用了最新的 `Vue3.4`、`Vite5`、`Vant4``Pinia``TypeScript``UnoCSS` 等主流技术开发,集成 `Dark Mode`(暗黑)模式和系统主题色,并且持久化保存,集成 `Mock` 数据,顺便写了登录/注册/找回密码 页面(包括逻辑),只需替换你的 API 即可,另外页面均可以 `<keep-alive>`,随便写了个包含 `NavBar``TabBar` 的 Layout集成了 `Axios``useECharts``IconSvg` 👋👋👋 Vue3 Vant4 Mobile 使用了最新的 `Vue3.5`、`Vite8`、`Vant4``Pinia``TypeScript``UnoCSS` 等主流技术开发,集成 `Dark Mode`(暗黑)模式和系统主题色,并且持久化保存,集成 `Mock` 数据,顺便写了登录/注册/找回密码 页面(包括逻辑),只需替换你的 API 即可,另外页面均可以 `<keep-alive>`,随便写了个包含 `NavBar``TabBar` 的 Layout集成了 `Axios``useECharts``IconSvg`
项目使用了 [antfu](https://github.com/antfu) 大佬的 [antfu/eslint-config](https://github.com/antfu/eslint-config) 作为代码规范检查工具,摆脱繁琐无聊的 Eslint 配置,配合 `cz-git``lint-staged``simple-git-hooks`可对暂存区代码提交校验,代码风格不合格可打断提交,保证多人协作开发时上游 Git 库的干净。 项目使用了 [antfu](https://github.com/antfu) 大佬的 [antfu/eslint-config](https://github.com/antfu/eslint-config) 作为代码规范检查工具,摆脱繁琐无聊的 Eslint 配置,配合 `cz-git``lint-staged``simple-git-hooks`可对暂存区代码提交校验,代码风格不合格可打断提交,保证多人协作开发时上游 Git 库的干净。

View File

@ -18,7 +18,7 @@
"url": "https://github.com/xiangshu233/vue3-vant4-mobile/issues" "url": "https://github.com/xiangshu233/vue3-vant4-mobile/issues"
}, },
"engines": { "engines": {
"node": "^20.9.0 || >=21.7.1", "node": "^20.19.0 || >=22.12.0",
"pnpm": ">=8.15.4" "pnpm": ">=8.15.4"
}, },
"scripts": { "scripts": {
@ -40,44 +40,44 @@
}, },
"dependencies": { "dependencies": {
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@unocss/reset": "^0.58.5", "@vueuse/core": "^14.2.1",
"@vueuse/core": "^10.7.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"date-fns": "^3.0.6", "date-fns": "^3.0.6",
"echarts": "^5.4.3", "echarts": "^5.4.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^3.0.4",
"pinia-plugin-persist": "^1.0.0", "pinia-plugin-persistedstate": "^4.7.1",
"qs": "^6.11.2", "qs": "^6.11.2",
"vant": "^4.8.1", "vant": "^4.9.22",
"vue": "^3.3.13", "vue": "^3.5.30",
"vue-router": "4.2.5" "vue-router": "^5.0.4"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.6.3", "@antfu/eslint-config": "^2.6.3",
"@commitlint/cli": "^18.4.3", "@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3", "@commitlint/config-conventional": "^18.4.3",
"@iconify/json": "^2.2.188", "@iconify/json": "^2.2.188",
"@oxc-parser/binding-win32-x64-msvc": "0.115.0",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/mockjs": "^1.0.10", "@types/mockjs": "^1.0.10",
"@types/node": "^20.10.5", "@types/node": "^25.5.0",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.11", "@types/qs": "^6.9.11",
"@unocss/eslint-plugin": "^0.58.4", "@unocss/eslint-plugin": "^66.6.7",
"@unocss/preset-icons": "^0.58.5", "@unocss/preset-icons": "^66.6.7",
"@unocss/preset-rem-to-px": "^0.58.5", "@unocss/preset-rem-to-px": "^66.6.7",
"@unocss/transformer-directives": "^0.58.4", "@unocss/reset": "^66.6.7",
"@unocss/transformer-variant-group": "^0.58.4", "@unocss/transformer-directives": "^66.6.7",
"@vitejs/plugin-vue": "^5.0.0", "@unocss/transformer-variant-group": "^66.6.7",
"@vitejs/plugin-vue": "^6.0.5",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cz-git": "^1.8.0", "cz-git": "^1.8.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-plugin-format": "^0.1.0", "eslint-plugin-format": "^0.1.0",
"tsx": "^3.0.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"less": "^4.2.0", "less": "^4.2.0",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.0",
@ -89,16 +89,18 @@
"rollup": "^4.9.1", "rollup": "^4.9.1",
"rollup-plugin-visualizer": "^5.11.0", "rollup-plugin-visualizer": "^5.11.0",
"simple-git-hooks": "^2.9.0", "simple-git-hooks": "^2.9.0",
"typescript": "^5.3.3", "terser": "^5.46.1",
"unocss": "^0.58.5", "tsx": "^4.21.0",
"unplugin-auto-import": "^0.17.5", "typescript": "^5.9.3",
"unplugin-vue-components": "^0.26.0", "unocss": "^66.6.7",
"vite": "^5.0.10", "unplugin-auto-import": "^21.0.0",
"unplugin-vue-components": "^32.0.0",
"vite": "^8.0.2",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2",
"vite-plugin-mock": "^2.9.8", "vite-plugin-mock": "^2.9.8",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^1.8.27" "vue-tsc": "^3.2.6"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"pre-commit": "pnpm lint-staged", "pre-commit": "pnpm lint-staged",

3795
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
<template> <template>
<vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()"> <vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()">
<routerView v-slot="{ Component }"> <RouterView v-slot="{ Component }">
<div class="absolute bottom-0 top-0 w-full overflow-hidden"> <div class="absolute bottom-0 top-0 w-full overflow-hidden">
<transition :name="getTransitionName" mode="out-in" appear> <transition :name="getTransitionName" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents"> <KeepAlive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </KeepAlive>
</transition> </transition>
</div> </div>
</routerView> </RouterView>
</vanConfigProvider> </vanConfigProvider>
</template> </template>

View File

@ -4,44 +4,35 @@
</svg> </svg>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
export default defineComponent({ defineOptions({ name: 'SvgIcon' })
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 16,
},
color: {
type: String,
default: '#333',
},
},
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const getStyle = computed((): CSSProperties => { const props = withDefaults(
const { size } = props defineProps<{
let s = `${size}` prefix?: string
s = `${s.replace('px', '')}px` name: string
return { size?: number | string
width: s, color?: string
height: s, }>(),
} {
}) prefix: 'icon',
size: 16,
return { symbolId, getStyle } color: '#333',
}, },
)
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const getStyle = computed((): CSSProperties => {
const { size } = props
let s = `${size}`
s = `${s.replace('px', '')}px`
return {
width: s,
height: s,
}
}) })
</script> </script>

View File

@ -0,0 +1,548 @@
<template>
<div class="floating-toolbar-wrap">
<nav
ref="toolbarRef"
class="floating-toolbar"
:class="{ dark: isDarkTheme }"
:style="toolbarCssVars"
aria-label="主导航"
>
<div class="ambient-glow" />
<div class="film-grain" />
<div
ref="indicatorRef"
class="active-indicator"
:style="{
transform: `translateX(${indicatorX}px)`,
opacity: activeNavIndex >= 0 ? 1 : 0,
}"
>
<div class="ring-glow" />
<div class="ring-clip">
<div class="ring-spin" />
</div>
<div class="inner-plate" />
</div>
<button
v-for="(item, idx) in navItems"
:key="item.path"
:ref="(el) => setNavRef(el, idx)"
type="button"
class="nav-btn"
:class="{
'active': idx === activeNavIndex,
'with-divider': idx < navItems.length - 1,
'nav-bounce': navBounceIndex === idx,
}"
:aria-label="item.label"
@click="handleNavClick(idx, item.path)"
>
<i :class="item.icon" />
</button>
<button
v-if="props.showDarkModeToggle"
type="button"
class="nav-btn toggle-btn"
:class="{ bounce: toggleBouncing }"
aria-label="切换主题"
@click="toggleTheme"
>
<span class="toggle-icon-wrap">
<i class="i-ph:sun-dim sun-icon" :class="{ hidden: darkModel }" />
<i class="i-ph:moon moon-icon" :class="{ show: darkModel }" />
</span>
</button>
</nav>
</div>
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useDark } from '@vueuse/core'
import { NavigationFailureType, isNavigationFailure, useRoute, useRouter } from 'vue-router'
import { useDesignSettingStore } from '@/store/modules/designSetting'
interface NavItem {
label: string
path: string
icon: string
}
interface Props {
items?: NavItem[]
showDarkModeToggle?: boolean
}
const props = withDefaults(defineProps<Props>(), {
items: () => [
{ label: 'Home', path: '/dashboard/index', icon: 'i-ph:house-line' },
{ label: 'Search', path: '/message/index', icon: 'i-ph:magnifying-glass' },
{ label: 'User', path: '/my/index', icon: 'i-ph:user-circle' },
],
showDarkModeToggle: true,
})
const designStore = useDesignSettingStore()
const currentRoute = useRoute()
const router = useRouter()
const navItems = computed(() => props.items)
const activeNavIndex = computed(() => {
const path = currentRoute.path
const items = navItems.value
if (items.length === 0) {
return -1
}
const index = items.findIndex((item) => {
const basePath = item.path.replace(/\/index$/, '')
return path === item.path || path.startsWith(basePath)
})
return index >= 0 ? index : 0
})
const toolbarRef = ref<HTMLElement | null>(null)
const indicatorRef = ref<HTMLElement | null>(null)
const navRefs = ref<(HTMLElement | null)[]>([])
const indicatorX = ref(0)
const isDark = useDark({
valueDark: 'dark',
valueLight: 'light',
disableTransition: false,
})
const darkModel = computed({
get: () => isDark.value || designStore.darkMode === 'dark',
set: (value: boolean) => {
isDark.value = value
designStore.setDarkMode(value ? 'dark' : 'light')
},
})
const isDarkTheme = computed(() => darkModel.value)
const toolbarColumns = computed(() => Math.max(1, navItems.value.length + (props.showDarkModeToggle ? 1 : 0)))
const toolbarCssVars = computed(() => ({
'--accent-color': designStore.appTheme,
'--accent-soft-color': `${designStore.appTheme}33`,
'--toolbar-max-width': toolbarColumns.value >= 5 ? '430px' : '340px',
'gridTemplateColumns': `repeat(${toolbarColumns.value}, 1fr)`,
}))
const toggleBouncing = ref(false)
const navBounceIndex = ref<number | null>(null)
let toggleTimer: number | null = null
let navBounceTimer: number | null = null
let resizeObserver: ResizeObserver | null = null
function setNavRef(el: Element | { $el?: Element } | null, index: number) {
if (!el) {
navRefs.value[index] = null
return
}
const element = el instanceof HTMLElement
? el
: ('$el' in el ? (el.$el as HTMLElement | undefined) : undefined)
navRefs.value[index] = element ?? null
}
function updateIndicatorPosition() {
const indicator = indicatorRef.value
const target = navRefs.value[activeNavIndex.value]
if (!indicator || !target) {
return
}
const indicatorWidth = indicator.offsetWidth || 48
indicatorX.value = target.offsetLeft + target.offsetWidth / 2 - indicatorWidth / 2
}
function scheduleIndicatorUpdate() {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
updateIndicatorPosition()
})
})
}
function bindResizeObserver() {
if (typeof ResizeObserver === 'undefined') {
return
}
if (resizeObserver) {
resizeObserver.disconnect()
}
resizeObserver = new ResizeObserver(() => {
scheduleIndicatorUpdate()
})
if (toolbarRef.value) {
resizeObserver.observe(toolbarRef.value)
}
if (indicatorRef.value) {
resizeObserver.observe(indicatorRef.value)
}
for (const navEl of navRefs.value) {
if (navEl) {
resizeObserver.observe(navEl)
}
}
}
function normalizePath(path: string) {
return path.replace(/\/+$/, '') || '/'
}
function goNav(path: string) {
// Avoid redundant navigation warnings when clicking current tab.
if (normalizePath(currentRoute.path) === normalizePath(path)) {
return
}
router.push(path).catch((err) => {
if (!isNavigationFailure(err, NavigationFailureType.duplicated)) {
throw err
}
})
}
function handleNavClick(index: number, path: string) {
navBounceIndex.value = null
if (navBounceTimer) {
window.clearTimeout(navBounceTimer)
}
void nextTick(() => {
navBounceIndex.value = index
navBounceTimer = window.setTimeout(() => {
navBounceIndex.value = null
}, 420)
})
goNav(path)
}
function toggleTheme() {
if (!props.showDarkModeToggle) {
return
}
toggleBouncing.value = false
if (toggleTimer) {
window.clearTimeout(toggleTimer)
}
void nextTick(() => {
toggleBouncing.value = true
toggleTimer = window.setTimeout(() => {
toggleBouncing.value = false
}, 420)
})
darkModel.value = !darkModel.value
}
watch(
() => currentRoute.path,
async () => {
await nextTick()
bindResizeObserver()
scheduleIndicatorUpdate()
},
{ immediate: false },
)
watch(activeNavIndex, async () => {
await nextTick()
scheduleIndicatorUpdate()
})
onMounted(async () => {
await nextTick()
bindResizeObserver()
scheduleIndicatorUpdate()
window.addEventListener('resize', scheduleIndicatorUpdate)
const storeDark = designStore.darkMode === 'dark'
if (isDark.value !== storeDark) {
isDark.value = storeDark
}
})
onBeforeUnmount(() => {
if (toggleTimer) {
window.clearTimeout(toggleTimer)
}
if (navBounceTimer) {
window.clearTimeout(navBounceTimer)
}
if (resizeObserver) {
resizeObserver.disconnect()
resizeObserver = null
}
window.removeEventListener('resize', scheduleIndicatorUpdate as () => void)
})
</script>
<style scoped lang="less">
.floating-toolbar-wrap {
pointer-events: none;
position: fixed;
z-index: 20;
left: 0;
right: 0;
bottom: calc(14px + env(safe-area-inset-bottom));
display: flex;
justify-content: center;
}
.floating-toolbar {
pointer-events: auto;
position: relative;
width: min(94vw, var(--toolbar-max-width));
height: 62px;
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: center;
padding: 6px;
border-radius: 31px;
background: rgba(16, 20, 30, 0.86);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(20px) saturate(140%);
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
overflow: hidden;
}
.floating-toolbar.dark {
background: rgba(16, 20, 30, 0.86);
border-color: rgba(255, 255, 255, 0.08);
}
.floating-toolbar:not(.dark) {
background: rgba(255, 255, 255, 0.9);
border-color: rgba(24, 39, 75, 0.12);
box-shadow: 0 6px 16px rgba(41, 58, 88, 0.08);
}
.ambient-glow {
position: absolute;
inset: -18px;
background: radial-gradient(
72% 100% at 50% 112%,
var(--accent-soft-color),
transparent 70%
);
pointer-events: none;
}
.film-grain {
position: absolute;
inset: 0;
opacity: 0.06;
pointer-events: none;
mix-blend-mode: soft-light;
background-image: radial-gradient(
circle at 22% 28%,
rgba(255, 255, 255, 0.38) 0.35px,
transparent 1px
),
radial-gradient(
circle at 78% 72%,
rgba(255, 255, 255, 0.26) 0.4px,
transparent 1px
);
background-size:
4px 4px,
5px 5px;
}
.active-indicator {
position: absolute;
left: 0;
top: 50%;
width: 62px;
height: 38px;
margin-top: -19px;
transition: transform 0.28s ease;
pointer-events: none;
}
.ring-glow {
position: absolute;
inset: 0;
border-radius: 22px;
background: var(--accent-soft-color);
opacity: 0.22;
filter: none;
box-shadow: none;
}
.ring-clip {
position: absolute;
inset: 0;
border-radius: 22px;
overflow: visible;
}
.ring-spin {
position: absolute;
inset: 0;
border-radius: 22px;
border: 1px solid var(--accent-color);
opacity: 0.35;
animation: none;
background: transparent;
}
.inner-plate {
position: absolute;
inset: 0;
border-radius: 22px;
background: transparent;
}
.floating-toolbar:not(.dark) .inner-plate {
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(24, 39, 75, 0.1);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.8),
0 2px 8px rgba(31, 41, 55, 0.12);
}
.nav-btn {
position: relative;
z-index: 2;
width: 100%;
height: 100%;
border: none;
background: transparent;
display: grid;
place-items: center;
color: rgba(255, 255, 255, 0.45);
font-size: 20px;
cursor: pointer;
transition: color 0.25s ease;
}
.floating-toolbar:not(.dark) .nav-btn {
color: rgba(44, 59, 88, 0.72);
}
.nav-btn.active {
color: var(--accent-color);
}
.floating-toolbar:not(.dark) .nav-btn.active {
color: #1f2937;
}
.nav-btn.with-divider::after {
content: '';
position: absolute;
right: 0;
top: 15px;
width: 1px;
height: 30px;
background: linear-gradient(
180deg,
transparent,
rgba(255, 255, 255, 0.14),
transparent
);
}
.floating-toolbar:not(.dark) .nav-btn.with-divider::after {
background: linear-gradient(
180deg,
transparent,
rgba(36, 54, 88, 0.12),
transparent
);
}
.theme-switch-wrap {
position: relative;
z-index: 2;
display: grid;
place-items: center;
}
.toggle-btn {
color: rgba(255, 255, 255, 0.6);
}
.floating-toolbar:not(.dark) .toggle-btn {
color: rgba(44, 59, 88, 0.78);
}
.floating-toolbar:not(.dark) .ring-glow {
background: rgba(255, 255, 255, 0.95);
opacity: 0.75;
}
.floating-toolbar:not(.dark) .ring-spin {
border-color: rgba(24, 39, 75, 0.14);
opacity: 1;
}
.toggle-icon-wrap {
position: relative;
width: 22px;
height: 22px;
}
.sun-icon,
.moon-icon {
position: absolute;
inset: 0;
transition:
opacity 0.28s ease,
transform 0.32s ease;
}
.sun-icon.hidden {
opacity: 0;
transform: rotate(90deg) scale(0.6);
}
.moon-icon {
opacity: 0;
transform: rotate(-90deg) scale(0.6);
}
.moon-icon.show {
opacity: 1;
transform: rotate(0deg) scale(1);
}
.toggle-btn.bounce {
animation: toggle-bounce 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
}
.nav-btn.nav-bounce i {
animation: nav-icon-bounce 0.42s cubic-bezier(0.34, 1.2, 0.64, 1);
}
@keyframes toggle-bounce {
0% {
transform: scale(1);
}
35% {
transform: scale(1.25);
}
70% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
@keyframes nav-icon-bounce {
0% {
transform: scale(1);
}
35% {
transform: scale(1.2);
}
70% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
}
</style>

View File

@ -1,66 +1,59 @@
<!-- eslint-disable prettier/prettier -->
<template> <template>
<div class="h-screen flex flex-col"> <div
<van-nav-bar v-if="getShowHeader" placeholder fixed :title="getTitle" /> class="layout-shell h-screen flex flex-col"
<routerView class="flex-1 overflow-x-hidden"> :class="{ dark: designStore.darkMode === 'dark' }"
>
<RouterView class="flex-1 overflow-x-hidden">
<template #default="{ Component, route }"> <template #default="{ Component, route }">
<!-- <KeepAlive v-if="keepAliveComponents" :include="keepAliveComponents">
keep-alive 标签的 include 属性是根据组件的 name 判断的
所以 index.vue list.vue 等页面 vue 文件里一定要写上 name
并且与 router 路由表中使用的 name 属性 一致否则无效
Vue 3.3 中新引入了 defineOptions 宏声明 name 属性
https://gist.github.com/sxzz/3995fc7251567c7c95de35f45539b9c2
-->
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" /> <component :is="Component" :key="route.fullPath" />
</keep-alive> </KeepAlive>
<component :is="Component" v-else :key="route.fullPath" /> <component :is="Component" v-else :key="route.fullPath" />
</template> </template>
</routerView> </RouterView>
<van-tabbar route class="tabbar">
<van-tabbar-item <!--
v-for="menu in getMenus" <van-tabbar route>
:key="menu.name" <van-tabbar-item icon="home-o" to="/dashboard/index">
replace 首页
:to="menu.path" </van-tabbar-item>
> <van-tabbar-item icon="search" to="/message/index">
<template #icon> 消息
<i :class="menu.meta?.icon" /> </van-tabbar-item>
</template> <van-tabbar-item icon="user-o" to="/my/index">
{{ menu.meta?.title }} 我的
</van-tabbar-item> </van-tabbar-item>
</van-tabbar> </van-tabbar>
-->
<FloatingNavBar :items="tabbarItems" :show-dark-mode-toggle="true" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ComputedRef } from 'vue'
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute } from 'vue-router' import FloatingNavBar from './components/FloatingNavBar.vue'
import type { RouteRecordRaw } from 'vue-router'
import { useRouteStore } from '@/store/modules/route' import { useRouteStore } from '@/store/modules/route'
import { useDesignSettingStore } from '@/store/modules/designSetting'
const routeStore = useRouteStore() const routeStore = useRouteStore()
// const designStore = useDesignSettingStore()
const keepAliveComponents = computed(() => routeStore.keepAliveComponents) const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
const currentRoute = useRoute()
const getTitle = computed(() => currentRoute.meta.title as string) const tabbarItems = [
{ label: 'Home', path: '/dashboard/index', icon: 'i-ph:house-line' },
// { label: 'Example', path: '/example/index', icon: 'i-ph:flask' },
const getMenus: ComputedRef<RouteRecordRaw[]> = computed(() => { label: 'Search', path: '/message/index', icon: 'i-ph:magnifying-glass' },
routeStore.menus.filter((item) => { { label: 'User', path: '/my/index', icon: 'i-ph:user-circle' },
return !item.meta?.innerPage ]
}),
)
const getShowHeader = computed(() => !currentRoute.meta.hiddenHeader)
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.tabbar { .layout-shell {
bottom: 0; background: #f7f8fa;
width: 100%; }
position: relative;
.layout-shell.dark {
background: #000000;
} }
</style> </style>

View File

@ -10,7 +10,7 @@ const routeModuleList: Array<RouteRecordRaw> = [
component: Layout, component: Layout,
meta: { meta: {
title: '主控台', title: '主控台',
icon: 'i-simple-icons:atlassian', icon: 'i-ph:house',
}, },
children: [ children: [
{ {
@ -30,7 +30,7 @@ const routeModuleList: Array<RouteRecordRaw> = [
component: Layout, component: Layout,
meta: { meta: {
title: '图表', title: '图表',
icon: 'i-simple-icons:soundcharts', icon: 'i-ph:chart-line',
}, },
children: [ children: [
{ {
@ -50,7 +50,7 @@ const routeModuleList: Array<RouteRecordRaw> = [
component: Layout, component: Layout,
meta: { meta: {
title: '示例', title: '示例',
icon: 'i-material-symbols:award-star', icon: 'i-ph:code',
}, },
children: [ children: [
{ {
@ -70,7 +70,7 @@ const routeModuleList: Array<RouteRecordRaw> = [
component: Layout, component: Layout,
meta: { meta: {
title: '我的', title: '我的',
icon: 'i-simple-icons:docsify', icon: 'i-ph:user',
}, },
children: [ children: [
{ {

View File

@ -15,29 +15,26 @@ const LOGIN_PATH = PageEnum.BASE_LOGIN
const whitePathList = [LOGIN_PATH] // no redirect whitelist const whitePathList = [LOGIN_PATH] // no redirect whitelist
export function createRouterGuards(router: Router) { export function createRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from) => {
// to: 即将要进入的目标 // to: 即将要进入的目标
// from: 当前导航正要离开的路由 // from: 当前导航正要离开的路由
NProgress.start() NProgress.start()
const userStore = useUserStoreWithOut() const userStore = useUserStoreWithOut()
if (from.path === LOGIN_PATH && to.name === PageEnum.ERROR_PAGE_NAME) { if (from.path === LOGIN_PATH && to.name === PageEnum.ERROR_PAGE_NAME) {
next(PageEnum.BASE_HOME) return PageEnum.BASE_HOME
return
} }
// Whitelist can be directly entered // Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) { if (whitePathList.includes(to.path as PageEnum)) {
next() return true
return
} }
const token = storage.get(ACCESS_TOKEN) const token = storage.get(ACCESS_TOKEN)
if (!token) { if (!token) {
// redirect login page // redirect login page
next(LOGIN_PATH) return LOGIN_PATH
return
} }
// 当上次更新时间为空时获取用户信息 // 当上次更新时间为空时获取用户信息
@ -46,12 +43,11 @@ export function createRouterGuards(router: Router) {
await userStore.GetUserInfo() await userStore.GetUserInfo()
} }
catch (err) { catch (err) {
next() return true
return
} }
} }
next() return true
}) })
// 进入某个路由之后触发的钩子 // 进入某个路由之后触发的钩子

View File

@ -1,9 +1,9 @@
import type { App } from 'vue' import type { App } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const store = createPinia() const store = createPinia()
store.use(piniaPersist) store.use(piniaPluginPersistedstate)
export function setupStore(app: App<Element>) { export function setupStore(app: App<Element>) {
app.use(store) app.use(store)

View File

@ -5,8 +5,7 @@ import type { DesignSettingState } from '@/settings/designSetting'
const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
export const useDesignSettingStore = defineStore({ export const useDesignSettingStore = defineStore('app-design-setting', {
id: 'app-design-setting',
state: (): DesignSettingState => ({ state: (): DesignSettingState => ({
darkMode, darkMode,
appTheme, appTheme,
@ -41,13 +40,8 @@ export const useDesignSettingStore = defineStore({
}, },
// 持久化 // 持久化
persist: { persist: {
enabled: true, key: 'DESIGN-SETTING',
strategies: [ storage: localStorage,
{
key: 'DESIGN-SETTING',
storage: localStorage,
},
],
}, },
}) })

View File

@ -8,8 +8,7 @@ export interface IRouteState {
keepAliveComponents: string[] keepAliveComponents: string[]
} }
export const useRouteStore = defineStore({ export const useRouteStore = defineStore('app-route', {
id: 'app-route',
state: (): IRouteState => ({ state: (): IRouteState => ({
menus: [], menus: [],
routers: [], routers: [],

View File

@ -33,8 +33,7 @@ interface LoginParams {
password: string password: string
} }
export const useUserStore = defineStore({ export const useUserStore = defineStore('app-user', {
id: 'app-user',
state: (): IUserState => ({ state: (): IUserState => ({
userInfo: null, userInfo: null,
token: undefined, token: undefined,

View File

@ -17,8 +17,8 @@ html.dark {
} }
body { body {
background-color: #121212; background-color: #000000;
color: var(--van-text-color) !important; color: rgba(255, 255, 255, 0.9) !important;
} }
} }
@ -93,15 +93,37 @@ a:hover {
} }
html.light { html.light {
body {
background-color: #f5f6fa;
}
.my-card { .my-card {
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 70%); background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(24, 39, 75, 0.1);
box-shadow: 0 4px 10px rgba(41, 58, 88, 0.05);
} }
} }
html.dark { html.dark {
.my-card { .my-card {
backdrop-filter: blur(10px); backdrop-filter: blur(12px);
background: rgba(30, 30, 30, 70%); background: rgba(18, 18, 18, 0.85);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
.van-cell-group--inset {
border-radius: 14px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.08);
}
.van-cell-group--inset .van-cell {
background: rgba(18, 18, 18, 0.6);
}
.van-cell-group .van-cell::after {
border-color: rgba(255, 255, 255, 0.08);
} }
} }

View File

@ -1,8 +1,7 @@
<template> <template>
<div class="h-screen flex flex-col items-center justify-center p-60px"> <div class="h-screen flex flex-col items-center justify-center p-60px">
<div class="wel-box w-full flex flex-col items-center justify-between"> <div class="wel-box w-full flex flex-col items-center justify-between">
<Logo class="!h-30 !w-30" /> <div class="page-title mb-4 mt-12 text-center">
<div class="text-darkBlue dark:text-garyWhite mb-4 mt-12 text-center text-2xl font-black">
{{ title }} {{ title }}
</div> </div>
<div class="mb-6 mt-4 w-full"> <div class="mb-6 mt-4 w-full">
@ -10,12 +9,12 @@
<van-swipe-item <van-swipe-item
v-for="(text, index) in getSwipeText" v-for="(text, index) in getSwipeText"
:key="index" :key="index"
class="text-center text-gray-700 leading-relaxed dark:text-gray-400" class="swipe-item text-center leading-relaxed"
> >
<p class="text-lg"> <p class="swipe-title">
{{ text.title }} {{ text.title }}
</p> </p>
<p class="text-sm"> <p class="swipe-details">
{{ text.details }} {{ text.details }}
</p> </p>
</van-swipe-item> </van-swipe-item>
@ -28,7 +27,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useDesignSettingStore } from '@/store/modules/designSetting' import { useDesignSettingStore } from '@/store/modules/designSetting'
import { useGlobSetting } from '@/hooks/setting' import { useGlobSetting } from '@/hooks/setting'
import Logo from '@/components/Logo.vue'
defineOptions({ defineOptions({
name: 'DashboardPage', name: 'DashboardPage',
@ -77,4 +75,56 @@ const getSwipeText = computed(() => {
}) })
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less">
.page-title {
font-size: 1.25rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
}
html.light .page-title {
color: #1a1a2e;
}
html.dark .page-title {
color: rgba(255, 255, 255, 0.95);
}
html.light .page-title::after,
html.dark .page-title::after {
content: '';
display: block;
width: 40px;
height: 2px;
margin: 8px auto 0;
background: linear-gradient(
90deg,
transparent,
v-bind('designStore.appTheme'),
transparent
);
border-radius: 1px;
}
.swipe-item {
padding: 0 8px;
}
html.light .swipe-title {
color: #333;
}
html.dark .swipe-title {
color: rgba(255, 255, 255, 0.88);
}
html.light .swipe-details {
color: #666;
}
html.dark .swipe-details {
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
}
</style>

View File

@ -1,12 +1,11 @@
<template> <template>
<div class="my-4"> <div class="example-page my-4 pb-24">
<van-cell-group inset> <van-cell-group inset>
<van-cell center title="🌓 暗黑模式"> <van-cell center title="🌓 暗黑模式">
<template #right-icon> <template #right-icon>
<i inline-block align-middle i="dark:carbon-moon carbon-sun" /> <i inline-block align-middle i="dark:carbon-moon carbon-sun" />
<span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span> <span class="mx-2">{{ isDark ? 'Dark' : 'Light' }}</span>
<span class="mx-2">{{ isDark }}</span> <van-switch v-model="darkSwitch" size="22" />
<van-switch v-model="checked" size="22" @click="toggle()" />
</template> </template>
</van-cell> </van-cell>
<template v-for="item in menuItems" :key="item.route"> <template v-for="item in menuItems" :key="item.route">
@ -17,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useDark, useToggle } from '@vueuse/core' import { useDark } from '@vueuse/core'
import { useDesignSettingStore } from '@/store/modules/designSetting' import { useDesignSettingStore } from '@/store/modules/designSetting'
const designStore = useDesignSettingStore() const designStore = useDesignSettingStore()
@ -25,16 +24,16 @@ const designStore = useDesignSettingStore()
const isDark = useDark({ const isDark = useDark({
valueDark: 'dark', valueDark: 'dark',
valueLight: 'light', valueLight: 'light',
disableTransition: false,
}) })
const checked = ref(isDark.value) const darkSwitch = computed({
get: () => isDark.value,
const toggleDark = useToggle(isDark) set: (value: boolean) => {
isDark.value = value
function toggle() { designStore.setDarkMode(value ? 'dark' : 'light')
toggleDark() },
designStore.setDarkMode(isDark.value ? 'dark' : 'light') })
}
const menuItems = [ const menuItems = [
{ title: '🐗 keep-alive', route: '/editNickname' }, { title: '🐗 keep-alive', route: '/editNickname' },

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div class="login-page">
<div class="h-screen flex justify-center p-8"> <div class="login-scroll px-8 pt-8">
<div class="w-full flex flex-col"> <div class="w-full flex flex-col">
<LoginTitle /> <LoginTitle />
<LoginForm /> <LoginForm />
@ -21,6 +21,22 @@ import LoginWave from './LoginWave.vue'
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.login-page {
position: relative;
height: 100dvh;
overflow-y: auto;
}
.login-scroll {
min-height: 100%;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 120px);
-webkit-overflow-scrolling: touch;
}
:deep(.wave-wrapper) {
pointer-events: none;
}
:deep(.van-field__left-icon) { :deep(.van-field__left-icon) {
display: flex; display: flex;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="my-card m-40px rounded-2xl p-30px shadow-xl"> <div class="my-card m-16px rounded-2xl p-24px">
<div ref="chartRef" :style="{ height: '350px' }" /> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
@ -7,11 +7,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { EChartsOption } from 'echarts' import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts' import { useECharts } from '@/hooks/web/useECharts'
import { useDesignSettingStore } from '@/store/modules/designSetting'
const chartRef = ref<HTMLDivElement | null>(null) const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>) const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const designStore = useDesignSettingStore()
const chartColors = [designStore.appTheme, '#4a9eff', '#5dd9a8', '#ff9f7a', '#7ec8e3', '#b8a9e0']
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
color: chartColors,
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {

View File

@ -1,5 +1,8 @@
<template> <template>
<div> <div class="message-page pb-24">
<div class="section-title">
数据概览
</div>
<lineChart /> <lineChart />
<barChart /> <barChart />
<pieChart /> <pieChart />
@ -12,4 +15,29 @@ import barChart from './barChart.vue'
import pieChart from './pieChart.vue' import pieChart from './pieChart.vue'
</script> </script>
<style scoped></style> <style scoped lang="less">
.section-title {
padding: 24px 16px 16px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.15em;
text-transform: uppercase;
}
html.light .section-title {
color: #555;
}
html.dark .section-title {
color: rgba(255, 255, 255, 0.5);
}
html.dark .section-title::after {
content: '';
display: block;
width: 24px;
height: 1px;
margin-top: 6px;
background: var(--van-primary-color);
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="my-card m-40px rounded-2xl p-30px shadow-xl"> <div class="my-card m-16px rounded-2xl p-24px">
<div ref="chartRef" :style="{ height: '350px' }" /> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
@ -7,11 +7,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { EChartsOption } from 'echarts' import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts' import { useECharts } from '@/hooks/web/useECharts'
import { useDesignSettingStore } from '@/store/modules/designSetting'
const chartRef = ref<HTMLDivElement | null>(null) const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>) const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const designStore = useDesignSettingStore()
const chartColors = [designStore.appTheme, '#4a9eff', '#5dd9a8', '#ff9f7a', '#7ec8e3']
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
color: chartColors,
title: { title: {
text: 'Stacked Area Chart', text: 'Stacked Area Chart',
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="my-card m-40px rounded-2xl p-30px shadow-xl"> <div class="my-card m-16px rounded-2xl p-24px">
<div ref="chartRef" :style="{ height: '350px' }" /> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
@ -7,11 +7,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { EChartsOption } from 'echarts' import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts' import { useECharts } from '@/hooks/web/useECharts'
import { useDesignSettingStore } from '@/store/modules/designSetting'
const chartRef = ref<HTMLDivElement | null>(null) const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>) const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const designStore = useDesignSettingStore()
const chartColors = [designStore.appTheme, '#4a9eff', '#5dd9a8', '#ff9f7a', '#7ec8e3', '#b8a9e0']
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
color: chartColors,
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
}, },

View File

@ -68,7 +68,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useDark, useToggle } from '@vueuse/core' import { useDark } from '@vueuse/core'
import NavBar from './components/NavBar.vue' import NavBar from './components/NavBar.vue'
import { useDesignSettingStore } from '@/store/modules/designSetting' import { useDesignSettingStore } from '@/store/modules/designSetting'
import { animates as animateOptions } from '@/settings/animateSetting' import { animates as animateOptions } from '@/settings/animateSetting'
@ -78,15 +78,14 @@ const designStore = useDesignSettingStore()
const isDark = useDark({ const isDark = useDark({
valueDark: 'dark', valueDark: 'dark',
valueLight: 'light', valueLight: 'light',
disableTransition: false,
}) })
const toggleDark = useToggle(isDark)
const getDarkMode = computed({ const getDarkMode = computed({
get: () => isDark.value, get: () => isDark.value,
set: () => { set: (value: boolean) => {
toggleDark() isDark.value = value
designStore.setDarkMode(isDark.value ? 'dark' : 'light') designStore.setDarkMode(value ? 'dark' : 'light')
}, },
}) })

View File

@ -1,8 +1,8 @@
<template> <template>
<div> <div class="my-page">
<div :style="getUserCoverBg" class="my-bg h-70" /> <div :style="getUserCoverBg" class="my-bg h-70" />
<div <div
class="my-card relative mx-6 flex flex-col items-center rounded-2xl pb-2 shadow-xl -top-18" class="my-card relative mx-5 flex flex-col items-center rounded-2xl pb-2 -top-18"
> >
<van-image <van-image
class="h-22 w-22 border-2 border-solid !absolute -top-10" class="h-22 w-22 border-2 border-solid !absolute -top-10"
@ -11,42 +11,42 @@
:src="avatar" :src="avatar"
/> />
<div class="mt-14 flex flex-col items-center"> <div class="mt-14 flex flex-col items-center">
<p class="mb-2 text-5 font-black"> <p class="profile-name mb-2 text-5 font-semibold">
{{ nickname }} {{ nickname }}
</p> </p>
<p class="text-4"> <p class="profile-sign text-4">
{{ sign }} {{ sign }}
</p> </p>
</div> </div>
<van-divider class="w-full" /> <van-divider class="profile-divider w-full" />
<van-cell :border="false" title="个人信息" is-link to="/editUserInfo"> <van-cell :border="false" title="个人信息" is-link to="/editUserInfo">
<template #icon> <template #icon>
<i class="i-mingcute:idcard-fill mr-2 text-xl" /> <i class="i-ph:user-circle mr-2 text-xl" />
</template> </template>
</van-cell> </van-cell>
<van-cell :border="false" title="账号与安全" is-link to="/accountSetting"> <van-cell :border="false" title="账号与安全" is-link to="/accountSetting">
<template #icon> <template #icon>
<i class="i-material-symbols:account-box mr-2 text-xl" /> <i class="i-ph:shield-check mr-2 text-xl" />
</template> </template>
</van-cell> </van-cell>
<van-cell :border="false" title="主题设置" is-link to="/themeSetting"> <van-cell :border="false" title="主题设置" is-link to="/themeSetting">
<template #icon> <template #icon>
<i class="i-material-symbols:palette mr-2 text-xl" /> <i class="i-ph:palette mr-2 text-xl" />
</template> </template>
</van-cell> </van-cell>
<van-cell :border="false" title="隐私政策" is-link> <van-cell :border="false" title="隐私政策" is-link>
<template #icon> <template #icon>
<i class="i-material-symbols:list-alt-rounded mr-2 text-xl" /> <i class="i-ph:file-text mr-2 text-xl" />
</template> </template>
</van-cell> </van-cell>
<van-cell :border="false" title="退出登录" is-link @click="showLogoutAction = true"> <van-cell :border="false" title="退出登录" is-link @click="showLogoutAction = true">
<template #icon> <template #icon>
<i class="i-solar:logout-3-bold mr-2 text-xl" /> <i class="i-ph:sign-out mr-2 text-xl" />
</template> </template>
</van-cell> </van-cell>
@ -88,6 +88,11 @@ const getUserCoverBg = computed(() => {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.my-page {
min-height: 100%;
padding-bottom: calc(110px + env(safe-area-inset-bottom));
}
.my-bg { .my-bg {
clip-path: inset(0 -55% 0 -55% round 0 0 100% 100%); clip-path: inset(0 -55% 0 -55% round 0 0 100% 100%);
background-size: cover; background-size: cover;
@ -100,10 +105,22 @@ const getUserCoverBg = computed(() => {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), #000); background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), #000);
opacity: 0.9; opacity: 0.92;
} }
} }
html.dark .profile-name {
color: rgba(255, 255, 255, 0.95);
}
html.dark .profile-sign {
color: rgba(255, 255, 255, 0.5);
}
html.dark .profile-divider {
border-color: rgba(255, 255, 255, 0.12);
}
.van-cell { .van-cell {
align-items: center; align-items: center;
background: transparent; background: transparent;
@ -112,4 +129,16 @@ const getUserCoverBg = computed(() => {
background-color: var(--van-cell-active-color); background-color: var(--van-cell-active-color);
} }
} }
html.dark .van-cell {
color: rgba(255, 255, 255, 0.85);
}
html.dark .van-cell__right-icon {
color: rgba(255, 255, 255, 0.5);
}
html.dark .van-cell:active {
background-color: rgba(255, 255, 255, 0.08);
}
</style> </style>

View File

@ -6,7 +6,7 @@
"useDefineForClassFields": true, "useDefineForClassFields": true,
"baseUrl": ".", "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "bundler",
"paths": { "paths": {
"@/*": ["src/*"], "@/*": ["src/*"],
"#/*": ["types/*"] "#/*": ["types/*"]

View File

@ -3,302 +3,317 @@
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue').EffectScope
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] const autoResetRef: typeof import('@vueuse/core').autoResetRef
const computed: typeof import('vue')['computed'] const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core')['computedAsync'] const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core')['computedEager'] const computedEager: typeof import('@vueuse/core').computedEager
const computedInject: typeof import('@vueuse/core')['computedInject'] const computedInject: typeof import('@vueuse/core').computedInject
const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] const computedWithControl: typeof import('@vueuse/core').computedWithControl
const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] const controlledComputed: typeof import('@vueuse/core').controlledComputed
const controlledRef: typeof import('@vueuse/core')['controlledRef'] const controlledRef: typeof import('@vueuse/core').controlledRef
const createApp: typeof import('vue')['createApp'] const createApp: typeof import('vue').createApp
const createEventHook: typeof import('@vueuse/core')['createEventHook'] const createEventHook: typeof import('@vueuse/core').createEventHook
const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] const createGlobalState: typeof import('@vueuse/core').createGlobalState
const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] const createInjectionState: typeof import('@vueuse/core').createInjectionState
const createPinia: typeof import('pinia')['createPinia'] const createPinia: typeof import('pinia').createPinia
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] const createRef: typeof import('@vueuse/core').createRef
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] const createSharedComposable: typeof import('@vueuse/core').createSharedComposable
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise
const customRef: typeof import('vue')['customRef'] const createUnrefFn: typeof import('@vueuse/core').createUnrefFn
const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] const customRef: typeof import('vue').customRef
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] const debouncedRef: typeof import('@vueuse/core').debouncedRef
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineComponent: typeof import('vue')['defineComponent'] const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineStore: typeof import('pinia')['defineStore'] const defineComponent: typeof import('vue').defineComponent
const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] const defineStore: typeof import('pinia').defineStore
const effectScope: typeof import('vue')['effectScope'] const eagerComputed: typeof import('@vueuse/core').eagerComputed
const extendRef: typeof import('@vueuse/core')['extendRef'] const effectScope: typeof import('vue').effectScope
const getActivePinia: typeof import('pinia')['getActivePinia'] const extendRef: typeof import('@vueuse/core').extendRef
const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentInstance: typeof import('vue').getCurrentInstance
const h: typeof import('vue')['h'] const getCurrentScope: typeof import('vue').getCurrentScope
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const inject: typeof import('vue')['inject'] const h: typeof import('vue').h
const injectLocal: typeof import('@vueuse/core')['injectLocal'] const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const isDefined: typeof import('@vueuse/core')['isDefined'] const inject: typeof import('vue').inject
const isProxy: typeof import('vue')['isProxy'] const injectLocal: typeof import('@vueuse/core').injectLocal
const isReactive: typeof import('vue')['isReactive'] const isDefined: typeof import('@vueuse/core').isDefined
const isReadonly: typeof import('vue')['isReadonly'] const isProxy: typeof import('vue').isProxy
const isRef: typeof import('vue')['isRef'] const isReactive: typeof import('vue').isReactive
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] const isReadonly: typeof import('vue').isReadonly
const mapActions: typeof import('pinia')['mapActions'] const isRef: typeof import('vue').isRef
const mapGetters: typeof import('pinia')['mapGetters'] const isShallow: typeof import('vue').isShallow
const mapState: typeof import('pinia')['mapState'] const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
const mapStores: typeof import('pinia')['mapStores'] const mapActions: typeof import('pinia').mapActions
const mapWritableState: typeof import('pinia')['mapWritableState'] const mapGetters: typeof import('pinia').mapGetters
const markRaw: typeof import('vue')['markRaw'] const mapState: typeof import('pinia').mapState
const nextTick: typeof import('vue')['nextTick'] const mapStores: typeof import('pinia').mapStores
const onActivated: typeof import('vue')['onActivated'] const mapWritableState: typeof import('pinia').mapWritableState
const onBeforeMount: typeof import('vue')['onBeforeMount'] const markRaw: typeof import('vue').markRaw
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const nextTick: typeof import('vue').nextTick
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onActivated: typeof import('vue').onActivated
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onDeactivated: typeof import('vue')['onDeactivated'] const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] const onClickOutside: typeof import('@vueuse/core').onClickOutside
const onLongPress: typeof import('@vueuse/core')['onLongPress'] const onDeactivated: typeof import('vue').onDeactivated
const onMounted: typeof import('vue')['onMounted'] const onElementRemoval: typeof import('@vueuse/core').onElementRemoval
const onRenderTracked: typeof import('vue')['onRenderTracked'] const onErrorCaptured: typeof import('vue').onErrorCaptured
const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onKeyStroke: typeof import('@vueuse/core').onKeyStroke
const onScopeDispose: typeof import('vue')['onScopeDispose'] const onLongPress: typeof import('@vueuse/core').onLongPress
const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onMounted: typeof import('vue').onMounted
const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] const onRenderTracked: typeof import('vue').onRenderTracked
const onUnmounted: typeof import('vue')['onUnmounted'] const onRenderTriggered: typeof import('vue').onRenderTriggered
const onUpdated: typeof import('vue')['onUpdated'] const onScopeDispose: typeof import('vue').onScopeDispose
const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] const onServerPrefetch: typeof import('vue').onServerPrefetch
const provide: typeof import('vue')['provide'] const onStartTyping: typeof import('@vueuse/core').onStartTyping
const provideLocal: typeof import('@vueuse/core')['provideLocal'] const onUnmounted: typeof import('vue').onUnmounted
const reactify: typeof import('@vueuse/core')['reactify'] const onUpdated: typeof import('vue').onUpdated
const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const reactive: typeof import('vue')['reactive'] const pausableWatch: typeof import('@vueuse/core').pausableWatch
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] const provide: typeof import('vue').provide
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] const provideLocal: typeof import('@vueuse/core').provideLocal
const reactivePick: typeof import('@vueuse/core')['reactivePick'] const reactify: typeof import('@vueuse/core').reactify
const readonly: typeof import('vue')['readonly'] const reactifyObject: typeof import('@vueuse/core').reactifyObject
const ref: typeof import('vue')['ref'] const reactive: typeof import('vue').reactive
const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
const refDebounced: typeof import('@vueuse/core')['refDebounced'] const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
const refDefault: typeof import('@vueuse/core')['refDefault'] const reactivePick: typeof import('@vueuse/core').reactivePick
const refThrottled: typeof import('@vueuse/core')['refThrottled'] const readonly: typeof import('vue').readonly
const refWithControl: typeof import('@vueuse/core')['refWithControl'] const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue')['resolveComponent'] const refAutoReset: typeof import('@vueuse/core').refAutoReset
const resolveRef: typeof import('@vueuse/core')['resolveRef'] const refDebounced: typeof import('@vueuse/core').refDebounced
const refDefault: typeof import('@vueuse/core').refDefault
const refManualReset: typeof import('@vueuse/core').refManualReset
const refThrottled: typeof import('@vueuse/core').refThrottled
const refWithControl: typeof import('@vueuse/core').refWithControl
const resolveComponent: typeof import('vue').resolveComponent
const resolveRef: typeof import('@vueuse/core').resolveRef
const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const setActivePinia: typeof import('pinia')['setActivePinia'] const setActivePinia: typeof import('pinia').setActivePinia
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue')['shallowRef'] const shallowRef: typeof import('vue').shallowRef
const storeToRefs: typeof import('pinia')['storeToRefs'] const storeToRefs: typeof import('pinia').storeToRefs
const syncRef: typeof import('@vueuse/core')['syncRef'] const syncRef: typeof import('@vueuse/core').syncRef
const syncRefs: typeof import('@vueuse/core')['syncRefs'] const syncRefs: typeof import('@vueuse/core').syncRefs
const templateRef: typeof import('@vueuse/core')['templateRef'] const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core')['throttledRef'] const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] const throttledWatch: typeof import('@vueuse/core').throttledWatch
const toRaw: typeof import('vue')['toRaw'] const toRaw: typeof import('vue').toRaw
const toReactive: typeof import('@vueuse/core')['toReactive'] const toReactive: typeof import('@vueuse/core').toReactive
const toRef: typeof import('vue')['toRef'] const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue')['toRefs'] const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue')['toValue'] const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue')['triggerRef'] const triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
const unref: typeof import('vue')['unref'] const unref: typeof import('vue').unref
const unrefElement: typeof import('@vueuse/core')['unrefElement'] const unrefElement: typeof import('@vueuse/core').unrefElement
const until: typeof import('@vueuse/core')['until'] const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core')['useAnimate'] const useAnimate: typeof import('@vueuse/core').useAnimate
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] const useArrayFind: typeof import('@vueuse/core').useArrayFind
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayMap: typeof import('@vueuse/core').useArrayMap
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
const useArraySome: typeof import('@vueuse/core')['useArraySome'] const useArraySome: typeof import('@vueuse/core').useArraySome
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue').useAttrs
const useBase64: typeof import('@vueuse/core')['useBase64'] const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core')['useBattery'] const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] const useBluetooth: typeof import('@vueuse/core').useBluetooth
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
const useCached: typeof import('@vueuse/core')['useCached'] const useCached: typeof import('@vueuse/core').useCached
const useClipboard: typeof import('@vueuse/core')['useClipboard'] const useClipboard: typeof import('@vueuse/core').useClipboard
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core')['useCloned'] const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core')['useColorMode'] const useColorMode: typeof import('@vueuse/core').useColorMode
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useCounter: typeof import('@vueuse/core')['useCounter'] const useCountdown: typeof import('@vueuse/core').useCountdown
const useCssModule: typeof import('vue')['useCssModule'] const useCounter: typeof import('@vueuse/core').useCounter
const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue')['useCssVars'] const useCssSupports: typeof import('@vueuse/core').useCssSupports
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] const useCssVar: typeof import('@vueuse/core').useCssVar
const useCycleList: typeof import('@vueuse/core')['useCycleList'] const useCssVars: typeof import('vue').useCssVars
const useDark: typeof import('@vueuse/core')['useDark'] const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] const useCycleList: typeof import('@vueuse/core').useCycleList
const useDebounce: typeof import('@vueuse/core')['useDebounce'] const useDark: typeof import('@vueuse/core').useDark
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] const useDateFormat: typeof import('@vueuse/core').useDateFormat
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] const useDebounce: typeof import('@vueuse/core').useDebounce
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] const useDevicesList: typeof import('@vueuse/core').useDevicesList
const useDraggable: typeof import('@vueuse/core')['useDraggable'] const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
const useDropZone: typeof import('@vueuse/core')['useDropZone'] const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] const useDraggable: typeof import('@vueuse/core').useDraggable
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] const useDropZone: typeof import('@vueuse/core').useDropZone
const useElementHover: typeof import('@vueuse/core')['useElementHover'] const useElementBounding: typeof import('@vueuse/core').useElementBounding
const useElementSize: typeof import('@vueuse/core')['useElementSize'] const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] const useElementHover: typeof import('@vueuse/core').useElementHover
const useEventBus: typeof import('@vueuse/core')['useEventBus'] const useElementSize: typeof import('@vueuse/core').useElementSize
const useEventListener: typeof import('@vueuse/core')['useEventListener'] const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
const useEventSource: typeof import('@vueuse/core')['useEventSource'] const useEventBus: typeof import('@vueuse/core').useEventBus
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] const useEventListener: typeof import('@vueuse/core').useEventListener
const useFavicon: typeof import('@vueuse/core')['useFavicon'] const useEventSource: typeof import('@vueuse/core').useEventSource
const useFetch: typeof import('@vueuse/core')['useFetch'] const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] const useFavicon: typeof import('@vueuse/core').useFavicon
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] const useFetch: typeof import('@vueuse/core').useFetch
const useFocus: typeof import('@vueuse/core')['useFocus'] const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFps: typeof import('@vueuse/core')['useFps'] const useFocus: typeof import('@vueuse/core').useFocus
const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useGamepad: typeof import('@vueuse/core')['useGamepad'] const useFps: typeof import('@vueuse/core').useFps
const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useIdle: typeof import('@vueuse/core')['useIdle'] const useGamepad: typeof import('@vueuse/core').useGamepad
const useImage: typeof import('@vueuse/core')['useImage'] const useGeolocation: typeof import('@vueuse/core').useGeolocation
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] const useId: typeof import('vue').useId
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useIdle: typeof import('@vueuse/core').useIdle
const useInterval: typeof import('@vueuse/core')['useInterval'] const useImage: typeof import('@vueuse/core').useImage
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] const useInterval: typeof import('@vueuse/core').useInterval
const useLink: typeof import('vue-router')['useLink'] const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] const useLastChanged: typeof import('@vueuse/core').useLastChanged
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] const useLink: typeof import('vue-router').useLink
const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useMemoize: typeof import('@vueuse/core')['useMemoize'] const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMemory: typeof import('@vueuse/core')['useMemory'] const useMediaControls: typeof import('@vueuse/core').useMediaControls
const useMounted: typeof import('@vueuse/core')['useMounted'] const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
const useMouse: typeof import('@vueuse/core')['useMouse'] const useMemoize: typeof import('@vueuse/core').useMemoize
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] const useMemory: typeof import('@vueuse/core').useMemory
const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] const useModel: typeof import('vue').useModel
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] const useMounted: typeof import('@vueuse/core').useMounted
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] const useMouse: typeof import('@vueuse/core').useMouse
const useNetwork: typeof import('@vueuse/core')['useNetwork'] const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
const useNow: typeof import('@vueuse/core')['useNow'] const useMousePressed: typeof import('@vueuse/core').useMousePressed
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
const useOnline: typeof import('@vueuse/core')['useOnline'] const useNetwork: typeof import('@vueuse/core').useNetwork
const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] const useNow: typeof import('@vueuse/core').useNow
const useParallax: typeof import('@vueuse/core')['useParallax'] const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useParentElement: typeof import('@vueuse/core')['useParentElement'] const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] const useOnline: typeof import('@vueuse/core').useOnline
const usePermission: typeof import('@vueuse/core')['usePermission'] const usePageLeave: typeof import('@vueuse/core').usePageLeave
const usePointer: typeof import('@vueuse/core')['usePointer'] const useParallax: typeof import('@vueuse/core').useParallax
const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] const useParentElement: typeof import('@vueuse/core').useParentElement
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] const usePermission: typeof import('@vueuse/core').usePermission
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] const usePointer: typeof import('@vueuse/core').usePointer
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePrevious: typeof import('@vueuse/core')['usePrevious'] const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const useRafFn: typeof import('@vueuse/core')['useRafFn'] const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
const useRoute: typeof import('vue-router')['useRoute'] const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency
const useRouter: typeof import('vue-router')['useRouter'] const usePrevious: typeof import('@vueuse/core').usePrevious
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] const useRafFn: typeof import('@vueuse/core').useRafFn
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useScroll: typeof import('@vueuse/core')['useScroll'] const useRoute: typeof import('vue-router').useRoute
const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] const useRouter: typeof import('vue-router').useRouter
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] const useSSRWidth: typeof import('@vueuse/core').useSSRWidth
const useShare: typeof import('@vueuse/core')['useShare'] const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation
const useSlots: typeof import('vue')['useSlots'] const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea
const useSorted: typeof import('@vueuse/core')['useSorted'] const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] const useScroll: typeof import('@vueuse/core').useScroll
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useStepper: typeof import('@vueuse/core')['useStepper'] const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useStorage: typeof import('@vueuse/core')['useStorage'] const useShare: typeof import('@vueuse/core').useShare
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] const useSlots: typeof import('vue').useSlots
const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] const useSorted: typeof import('@vueuse/core').useSorted
const useSupported: typeof import('@vueuse/core')['useSupported'] const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
const useSwipe: typeof import('@vueuse/core')['useSwipe'] const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useStepper: typeof import('@vueuse/core').useStepper
const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] const useStorage: typeof import('@vueuse/core').useStorage
const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useThrottle: typeof import('@vueuse/core')['useThrottle'] const useSupported: typeof import('@vueuse/core').useSupported
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] const useSwipe: typeof import('@vueuse/core').useSwipe
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] const useTemplateRef: typeof import('vue').useTemplateRef
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
const useTimeout: typeof import('@vueuse/core')['useTimeout'] const useTextDirection: typeof import('@vueuse/core').useTextDirection
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] const useTextSelection: typeof import('@vueuse/core').useTextSelection
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] const useThrottle: typeof import('@vueuse/core').useThrottle
const useTitle: typeof import('@vueuse/core')['useTitle'] const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
const useToNumber: typeof import('@vueuse/core')['useToNumber'] const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
const useToString: typeof import('@vueuse/core')['useToString'] const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
const useToggle: typeof import('@vueuse/core')['useToggle'] const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl
const useTransition: typeof import('@vueuse/core')['useTransition'] const useTimeout: typeof import('@vueuse/core').useTimeout
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn
const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll
const useVModel: typeof import('@vueuse/core')['useVModel'] const useTimestamp: typeof import('@vueuse/core').useTimestamp
const useVModels: typeof import('@vueuse/core')['useVModels'] const useTitle: typeof import('@vueuse/core').useTitle
const useVibrate: typeof import('@vueuse/core')['useVibrate'] const useToNumber: typeof import('@vueuse/core').useToNumber
const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] const useToString: typeof import('@vueuse/core').useToString
const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] const useToggle: typeof import('@vueuse/core').useToggle
const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] const useTransition: typeof import('@vueuse/core').useTransition
const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] const useUserMedia: typeof import('@vueuse/core').useUserMedia
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] const useVModel: typeof import('@vueuse/core').useVModel
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] const useVModels: typeof import('@vueuse/core').useVModels
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] const useVibrate: typeof import('@vueuse/core').useVibrate
const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] const useVirtualList: typeof import('@vueuse/core').useVirtualList
const watch: typeof import('vue')['watch'] const useWakeLock: typeof import('@vueuse/core').useWakeLock
const watchArray: typeof import('@vueuse/core')['watchArray'] const useWebNotification: typeof import('@vueuse/core').useWebNotification
const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] const useWebSocket: typeof import('@vueuse/core').useWebSocket
const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] const useWebWorker: typeof import('@vueuse/core').useWebWorker
const watchDeep: typeof import('@vueuse/core')['watchDeep'] const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
const watchEffect: typeof import('vue')['watchEffect'] const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] const useWindowSize: typeof import('@vueuse/core').useWindowSize
const watchOnce: typeof import('@vueuse/core')['watchOnce'] const watch: typeof import('vue').watch
const watchPausable: typeof import('@vueuse/core')['watchPausable'] const watchArray: typeof import('@vueuse/core').watchArray
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchAtMost: typeof import('@vueuse/core').watchAtMost
const watchSyncEffect: typeof import('vue')['watchSyncEffect'] const watchDebounced: typeof import('@vueuse/core').watchDebounced
const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] const watchDeep: typeof import('@vueuse/core').watchDeep
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] const watchEffect: typeof import('vue').watchEffect
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
const whenever: typeof import('@vueuse/core')['whenever'] const watchImmediate: typeof import('@vueuse/core').watchImmediate
const watchOnce: typeof import('@vueuse/core').watchOnce
const watchPausable: typeof import('@vueuse/core').watchPausable
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
const watchThrottled: typeof import('@vueuse/core').watchThrottled
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
const whenever: typeof import('@vueuse/core').whenever
} }
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue') import('vue')
} }

View File

@ -75,9 +75,9 @@ export default defineConfig({
// 因此无法生成对应的 CSS。为了解决这个问题你可以使用 UnoCSS 的 safelist 选项来指定一些始终需要生成的 CSS 类。 // 因此无法生成对应的 CSS。为了解决这个问题你可以使用 UnoCSS 的 safelist 选项来指定一些始终需要生成的 CSS 类。
// https://unocss.dev/guide/advanced#safelist // https://unocss.dev/guide/advanced#safelist
safelist: [ safelist: [
'i-simple-icons:atlassian', 'i-ph:house',
'i-simple-icons:soundcharts', 'i-ph:chart-line',
'i-simple-icons:docsify', 'i-ph:code',
'i-material-symbols:award-star', 'i-ph:user',
], ],
}) })

View File

@ -69,16 +69,17 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
__APP_INFO__: JSON.stringify(__APP_INFO__), __APP_INFO__: JSON.stringify(__APP_INFO__),
}, },
esbuild: {
// 使用 esbuild 压缩 剔除 console.log
drop: VITE_DROP_CONSOLE ? ['debugger', 'console'] : [],
// minify: true, // minify: true, 等于 minify: 'esbuild',
},
build: { build: {
// 设置最终构建的浏览器兼容目标 // 设置最终构建的浏览器兼容目标
target: 'es2015', target: 'es2015',
minify: 'esbuild', // Vite 8 的 oxc 不支持 drop 选项,按需切换到 terser 去除 console/debugger
minify: VITE_DROP_CONSOLE ? 'terser' : 'oxc',
terserOptions: {
compress: {
drop_console: VITE_DROP_CONSOLE,
drop_debugger: VITE_DROP_CONSOLE,
},
},
// 构建后是否生成 source map 文件(用于线上报错代码报错映射对应代码) // 构建后是否生成 source map 文件(用于线上报错代码报错映射对应代码)
sourcemap: false, sourcemap: false,
cssTarget: 'chrome80', cssTarget: 'chrome80',
@ -98,8 +99,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
reportCompressedSize: true, reportCompressedSize: true,
// chunk 大小警告的限制(以 kbs 为单位) // chunk 大小警告的限制(以 kbs 为单位)
chunkSizeWarningLimit: 2000, chunkSizeWarningLimit: 2000,
// 自定义底层的 Rollup 打包配置 // 自定义底层的 Rolldown 打包配置
rollupOptions: { rolldownOptions: {
// 静态资源分类打包 // 静态资源分类打包
output: { output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称 chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称