feat: New layout system (#62)

This commit is contained in:
CharleeWa 2024-02-27 01:03:41 +08:00
parent 08a0b2571b
commit c776f49d8c
24 changed files with 198 additions and 83 deletions

View File

@ -34,6 +34,8 @@
- 🍍 [State Management via Pinia](https://pinia.vuejs.org)
- 📑 [Layout system](./src/layouts)
- 🎨 [UnoCSS](https://github.com/antfu/unocss) - the instant on-demand atomic CSS engine
- 🔥 Use the [new `<script setup>` syntax](https://github.com/vuejs/rfcs/pull/227)
@ -79,6 +81,7 @@
- [Vue Router](https://github.com/vuejs/router)
- [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) - file system based routing
- [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) - layouts for pages
- [Pinia](https://pinia.vuejs.org) - Intuitive, type safe, light and flexible Store for Vue using the composition api
- [`pinia-plugin-persistedstate`](https://github.com/prazdevs/pinia-plugin-persistedstate) - Configurable persistence and rehydration of Pinia stores
- [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) - components auto import
@ -86,6 +89,9 @@
- [vite-plugin-vconsole](https://github.com/vadxq/vite-plugin-vconsole) - A lightweight, extendable front-end developer tool for mobile web page
- [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) - Vite Plugin for API mock dev server
- [postcss-mobile-forever](https://github.com/wswmsword/postcss-mobile-forever) - To adapt different displays by one mobile viewport
- [vite-plugin-vue-devtools](https://github.com/vuejs/devtools-next) - Designed to enhance the Vue developer experience
- [vueuse](https://github.com/antfu/vueuse) - collection of useful composition APIs
- [@unhead/vue](https://github.com/unjs/unhead) - manipulate document head reactively
### Coding Style

View File

@ -34,6 +34,8 @@
- 🍍 [使用 Pinia 的状态管理](https://pinia.vuejs.org)
- 📑 [布局系统](./src/layouts)
- 🎨 [UnoCSS](https://github.com/antfu/unocss) - 高性能且极具灵活性的即时原子化 CSS 引擎
- 🔥 使用 [新的 `<script setup>` 语法](https://github.com/vuejs/rfcs/pull/227)
@ -79,6 +81,7 @@
- [Vue Router](https://github.com/vuejs/router)
- [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) - 以文件系统为基础的路由
- [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) - 页面布局系统
- [Pinia](https://pinia.vuejs.org) - 直接的, 类型安全的, 使用 Composition API 的轻便灵活的 Vue 状态管理库
- [`pinia-plugin-persistedstate`](https://github.com/prazdevs/pinia-plugin-persistedstate) - 适用于 Pinia 的持久化存储插件
- [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) - 自动加载组件
@ -86,6 +89,9 @@
- [vite-plugin-vconsole](https://github.com/vadxq/vite-plugin-vconsole) - vConsole 的 vite 插件
- [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) - vite mock 开发服务mock-dev-server插件
- [postcss-mobile-forever](https://github.com/wswmsword/postcss-mobile-forever) - 一款 PostCSS 插件,将固定尺寸的移动端视图转为具有最大宽度的可伸缩的移动端视图
- [vite-plugin-vue-devtools](https://github.com/vuejs/devtools-next) - 旨在增强Vue开发者体验的Vite插件
- [vueuse](https://github.com/antfu/vueuse) - 实用的 Composition API 工具合集
- [@unhead/vue](https://github.com/unjs/unhead) - 响应式地操作文档头信息
### 编码风格

View File

@ -10,26 +10,28 @@ import { VantResolver } from 'unplugin-vue-components/resolvers'
import { unheadVueComposablesImports } from '@unhead/vue'
import VueDevTools from 'vite-plugin-vue-devtools'
import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
import Layouts from 'vite-plugin-vue-layouts'
import UnoCSS from 'unocss/vite'
import { createViteVConsole } from './vconsole'
export function createVitePlugins() {
return [
// https://github.com/posva/unplugin-vue-router
VueRouter({
routesFolder: 'src/views',
extensions: ['.vue'],
routesFolder: 'src/pages',
dts: 'src/typed-router.d.ts',
}),
vue(),
vueJsx(),
visualizer(),
UnoCSS(),
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
Layouts(),
// https://github.com/pengzhanbo/vite-plugin-mock-dev-server
mockDevServerPlugin(),
legacy({
targets: ['defaults', 'not IE 11'],
}),
// https://github.com/antfu/unplugin-vue-components
Components({
extensions: ['vue'],
resolvers: [VantResolver()],
@ -37,6 +39,7 @@ export function createVitePlugins() {
dts: 'src/components.d.ts',
}),
// https://github.com/antfu/unplugin-auto-import
AutoImport({
include: [
/\.[tj]sx?$/,
@ -59,8 +62,21 @@ export function createVitePlugins() {
],
}),
// https://github.com/antfu/unocss
// see uno.config.ts for config
UnoCSS(),
// https://github.com/vadxq/vite-plugin-vconsole
createViteVConsole(),
// https://github.com/webfansplz/vite-plugin-vue-devtools
VueDevTools(),
vueJsx(),
visualizer(),
legacy({
targets: ['defaults', 'not IE 11'],
}),
]
}

View File

@ -68,6 +68,7 @@
"vite-plugin-mock-dev-server": "^1.4.7",
"vite-plugin-vconsole": "^2.1.1",
"vite-plugin-vue-devtools": "^7.0.14",
"vite-plugin-vue-layouts": "^0.11.0",
"vitest": "^1.2.2",
"vue-tsc": "^1.8.27"
},

View File

@ -154,6 +154,9 @@ devDependencies:
vite-plugin-vue-devtools:
specifier: ^7.0.14
version: 7.0.14(rollup@4.9.6)(vite@5.0.12)
vite-plugin-vue-layouts:
specifier: ^0.11.0
version: 0.11.0(vite@5.0.12)(vue-router@4.2.5)(vue@3.4.15)
vitest:
specifier: ^1.2.2
version: 1.2.2(@types/node@20.11.16)(less@4.2.0)(terser@5.27.0)
@ -7385,6 +7388,22 @@ packages:
- supports-color
dev: true
/vite-plugin-vue-layouts@0.11.0(vite@5.0.12)(vue-router@4.2.5)(vue@3.4.15):
resolution: {integrity: sha512-uh6NW7lt+aOXujK4eHfiNbeo55K9OTuB7fnv+5RVc4OBn/cZull6ThXdYH03JzKanUfgt6QZ37NbbtJ0og59qw==}
peerDependencies:
vite: ^4.0.0 || ^5.0.0
vue: ^3.2.4
vue-router: ^4.0.11
dependencies:
debug: 4.3.4
fast-glob: 3.3.2
vite: 5.0.12(@types/node@20.11.16)(less@4.2.0)(terser@5.27.0)
vue: 3.4.15(typescript@5.3.3)
vue-router: 4.2.5(vue@3.4.15)
transitivePeerDependencies:
- supports-color
dev: true
/vite@5.0.12(@types/node@20.11.16)(less@4.2.0)(terser@5.27.0):
resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==}
engines: {node: ^18.0.0 || >=20.0.0}

1
src/components.d.ts vendored
View File

@ -16,6 +16,7 @@ declare module 'vue' {
VanCellGroup: typeof import('vant/es')['CellGroup']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanEmpty: typeof import('vant/es')['Empty']
VanIcon: typeof import('vant/es')['Icon']
VanNavBar: typeof import('vant/es')['NavBar']
VanSpace: typeof import('vant/es')['Space']
VanSwitch: typeof import('vant/es')['Switch']

View File

@ -1,5 +1,5 @@
<template>
<main class="h-full w-full p-16 py-60">
<div class="p-16 py-60">
<slot />
</main>
</div>
</template>

21
src/layouts/404.vue Normal file
View File

@ -0,0 +1,21 @@
<script setup lang="ts">
const router = useRouter()
</script>
<template>
<main p="x4 y16" text-18 text="center gray-300 dark:gray-200">
<van-icon name="warn-o" size="3em" />
<RouterView />
<div class="mt-10">
<button van-haptics-feedback btn m="3 t8" @click="router.back()">
Back
</button>
</div>
</main>
</template>
<style scoped>
</style>

14
src/layouts/README.md Normal file
View File

@ -0,0 +1,14 @@
# 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.
With [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) and [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts), you can specify the layout in the page's SFCs like this:
```vue
<route lang="yaml">
meta:
layout: home
</route>
```

9
src/layouts/default.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<main text="gray-700 dark:gray-200">
<RouterView />
<div mx-auto mt-15 text-center text-14 opacity-50>
Default Layout
</div>
</main>
</template>

9
src/layouts/home.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<main py-20 text="gray-700 dark:gray-200">
<RouterView />
<div mx-auto mt-15 text-center text-14 opacity-50>
Home Layout
</div>
</main>
</template>

10
src/pages/[...all].vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<div>
Not found
</div>
</template>
<route lang="yaml">
meta:
layout: 404
</route>

View File

@ -21,7 +21,7 @@ const onClickLeft = () => history.back()
</script>
<template>
<div class="h-full w-full">
<div>
<VanNavBar title="🍍 持久化 Pinia 状态" left-arrow fixed @click-left="onClickLeft" />
<Container>
@ -35,7 +35,7 @@ const onClickLeft = () => history.back()
<p class="mt-4">
number<strong class="text-green-500"> {{ counter }} </strong>
</p>
<button class="btn border-none btn-green" @click="add">
<button class="btn" @click="add">
Add
</button>
</Container>

56
src/pages/index.vue Normal file
View File

@ -0,0 +1,56 @@
<script setup lang="ts">
import useAppStore from '@/stores/modules/app'
definePage({
name: 'main',
meta: {
level: 1,
},
})
const appStore = useAppStore()
const checked = ref<boolean>(isDark.value)
watch(
() => isDark.value,
(newMode) => {
checked.value = newMode
},
{ immediate: true },
)
function toggle() {
toggleDark()
appStore.swithMode(isDark.value ? 'dark' : 'light')
}
</script>
<template>
<VanCellGroup inset>
<VanCell center title="🌗 暗黑模式">
<template #right-icon>
<VanSwitch v-model="checked" size="20px" @click="toggle()" />
</template>
</VanCell>
<VanCell title="💿 Mock 指南" to="mock" is-link />
<VanCell title="📊 Echarts 演示" to="charts" is-link />
<VanCell title="🎨 Unocss 示例" to="unocss" is-link />
<VanCell title="🍍 持久化 Pinia 状态" to="counter" is-link />
<VanCell title="🙅 404 演示" to="unknown" is-link />
<VanCell center>
<template #title>
<span class="mr-4 v-middle">🚀 欢迎补充</span>
<VanTag type="primary">
PR
</VanTag>
</template>
</VanCell>
</VanCellGroup>
</template>
<route lang="yaml">
meta:
layout: home
</route>

View File

@ -24,7 +24,7 @@ const onClickLeft = () => history.back()
</script>
<template>
<div class="h-full w-full">
<div>
<VanNavBar title="💿 Mock 指南" left-arrow fixed @click-left="onClickLeft" />
<Container>

View File

@ -11,7 +11,7 @@ const onClickLeft = () => history.back()
</script>
<template>
<div class="h-full w-full">
<div>
<VanNavBar title="🎨 Unocss" left-arrow fixed @click-left="onClickLeft" />
<Container>
@ -21,8 +21,8 @@ const onClickLeft = () => history.back()
<p class="mt-4 text-gray-700 dark:text-white">
This is a simple example of Unocss in action.
</p>
<button class="btn border-none btn-green">
Click me
<button class="btn">
Button
</button>
</Container>
</div>

View File

@ -1,8 +1,8 @@
# `File-based Routing`
Routes will be auto-generated for Vue files in the **src/views** dir with the same file structure.
Routes will be auto-generated for Vue files in the **src/pages** dir with the same file structure.
Check out [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) for more details.
**src/views** 目录下的 Vue 文件会自动生成相同结构的路由。
**src/pages** 目录下的 Vue 文件会自动生成相同结构的路由。
查看[`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router)了解更多细节。

View File

@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router/auto'
import { setupLayouts } from 'virtual:generated-layouts'
import NProgress from 'nprogress'
import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
@ -8,6 +9,7 @@ NProgress.configure({ showSpinner: true, parent: '#app' })
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_APP_PUBLIC_PATH),
extendRoutes: routes => setupLayouts(routes),
})
router.beforeEach((to, from, next) => {

View File

@ -40,6 +40,7 @@ import type {
declare module 'vue-router/auto/routes' {
export interface RouteNamedMap {
'main': RouteRecordInfo<'main', '/', Record<never, never>, Record<never, never>>,
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
'charts': RouteRecordInfo<'charts', '/charts', Record<never, never>, Record<never, never>>,
'counter': RouteRecordInfo<'counter', '/counter', Record<never, never>, Record<never, never>>,
'mock': RouteRecordInfo<'mock', '/mock', Record<never, never>, Record<never, never>>,

View File

@ -1,52 +0,0 @@
<script setup lang="ts">
import useAppStore from '@/stores/modules/app'
definePage({
name: 'main',
meta: {
level: 1,
},
})
const appStore = useAppStore()
const checked = ref<boolean>(isDark.value)
watch(
() => isDark.value,
(newMode) => {
checked.value = newMode
},
{ immediate: true },
)
function toggle() {
toggleDark()
appStore.swithMode(isDark.value ? 'dark' : 'light')
}
</script>
<template>
<main class="h-full w-full py-30">
<VanCellGroup inset>
<VanCell center title="🌗 暗黑模式">
<template #right-icon>
<VanSwitch v-model="checked" size="20px" @click="toggle()" />
</template>
</VanCell>
<VanCell title="💿 Mock 指南" to="mock" is-link />
<VanCell title="📊 Echarts 演示" to="charts" is-link />
<VanCell title="🎨 Unocss 示例" to="unocss" is-link />
<VanCell title="🍍 持久化 Pinia 状态" to="counter" is-link />
<VanCell center>
<template #title>
<span class="mr-4 v-middle">🚀 欢迎补充</span>
<VanTag type="primary">
PR
</VanTag>
</template>
</VanCell>
</VanCellGroup>
</main>
</template>

View File

@ -10,7 +10,11 @@
"paths": {
"@/*": ["src/*"]
},
"types": ["node"],
"types": [
"node",
"unplugin-vue-router/client",
"vite-plugin-vue-layouts/client"
],
"allowJs": true,
"strictNullChecks": false,
"noImplicitAny": false,

View File

@ -15,18 +15,10 @@ export default defineConfig({
// https://juejin.cn/post/7262975395620618298
baseFontSize: 4,
}),
presetMini({
dark: {
dark: '.van-theme-dark',
light: '.van-theme-light',
},
}),
presetMini(),
],
shortcuts: {
shortcuts: [
// shortcuts to multiple utilities
'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',
'btn-green': 'text-white bg-green-500 hover:bg-green-700',
'btn-blue': 'text-white bg-blue-500 hover:bg-blue-700',
'centered': 'absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2',
},
['btn', 'px-6 py-3 rounded-3 border-none inline-block bg-green-400 text-white cursor-pointer !outline-none hover:bg-green-600 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
],
})