项目初始化
This commit is contained in:
commit
dc07b81438
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"nuxtr.nuxtr-vscode",
|
||||
"vue.vscode-typescript-vue-plugin",
|
||||
"vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.iconify",
|
||||
"lokalise.i18n-ally"
|
||||
]
|
||||
}
|
51
.vscode/settings.json
vendored
Normal file
51
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "postcss"
|
||||
},
|
||||
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"i18n/locales"
|
||||
],
|
||||
"i18n-ally.sourceLanguage": "zh-CN",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off" },
|
||||
{ "rule": "*-indent", "severity": "off" },
|
||||
{ "rule": "*-spacing", "severity": "off" },
|
||||
{ "rule": "*-spaces", "severity": "off" },
|
||||
{ "rule": "*-order", "severity": "off" },
|
||||
{ "rule": "*-dangle", "severity": "off" },
|
||||
{ "rule": "*-newline", "severity": "off" },
|
||||
{ "rule": "*quotes", "severity": "off" },
|
||||
{ "rule": "*semi", "severity": "off" }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
]
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Charlie Wang ✨
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
136
README.md
Normal file
136
README.md
Normal file
@ -0,0 +1,136 @@
|
||||
<!-- markdownlint-disable MD033 MD041 -->
|
||||
|
||||
<div id="top" align="center">
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/easy-temps/easy-static/cover.png" alt="cover" />
|
||||
|
||||
<h1 align="center">nuxt-vant-mobile</h1>
|
||||
|
||||
An mobile web apps template based on the Nuxt _⁴_ ecosystem.
|
||||
|
||||
一个基于 Nuxt _⁴_ 生态系统的移动 web 应用模板,帮助你快速完成业务开发。
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/github/license/easy-temps/nuxt-vant-mobile" alt="license" />
|
||||
<img src="https://img.shields.io/github/package-json/v/easy-temps/nuxt-vant-mobile" alt="version" />
|
||||
<img src="https://img.shields.io/github/repo-size/easy-temps/nuxt-vant-mobile" alt="repo-size" />
|
||||
<img src="https://img.shields.io/github/languages/top/easy-temps/nuxt-vant-mobile" alt="languages" />
|
||||
<img src="https://img.shields.io/github/issues-closed/easy-temps/nuxt-vant-mobile" alt="issues" />
|
||||
</p>
|
||||
|
||||
[文档](https://easy-temps.github.io/easy-docs/nuxt3-vant-mobile/) / [交流](https://github.com/easy-temps/vue3-vant-mobile/issues/56) / [反馈](https://github.com/easy-temps/nuxt-vant-mobile/issues)
|
||||
|
||||
🖥 <a href="https://nuxt-vant-mobile.netlify.app">Online Preview</a>
|
||||
|
||||
[](https://app.netlify.com/sites/nuxt-vant-mobile/deploys)
|
||||
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
- 💚 [Nuxt](https://nuxt.com/) - SSR, ESR, File-based routing, components auto importing, modules, etc
|
||||
|
||||
- ⚡️ Vite - Instant HMR
|
||||
|
||||
- 🎨 [UnoCSS](https://github.com/unocss/unocss) - The instant on-demand atomic CSS engine
|
||||
|
||||
- 😃 Use icons from any icon sets in Pure CSS, powered by [UnoCSS](https://github.com/unocss/unocss)
|
||||
|
||||
- 🔥 The `<script setup>` syntax
|
||||
|
||||
- 🌍 [I18n ready](./i18n/locales)
|
||||
|
||||
- 🍍 [State Management via Pinia](https://github.com/vuejs/pinia), see [./app/composables/counter.ts](./app/composables/counter.ts)
|
||||
|
||||
- 📑 [Layout system](./app/layouts)
|
||||
|
||||
- 📥 APIs auto importing - for Composition API and custom composables
|
||||
|
||||
- 🦾 TypeScript, of course
|
||||
|
||||
- ☁️ Deploy on [Netlify](https://www.netlify.com), zero-config
|
||||
|
||||
## Nuxt Modules
|
||||
|
||||
- [Vant](https://github.com/youzan/vant) - Vue UI library for mobile web apps
|
||||
- [Nuxt ESLint](https://github.com/nuxt/eslint) - collection of ESLint-related packages for Nuxt
|
||||
- [i18n](https://github.com/nuxt-modules/i18n) - i18n module for Nuxt
|
||||
- [ColorMode](https://github.com/nuxt-modules/color-mode) - dark and Light mode with auto detection made easy with Nuxt
|
||||
- [UnoCSS](https://github.com/unocss/unocss) - the instant on-demand atomic CSS engine
|
||||
- [Pinia](https://github.com/vuejs/pinia) - intuitive, type safe, light and flexible Store for Vue
|
||||
- [Pinia Persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate) - configurable persistence and rehydration of Pinia stores
|
||||
- [DevTools](https://github.com/nuxt/devtools) - unleash Nuxt Developer Experience
|
||||
|
||||
## IDE
|
||||
|
||||
We recommend using [VS Code](https://code.visualstudio.com/) with [Volar](https://github.com/johnsoncodehk/volar) to get the best experience (You might want to disable [Vetur](https://vuejs.github.io/vetur/) if you have it)
|
||||
|
||||
## Try it now
|
||||
|
||||
### GitHub Template
|
||||
|
||||
[Create a repo from this template on GitHub](https://github.com/easy-temps/nuxt-vant-mobile/generate)
|
||||
|
||||
### Clone to local
|
||||
|
||||
If you prefer to do it manually with the cleaner git history
|
||||
|
||||
```bash
|
||||
npx tiged easy-temps/nuxt-vant-mobile my-nuxt-app
|
||||
cd my-nuxt-app
|
||||
pnpm i # If you don't have pnpm installed, run: npm install -g pnpm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Development
|
||||
|
||||
Just run and visit <http://localhost:3000>
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To build the App, run
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
And you will see the generated file in `.output` that ready to be served.
|
||||
|
||||
### Deploy on Netlify
|
||||
|
||||
Go to [Netlify](https://app.netlify.com/start) and select your clone, `OK` along the way, and your App will be live in a minute.
|
||||
|
||||
## Community 👏
|
||||
|
||||
We recommend that [issue](https://github.com/easy-temps/nuxt-vant-mobile/issues) be used for problem feedback, or [Wechat group](https://github.com/easy-temps/vue3-vant-mobile/issues/56).
|
||||
|
||||
## Donation ☕
|
||||
|
||||
[Buy Me a Coffee](https://github.com/CharleeWa/sponsor)
|
||||
|
||||
<h2 align="center">💝 Our Sponsors 💝</h2>
|
||||
|
||||
<p align="center">
|
||||
Your sponsorship will help us continue to iterate on this exciting project! 🚀
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/keyFeng"><img src="https://avatars.githubusercontent.com/u/52267976?v=4" width="60px" alt="keyFeng" /></a>
|
||||
<a href="https://github.com/ljt990218"><img src="https://avatars.githubusercontent.com/u/50509815?v=4" width="60px" alt="ljt990218" /></a>
|
||||
<a href="https://github.com/heked"><img src="https://avatars.githubusercontent.com/u/14127731?v=4" width="60px" alt="heked" /></a>
|
||||
<a href="https://github.com/topcnm"><img src="https://avatars.githubusercontent.com/u/8057893?v=4" width="60px" alt="topcnm" /></a>
|
||||
<a href="https://github.com/qiyue2015"><img src="https://avatars.githubusercontent.com/u/11554433?v=4" width="60px" alt="qiyue2015" /></a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE) License
|
||||
|
||||
<p align="right">
|
||||
<a href="#top">⬆️ Back to Top</a>
|
||||
</p>
|
55
app/api/http.ts
Normal file
55
app/api/http.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import type { $Fetch } from 'ofetch'
|
||||
|
||||
import { useRuntimeConfig } from '#app'
|
||||
import { ofetch } from 'ofetch'
|
||||
|
||||
type HttpStatusErrorHandler = (message: string, statusCode: number) => void
|
||||
let httpStatusErrorHandler: HttpStatusErrorHandler
|
||||
|
||||
let http: $Fetch
|
||||
|
||||
export function setupHttp() {
|
||||
if (http)
|
||||
return http
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.apiBase as string
|
||||
|
||||
http = ofetch.create({
|
||||
baseURL,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
async onRequest({ options }) {
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
}
|
||||
},
|
||||
async onResponseError({ response }) {
|
||||
const { message } = response._data
|
||||
if (Array.isArray(message)) {
|
||||
message.forEach((item) => {
|
||||
httpStatusErrorHandler?.(item, response.status)
|
||||
})
|
||||
}
|
||||
else {
|
||||
httpStatusErrorHandler?.(message, response.status)
|
||||
}
|
||||
return Promise.reject(response._data)
|
||||
},
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function injectHttpStatusErrorHandler(handler: HttpStatusErrorHandler) {
|
||||
httpStatusErrorHandler = handler
|
||||
}
|
||||
|
||||
export function getHttp() {
|
||||
if (!http) {
|
||||
throw new Error('HTTP client not initialized. Call setupHttp first.')
|
||||
}
|
||||
return http
|
||||
}
|
8
app/api/prose.ts
Normal file
8
app/api/prose.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { getHttp } from './http'
|
||||
|
||||
export async function getProse() {
|
||||
const http = getHttp()
|
||||
return await http('/api/prose', {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
28
app/app.vue
Normal file
28
app/app.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import type { ConfigProviderTheme } from 'vant'
|
||||
import useKeepalive from '~/composables/keepalive'
|
||||
import { appName } from '~/constants'
|
||||
|
||||
useHead({
|
||||
title: appName,
|
||||
})
|
||||
|
||||
const color = useColorMode()
|
||||
|
||||
const mode = computed(() => {
|
||||
return color.value as ConfigProviderTheme
|
||||
})
|
||||
|
||||
const keepAliveRouteNames = computed(() => {
|
||||
return useKeepalive().routeCaches as string[]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VanConfigProvider :theme="mode">
|
||||
<NuxtLoadingIndicator color="repeating-linear-gradient(to right,var(--c-primary) 0%,var(--c-primary-active) 100%)" />
|
||||
<NuxtLayout>
|
||||
<NuxtPage :keepalive="{ include: keepAliveRouteNames }" />
|
||||
</NuxtLayout>
|
||||
</VanConfigProvider>
|
||||
</template>
|
30
app/components/AppFooter.vue
Normal file
30
app/components/AppFooter.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppFooterRouteNames as names } from '~/config'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const active = ref(0)
|
||||
|
||||
const show = computed(() => {
|
||||
if (route.name && names.includes(route.name))
|
||||
return true
|
||||
return false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-tabbar v-if="show" v-model="active" route placeholder fixed>
|
||||
<van-tabbar-item replace to="/">
|
||||
<span>{{ $t('tabbar.home') }}</span>
|
||||
<template #icon>
|
||||
<div class="i-carbon:home" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item replace to="/profile">
|
||||
<span>{{ $t('tabbar.profile') }}</span>
|
||||
<template #icon>
|
||||
<div class="i-carbon:user" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</template>
|
32
app/components/AppHeader.vue
Normal file
32
app/components/AppHeader.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppFooterRouteNames as routeWhiteList } from '~/config'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function onBack() {
|
||||
if (window.history.state.back)
|
||||
history.back()
|
||||
else
|
||||
router.replace('/')
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const title = computed(() => {
|
||||
if (!route.meta)
|
||||
return ''
|
||||
return route.meta.i18n ? t(route.meta.i18n) : (route.meta.title || '')
|
||||
})
|
||||
|
||||
const showLeftArrow = computed(() => route.name && routeWhiteList.includes(route.name))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VanNavBar
|
||||
:title="title"
|
||||
:left-arrow="!showLeftArrow"
|
||||
placeholder clickable fixed
|
||||
@click-left="onBack"
|
||||
/>
|
||||
</template>
|
18
app/composables/counter.ts
Normal file
18
app/composables/counter.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const useCounter = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
increment,
|
||||
}
|
||||
}, {
|
||||
persist: true,
|
||||
})
|
||||
|
||||
export default useCounter
|
24
app/composables/keepalive.ts
Normal file
24
app/composables/keepalive.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { RouteLocationNormalized, RouteRecordName } from 'vue-router'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const useKeepalive = defineStore('keepalive', () => {
|
||||
const routeCaches = ref<RouteRecordName[]>([])
|
||||
|
||||
const addRoute = (route: RouteLocationNormalized) => {
|
||||
if (!route.name)
|
||||
return
|
||||
|
||||
if (routeCaches.value.includes(route.name))
|
||||
return
|
||||
|
||||
if (route?.meta?.keepalive)
|
||||
routeCaches.value.push(route.name)
|
||||
}
|
||||
|
||||
return {
|
||||
routeCaches,
|
||||
addRoute,
|
||||
}
|
||||
})
|
||||
|
||||
export default useKeepalive
|
6
app/config/index.ts
Normal file
6
app/config/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { RouteRecordName } from 'vue-router'
|
||||
|
||||
/**
|
||||
* Use the AppFooter routing whitelist
|
||||
*/
|
||||
export const useAppFooterRouteNames: RouteRecordName[] = ['index', 'profile']
|
2
app/constants/index.ts
Normal file
2
app/constants/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const appName = 'nuxt-vant-mobile'
|
||||
export const appDescription = 'Nuxt H5 Starter Template'
|
23
app/layouts/404.vue
Normal file
23
app/layouts/404.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
|
||||
function onBack() {
|
||||
if (window.history.state.back)
|
||||
history.back()
|
||||
else
|
||||
router.replace('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main text="center gray-300 dark:gray-200 18" py="20">
|
||||
<van-icon name="warn-o" size="3em" />
|
||||
<slot />
|
||||
|
||||
<div class="mt-10">
|
||||
<button van-haptics-feedback btn m="3 t8" @click="onBack">
|
||||
{{ $t('error_page.back_btn') }}
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
15
app/layouts/README.md
Normal file
15
app/layouts/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Layouts
|
||||
|
||||
Vue components in this dir are used as layouts.
|
||||
|
||||
By default, `default.vue` will be used unless an alternative is specified in the route meta.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'home',
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Learn more on <https://nuxt.com/docs/guide/directory-structure/layouts>
|
11
app/layouts/default.vue
Normal file
11
app/layouts/default.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<main class="flex flex-col min-h-svh">
|
||||
<AppHeader class="h-[var(--van-nav-bar-height)]" />
|
||||
|
||||
<div class="flex-1 p-16 pb-[var(--van-nav-bar-height)]">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<AppFooter />
|
||||
</main>
|
||||
</template>
|
7
app/middleware/route.global.ts
Normal file
7
app/middleware/route.global.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import useKeepalive from '~/composables/keepalive'
|
||||
|
||||
export default defineNuxtRouteMiddleware((to: RouteLocationNormalized) => {
|
||||
if (to.meta && to.meta.keepalive)
|
||||
useKeepalive().addRoute(to)
|
||||
})
|
9
app/pages/[...all].vue
Normal file
9
app/pages/[...all].vue
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: '404',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div> {{ $t('error_page.txt') }} </div>
|
||||
</template>
|
35
app/pages/counter/index.vue
Normal file
35
app/pages/counter/index.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import useCounter from '~/composables/counter'
|
||||
|
||||
definePageMeta({
|
||||
title: '🍍 持久化 Pinia 状态',
|
||||
i18n: 'menu.persistPiniaState',
|
||||
})
|
||||
|
||||
const counter = useCounter()
|
||||
|
||||
function add() {
|
||||
counter.increment()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-6xl color-pink font-semibold">
|
||||
Hello, Pinia!
|
||||
</h1>
|
||||
|
||||
<p class="mt-10 text-gray-700 dark:text-white">
|
||||
{{ $t('counter_page.label') }}
|
||||
</p>
|
||||
|
||||
<p class="mt-10">
|
||||
{{ $t('counter_page.label_num') }}:
|
||||
<strong class="text-green-500"> {{ counter.count }} </strong>
|
||||
</p>
|
||||
|
||||
<button class="mt-10 btn" @click="add">
|
||||
{{ $t('counter_page.btn_add') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
95
app/pages/index.vue
Normal file
95
app/pages/index.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import type { LocaleObject } from '@nuxtjs/i18n'
|
||||
import type { PickerColumn } from 'vant'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { Locale } from 'vant'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '主页',
|
||||
i18n: 'menu.home',
|
||||
})
|
||||
|
||||
const color = useColorMode()
|
||||
|
||||
useHead({
|
||||
meta: [{
|
||||
id: 'theme-color',
|
||||
name: 'theme-color',
|
||||
content: () => color.value === 'dark' ? '#222222' : '#ffffff',
|
||||
}],
|
||||
})
|
||||
|
||||
const checked = computed({
|
||||
get: () => color.value === 'dark',
|
||||
set: (val: boolean) => {
|
||||
color.preference = val ? 'dark' : 'light'
|
||||
},
|
||||
})
|
||||
|
||||
const { setLocale, t } = useI18n()
|
||||
const i18n = useNuxtApp().$i18n
|
||||
|
||||
const showLanguagePicker = ref(false)
|
||||
|
||||
const languageValues = ref<string[]>([i18n.locale.value])
|
||||
|
||||
const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
|
||||
|
||||
const menus = computed(() => [
|
||||
{ title: t('menu.unocssExample'), route: 'unocss' },
|
||||
{ title: t('menu.keepAlive'), route: 'keepalive' },
|
||||
{ title: t('menu.persistPiniaState'), route: 'counter' },
|
||||
{ title: t('menu.fetch'), route: 'prose' },
|
||||
{ title: t('menu.404Demo'), route: 'unknown' },
|
||||
])
|
||||
|
||||
function onLanguageConfirm(event: { selectedOptions: PickerColumn }) {
|
||||
const lang = event.selectedOptions[0]?.code
|
||||
|
||||
setLocale(lang)
|
||||
Locale.use(lang)
|
||||
localStorage.setItem('lang', lang)
|
||||
|
||||
showLanguagePicker.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VanCellGroup inset>
|
||||
<VanCell :title="$t('menu.darkMode')" center>
|
||||
<template #right-icon>
|
||||
<ClientOnly>
|
||||
<VanSwitch
|
||||
v-model="checked"
|
||||
size="20px"
|
||||
aria-label="on/off Dark Mode"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
</VanCell>
|
||||
|
||||
<VanCell
|
||||
:title="$t('menu.language')"
|
||||
:value="locales.find(i => i.code === i18n.locale.value)?.name"
|
||||
is-link
|
||||
@click="showLanguagePicker = true"
|
||||
/>
|
||||
|
||||
<template v-for="item in menus" :key="item.route">
|
||||
<VanCell :title="item.title" :to="item.route" is-link />
|
||||
</template>
|
||||
</VanCellGroup>
|
||||
|
||||
<van-popup v-model:show="showLanguagePicker" position="bottom">
|
||||
<van-picker
|
||||
v-model="languageValues"
|
||||
:columns="locales"
|
||||
:columns-field-names="{ text: 'name', value: 'code' }"
|
||||
@confirm="onLanguageConfirm"
|
||||
@cancel="showLanguagePicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
21
app/pages/keepalive/index.vue
Normal file
21
app/pages/keepalive/index.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Keepalive',
|
||||
})
|
||||
|
||||
definePageMeta({
|
||||
name: 'Keepalive',
|
||||
keepalive: true,
|
||||
title: '🧡 KeepAlive',
|
||||
i18n: 'menu.keepAlive',
|
||||
})
|
||||
|
||||
const value = ref(1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p> {{ $t('keepalive_page.label') }} </p>
|
||||
<van-stepper v-model="value" class="mt-10" />
|
||||
</div>
|
||||
</template>
|
13
app/pages/profile/index.vue
Normal file
13
app/pages/profile/index.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '我的',
|
||||
i18n: 'menu.profile',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div mx-auto mb-60 pt-15 text-center text-16 text-dark dark:text-white>
|
||||
{{ $t('profile_page.txt') }}
|
||||
</div>
|
||||
</template>
|
42
app/pages/prose/index.vue
Normal file
42
app/pages/prose/index.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts" setup>
|
||||
import { useProseStore } from '~/stores/prose'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
title: '随笔',
|
||||
i18n: 'menu.fetch',
|
||||
})
|
||||
|
||||
const proseStore = useProseStore()
|
||||
|
||||
function fetch() {
|
||||
proseStore.fetchProse()
|
||||
}
|
||||
|
||||
function clear() {
|
||||
proseStore.clearProse()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="h-300 flex items-center justify-center rounded-15 bg-white p-16 dark:bg-[--van-background-2]">
|
||||
<div v-if="proseStore.prose" class="text-16 leading-26">
|
||||
{{ proseStore.prose }}
|
||||
</div>
|
||||
|
||||
<ClientOnly v-else>
|
||||
<van-empty :description="$t('prose_page.btn_empty_desc')" />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
||||
<van-space class="m-10" direction="vertical" fill>
|
||||
<van-button type="primary" round block @click="fetch">
|
||||
{{ $t('prose_page.btn_fetch') }}
|
||||
</van-button>
|
||||
<van-button type="default" round block @click="clear">
|
||||
{{ $t('prose_page.btn_clear') }}
|
||||
</van-button>
|
||||
</van-space>
|
||||
</div>
|
||||
</template>
|
22
app/pages/unocss/index.vue
Normal file
22
app/pages/unocss/index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
title: '🎨 Unocss 示例',
|
||||
i18n: 'menu.unocssExample',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-6xl color-pink font-semibold">
|
||||
{{ $t('unocss_page.hello', ['Unocss!']) }}
|
||||
</h1>
|
||||
|
||||
<p class="mt-10 text-gray-700 dark:text-white">
|
||||
{{ $t('unocss_page.desc') }}
|
||||
</p>
|
||||
|
||||
<button class="mt-10 btn">
|
||||
{{ $t('unocss_page.btn_txt') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
5
app/plugins/http.ts
Normal file
5
app/plugins/http.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { setupHttp } from '~/api/http'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
setupHttp()
|
||||
})
|
26
app/plugins/i18n.ts
Normal file
26
app/plugins/i18n.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { Locale as TypeLocale } from '#i18n'
|
||||
import { Locale } from 'vant'
|
||||
import enUS from 'vant/es/locale/lang/en-US'
|
||||
import zhCN from 'vant/es/locale/lang/zh-CN'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
// 载入 vant 语言包
|
||||
Locale.use('zh-CN', zhCN)
|
||||
Locale.use('en-US', enUS)
|
||||
|
||||
if (import.meta.client) {
|
||||
const i18n = useNuxtApp().$i18n
|
||||
const { setLocale } = i18n
|
||||
|
||||
const lang = localStorage.getItem('lang')
|
||||
|
||||
if (lang) {
|
||||
setLocale(lang as TypeLocale)
|
||||
Locale.use(lang)
|
||||
}
|
||||
else {
|
||||
setLocale(i18n.locale.value)
|
||||
Locale.use(i18n.locale.value)
|
||||
}
|
||||
}
|
||||
})
|
33
app/stores/prose.ts
Normal file
33
app/stores/prose.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getProse } from '~/api/prose'
|
||||
|
||||
export const useProseStore = defineStore(
|
||||
'prose',
|
||||
() => {
|
||||
const prose = ref<string>('')
|
||||
|
||||
function initProse(val: string) {
|
||||
if (!prose.value) {
|
||||
prose.value = ''
|
||||
}
|
||||
|
||||
prose.value = val
|
||||
}
|
||||
|
||||
function clearProse() {
|
||||
prose.value = ''
|
||||
}
|
||||
|
||||
async function fetchProse() {
|
||||
const res = await getProse()
|
||||
initProse(res.result)
|
||||
}
|
||||
|
||||
return {
|
||||
prose,
|
||||
initProse,
|
||||
clearProse,
|
||||
fetchProse,
|
||||
}
|
||||
},
|
||||
)
|
4
app/styles/default-theme.css
Normal file
4
app/styles/default-theme.css
Normal file
@ -0,0 +1,4 @@
|
||||
:root:root {
|
||||
--van-primary-color: var(--c-primary);
|
||||
--van-cell-group-inset-padding: 0;
|
||||
}
|
14
app/styles/global.css
Normal file
14
app/styles/global.css
Normal file
@ -0,0 +1,14 @@
|
||||
#__nuxt {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--van-gray-1);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background: #222;
|
||||
color-scheme: dark;
|
||||
}
|
15
app/styles/vars.css
Normal file
15
app/styles/vars.css
Normal file
@ -0,0 +1,15 @@
|
||||
:root {
|
||||
--c-primary: rgb(var(--c-primary-500));
|
||||
--c-primary-active: rgb(var(--c-primary-600));
|
||||
|
||||
/* main color ratio */
|
||||
--c-primary-100: 217 251 232;
|
||||
--c-primary-200: 179 245 209;
|
||||
--c-primary-300: 117 237 174;
|
||||
--c-primary-400: 0 220 130;
|
||||
--c-primary-500: 0 193 106;
|
||||
--c-primary-600: 0 161 85;
|
||||
--c-primary-700: 0 127 69;
|
||||
--c-primary-800: 1 101 56;
|
||||
--c-primary-900: 10 83 49;
|
||||
}
|
10
app/types/vue-router.d.ts
vendored
Normal file
10
app/types/vue-router.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
/** page title */
|
||||
title?: string
|
||||
/** i18n key */
|
||||
i18n?: string
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
10
app/utils/preload.ts
Normal file
10
app/utils/preload.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default function preload() {
|
||||
return `
|
||||
;(function() {
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const setting = localStorage.getItem('nuxt-color-mode') || 'auto';
|
||||
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
||||
document.documentElement.classList.toggle('van-theme-dark', true);
|
||||
})()
|
||||
`
|
||||
}
|
32
commitlint.config.ts
Normal file
32
commitlint.config.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { UserConfig } from '@commitlint/types'
|
||||
import { RuleConfigSeverity } from '@commitlint/types'
|
||||
|
||||
const Configuration: UserConfig = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
formatter: '@commitlint/format',
|
||||
rules: {
|
||||
'type-enum': [
|
||||
RuleConfigSeverity.Error,
|
||||
'always',
|
||||
[
|
||||
'feat',
|
||||
'fix',
|
||||
'perf',
|
||||
'style',
|
||||
'docs',
|
||||
'test',
|
||||
'refactor',
|
||||
'build',
|
||||
'ci',
|
||||
'chore',
|
||||
'revert',
|
||||
'wip',
|
||||
'workflow',
|
||||
'types',
|
||||
'release',
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default Configuration
|
9
eslint.config.js
Normal file
9
eslint.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
import nuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default nuxt(
|
||||
antfu({
|
||||
unocss: true,
|
||||
formatters: true,
|
||||
}),
|
||||
)
|
11
i18n/i18n.config.ts
Normal file
11
i18n/i18n.config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { currentLocales } from './i18n'
|
||||
|
||||
export default defineI18nConfig(() => {
|
||||
return {
|
||||
legacy: false,
|
||||
availableLocales: currentLocales.map(l => l.code),
|
||||
fallbackLocale: 'zh-CN',
|
||||
fallbackWarn: true,
|
||||
missingWarn: true,
|
||||
}
|
||||
})
|
26
i18n/i18n.ts
Normal file
26
i18n/i18n.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { LocaleObject } from '@nuxtjs/i18n'
|
||||
|
||||
const locales: LocaleObject[] = [
|
||||
{
|
||||
code: 'zh-CN',
|
||||
file: 'zh-CN.json',
|
||||
name: '简体中文',
|
||||
},
|
||||
{
|
||||
code: 'en-US',
|
||||
file: 'en-US.json',
|
||||
name: 'English',
|
||||
},
|
||||
]
|
||||
|
||||
function buildLocales() {
|
||||
const useLocales = Object.values(locales).reduce((acc, data) => {
|
||||
acc.push(data)
|
||||
|
||||
return acc
|
||||
}, <LocaleObject[]>[])
|
||||
|
||||
return useLocales.sort((a, b) => a.code.localeCompare(b.code))
|
||||
}
|
||||
|
||||
export const currentLocales = buildLocales()
|
42
i18n/locales/en-US.json
Normal file
42
i18n/locales/en-US.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"profile": "Profile",
|
||||
"darkMode": "🌗 Dark Mode",
|
||||
"language": "📚 Language",
|
||||
"404Demo": "🙅 Page 404 Demo",
|
||||
"unocssExample": "🎨 Unocss example",
|
||||
"keepAlive": "🧡 KeepAlive Demo",
|
||||
"persistPiniaState": "💾 Persist Pinia State",
|
||||
"fetch": "🏄 Network Request"
|
||||
},
|
||||
"tabbar": {
|
||||
"home": "Home",
|
||||
"profile": "Profile"
|
||||
},
|
||||
"unocss_page": {
|
||||
"hello": "Hello {0}",
|
||||
"desc": "This is a simple example of Unocss in action.",
|
||||
"btn_txt": "Button"
|
||||
},
|
||||
"error_page": {
|
||||
"back_btn": "Back",
|
||||
"txt": "Not found"
|
||||
},
|
||||
"profile_page": {
|
||||
"txt": "WIP"
|
||||
},
|
||||
"keepalive_page": {
|
||||
"label": "The current component will be cached"
|
||||
},
|
||||
"counter_page": {
|
||||
"label": "This is a simple example of persisting Pinia state. To verify its effectiveness, you can refresh the interface and observe it.",
|
||||
"label_num": "Number",
|
||||
"btn_add": "Add"
|
||||
},
|
||||
"prose_page": {
|
||||
"btn_fetch": "Fetch",
|
||||
"btn_clear": "Clear",
|
||||
"btn_empty_desc": "No data"
|
||||
}
|
||||
}
|
42
i18n/locales/zh-CN.json
Normal file
42
i18n/locales/zh-CN.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"menu": {
|
||||
"home": "主页",
|
||||
"profile": "我的",
|
||||
"darkMode": "🌗 暗黑模式",
|
||||
"language": "📚 语言",
|
||||
"404Demo": "🙅 404页 演示",
|
||||
"unocssExample": "🎨 Unocss 示例",
|
||||
"keepAlive": "🧡 KeepAlive 演示",
|
||||
"persistPiniaState": "💾 持久化 Pinia 状态",
|
||||
"fetch": "🏄 网络请求"
|
||||
},
|
||||
"tabbar": {
|
||||
"home": "主页",
|
||||
"profile": "我的"
|
||||
},
|
||||
"unocss_page": {
|
||||
"hello": "你好 {0}",
|
||||
"desc": "这是 unocss 一个简单例子。",
|
||||
"btn_txt": "按钮"
|
||||
},
|
||||
"error_page": {
|
||||
"back_btn": "返回",
|
||||
"txt": "没有找到"
|
||||
},
|
||||
"profile_page": {
|
||||
"txt": "未完成"
|
||||
},
|
||||
"keepalive_page": {
|
||||
"label": "当前组件将会被缓存"
|
||||
},
|
||||
"counter_page": {
|
||||
"label": "这是一个简单的持久化 Pinia 状态的例子。为了验证其有效性,你可以刷新界面并观察它。",
|
||||
"label_num": "数字",
|
||||
"btn_add": "增加"
|
||||
},
|
||||
"prose_page": {
|
||||
"btn_fetch": "拉取",
|
||||
"btn_clear": "清空",
|
||||
"btn_empty_desc": "暂无数据"
|
||||
}
|
||||
}
|
129
nuxt.config.ts
Normal file
129
nuxt.config.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import process from 'node:process'
|
||||
import { appDescription } from './app/constants/index'
|
||||
import preload from './app/utils/preload'
|
||||
import { currentLocales } from './i18n/i18n'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@vant/nuxt',
|
||||
'@unocss/nuxt',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxt/eslint',
|
||||
'@nuxtjs/i18n',
|
||||
'@pinia/nuxt',
|
||||
'pinia-plugin-persistedstate/nuxt',
|
||||
],
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: process.env.NUXT_PUBLIC_API_BASE,
|
||||
},
|
||||
},
|
||||
|
||||
css: [
|
||||
'@unocss/reset/tailwind.css',
|
||||
'./app/styles/vars.css',
|
||||
'./app/styles/global.css',
|
||||
'./app/styles/default-theme.css',
|
||||
],
|
||||
|
||||
postcss: {
|
||||
plugins: {
|
||||
'autoprefixer': {},
|
||||
|
||||
// https://github.com/wswmsword/postcss-mobile-forever
|
||||
'postcss-mobile-forever': {
|
||||
appSelector: '#__nuxt',
|
||||
viewportWidth: 375,
|
||||
maxDisplayWidth: 600,
|
||||
// devtools excluded
|
||||
exclude: /@nuxt/,
|
||||
border: true,
|
||||
rootContainingBlockSelectorList: [
|
||||
'van-tabbar',
|
||||
'van-popup',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
colorMode: {
|
||||
classSuffix: '',
|
||||
preference: 'system',
|
||||
fallback: 'light',
|
||||
storageKey: 'nuxt-color-mode',
|
||||
},
|
||||
|
||||
i18n: {
|
||||
locales: currentLocales,
|
||||
lazy: true,
|
||||
strategy: 'no_prefix',
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
},
|
||||
langDir: 'locales',
|
||||
defaultLocale: 'zh-CN',
|
||||
vueI18n: './i18n/i18n.config.ts',
|
||||
},
|
||||
|
||||
app: {
|
||||
head: {
|
||||
viewport: 'width=device-width,initial-scale=1,viewport-fit=cover',
|
||||
link: [
|
||||
{ rel: 'icon', href: '/favicon.ico', sizes: 'any' },
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' },
|
||||
{ name: 'description', content: appDescription },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: '#ffffff' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
||||
],
|
||||
script: [
|
||||
{ innerHTML: preload(), type: 'text/javascript', tagPosition: 'head' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@intlify/core-base',
|
||||
'@intlify/shared',
|
||||
'is-https',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
experimental: {
|
||||
typedPages: true,
|
||||
},
|
||||
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
typescript: {
|
||||
shim: false,
|
||||
},
|
||||
|
||||
features: {
|
||||
// For UnoCSS
|
||||
inlineStyles: false,
|
||||
},
|
||||
|
||||
eslint: {
|
||||
config: {
|
||||
standalone: false,
|
||||
},
|
||||
},
|
||||
|
||||
future: {
|
||||
compatibilityVersion: 4,
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-09-24',
|
||||
})
|
61
package.json
Normal file
61
package.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "nuxt-vant-mobile",
|
||||
"type": "module",
|
||||
"version": "0.3.0",
|
||||
"packageManager": "pnpm@9.15.1",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"release": "bumpp --commit --push --tag"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@nuxtjs/i18n": "^9.1.1",
|
||||
"nuxt": "^3.15.0",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.12.1",
|
||||
"@iconify-json/carbon": "^1.2.5",
|
||||
"@nuxt/eslint": "^0.7.4",
|
||||
"@pinia/nuxt": "^0.9.0",
|
||||
"@unocss/eslint-plugin": "0.65.2",
|
||||
"@unocss/nuxt": "0.65.2",
|
||||
"@unocss/preset-rem-to-px": "0.65.2",
|
||||
"@vant/nuxt": "^1.0.6",
|
||||
"bumpp": "^9.9.2",
|
||||
"pinia": "^2.3.0",
|
||||
"postcss-mobile-forever": "^4.3.1",
|
||||
"typescript": "~5.7.2",
|
||||
"vant": "^4.9.15"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"meow": "^12.x",
|
||||
"@intlify/shared": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"allowedDeprecatedVersions": {
|
||||
"glob": "*",
|
||||
"are-we-there-yet": "2",
|
||||
"gauge": "3",
|
||||
"inflight": "1",
|
||||
"npmlog": "5",
|
||||
"rimraf": "3"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"vite": "^6.0.5"
|
||||
},
|
||||
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
]
|
||||
}
|
10523
pnpm-lock.yaml
Normal file
10523
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
3
server/tsconfig.json
Normal file
3
server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
45
uno.config.ts
Normal file
45
uno.config.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import presetRemToPx from '@unocss/preset-rem-to-px'
|
||||
|
||||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetTypography,
|
||||
presetUno,
|
||||
presetWebFonts,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from 'unocss'
|
||||
|
||||
// https://unocss.dev/guide/config-file
|
||||
export default defineConfig({
|
||||
shortcuts: [
|
||||
// shortcuts to multiple utilities
|
||||
['btn', 'px-6 py-3 rounded-3 inline-block bg-primary text-white cursor-pointer hover:bg-primary-hover disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
|
||||
],
|
||||
|
||||
presets: [
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetIcons(),
|
||||
presetTypography(),
|
||||
presetWebFonts(),
|
||||
presetRemToPx({
|
||||
baseFontSize: 4,
|
||||
}),
|
||||
],
|
||||
|
||||
transformers: [
|
||||
transformerDirectives(),
|
||||
transformerVariantGroup(),
|
||||
],
|
||||
|
||||
theme: {
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: 'var(--c-primary)',
|
||||
hover: 'var(--c-primary-active)',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue
Block a user