├── .husky
├── .gitignore
└── pre-commit
├── .vscode
└── extensions.json
├── images
├── qr.png
├── home.png
├── i18n.png
├── login.png
└── theme.png
├── public
└── favicon.ico
├── src
├── assets
│ ├── logo.png
│ ├── css
│ │ ├── theme.css
│ │ └── index.css
│ └── svg
│ │ ├── logo.svg
│ │ └── password.svg
├── api
│ └── index.ts
├── layout
│ ├── NavBar.vue
│ ├── Tabbar.vue
│ └── Index.vue
├── store
│ ├── index.ts
│ └── modules
│ │ └── app.ts
├── views
│ ├── tabbar
│ │ ├── Category.vue
│ │ ├── User.vue
│ │ └── Home.vue
│ └── Login.vue
├── env.d.ts
├── plugins
│ ├── vant.ts
│ └── i18n.ts
├── utils
│ ├── auth.ts
│ └── request.ts
├── config
│ ├── locales
│ │ ├── zh-CN.ts
│ │ └── en-US.ts
│ └── index.ts
├── main.ts
├── App.vue
├── components
│ └── SvgIcon.vue
└── router
│ └── index.ts
├── tsconfig.node.json
├── postcss.config.js
├── .gitignore
├── index.html
├── .editorconfig
├── tsconfig.json
├── .github
└── workflows
│ ├── ci.yml
│ └── sync-gitee.yml
├── .prettierrc.js
├── vite.config.ts
├── docs
├── github-action.md
└── 项目搭建.md
├── package.json
├── .eslintrc.js
└── README.md
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run lint
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["johnsoncodehk.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/images/qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/images/qr.png
--------------------------------------------------------------------------------
/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/images/home.png
--------------------------------------------------------------------------------
/images/i18n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/images/i18n.png
--------------------------------------------------------------------------------
/images/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/images/login.png
--------------------------------------------------------------------------------
/images/theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/images/theme.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vangleer/vue3-vite-vant-h5-template/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import request from '../utils/request'
2 |
3 | export const login = (data: any) => request.post('/login', data)
4 |
--------------------------------------------------------------------------------
/src/layout/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node"
6 | },
7 | "include": ["vite.config.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue'
2 | import { createPinia } from 'pinia'
3 |
4 | export const setupStore = (app: App) => {
5 | app.use(createPinia())
6 | }
7 |
8 | export * from './modules/app'
9 |
--------------------------------------------------------------------------------
/src/views/tabbar/Category.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ t('category.title') }}
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-pxtorem': {
4 | rootValue: 37.5,
5 | propList: ['*']
6 | },
7 | 'postcss-import': require('postcss-import'),
8 | autoprefixer: require('autoprefixer')
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/css/theme.css:
--------------------------------------------------------------------------------
1 | /* 白色主题 */
2 | .van-theme-light {
3 | --default-color: #272e2d;
4 | --van-blue: #12C8B9;
5 | --common-background-color: var(--van-background);
6 | }
7 |
8 | /* 黑色主题 */
9 | .van-theme-dark {
10 | background-color: #000;
11 | color: var(--van-text-color);
12 | --common-background-color: var(--van-background-3);
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/plugins/vant.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue'
2 | import { Button, Icon, NavBar, Tabbar, TabbarItem, ConfigProvider, Cell } from 'vant'
3 |
4 | const components = [Button, NavBar, Tabbar, TabbarItem, Icon, ConfigProvider, Cell]
5 |
6 | export const setupVant = (app: App) => {
7 | components.forEach((component) => {
8 | app.component(component.name, component)
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Vite App
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/utils/auth.ts:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 | import { useAppStore } from '/@/store'
3 | export const TOKEN_KEY = 'token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TOKEN_KEY)
7 | }
8 |
9 | export function setToken(token: string) {
10 | Cookies.set(TOKEN_KEY, token)
11 | useAppStore().setState({ token })
12 | }
13 |
14 | // 删除token
15 | export function removeToken() {
16 | Cookies.remove(TOKEN_KEY)
17 | }
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*] # 表示所有文件适用
6 | charset = utf-8 # 设置文件字符集为 utf-8
7 | indent_style = space # 缩进风格(tab | space)
8 | indent_size = 2 # 缩进大小
9 | end_of_line = lf # 控制换行类型(lf | cr | crlf)
10 | trim_trailing_whitespace = true # 去除行首的任意空白字符
11 | insert_final_newline = true # 始终在文件末尾插入一个新行
12 |
13 | [*.md] # 表示仅 md 文件适用以下规则
14 | max_line_length = off
15 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/src/layout/Tabbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ t(item.meta.title) }}
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/tabbar/User.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ t('user.title') }}
3 | {{ t('user.logout') }}
4 |
5 |
6 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/config/locales/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | username: '用户名',
3 | password: '密码',
4 | forgotPassword: '忘记密码/解绑',
5 | login: '登录',
6 | register: '注册',
7 | success: '成功',
8 | pleaseInput: '请输入',
9 | home: {
10 | title: '首页',
11 | tip: 'Vue3移动端开发模板',
12 | title1: '主题切换',
13 | title2: '国际化',
14 | title3: 'rem移动端适配方案',
15 | title4: 'axios二次封装',
16 | title5: 'pinia状态管理',
17 | reminder: '提示'
18 | },
19 | category: {
20 | title: '分类'
21 | },
22 | user: {
23 | title: '我的',
24 | logout: '退出登录'
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import 'amfe-flexible'
3 | import './assets/css/index.css'
4 | import './assets/css/theme.css'
5 | import 'virtual:svg-icons-register'
6 | import { useI18n } from './plugins/i18n'
7 | import { setupStore } from './store'
8 | import { setupVant } from './plugins/vant'
9 | import router from './router'
10 | import App from './App.vue'
11 |
12 | const app = createApp(App)
13 |
14 | useI18n(app)
15 | setupVant(app)
16 | setupStore(app)
17 | app.use(router)
18 |
19 | router.isReady().then(() => {
20 | app.mount('#app')
21 | })
22 |
--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------
1 | export const CONFIG_KEY = 'app-config'
2 | // 默认配置
3 | const defaultConfig = {
4 | theme: 'light',
5 | locale: 'zh'
6 | }
7 |
8 | export function getConfig() {
9 | return JSON.parse(localStorage.getItem(CONFIG_KEY) || JSON.stringify(defaultConfig))
10 | }
11 | export function setConfig(conf: any) {
12 | const config = JSON.parse(localStorage.getItem(CONFIG_KEY) || JSON.stringify(defaultConfig))
13 |
14 | Object.keys(conf).forEach((key) => {
15 | config[key] = conf[key]
16 | })
17 |
18 | localStorage.setItem(CONFIG_KEY, JSON.stringify(config))
19 | }
20 |
--------------------------------------------------------------------------------
/src/config/locales/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | username: 'Username',
3 | password: 'Password',
4 | forgotPassword: 'forget the password',
5 | login: 'Login',
6 | register: 'Register',
7 | success: 'Success',
8 | pleaseInput: 'Please Enter',
9 | home: {
10 | title: 'Home',
11 | tip: 'Vue3 Mobile template',
12 | title1: 'Theme',
13 | title2: 'I18n',
14 | title3: 'Rem',
15 | title4: 'Axios',
16 | title5: 'Pinia',
17 | reminder: 'Reminder'
18 | },
19 | category: {
20 | title: 'Category'
21 | },
22 | user: {
23 | title: 'User',
24 | logout: 'Logout'
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/layout/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "esModuleInterop": true,
13 | "baseUrl": ".",
14 | "lib": [
15 | "esnext",
16 | "dom"
17 | ],
18 | "skipLibCheck": true,
19 | "paths": {
20 | "/@/*": [
21 | "src/*"
22 | ]
23 | }
24 | },
25 | "include": [
26 | "src/**/*.ts",
27 | "src/**/*.d.ts",
28 | "src/**/*.tsx",
29 | "src/**/*.vue"
30 | ],
31 | "references": [
32 | {
33 | "path": "./tsconfig.node.json"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Vue3 Vite Vant H5 Template
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | build-and-deploy:
8 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession.
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout 🛎️
12 | uses: actions/checkout@v3
13 |
14 | - name: Install and Build 🔧 # 安装依赖、打包,如果提前已打包好无需这一步
15 | run: |
16 | npm install
17 | npm run build
18 | cp images/ -r dist/assets
19 |
20 | - name: Deploy 🚀
21 | uses: JamesIves/github-pages-deploy-action@v4.3.3
22 | with:
23 | branch: gh-pages # The branch the action should deploy to.
24 | folder: dist # The folder the action should deploy.
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100, // 单行输出(不折行)的(最大)长度
3 | tabWidth: 2, // 每个缩进级别的空格数
4 | tabs: false, // 使用制表符 (tab) 缩进行而不是空格 (space)。
5 | semi: false, // 是否在语句末尾打印分号
6 | singleQuote: true, // 是否使用单引号
7 | quoteProps: "as-needed", // 仅在需要时在对象属性周围添加引号
8 | jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
9 | trailingComma: "none", // 去除对象最末尾元素跟随的逗号
10 | bracketSpacing: true, // 是否在对象属性添加空格
11 | jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
12 | arrowParens: "always", // 箭头函数,只有一个参数的时候,也需要括号
13 | proseWrap: "always", // 当超出print width(上面有这个参数)时就折行
14 | htmlWhitespaceSensitivity: "ignore", // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
15 | vueIndentScriptAndStyle: false, // 在VUE文件中不要缩进脚本和样式标记
16 | stylelintIntegration: true,
17 | endOfLine: "auto"
18 | }
--------------------------------------------------------------------------------
/src/components/SvgIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
26 |
27 |
35 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosRequestConfig, AxiosInstance } from 'axios'
2 | import qs from 'qs'
3 |
4 | const defaultConfig: AxiosRequestConfig = {
5 | baseURL: '/api/v1',
6 | timeout: 10000,
7 | // 数组格式参数序列化
8 | paramsSerializer: (params) => qs.stringify(params, { indices: false })
9 | }
10 |
11 | const instance: AxiosInstance = axios.create({
12 | ...defaultConfig
13 | })
14 |
15 | // 请求拦截器
16 | instance.interceptors.request.use(
17 | (config) => {
18 | const token = localStorage.getItem('token')
19 | if (token) {
20 | config.headers['Authorization'] = `Bearer ${token}`
21 | }
22 |
23 | return config
24 | },
25 | (error) => Promise.reject(error)
26 | )
27 |
28 | // 响应拦截器
29 | instance.interceptors.response.use(
30 | (response) => {
31 | return response.data
32 | },
33 | (error) => Promise.reject(error)
34 | )
35 |
36 | export default instance
37 |
--------------------------------------------------------------------------------
/src/assets/svg/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/workflows/sync-gitee.yml:
--------------------------------------------------------------------------------
1 | name: Sync to Gitee
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Sync to Gitee
12 | uses: wearerequired/git-mirror-action@master
13 | env:
14 | # 在 Settings->Secrets
15 | SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }}
16 | with:
17 | # GitHub 源仓库地址
18 | source-repo: git@github.com:vangleer/vue3-vite-vant-h5-template.git
19 | # Gitee 目标仓库地址
20 | destination-repo: git@gitee.com:huangwantong/vue3-vite-vant-h5-template.git
21 |
22 | # - name: Build Gitee Pages # 部署 gitee pages
23 | # uses: yanglbme/gitee-pages-action@master
24 | # with:
25 | # # 注意替换为你的 Gitee 用户名
26 | # gitee-username: huangwantong
27 | # # 注意在 Settings->Secrets 配置 GITEE_PASSWORD
28 | # gitee-password: ${{ secrets.GITEE_PASSWORD }}
29 | # # 注意替换为你的 Gitee 仓库
30 | # gitee-repo: vue3-vite-vant-h5-template
31 | # branch: gh-pages
32 |
--------------------------------------------------------------------------------
/src/plugins/i18n.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue'
2 | import { createI18n } from 'vue-i18n'
3 | import { Locale } from 'vant'
4 | import { getConfig } from '/@/config'
5 | import enLocale from 'vant/es/locale/lang/en-US'
6 | import zhLocale from 'vant/es/locale/lang/zh-CN'
7 | import zhCN from '../config/locales/zh-CN'
8 | import enUS from '../config/locales/en-US'
9 |
10 | export const i18nKey = 'responsive-locale'
11 |
12 | const messages = {
13 | zh: zhCN,
14 | en: enUS
15 | }
16 |
17 | export const i18n = createI18n({
18 | legacy: false,
19 | locale: getConfig().locale,
20 | fallbackLocale: 'en',
21 | messages
22 | })
23 | export function useI18n(app: App) {
24 | app.use(i18n)
25 | }
26 |
27 | export const t = (key: string) => i18n.global.t(key)
28 |
29 | /**
30 | * 设置语言
31 | * @param key string | undefined
32 | */
33 | export function changeLocale(key?: 'zh' | 'en') {
34 | if (!key) {
35 | key = i18n.global.locale.value === 'zh' ? 'en' : 'zh'
36 | }
37 | i18n.global.locale.value = key
38 | key === 'zh' ? Locale.use('zh-CN', zhLocale) : Locale.use('en-US', enLocale)
39 | return key
40 | }
41 |
--------------------------------------------------------------------------------
/src/store/modules/app.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import { changeLocale } from '/@/plugins/i18n'
3 | import { getConfig, setConfig } from '/@/config'
4 | const config = getConfig()
5 | type AppStateType = {
6 | token?: string
7 | title?: string
8 | theme?: string
9 | locale?: string
10 | }
11 | export const useAppStore = defineStore({
12 | id: 't-app',
13 | state: (): AppStateType => ({
14 | title: '',
15 | token: '',
16 | theme: config.theme as string,
17 | locale: config.locale as string
18 | }),
19 | actions: {
20 | setState(state: AppStateType) {
21 | const keys: any = Object.keys(state)
22 | keys.forEach((key: keyof AppStateType) => {
23 | this[key] = state[key]
24 | })
25 | },
26 | changeTheme(theme?: string) {
27 | if (theme) {
28 | this.theme = theme
29 | } else {
30 | this.theme = this.theme === 'light' ? 'dark' : 'light'
31 | }
32 | setConfig({ theme: this.theme })
33 | },
34 | changeLocale(locale?: 'zh' | 'en') {
35 | const key = changeLocale(locale)
36 | this.locale = key
37 | setConfig({ locale: key })
38 | }
39 | }
40 | })
41 |
--------------------------------------------------------------------------------
/src/assets/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | -moz-osx-font-smoothing: grayscale;
7 | -webkit-font-smoothing: antialiased;
8 | text-rendering: optimizelegibility;
9 | font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial,
10 | sans-serif;
11 | }
12 |
13 | html {
14 | width: 100%;
15 | height: 100%;
16 | box-sizing: border-box;
17 | }
18 |
19 | #app {
20 | width: 100%;
21 | height: 100%;
22 | font-size: 16px;
23 | color: var(--default-color);
24 | }
25 |
26 | label {
27 | font-weight: 700;
28 | }
29 |
30 | *,
31 | *::before,
32 | *::after {
33 | box-sizing: inherit;
34 | margin: 0;
35 | padding: 0;
36 | }
37 |
38 | a:focus,
39 | a:active {
40 | outline: none;
41 | }
42 |
43 | a,
44 | a:focus,
45 | a:hover {
46 | cursor: pointer;
47 | color: inherit;
48 | text-decoration: none;
49 | }
50 |
51 | div:focus {
52 | outline: none;
53 | }
54 |
55 | ul {
56 | margin: 0;
57 | padding: 0;
58 | list-style: none;
59 | }
60 |
61 | .clearfix::after {
62 | visibility: hidden;
63 | display: block;
64 | font-size: 0;
65 | content: ' ';
66 | clear: both;
67 | height: 0;
68 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import svgLoader from 'vite-svg-loader'
4 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
5 | import styleImport, { VantResolve } from 'vite-plugin-style-import'
6 | import { resolve } from 'path'
7 | // 路径查找
8 | const pathResolve = (dir: string): string => {
9 | return resolve(__dirname, '.', dir)
10 | }
11 |
12 | // 设置别名
13 | const alias: Record = {
14 | '/@': pathResolve('src'),
15 | '@': pathResolve('src'),
16 | '@build': pathResolve('build')
17 | }
18 | // https://vitejs.dev/config/
19 | export default ({ mode }) => defineConfig({
20 | resolve: { alias },
21 | base: mode === 'production' ? '/vue3-vite-vant-h5-template/' : '/',
22 | plugins: [
23 | vue(),
24 | styleImport({
25 | resolves: [VantResolve()]
26 | }),
27 | svgLoader(),
28 | createSvgIconsPlugin({
29 | // 指定需要缓存的图标文件夹
30 | iconDirs: [pathResolve('src/assets/svg')],
31 | // 指定symbolId格式
32 | symbolId: 'icon-[dir]-[name]'
33 | })
34 | ],
35 | server: {
36 | // 是否开启 https
37 | https: false,
38 | // 端口号
39 | port: 3002,
40 | host: '0.0.0.0',
41 | // 本地跨域代理
42 | proxy: {
43 | '/api/v1': {
44 | target: 'http://192.168.1.25:8081',
45 | changeOrigin: true,
46 | }
47 | }
48 | }
49 | })
50 |
51 |
--------------------------------------------------------------------------------
/docs/github-action.md:
--------------------------------------------------------------------------------
1 | # Github Actions 配置
2 |
3 | ### [参考文档](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html)
4 |
5 | ### 修改部署配置
6 | 1. 在package.json中添加一个homepage字段
7 | ```json
8 | "homepage": "https://[username].github.io/github-actions-demo"
9 | ```
10 | - 将[username]替换成你的 GitHub 用户名
11 |
12 | 2. 在vite.config.ts中修改base
13 | ```js
14 | export default ({ mode }) => defineConfig({
15 | base: mode === 'production' ? '/vue3-vite-vant-h5-template/' : '/',
16 | })
17 | ```
18 | - 这需要注意一下,由于github pages默认的地址是包含子目录的,所以我这这需要指定一下 base 的路径为我们的项目名。
19 |
20 |
21 | ### 配置文件
22 |
23 | - 在这个仓库的.github/workflows目录,生成一个 workflow 文件,名字可以随便取,这个示例是ci.yml
24 | ```yml
25 | # ci.yml
26 | name: Vue3 Vite Vant H5 Template
27 | on:
28 | push:
29 | branches:
30 | - main
31 | jobs:
32 | build-and-deploy:
33 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession.
34 | runs-on: ubuntu-latest
35 | steps:
36 | - name: Checkout 🛎️
37 | uses: actions/checkout@v3
38 |
39 | - name: Install and Build 🔧 # 安装依赖、打包,如果提前已打包好无需这一步
40 | run: |
41 | npm install
42 | npm run build
43 |
44 | - name: Deploy 🚀
45 | uses: JamesIves/github-pages-deploy-action@v4.3.3
46 | with:
47 | branch: gh-pages # The branch the action should deploy to.
48 | folder: dist # The folder the action should deploy.
49 |
50 | ```
51 | - 如果不是自己提交的话需要
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-vite-vant-h5-template",
3 | "private": true,
4 | "version": "0.1.0",
5 | "homepage": "https://vangleer.github.io/vue3-vite-vant-h5-template",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "lint": "eslint ./src/**/*{.js,.jsx,.ts,.tsx,.vue} --fix",
11 | "prettier": "prettier --write \"src/**/*.{js,jsx,json,ts,tsx,css,less,scss,vue,html,md}\"",
12 | "prepare": "husky install"
13 | },
14 | "dependencies": {
15 | "amfe-flexible": "^2.2.1",
16 | "axios": "^0.27.2",
17 | "js-cookie": "^3.0.1",
18 | "pinia": "^2.0.13",
19 | "qs": "^6.10.3",
20 | "vant": "4.0.0-alpha.0",
21 | "vite-svg-loader": "^3.3.0",
22 | "vue": "^3.2.25",
23 | "vue-i18n": "9.2.0-beta.33",
24 | "vue-router": "4"
25 | },
26 | "devDependencies": {
27 | "@types/js-cookie": "^3.0.2",
28 | "@types/node": "^17.0.29",
29 | "@types/qs": "^6.9.7",
30 | "@typescript-eslint/eslint-plugin": "^5.21.0",
31 | "@typescript-eslint/parser": "^5.21.0",
32 | "@vitejs/plugin-vue": "^2.3.1",
33 | "autoprefixer": "^10.4.7",
34 | "eslint": "^8.14.0",
35 | "eslint-config-prettier": "^8.5.0",
36 | "eslint-plugin-prettier": "^4.0.0",
37 | "eslint-plugin-vue": "^8.7.1",
38 | "husky": "^7.0.4",
39 | "lint-staged": "^12.4.1",
40 | "postcss": "^8.4.13",
41 | "postcss-import": "^14.1.0",
42 | "postcss-pxtorem": "^6.0.0",
43 | "prettier": "^2.6.2",
44 | "sass": "^1.51.0",
45 | "ts-node": "^10.7.0",
46 | "typescript": "^4.5.4",
47 | "vite": "^2.9.5",
48 | "vite-plugin-style-import": "1.4.1",
49 | "vite-plugin-svg-icons": "^2.0.1",
50 | "vue-global-api": "^0.4.1",
51 | "vue-tsc": "^0.34.7"
52 | },
53 | "lint-staged": {
54 | "*.{js,jsx,vue,ts,tsx}": "npm run lint"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
2 | import Login from '/@/views/Login.vue'
3 | import Layout from '/@/layout/Index.vue'
4 | import Home from '/@/views/tabbar/Home.vue'
5 | import Category from '/@/views/tabbar/Category.vue'
6 | import User from '/@/views/tabbar/User.vue'
7 | import { useAppStore } from '/@/store'
8 | import { getToken } from '/@/utils/auth'
9 | import { t } from '/@/plugins/i18n'
10 | export const tabbar: Array = [
11 | {
12 | path: '/home',
13 | component: Home,
14 | meta: {
15 | title: 'home.title',
16 | icon: 'home-o'
17 | }
18 | },
19 | {
20 | path: '/category',
21 | component: Category,
22 | meta: {
23 | title: 'category.title',
24 | icon: 'qr'
25 | }
26 | },
27 | {
28 | path: '/user',
29 | component: User,
30 | meta: {
31 | title: 'user.title',
32 | icon: 'user-o'
33 | }
34 | }
35 | ]
36 | const routes: Array = [
37 | {
38 | path: '/',
39 | component: Layout,
40 | redirect: '/home',
41 | children: tabbar
42 | },
43 | {
44 | path: '/login',
45 | component: Login
46 | }
47 | ]
48 |
49 | const router = createRouter({
50 | history: createWebHashHistory(),
51 | routes
52 | })
53 |
54 | // 路由白名单
55 | const whiteList = ['/login']
56 |
57 | router.beforeEach((to, from, next) => {
58 | const title = (to.meta.title as string) || 'Vite App'
59 | document.title = t(title)
60 | const token = getToken()
61 | if (to.path === '/login' || whiteList.includes(to.path)) {
62 | if (token) {
63 | next('/')
64 | } else {
65 | next()
66 | }
67 | } else {
68 | const store = useAppStore()
69 | if (!token) {
70 | store.setState({ title })
71 | return next('/login')
72 | }
73 |
74 | store.setState({ title, token })
75 | next()
76 | }
77 | })
78 |
79 | export default router
80 |
--------------------------------------------------------------------------------
/src/views/tabbar/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue3-vite-vant-h5-template
6 |
7 |
{{ t('home.tip') }}
8 |
9 |
10 |
16 | {{ t(item.title) }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
56 |
57 |
92 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | globals: {
8 | Message: true,
9 | env: true,
10 | useRoute: true,
11 | useRouter: true,
12 | useStore: true
13 | },
14 | /* 指定如何解析语法。可以为空,但若不为空,只能配该值,原因见下文。*/
15 | parser: 'vue-eslint-parser',
16 | /* 优先级低于parse的语法解析配置 */
17 | parserOptions: {
18 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
19 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
20 | sourceType: 'module', // Allows for the use of imports
21 | ecmaFeatures: {
22 | jsx: true
23 | }
24 | },
25 | extends: [
26 | 'vue-global-api',
27 | 'eslint:recommended',
28 | 'plugin:vue/vue3-recommended',
29 | 'plugin:@typescript-eslint/recommended',
30 | 'prettier',
31 | 'plugin:prettier/recommended'
32 | ],
33 | plugins: ['vue'],
34 | rules: {
35 | 'no-console': process.env.NODE_ENV === 'production' ? 1 : 0,
36 | 'no-debugger': process.env.NODE_ENV === 'production' ? 1 : 0,
37 | 'no-useless-concat': 1, // 禁止不必要的字符串字面量或模板字面量的连接
38 | 'no-useless-escape': 0, // 禁止不必要的转义字符
39 | 'consistent-return': 0, // 要求 return 语句要么总是指定返回的值,要么不指定
40 | 'camelcase': 0, // 强制使用骆驼拼写法命名约定
41 | 'no-redeclare': 1, // 禁止多次声明同一变量
42 | 'array-callback-return': 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
43 | 'default-case': 1, // 要求 switch 语句中有 default 分支
44 | 'no-fallthrough': 1, // 禁止 case 语句落空
45 | 'no-lonely-if': 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
46 | 'no-irregular-whitespace': 1, // 禁止在字符串和注释之外不规则的空白
47 | 'prefer-const': 0, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
48 | 'no-use-before-define': 1, // 禁止在变量定义之前使用它们
49 | 'vue/attributes-order': 2, // vue api使用顺序
50 | 'vue/no-multiple-template-root': 0,
51 | '@typescript-eslint/explicit-module-boundary-types': 0,
52 | '@typescript-eslint/no-var-requires': 0,
53 | '@typescript-eslint/no-unused-vars': 0,
54 | '@typescript-eslint/ban-ts-comment': 0,
55 | '@typescript-eslint/no-explicit-any': 0,
56 | '@typescript-eslint/no-empty-function': 0,
57 | 'vue/multi-word-component-names': 0
58 | },
59 | overrides: [
60 | {
61 | files: ['**/__tests__/*.{j,t}s?(x)'],
62 | env: {
63 | mocha: true
64 | }
65 | }
66 | ]
67 | }
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
30 |
{{ t('login') }}
31 |
32 |
33 |
34 |
66 |
67 |
131 |
--------------------------------------------------------------------------------
/src/assets/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/项目搭建.md:
--------------------------------------------------------------------------------
1 | # vue3-vite-vant-h5-template 最新版vue移动端开发模板
2 |
3 | - 使用技术 Vue 3 + TypeScript + Vite + Vant + pinia + vue-router + axios + vuei18n
4 | - 项目使用 pnpm 包管理工具,用法和 npm 没有什么区别,官方地址:https://www.pnpm.cn/
5 |
6 | ## 项目搭建
7 | - 参考vite官网:https://vitejs.cn
8 | ```
9 | pnpm create vite
10 | ```
11 | ## 代码规范
12 |
13 | ### 集成 editorconfig 配置
14 |
15 | - EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
16 |
17 | ```yaml
18 | # http://editorconfig.org
19 |
20 | root = true
21 |
22 | [*] # 表示所有文件适用
23 | charset = utf-8 # 设置文件字符集为 utf-8
24 | indent_style = space # 缩进风格(tab | space)
25 | indent_size = 2 # 缩进大小
26 | end_of_line = lf # 控制换行类型(lf | cr | crlf)
27 | trim_trailing_whitespace = true # 去除行首的任意空白字符
28 | insert_final_newline = true # 始终在文件末尾插入一个新行
29 |
30 | [*.md] # 表示仅 md 文件适用以下规则
31 | max_line_length = off
32 | trim_trailing_whitespace = false
33 | ```
34 |
35 | - VSCode 需要安装一个插件:EditorConfig for VS Code
36 |
37 | 
38 |
39 | ### 配置 eslint、prettier
40 | ```
41 | pnpm i eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin vue-global-api -D
42 | ```
43 |
44 | - 在根目录下建立 eslint 配置文件: .eslintrc.js
45 | ```js
46 | module.exports = {
47 | root: true,
48 | env: {
49 | browser: true,
50 | node: true
51 | },
52 | globals: {
53 | Message: true,
54 | env: true,
55 | useRoute: true,
56 | useRouter: true,
57 | useStore: true
58 | },
59 | /* 指定如何解析语法。可以为空,但若不为空,只能配该值,原因见下文。*/
60 | parser: 'vue-eslint-parser',
61 | /* 优先级低于parse的语法解析配置 */
62 | parserOptions: {
63 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
64 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
65 | sourceType: 'module', // Allows for the use of imports
66 | ecmaFeatures: {
67 | jsx: true
68 | }
69 | },
70 | extends: [
71 | 'vue-global-api',
72 | 'eslint:recommended',
73 | 'plugin:vue/vue3-recommended',
74 | 'plugin:@typescript-eslint/recommended',
75 | 'prettier',
76 | 'plugin:prettier/recommended'
77 | ],
78 | plugins: ['vue'],
79 | rules: {
80 | 'no-console': process.env.NODE_ENV === 'production' ? 1 : 0,
81 | 'no-debugger': process.env.NODE_ENV === 'production' ? 1 : 0,
82 | 'no-useless-concat': 1, // 禁止不必要的字符串字面量或模板字面量的连接
83 | 'no-useless-escape': 0, // 禁止不必要的转义字符
84 | 'consistent-return': 0, // 要求 return 语句要么总是指定返回的值,要么不指定
85 | 'camelcase': 0, // 强制使用骆驼拼写法命名约定
86 | 'no-redeclare': 1, // 禁止多次声明同一变量
87 | 'array-callback-return': 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
88 | 'default-case': 1, // 要求 switch 语句中有 default 分支
89 | 'no-fallthrough': 1, // 禁止 case 语句落空
90 | 'no-lonely-if': 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
91 | 'no-irregular-whitespace': 1, // 禁止在字符串和注释之外不规则的空白
92 | 'prefer-const': 0, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
93 | 'no-use-before-define': 1, // 禁止在变量定义之前使用它们
94 | 'vue/attributes-order': 2, // vue api使用顺序
95 | 'vue/no-multiple-template-root': 0,
96 | '@typescript-eslint/explicit-module-boundary-types': 0,
97 | '@typescript-eslint/no-var-requires': 0,
98 | '@typescript-eslint/no-unused-vars': 0,
99 | '@typescript-eslint/ban-ts-comment': 0,
100 | '@typescript-eslint/no-explicit-any': 0,
101 | '@typescript-eslint/no-empty-function': 0,
102 | 'vue/multi-word-component-names': 0
103 | },
104 | overrides: [
105 | {
106 | files: ['**/__tests__/*.{j,t}s?(x)'],
107 | env: {
108 | mocha: true
109 | }
110 | }
111 | ]
112 | }
113 | ```
114 | - 在根目录下建立 prettier 配置文件: .prettierrc.js
115 | ```js
116 | module.exports = {
117 | printWidth: 100, // 单行输出(不折行)的(最大)长度
118 | tabWidth: 2, // 每个缩进级别的空格数
119 | tabs: false, // 使用制表符 (tab) 缩进行而不是空格 (space)。
120 | semi: false, // 是否在语句末尾打印分号
121 | singleQuote: true, // 是否使用单引号
122 | quoteProps: "as-needed", // 仅在需要时在对象属性周围添加引号
123 | jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
124 | trailingComma: "none", // 去除对象最末尾元素跟随的逗号
125 | bracketSpacing: true, // 是否在对象属性添加空格
126 | jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
127 | arrowParens: "always", // 箭头函数,只有一个参数的时候,也需要括号
128 | proseWrap: "always", // 当超出print width(上面有这个参数)时就折行
129 | htmlWhitespaceSensitivity: "ignore", // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
130 | vueIndentScriptAndStyle: false, // 在VUE文件中不要缩进脚本和样式标记
131 | stylelintIntegration: true,
132 | endOfLine: "auto"
133 | }
134 |
135 | ```
136 |
137 | ### Husky 和 Lint-staged 配置 Pre-commit 检查
138 | ```
139 | npx mrm@2 lint-staged
140 | ```
141 | - 运行上面这条命令后会在package.json中添加一条脚本和lint-staged配置
142 | ```json
143 | {
144 | "scripts": {
145 | "lint": "eslint ./src/**/*{.js,.jsx,.ts,.tsx,.vue} --fix", // 这条除外
146 | "prepare": "husky install"
147 | },
148 | "devDependencies": {
149 | "husky": "^7.0.4",
150 | "lint-staged": "^12.4.1",
151 | },
152 | "lint-staged": {
153 | "*.{js,jsx,vue,ts,tsx}": "npm run lint"
154 | }
155 | }
156 | ```
157 | - 安装 lint-staged 和 husky 相关的依赖
158 | - 然后会在更目录创建一个.husky目录,这一步在windows上可能会出错,执行 npx husky install 创建,该目录下有一个pre-commit文件在每次提交代码的时候会执行,可以修改里面的运行脚本,自定义提交需要做的工作
159 | ```shell
160 | #!/bin/sh
161 | . "$(dirname "$0")/_/husky.sh"
162 |
163 | # npx lint-staged
164 | npm run lint
165 | ```
166 |
167 | 注意:由于我们使用的是pnpm,在执行npx mrm@2 lint-staged的时候,使用的是npm去安装依赖,而且在package.json中没有相关的版本记录,我的解决方法是再使用pnpm i husky lint-staged -D安装一次
168 |
169 | ## 第三方库集成
170 |
171 | ### 路由 vue-router
172 |
173 | 安装依赖
174 |
175 | ```
176 | pnpm install vue-router@4
177 | ```
178 |
179 | src 目录创建 router/index.ts
180 |
181 | ```ts
182 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
183 | import Login from '/@/views/login.vue'
184 | const routes: Array = [
185 | {
186 | path: '/login',
187 | component: Login
188 | }
189 | ]
190 |
191 | const router = createRouter({
192 | history: createWebHashHistory(),
193 | routes
194 | })
195 |
196 | export default router
197 | ```
198 |
199 | 在 main.ts 中注册路由
200 |
201 | ```ts
202 | import { createApp } from 'vue'
203 | import router from './router'
204 | import App from './App.vue'
205 |
206 | const app = createApp(App)
207 | app.use(router).mount('#app')
208 | ```
209 |
210 | App.vue 中使用路由占位符
211 |
212 | ```html
213 |
214 |
215 |
216 |
217 |
218 | ```
219 |
220 | ### 状态管理 pinia
221 |
222 | ```shell
223 | pnpm install pinia
224 | ```
225 |
226 | src 目录创建 store/index.ts
227 |
228 | ```ts
229 | import { App } from 'vue'
230 | import { createPinia } from 'pinia'
231 |
232 | export const setupStore = (app: App) => {
233 | app.use(createPinia())
234 | }
235 | ```
236 |
237 | 在 main.ts 中注册
238 |
239 | ```ts
240 | import { setupStore } from './store'
241 | setupStore(app)
242 | ```
243 |
244 | ### 引入Vant 推荐[官方文档](https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart)
245 | ```
246 | pnpm add vant
247 | ```
248 | 1. 在 vite 项目中按需引入组件(推荐)
249 | ```
250 | pnpm add vite-plugin-style-import@1.4.1 -D
251 | ```
252 | 2. 配置插件 安装完成后,在 vite.config.ts 文件中配置插件:
253 | ```ts
254 | import { defineConfig } from 'vite'
255 | import vue from '@vitejs/plugin-vue'
256 | import styleImport, { VantResolve } from 'vite-plugin-style-import'
257 | import { resolve } from 'path'
258 | // 路径查找
259 | const pathResolve = (dir: string): string => {
260 | return resolve(__dirname, '.', dir)
261 | }
262 |
263 | // 设置别名
264 | const alias: Record = {
265 | '/@': pathResolve('src'),
266 | '@': pathResolve('src'),
267 | '@build': pathResolve('build')
268 | }
269 | // https://vitejs.dev/config/
270 | export default defineConfig({
271 | resolve: { alias },
272 | plugins: [
273 | vue(),
274 | styleImport({
275 | resolves: [VantResolve()]
276 | })
277 | ]
278 | })
279 |
280 | ```
281 |
282 | 3. 引入组件 完成以上两步,就可以直接使用 Vant 组件了:
283 | ```ts
284 | import { createApp } from 'vue'
285 | import { Button } from 'vant'
286 |
287 | const app = createApp()
288 | app.use(Button)
289 | ```
290 |
291 | ### rem适配
292 | ```
293 | pnpm add postcss postcss-import postcss-pxtorem autoprefixer -D
294 | pnpm add amfe-flexible -S
295 | ```
296 | 新建 postcss.config.js
297 | ```js
298 | module.exports = {
299 | plugins: {
300 | 'postcss-pxtorem': {
301 | rootValue: 37.5,
302 | propList: ['*']
303 | },
304 | 'postcss-import': require('postcss-import'),
305 | autoprefixer: require('autoprefixer')
306 | }
307 | }
308 | ```
309 | 在main.ts中引入 amfe-flexible
310 | ```ts
311 | // main.ts
312 | import 'amfe-flexible'
313 | ```
314 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue3-vite-vant-h5-template 最新版vue移动端开发模板
2 |
3 | 开箱即用的 Vue3 + Vant4 移动端模板
4 |
5 | - 使用技术 Vue 3 + TypeScript + Vite + Vant + pinia + vue-router + axios + vuei18n
6 | - 支持rem移动端适配方案
7 | - axios二次封装
8 | - 主题切换
9 | - 支持国际化
10 | - 项目使用 pnpm 包管理工具,用法和 npm 没有什么区别,官方地址:https://www.pnpm.cn/
11 |
12 | ## 预览
13 |
14 | [在线预览](https://vangleer.github.io/vue3-vite-vant-h5-template/#/home)
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## 项目搭建
24 | - 参考vite官网:https://vitejs.cn
25 |
26 | ## 代码规范
27 |
28 | ### 集成 editorconfig 配置
29 |
30 | - EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
31 |
32 | ```yaml
33 | # http://editorconfig.org
34 |
35 | root = true
36 |
37 | [*] # 表示所有文件适用
38 | charset = utf-8 # 设置文件字符集为 utf-8
39 | indent_style = space # 缩进风格(tab | space)
40 | indent_size = 2 # 缩进大小
41 | end_of_line = lf # 控制换行类型(lf | cr | crlf)
42 | trim_trailing_whitespace = true # 去除行首的任意空白字符
43 | insert_final_newline = true # 始终在文件末尾插入一个新行
44 |
45 | [*.md] # 表示仅 md 文件适用以下规则
46 | max_line_length = off
47 | trim_trailing_whitespace = false
48 | ```
49 |
50 | - VSCode 需要安装一个插件:EditorConfig for VS Code
51 |
52 | 
53 |
54 | ### 配置 eslint、prettier
55 | ```
56 | pnpm i eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin vue-global-api -D
57 | ```
58 |
59 | - 在根目录下建立 eslint 配置文件: .eslintrc.js
60 | ```js
61 | module.exports = {
62 | root: true,
63 | env: {
64 | browser: true,
65 | node: true
66 | },
67 | globals: {
68 | Message: true,
69 | env: true,
70 | useRoute: true,
71 | useRouter: true,
72 | useStore: true
73 | },
74 | /* 指定如何解析语法。可以为空,但若不为空,只能配该值,原因见下文。*/
75 | parser: 'vue-eslint-parser',
76 | /* 优先级低于parse的语法解析配置 */
77 | parserOptions: {
78 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser
79 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
80 | sourceType: 'module', // Allows for the use of imports
81 | ecmaFeatures: {
82 | jsx: true
83 | }
84 | },
85 | extends: [
86 | 'vue-global-api',
87 | 'eslint:recommended',
88 | 'plugin:vue/vue3-recommended',
89 | 'plugin:@typescript-eslint/recommended',
90 | 'prettier',
91 | 'plugin:prettier/recommended'
92 | ],
93 | plugins: ['vue'],
94 | rules: {
95 | 'no-console': process.env.NODE_ENV === 'production' ? 1 : 0,
96 | 'no-debugger': process.env.NODE_ENV === 'production' ? 1 : 0,
97 | 'no-useless-concat': 1, // 禁止不必要的字符串字面量或模板字面量的连接
98 | 'no-useless-escape': 0, // 禁止不必要的转义字符
99 | 'consistent-return': 0, // 要求 return 语句要么总是指定返回的值,要么不指定
100 | 'camelcase': 0, // 强制使用骆驼拼写法命名约定
101 | 'no-redeclare': 1, // 禁止多次声明同一变量
102 | 'array-callback-return': 1, // 强制数组方法的回调函数中有 return 语句,Array有几种过滤,映射和折叠的方法。如果我们忘记return在这些回调中写入语句,那可能是一个错误。
103 | 'default-case': 1, // 要求 switch 语句中有 default 分支
104 | 'no-fallthrough': 1, // 禁止 case 语句落空
105 | 'no-lonely-if': 1, // 禁止 if 作为唯一的语句出现在 else 语句中.如果一个if陈述是该else块中唯一的陈述,那么使用一个else if表格通常会更清晰。
106 | 'no-irregular-whitespace': 1, // 禁止在字符串和注释之外不规则的空白
107 | 'prefer-const': 0, // 要求使用 const 声明那些声明后不再被修改的变量.如果一个变量从不重新分配,使用const声明更好。const 声明告诉读者,“这个变量永远不会被重新分配,”减少认知负荷并提高可维护性。
108 | 'no-use-before-define': 1, // 禁止在变量定义之前使用它们
109 | 'vue/attributes-order': 2, // vue api使用顺序
110 | 'vue/no-multiple-template-root': 0,
111 | '@typescript-eslint/explicit-module-boundary-types': 0,
112 | '@typescript-eslint/no-var-requires': 0,
113 | '@typescript-eslint/no-unused-vars': 0,
114 | '@typescript-eslint/ban-ts-comment': 0,
115 | '@typescript-eslint/no-explicit-any': 0,
116 | '@typescript-eslint/no-empty-function': 0,
117 | 'vue/multi-word-component-names': 0
118 | },
119 | overrides: [
120 | {
121 | files: ['**/__tests__/*.{j,t}s?(x)'],
122 | env: {
123 | mocha: true
124 | }
125 | }
126 | ]
127 | }
128 | ```
129 | - 在根目录下建立 prettier 配置文件: .prettierrc.js
130 | ```js
131 | module.exports = {
132 | printWidth: 100, // 单行输出(不折行)的(最大)长度
133 | tabWidth: 2, // 每个缩进级别的空格数
134 | tabs: false, // 使用制表符 (tab) 缩进行而不是空格 (space)。
135 | semi: false, // 是否在语句末尾打印分号
136 | singleQuote: true, // 是否使用单引号
137 | quoteProps: "as-needed", // 仅在需要时在对象属性周围添加引号
138 | jsxSingleQuote: false, // jsx 不使用单引号,而使用双引号
139 | trailingComma: "none", // 去除对象最末尾元素跟随的逗号
140 | bracketSpacing: true, // 是否在对象属性添加空格
141 | jsxBracketSameLine: true, // 将 > 多行 JSX 元素放在最后一行的末尾,而不是单独放在下一行(不适用于自闭元素),默认false,这里选择>不另起一行
142 | arrowParens: "always", // 箭头函数,只有一个参数的时候,也需要括号
143 | proseWrap: "always", // 当超出print width(上面有这个参数)时就折行
144 | htmlWhitespaceSensitivity: "ignore", // 指定 HTML 文件的全局空白区域敏感度, "ignore" - 空格被认为是不敏感的
145 | vueIndentScriptAndStyle: false, // 在VUE文件中不要缩进脚本和样式标记
146 | stylelintIntegration: true,
147 | endOfLine: "auto"
148 | }
149 |
150 | ```
151 |
152 | ### Husky 和 Lint-staged 配置 Pre-commit 检查
153 | ```
154 | npx mrm@2 lint-staged
155 | ```
156 | - 运行上面这条命令后会在package.json中添加一条脚本和lint-staged配置
157 | ```json
158 | {
159 | "scripts": {
160 | "lint": "eslint ./src/**/*{.js,.jsx,.ts,.tsx,.vue} --fix", // 这条除外
161 | "prepare": "husky install"
162 | },
163 | "devDependencies": {
164 | "husky": "^7.0.4",
165 | "lint-staged": "^12.4.1",
166 | },
167 | "lint-staged": {
168 | "*.{js,jsx,vue,ts,tsx}": "npm run lint"
169 | }
170 | }
171 | ```
172 | - 安装 lint-staged 和 husky 相关的依赖
173 | - 然后会在更目录创建一个.husky目录,这一步在windows上可能会出错,执行 npx husky install 创建,该目录下有一个pre-commit文件在每次提交代码的时候会执行,可以修改里面的运行脚本,自定义提交需要做的工作
174 | ```shell
175 | #!/bin/sh
176 | . "$(dirname "$0")/_/husky.sh"
177 |
178 | # npx lint-staged
179 | npm run lint
180 | ```
181 |
182 | 注意:由于我们使用的是pnpm,在执行npx mrm@2 lint-staged的时候,使用的是npm去安装依赖,而且在package.json中没有相关的版本记录,我的解决方法是再使用pnpm i husky lint-staged -D安装一次
183 |
184 | ## 第三方库集成
185 |
186 | ### 路由 vue-router
187 |
188 | 安装依赖
189 |
190 | ```
191 | pnpm install vue-router@4
192 | ```
193 |
194 | src 目录创建 router/index.ts
195 |
196 | ```ts
197 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
198 | import Login from '/@/views/login.vue'
199 | const routes: Array = [
200 | {
201 | path: '/login',
202 | component: Login
203 | }
204 | ]
205 |
206 | const router = createRouter({
207 | history: createWebHashHistory(),
208 | routes
209 | })
210 |
211 | export default router
212 | ```
213 |
214 | 在 main.ts 中注册路由
215 |
216 | ```ts
217 | import { createApp } from 'vue'
218 | import router from './router'
219 | import App from './App.vue'
220 |
221 | const app = createApp(App)
222 | app.use(router).mount('#app')
223 | ```
224 |
225 | App.vue 中使用路由占位符
226 |
227 | ```html
228 |
229 |
230 |
231 |
232 |
233 | ```
234 |
235 | ### 状态管理 pinia
236 |
237 | ```shell
238 | pnpm install pinia
239 | ```
240 |
241 | src 目录创建 store/index.ts
242 |
243 | ```ts
244 | import { App } from 'vue'
245 | import { createPinia } from 'pinia'
246 |
247 | export const setupStore = (app: App) => {
248 | app.use(createPinia())
249 | }
250 | ```
251 |
252 | 在 main.ts 中注册
253 |
254 | ```ts
255 | import { setupStore } from './store'
256 | setupStore(app)
257 | ```
258 |
259 | ### 引入Vant 推荐[官方文档](https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart)
260 | ```
261 | pnpm add vant
262 | ```
263 | 1. 在 vite 项目中按需引入组件(推荐)
264 | ```
265 | pnpm add vite-plugin-style-import@1.4.1 -D
266 | ```
267 | 2. 配置插件 安装完成后,在 vite.config.ts 文件中配置插件:
268 | ```ts
269 | import { defineConfig } from 'vite'
270 | import vue from '@vitejs/plugin-vue'
271 | import styleImport, { VantResolve } from 'vite-plugin-style-import'
272 | import { resolve } from 'path'
273 | // 路径查找
274 | const pathResolve = (dir: string): string => {
275 | return resolve(__dirname, '.', dir)
276 | }
277 |
278 | // 设置别名
279 | const alias: Record = {
280 | '/@': pathResolve('src'),
281 | '@': pathResolve('src'),
282 | '@build': pathResolve('build')
283 | }
284 | // https://vitejs.dev/config/
285 | export default defineConfig({
286 | resolve: { alias },
287 | plugins: [
288 | vue(),
289 | styleImport({
290 | resolves: [VantResolve()]
291 | })
292 | ]
293 | })
294 |
295 | ```
296 |
297 | 3. 引入组件 完成以上两步,就可以直接使用 Vant 组件了:
298 | ```ts
299 | import { createApp } from 'vue'
300 | import { Button } from 'vant'
301 |
302 | const app = createApp()
303 | app.use(Button)
304 | ```
305 |
306 | ### rem适配
307 | ```
308 | pnpm add postcss postcss-import postcss-pxtorem autoprefixer -D
309 | pnpm add amfe-flexible -S
310 | ```
311 | 新建 postcss.config.js
312 | ```js
313 | module.exports = {
314 | plugins: {
315 | 'postcss-pxtorem': {
316 | rootValue: 37.5,
317 | propList: ['*']
318 | },
319 | 'postcss-import': require('postcss-import'),
320 | autoprefixer: require('autoprefixer')
321 | }
322 | }
323 | ```
324 | 在main.ts中引入 amfe-flexible
325 | ```ts
326 | // main.ts
327 | import 'amfe-flexible'
328 | ```
329 |
--------------------------------------------------------------------------------