├── .env ├── src ├── styles │ ├── main.less │ └── base.less ├── decorators │ ├── index.ts │ └── injectable.ts ├── plugins │ ├── antdv.ts │ ├── vue.ts │ └── i18n.ts ├── i18n │ ├── index.ts │ ├── zhCN.ts │ └── enUS.ts ├── layouts │ ├── BlankLayout │ │ └── index.vue │ └── AppLayout │ │ ├── components │ │ ├── AppFooter.vue │ │ ├── AppSider.less │ │ ├── AppContent.vue │ │ ├── AppHeader.vue │ │ └── AppSider.tsx │ │ └── index.vue ├── assets │ ├── fonts │ │ ├── Helvetica.eot │ │ ├── Helvetica.otf │ │ ├── Helvetica.ttf │ │ ├── Helvetica.woff │ │ └── Helvetica.woff2 │ ├── images │ │ └── h-slider1.png │ └── logo.svg ├── components │ ├── DemoGlobalComponent.vue │ ├── icons │ │ ├── IconSupport.vue │ │ ├── IconTooling.vue │ │ ├── IconCommunity.vue │ │ ├── IconDocumentation.vue │ │ └── IconEcosystem.vue │ ├── __tests__ │ │ └── HelloWorld.spec.ts │ └── AppRouterView.vue ├── stores │ ├── index.ts │ ├── counter.ts │ ├── app.ts │ └── user.ts ├── hooks │ ├── index.ts │ ├── useRequest │ │ ├── interceptor │ │ │ ├── index.ts │ │ │ ├── request.ts │ │ │ └── response.ts │ │ └── index.ts │ ├── useCopyText.ts │ └── useDeepCopy.ts ├── shims-vue.d.ts ├── views │ ├── home │ │ └── index.vue │ ├── sfcI18N │ │ ├── i18n.json │ │ └── index.vue │ └── login │ │ └── index.vue ├── router │ ├── guards │ │ ├── afterEach.ts │ │ ├── index.ts │ │ └── beforeEach.ts │ └── index.ts ├── types │ └── index.ts ├── main.ts ├── App.vue └── components.d.ts ├── env.d.ts ├── .env.preview ├── .env.production ├── .husky ├── pre-commit └── commit-msg ├── .env.development ├── public ├── favicon.ico └── loading.svg ├── .vscode ├── extensions.json └── settings.json ├── .commitlintrc.js ├── .editorconfig ├── .prettierrc.json ├── tsconfig.vitest.json ├── tsconfig.json ├── docker-compose.dev.yml ├── docker-compose.beta.yml ├── tsconfig.config.json ├── .gitignore ├── .gitlab-ci.yml ├── tsconfig.app.json ├── mock └── auth.ts ├── index.html ├── LICENSE ├── .czrc ├── vite.config.ts ├── .eslintrc.cjs ├── CHANGELOG.md ├── package.json ├── README-zh_CN.md └── README.md /.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/main.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export { injectable } from './injectable' 2 | -------------------------------------------------------------------------------- /src/plugins/antdv.ts: -------------------------------------------------------------------------------- 1 | import 'ant-design-vue/es/message/style/css' 2 | -------------------------------------------------------------------------------- /.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=preview 2 | VUE_APP_BASE_URL=https://preview.domain.com 3 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_BASE_URL=https://api.domain.com 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | npx lint-staged 4 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export { enUS } from './enUS' 2 | export { zhCN } from './zhCN' 3 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_BASE_URL= http://192.168.1.100:8000 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/fonts/Helvetica.eot -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/fonts/Helvetica.otf -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/fonts/Helvetica.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/fonts/Helvetica.woff -------------------------------------------------------------------------------- /src/assets/images/h-slider1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/images/h-slider1.png -------------------------------------------------------------------------------- /src/components/DemoGlobalComponent.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/assets/fonts/Helvetica.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VuePlusOrg/vue3-base/HEAD/src/assets/fonts/Helvetica.woff2 -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | export { useCounterStore } from './counter' 2 | export { useUserStore } from './user' 3 | export { useAppStore } from './app' 4 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @reference https://commitlint.js.org/#/ 3 | */ 4 | module.exports = { 5 | extends: ['@commitlint/config-conventional'] 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useCopyText } from './useCopyText' 2 | export { useDeepCopy } from './useDeepCopy' 3 | export { useRequest } from './useRequest' 4 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/i18n/zhCN.ts: -------------------------------------------------------------------------------- 1 | export const zhCN = { 2 | 'Navigate To New Page': '跳转至新页面', 3 | 'Change Language': '切换语言', 4 | Test: '测试', 5 | Internationalization: '国际化', 6 | Dashboard: '仪表盘' 7 | } 8 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/components/AppFooter.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 2 | name: Home 3 | 4 | 5 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/views/sfcI18N/i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "enUS": { 3 | "This is a text for test I18N.": "This is a text for test I18N." 4 | }, 5 | "zhCN": { 6 | "This is a text for test I18N.": "这是一段用于测试I18N的语句" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = off 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "none", 5 | "endOfLine": "lf", 6 | "proseWrap": "preserve", 7 | "arrowParens": "avoid", 8 | "singleAttributePerLine": true 9 | } 10 | -------------------------------------------------------------------------------- /src/i18n/enUS.ts: -------------------------------------------------------------------------------- 1 | export const enUS = { 2 | 'Navigate To New Page': 'Navigate To New Page', 3 | 'Change Language': 'Change Language', 4 | Test: 'Test', 5 | Internationalization: 'Internationalization', 6 | Dashboard: 'Dashboard' 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": ["dist"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "lib": [], 7 | "types": ["node", "jsdom", "vite/client", "vite-plugin-pages/client"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.config.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | }, 10 | { 11 | "path": "./tsconfig.vitest.json" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/router/guards/afterEach.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | 3 | export default class AfterEachGuard { 4 | /** 5 | * Load after each router guard 6 | * @param router router instance 7 | */ 8 | public static load(router: Router) { 9 | router.afterEach(() => {}) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | vue3-ts-base-dev: 4 | container_name: vue3-ts-base-dev 5 | restart: always 6 | platform: linux/x86_64 7 | image: nginx 8 | volumes: 9 | - ./dist:/app 10 | - ./nginx.conf:/etc/nginx/nginx.conf 11 | ports: 12 | - 10000:80 13 | -------------------------------------------------------------------------------- /docker-compose.beta.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | vue3-ts-base-beta: 4 | container_name: vue3-ts-base-beta 5 | restart: always 6 | platform: linux/x86_64 7 | image: nginx 8 | volumes: 9 | - ./dist:/app 10 | - ./nginx.conf:/etc/nginx/nginx.conf 11 | ports: 12 | - 10001:80 13 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface MenuConfig { 2 | icon?: any 3 | 4 | /** 5 | * The title for show. 6 | */ 7 | title: string 8 | 9 | /** 10 | * The name of route for navigate. 11 | */ 12 | routeName: string 13 | 14 | /** 15 | * Config for submenu. 16 | */ 17 | children?: MenuConfig[] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "playwright.config.*" 8 | ], 9 | "compilerOptions": { 10 | "composite": true, 11 | "types": ["node", "vite/client"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/views/sfcI18N/index.vue: -------------------------------------------------------------------------------- 1 | 2 | name: SFCI18N 3 | 4 | 5 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import routes from 'virtual:generated-pages' 3 | import RouterGuard from './guards' 4 | 5 | const router = createRouter({ 6 | history: createWebHistory(import.meta.env.BASE_URL), 7 | routes 8 | }) 9 | 10 | RouterGuard.load(router) 11 | 12 | export default router 13 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/components/AppSider.less: -------------------------------------------------------------------------------- 1 | .sider { 2 | &__logo { 3 | // color: white; 4 | color: white; 5 | padding: 16px; 6 | display: flex; 7 | justify-content: center; 8 | &__image { 9 | width: 32px; 10 | } 11 | &__text { 12 | font-size: 20px; 13 | font-weight: bold; 14 | margin-left: 12px; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | import { defineStore } from 'pinia' 3 | 4 | export const useCounterStore = defineStore('counter', () => { 5 | const count = ref(0) 6 | const doubleCount = computed(() => count.value * 2) 7 | const increment = () => { 8 | count.value += 1 9 | } 10 | 11 | return { count, doubleCount, increment } 12 | }) 13 | -------------------------------------------------------------------------------- /src/decorators/injectable.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | export const injectable = ( 3 | Ctor: T 4 | ) => { 5 | let instance!: any 6 | return new Proxy(Ctor, { 7 | construct(t, args) { 8 | if (!instance) { 9 | instance = new Ctor(args) 10 | } 11 | return instance 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/router/guards/index.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router' 2 | import AfterEachGuards from './afterEach' 3 | import BeforeEachGuards from './beforeEach' 4 | 5 | export default class RouterGuards { 6 | /** 7 | * Load router guard 8 | */ 9 | public static load(router: Router) { 10 | AfterEachGuards.load(router) 11 | BeforeEachGuards.load(router) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/hooks/useRequest/interceptor/index.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosInstance } from 'axios' 2 | import RequestInterceptor from './request' 3 | import ResponseInterceptor from './response' 4 | 5 | export default class AxiosInterceptor { 6 | public static addInterceptor(axiosInstance: AxiosInstance) { 7 | RequestInterceptor.load(axiosInstance) 8 | ResponseInterceptor.load(axiosInstance) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.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 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .idea 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | stats.html 29 | -------------------------------------------------------------------------------- /src/components/__tests__/HelloWorld.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import HelloWorld from '../HelloWorld.vue' 5 | 6 | describe('HelloWorld', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) 9 | expect(wrapper.text()).toContain('Hello Vitest') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | import VuePlugin from '@/plugins/vue' 5 | import I18NPlugin from '@/plugins/i18n' 6 | import '@/plugins/antdv' 7 | 8 | import './styles/main.less' 9 | 10 | const app = createApp(App) 11 | const vuePlugin = new VuePlugin(app) 12 | const i18NPlugin = new I18NPlugin(app) 13 | 14 | vuePlugin.load() 15 | i18NPlugin.load() 16 | vuePlugin.mount() 17 | -------------------------------------------------------------------------------- /src/stores/app.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { defineStore } from 'pinia' 3 | 4 | export const useAppStore = defineStore('app', () => { 5 | const locale = ref('enUS') 6 | const routeNameStack = ref([]) 7 | 8 | const changeLocale = (newLocale: string) => { 9 | locale.value = newLocale 10 | } 11 | 12 | return { 13 | locale, 14 | routeNameStack, 15 | changeLocale 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:14.18.0 2 | 3 | variables: 4 | GIT_SSL_NO_VERIFY: '1' 5 | DOCKER_DRIVER: 'overlay2' 6 | DOCKER_TLS_CERTDIR: '' 7 | TZ: 'Asia/Shanghai' 8 | 9 | stages: 10 | - test 11 | 12 | Lint Test: 13 | stage: test 14 | tags: 15 | - test 16 | rules: 17 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 18 | script: 19 | - yarn global add pnpm 20 | - pnpm install 21 | - pnpm lint:check 22 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/components/AppContent.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/hooks/useRequest/index.ts: -------------------------------------------------------------------------------- 1 | import Axios from 'axios' 2 | import AxiosInterceptor from './interceptor' 3 | 4 | export const useRequest = () => { 5 | const axiosInstance = Axios.create({ 6 | baseURL: import.meta.env.VUE_APP_BASE_URL, 7 | headers: { 8 | 'Content-Type': 'application/json' 9 | } 10 | }) 11 | 12 | axiosInstance.defaults.timeout = 100000 13 | AxiosInterceptor.addInterceptor(axiosInstance) 14 | 15 | return axiosInstance 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": [ 4 | "env.d.ts", 5 | "src/**/*", 6 | "src/**/*.vue", 7 | "src/**/*.ts", 8 | "src/**/*.tsx", 9 | "mock" 10 | ], 11 | "exclude": ["src/**/__tests__/*"], 12 | "compilerOptions": { 13 | "composite": true, 14 | "baseUrl": ".", 15 | "paths": { 16 | "@/*": ["./src/*"] 17 | }, 18 | "types": ["ant-design-vue/typings/global", "./types"], 19 | "experimentalDecorators": true, 20 | "jsx": "preserve" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useRequest/interceptor/request.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosInstance, AxiosRequestConfig } from 'axios' 2 | import { useUserStore } from '@/stores' 3 | 4 | export default class RequestInterceptor { 5 | public static load(axiosInstance: AxiosInstance) { 6 | axiosInstance.interceptors.request.use( 7 | async (config: AxiosRequestConfig) => { 8 | const { token } = useUserStore() 9 | 10 | if (token) { 11 | config.headers?.options?.set('Authorization', `Bearer ${token}`) 12 | } 13 | 14 | return config 15 | } 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mock/auth.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | url: '/auth/login', 4 | method: 'get', 5 | response: () => { 6 | return { 7 | code: 0, 8 | message: null, 9 | data: { 10 | userName: 'Mathon', 11 | userId: '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed', 12 | gender: 0, 13 | token: 14 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyTmFtZSI6Ik1hdGhvbiIsInVzZXJJZCI6IjFiOWQ2YmNkLWJiZmQtNGIyZC05YjVkLWFiOGRmYmJkNGJlZCIsImdlbmRlciI6MH0.OUmLW2Be7oZX1BUQlHXymGRlHDYOSeGSjAG-tRzKib4' 15 | } 16 | } 17 | } 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /src/hooks/useCopyText.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copy text to clipboard 3 | * @param text the text content to be copied 4 | */ 5 | export const useCopyText = (text: string) => { 6 | if (navigator.clipboard) { 7 | navigator.clipboard.writeText(text) 8 | } else { 9 | const textarea = document.createElement('textarea') 10 | document.body.appendChild(textarea) 11 | textarea.style.position = 'fixed' 12 | textarea.style.clip = 'rect(0 0 0 0)' 13 | textarea.style.top = '10px' 14 | textarea.value = text 15 | textarea.select() 16 | document.execCommand('copy', true) 17 | document.body.removeChild(textarea) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/plugins/vue.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import type { Router } from 'vue-router' 3 | import { createPinia, type Pinia } from 'pinia' 4 | import router from '@/router' 5 | 6 | export default class VuePlugin { 7 | private app!: App 8 | 9 | private router!: Router 10 | 11 | private store!: Pinia 12 | 13 | constructor(app: App) { 14 | this.app = app 15 | this.router = router 16 | this.store = createPinia() 17 | } 18 | 19 | public load() { 20 | this.app.use(this.store) 21 | this.app.use(this.router) 22 | } 23 | 24 | public mount() { 25 | this.app.mount('#app') 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/plugins/i18n.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { createI18n, type I18n } from 'vue-i18n' 3 | import { useAppStore } from '@/stores' 4 | import { zhCN, enUS } from '@/i18n' 5 | 6 | export default class I18NPlugin { 7 | private app!: App 8 | 9 | public i18n!: I18n 10 | 11 | constructor(app: App) { 12 | this.app = app 13 | } 14 | 15 | public load() { 16 | const appStore = useAppStore() 17 | this.i18n = createI18n({ 18 | legacy: false, 19 | locale: appStore.locale, 20 | fallbackLocale: appStore.locale, 21 | messages: { 22 | zhCN, 23 | enUS 24 | } 25 | }) 26 | this.app.use(this.i18n) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/base.less: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | margin: 0; 6 | position: relative; 7 | font-weight: normal; 8 | } 9 | 10 | body { 11 | min-height: 100vh; 12 | color: var(--color-text); 13 | background: var(--color-background); 14 | transition: color 0.5s, background-color 0.5s; 15 | line-height: 1.6; 16 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 17 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 18 | font-size: 15px; 19 | text-rendering: optimizeLegibility; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | 24 | #app { 25 | height: 100%; 26 | } 27 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 17 | 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | vue3-base 14 | 24 | 25 | 26 |
27 | loading 31 |
32 |
33 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 VuePlusOrg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /src/hooks/useDeepCopy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Object deep copy 3 | * @param data incoming object 4 | * @param hash for internal recursive use, external calls do not need to be passed in 5 | * @returns new object 6 | */ 7 | export const useDeepCopy = (data: any, hash = new WeakMap()) => { 8 | if (typeof data !== 'object' || data === null) { 9 | throw new TypeError('The incoming parameter is not an object') 10 | } 11 | if (hash.has(data)) { 12 | return hash.get(data) 13 | } 14 | const newData: any = {} 15 | const dataKeys = Object.keys(data) 16 | dataKeys.forEach(value => { 17 | const currentDataValue = data[value] 18 | if (typeof currentDataValue !== 'object' || currentDataValue === null) { 19 | newData[value] = currentDataValue 20 | } else if (Array.isArray(currentDataValue)) { 21 | newData[value] = [...currentDataValue] 22 | } else if (currentDataValue instanceof Set) { 23 | newData[value] = new Set([...currentDataValue]) 24 | } else if (currentDataValue instanceof Map) { 25 | newData[value] = new Map([...currentDataValue]) 26 | } else { 27 | hash.set(data, data) 28 | newData[value] = useDeepCopy(currentDataValue, hash) 29 | } 30 | }) 31 | return newData 32 | } 33 | -------------------------------------------------------------------------------- /src/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from 'vue' 2 | import { defineStore } from 'pinia' 3 | import { useRouter } from 'vue-router' 4 | import { message } from 'ant-design-vue' 5 | import { useRequest } from '@/hooks' 6 | 7 | export const useUserStore = defineStore('user', () => { 8 | const request = useRequest() 9 | const router = useRouter() 10 | 11 | const token = ref('') 12 | 13 | const userInfo = reactive({ 14 | userName: '', 15 | userId: '', 16 | gender: -1 17 | }) 18 | 19 | const login = async (userName: string, password: string) => { 20 | const response = await request.get('/auth/login', { 21 | params: { userName, password } 22 | }) 23 | 24 | const userDetailInfo = response.data.data 25 | 26 | userInfo.userName = userDetailInfo.userName 27 | userInfo.userId = userDetailInfo.userId 28 | userInfo.userName = userDetailInfo.userName 29 | 30 | message.success('Login Sucess!') 31 | 32 | router.replace({ name: 'Home' }) 33 | } 34 | 35 | const logout = () => { 36 | token.value = '' 37 | userInfo.userName = '' 38 | userInfo.userId = '' 39 | userInfo.gender = -1 40 | 41 | router.replace({ name: 'Login' }) 42 | } 43 | 44 | return { 45 | token, 46 | userInfo, 47 | login, 48 | logout 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/components/AppRouterView.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | 22 | 55 | -------------------------------------------------------------------------------- /src/router/guards/beforeEach.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalized, Router } from 'vue-router' 2 | import { useAppStore } from '@/stores/app' 3 | 4 | export default class BeforeEachGuard { 5 | /** 6 | * Load before each router guard 7 | * @param router router instance 8 | */ 9 | public static load(router: Router) { 10 | router.beforeEach(to => { 11 | BeforeEachGuard.recordRouteNameStack(to) 12 | }) 13 | } 14 | 15 | /** 16 | * Record the routing stack name for use by `AppRouterView` 17 | * @param to route to 18 | */ 19 | public static recordRouteNameStack(to: RouteLocationNormalized) { 20 | if (to.name !== undefined) { 21 | const { routeNameStack } = useAppStore() 22 | const IS_ROUTE_STACK_EMPTY = routeNameStack.length === 0 23 | 24 | if (IS_ROUTE_STACK_EMPTY) { 25 | routeNameStack.push(to.name as string) 26 | } else { 27 | const NEW_PAGE_IN_ROUTE_STACK_POSITION = routeNameStack.indexOf( 28 | to.name as string 29 | ) 30 | const IS_NEW_ROUTE = NEW_PAGE_IN_ROUTE_STACK_POSITION === -1 31 | 32 | if (IS_NEW_ROUTE) { 33 | routeNameStack.push(to.name as string) 34 | } else { 35 | routeNameStack.splice( 36 | NEW_PAGE_IN_ROUTE_STACK_POSITION + 1, 37 | routeNameStack.length - NEW_PAGE_IN_ROUTE_STACK_POSITION + 1 38 | ) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "cz-conventional-changelog", 3 | "disableScopeLowerCase": false, 4 | "disableSubjectLowerCase": false, 5 | "maxHeaderWidth": 100, 6 | "maxLineWidth": 100, 7 | "defaultType": "", 8 | "defaultScope": "", 9 | "defaultSubject": "", 10 | "defaultBody": "", 11 | "defaultIssues": "", 12 | "types": { 13 | "feat": { 14 | "title": "feat", 15 | "description": "feature added" 16 | }, 17 | "fix": { 18 | "title": "fix", 19 | "description": "bug fixed" 20 | }, 21 | "perf": { 22 | "title": "perf", 23 | "description": "feature optimization" 24 | }, 25 | "refactor": { 26 | "description": "internal implementation refactoring", 27 | "title": "refactor" 28 | }, 29 | "docs": { 30 | "description": "documents updating", 31 | "title": "docs" 32 | }, 33 | "ci": { 34 | "description": "continuous integration & continuous deployment", 35 | "title": "ci" 36 | }, 37 | "chore": { 38 | "description": "updated for non-src folders", 39 | "title": "chore" 40 | }, 41 | "build": { 42 | "description": "system building or package dependency updating", 43 | "title": "build" 44 | }, 45 | "test": { 46 | "description": "unit tests updated", 47 | "title": "test" 48 | }, 49 | "revert": { 50 | "description": "git version reverted", 51 | "title": "revert" 52 | }, 53 | "wip": { 54 | "description": "feature is under development, commit to switch other branches for other development", 55 | "title": "wip" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[nginx]": { 3 | "editor.defaultFormatter": "raynigon.nginx-formatter" 4 | }, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 7 | }, 8 | "[javascriptreact]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 13 | }, 14 | "eslint.validate": [ 15 | "vue", 16 | "html", 17 | "javascript", 18 | "javascriptreact", 19 | "typescript", 20 | "typescriptreact" 21 | ], 22 | "eslint.options": { 23 | "extensions": [".js", ".vue", ".ts", ".tsx"] 24 | }, 25 | "[vue]": { 26 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 27 | }, 28 | "editor.tabSize": 2, 29 | "[json]": { 30 | "editor.defaultFormatter": "esbenp.prettier-vscode" 31 | }, 32 | "[jsonc]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "debug.javascript.autoAttachFilter": "smart", 36 | "editor.formatOnSave": true, 37 | "[html]": { 38 | "editor.defaultFormatter": "esbenp.prettier-vscode" 39 | }, 40 | "[dart]": { 41 | "editor.formatOnSave": true, 42 | "editor.formatOnType": true, 43 | "editor.rulers": [80], 44 | "editor.selectionHighlight": false, 45 | "editor.suggest.snippetsPreventQuickSuggestions": false, 46 | "editor.suggestSelection": "first", 47 | "editor.tabCompletion": "onlySnippets", 48 | "editor.wordBasedSuggestions": false 49 | }, 50 | "dart.openDevTools": "flutter", 51 | "dart.debugExternalPackageLibraries": true, 52 | "dart.debugSdkLibraries": true, 53 | "workbench.editor.splitInGroupLayout": "vertical", 54 | "terminal.integrated.shellIntegration.enabled": true, 55 | "eslint.format.enable": true, 56 | "volar.format.initialIndent": { 57 | "html": true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | import { dirname, resolve } from 'node:path' 3 | import { defineConfig, type PluginOption } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import { visualizer } from 'rollup-plugin-visualizer' 7 | 8 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' 9 | import Components from 'unplugin-vue-components/vite' 10 | import Pages from 'vite-plugin-pages' 11 | import VueSetupExtend from 'vite-plugin-vue-setup-extend' 12 | import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' 13 | import { viteMockServe } from 'vite-plugin-mock' 14 | 15 | const IS_PRODUCTION = process.env.NODE_ENV === 'production' 16 | 17 | const plugins: PluginOption[] = [ 18 | vue(), 19 | vueJsx(), 20 | Components({ 21 | dirs: ['src/components'], 22 | resolvers: [AntDesignVueResolver()], 23 | extensions: ['vue'], 24 | dts: 'src/components.d.ts' 25 | }), 26 | Pages({ 27 | dirs: [ 28 | { dir: 'src/views', baseRoute: '' }, 29 | { dir: 'src/views/home', baseRoute: '/' } 30 | ], 31 | importMode: 'async', 32 | exclude: ['**/components/**'] 33 | }), 34 | VueSetupExtend(), 35 | VueI18nPlugin({ 36 | include: resolve(dirname(fileURLToPath(import.meta.url)), 'src/i18n/*') 37 | }), 38 | viteMockServe({ 39 | mockPath: 'mock', 40 | supportTs: true 41 | }) 42 | ] 43 | 44 | if (IS_PRODUCTION) { 45 | plugins.push( 46 | visualizer({ 47 | open: true, 48 | gzipSize: true, 49 | brotliSize: true 50 | }) as PluginOption 51 | ) 52 | } 53 | 54 | export default defineConfig({ 55 | plugins, 56 | resolve: { 57 | alias: { 58 | '@': fileURLToPath(new URL('./src', import.meta.url)) 59 | } 60 | }, 61 | logLevel: IS_PRODUCTION ? 'silent' : 'info', 62 | server: { 63 | host: '0.0.0.0' 64 | }, 65 | build: { 66 | target: 'es6' 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 45 | 46 | 79 | -------------------------------------------------------------------------------- /src/components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/core/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | export {} 7 | 8 | declare module '@vue/runtime-core' { 9 | export interface GlobalComponents { 10 | ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] 11 | ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] 12 | AButton: typeof import('ant-design-vue/es')['Button'] 13 | ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] 14 | AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] 15 | ADropdown: typeof import('ant-design-vue/es')['Dropdown'] 16 | AForm: typeof import('ant-design-vue/es')['Form'] 17 | AFormItem: typeof import('ant-design-vue/es')['FormItem'] 18 | AInput: typeof import('ant-design-vue/es')['Input'] 19 | AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] 20 | ALayout: typeof import('ant-design-vue/es')['Layout'] 21 | ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] 22 | ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter'] 23 | ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] 24 | ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] 25 | AMenu: typeof import('ant-design-vue/es')['Menu'] 26 | AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] 27 | AppRouterView: typeof import('./components/AppRouterView.vue')['default'] 28 | ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] 29 | DemoGlobalComponent: typeof import('./components/DemoGlobalComponent.vue')['default'] 30 | IconCommunity: typeof import('./components/icons/IconCommunity.vue')['default'] 31 | IconDocumentation: typeof import('./components/icons/IconDocumentation.vue')['default'] 32 | IconEcosystem: typeof import('./components/icons/IconEcosystem.vue')['default'] 33 | IconSupport: typeof import('./components/icons/IconSupport.vue')['default'] 34 | IconTooling: typeof import('./components/icons/IconTooling.vue')['default'] 35 | RouterLink: typeof import('vue-router')['RouterLink'] 36 | RouterView: typeof import('vue-router')['RouterView'] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/layouts/AppLayout/components/AppSider.tsx: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, ref, h } from 'vue' 2 | import { Menu, MenuItem, LayoutSider, SubMenu } from 'ant-design-vue' 3 | import { DashboardOutlined } from '@ant-design/icons-vue' 4 | import { useI18n } from 'vue-i18n' 5 | import { useRouter } from 'vue-router' 6 | import type { MenuConfig } from '@/types' 7 | import './AppSider.less' 8 | 9 | export default defineComponent({ 10 | setup() { 11 | const { t } = useI18n({ 12 | inheritLocale: true, 13 | useScope: 'global' 14 | }) 15 | const router = useRouter() 16 | 17 | const collapsed = ref(false) 18 | const selectedKeys = ref(['1']) 19 | const menuConfig = ref([ 20 | { 21 | routeName: 'aaa', 22 | title: 'Dashboard', 23 | icon: DashboardOutlined 24 | } 25 | ]) 26 | 27 | const menuDOM = computed(() => { 28 | const DOMList: JSX.Element[] = [] 29 | 30 | menuConfig.value.forEach(config => { 31 | if (!Array.isArray(config.children)) { 32 | DOMList.push( 33 | router.push({ name: config.routeName })} 36 | > 37 | {h(config.icon)} 38 | {t(config.title)} 39 | 40 | ) 41 | } else { 42 | DOMList.push( 43 | 48 | {h(config.icon)} 49 | {config.title} 50 | 51 | } 52 | > 53 | {config.children.map(subConfig => ( 54 | router.push({ name: subConfig.routeName })} 57 | > 58 | {subConfig.title} 59 | 60 | ))} 61 | 62 | ) 63 | } 64 | }) 65 | 66 | return DOMList 67 | }) 68 | 69 | return () => ( 70 | 75 | 83 | 88 | {menuDOM.value.map(item => item)} 89 | 90 | 91 | ) 92 | } 93 | }) 94 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | // eslint-disable-next-line import/no-extraneous-dependencies 3 | require('@rushstack/eslint-patch/modern-module-resolution') 4 | 5 | module.exports = { 6 | root: true, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true 11 | }, 12 | globals: { 13 | JSX: 'readonly' 14 | }, 15 | extends: [ 16 | 'plugin:vue/vue3-recommended', 17 | 'eslint:recommended', 18 | '@vue/eslint-config-typescript', 19 | 'airbnb-base', 20 | '@vue/eslint-config-prettier', 21 | 'prettier' 22 | ], 23 | plugins: ['vue', '@typescript-eslint/eslint-plugin', 'prettier'], 24 | rules: { 25 | 'prettier/prettier': 'error', 26 | 27 | 'no-var': 'error', 28 | 29 | 'no-trailing-spaces': 'error', 30 | 31 | 'no-eval': 'error', 32 | 33 | 'no-loop-func': 'error', 34 | 35 | 'no-new-object': 'error', 36 | 37 | 'no-param-reassign': 'error', 38 | 39 | 'no-dupe-class-members': 'error', 40 | 41 | 'no-duplicate-imports': 'error', 42 | 43 | 'object-shorthand': 'error', 44 | 45 | 'prefer-const': 'error', 46 | 47 | 'prefer-template': 'error', 48 | 49 | 'prefer-arrow-callback': 'error', 50 | 51 | 'template-curly-spacing': 'error', 52 | 53 | 'space-before-blocks': 'error', 54 | 55 | 'func-style': 'error', 56 | 57 | 'arrow-spacing': 'error', 58 | 59 | eqeqeq: 'error', 60 | 61 | quotes: ['error', 'single'], 62 | 63 | semi: 'off', 64 | 65 | 'linebreak-style': ['error', 'unix'], 66 | 67 | 'arrow-parens': ['error', 'as-needed'], 68 | 69 | 'eol-last': ['error', 'always'], 70 | 71 | 'vue/max-attributes-per-line': [ 72 | 'error', 73 | { 74 | singleline: 5, 75 | multiline: { 76 | max: 1 77 | } 78 | } 79 | ], 80 | 81 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 82 | 83 | 'import/prefer-default-export': 'off', 84 | 85 | 'import/no-unresolved': 'off', 86 | 87 | 'import/extensions': 'off', 88 | 89 | 'vue/multi-word-component-names': 'off', 90 | 91 | '@typescript-eslint/no-explicit-any': 'off', 92 | 93 | '@typescript-eslint/member-delimiter-style': 'off', 94 | 95 | '@typescript-eslint/no-var-requires': 'off', 96 | 97 | '@typescript-eslint/ban-ts-ignore': 'off', 98 | 99 | '@typescript-eslint/class-name-casing': 'off', 100 | 101 | '@typescript-eslint/explicit-module-boundary-types': 'off' 102 | }, 103 | parserOptions: { 104 | ecmaVersion: 'latest', 105 | parser: '@typescript-eslint/parser' 106 | }, 107 | overrides: [ 108 | { 109 | files: [ 110 | '**/tests/*.{j,t}s?(x)', 111 | '**/tests/**/*.spec.{j,t}s?(x)', 112 | '**/tests/*.spec.{j,t}s?(x)' 113 | ] 114 | } 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.3](https://github.com/VuePlusOrg/vue3-base/compare/v1.1.2...v1.1.3) (2023-02-06) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **app-sider:** fix the problem that Sider with submenus renders abnormally ([90218db](https://github.com/VuePlusOrg/vue3-base/commit/90218dbf168d66ddd0dccaeea2e511cb9c4c93fe)) 7 | 8 | 9 | 10 | ## [1.1.2](https://github.com/VuePlusOrg/vue3-base/compare/v1.1.1...v1.1.2) (2023-01-18) 11 | 12 | 13 | ### Features 14 | 15 | * **index.html & app.vue:** added a global loading pop-up window before the resource file is loaded ([9c76140](https://github.com/VuePlusOrg/vue3-base/commit/9c7614011e8401c4e4e4d80dfa7c38d610ebf267)) 16 | 17 | 18 | 19 | ## [1.1.1](https://github.com/VuePlusOrg/vue3-base/compare/v1.1.0...v1.1.1) (2023-01-03) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **vite.config.ts:** fix the problem that the type of visualizer plugin passed in is wrong ([660b0cb](https://github.com/VuePlusOrg/vue3-base/commit/660b0cb12ecc80fad3b209f4685cd34b5bacd730)) 25 | 26 | 27 | ### Features 28 | 29 | * **.husky:** added commit message format check and pre-commit code lint check ([7bd0c7f](https://github.com/VuePlusOrg/vue3-base/commit/7bd0c7f65bc2685daa499b24f3663ef6cef53a4a)) 30 | * **app-sider:** added sider support for internationalization ([eeac43a](https://github.com/VuePlusOrg/vue3-base/commit/eeac43a053298bb984340d7392c2282c881b095c)) 31 | * **tsconfig.app.json:** added TypeScript support for ts files and tsx files ([b527f33](https://github.com/VuePlusOrg/vue3-base/commit/b527f33aa924e7ae306fcd33b042ae4eecf4f3c3)) 32 | 33 | 34 | 35 | ## [1.1.0](https://github.com/VuePlusOrg/vue3-base/compare/v1.0.0...v1.1.0) (2022-12-30) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * **use-request:** fix the problem that environment variables cannot be read normally ([103a632](https://github.com/VuePlusOrg/vue3-base/commit/103a6320364aafbcb0d7758b26c22c4a5fe74ca3)) 41 | 42 | 43 | ### Features 44 | 45 | * **layout:** add layout module ([dcf621a](https://github.com/VuePlusOrg/vue3-base/commit/dcf621a1780942260b636494da51697d372fe7bd)) 46 | 47 | 48 | ### Performance Improvements 49 | 50 | * **home-view:** clean up unnecessary logic of HomeView ([4c75606](https://github.com/VuePlusOrg/vue3-base/commit/4c75606ac1fee80aa9b3709daca799713e901520)) 51 | * **use-log-out & use-request & user-store:** remove the useLogOut hook ([0d601cb](https://github.com/VuePlusOrg/vue3-base/commit/0d601cb3b34463861acdf8766976cf7bf1985525)) 52 | * **user-store:** Modify the default gender to -1 ([ebc4dd2](https://github.com/VuePlusOrg/vue3-base/commit/ebc4dd20e380c7fcaa305b327e9f333d6a45c82f)) 53 | * **views:** add route name for page ([2e7bf2d](https://github.com/VuePlusOrg/vue3-base/commit/2e7bf2d143488887fd2cb602dd9e0f3d6549e1e8)) 54 | * **views:** remove counter page & mock page ([d6a9a67](https://github.com/VuePlusOrg/vue3-base/commit/d6a9a67d3f218355975a4e2bdc099d6f08bbc382)) 55 | 56 | 57 | 58 | ## [1.0.0](https://github.com/VuePlusOrg/vue3-base/compare/f602c5a4972f5b5b5c7aebbc201ab533e87025b3...v1.0.0) (2022-12-26) 59 | 60 | 61 | ### Features 62 | 63 | * basically complete the project upgrade ([178f141](https://github.com/VuePlusOrg/vue3-base/commit/178f141bd3119e00e48fd2add1f8a24d6b70642a)) 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 2 | name: Login 3 | meta: 4 | requiresAuth: false 5 | 6 | 7 | 73 | 74 | 87 | 88 | 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-base", 3 | "version": "1.1.3", 4 | "private": true, 5 | "description": "A Vue3-based project infrastructure that can help you quickly and engineer Vue3-based project development.", 6 | "author": { 7 | "name": "CNMathon", 8 | "email": "cnmathon@gmail.com", 9 | "url": "http://github.com/CNMathon" 10 | }, 11 | "scripts": { 12 | "serve": "pnpm serve:develop", 13 | "serve:develop": "vite --mode development", 14 | "serve:preview": "vite --mode preview", 15 | "serve:product": "vite --mode production", 16 | "build": "pnpm build:product", 17 | "build:develop": "NODE_ENV=development run-p type-check build-only", 18 | "build:preview": "NODE_ENV=preview run-p type-check build-only", 19 | "build:product": "NODE_ENV=production run-p type-check build-only", 20 | "test:unit": "vitest --environment jsdom --root src/", 21 | "build-only": "vite build", 22 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", 23 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 24 | "lint:check": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore", 25 | "commit": "npx cz", 26 | "husky:install": "husky install", 27 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" 28 | }, 29 | "dependencies": { 30 | "@ant-design/icons-vue": "^6.1.0", 31 | "ant-design-vue": "^3.2.15", 32 | "axios": "^1.2.1", 33 | "pinia": "^2.0.28", 34 | "vue": "^3.2.45", 35 | "vue-i18n": "9", 36 | "vue-router": "^4.1.6" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.3.0", 40 | "@commitlint/config-conventional": "^17.3.0", 41 | "@intlify/unplugin-vue-i18n": "^0.8.1", 42 | "@rushstack/eslint-patch": "^1.1.4", 43 | "@types/jsdom": "^20.0.1", 44 | "@types/mockjs": "^1.0.7", 45 | "@types/node": "^18.11.12", 46 | "@vitejs/plugin-vue": "^4.0.0", 47 | "@vitejs/plugin-vue-jsx": "^3.0.0", 48 | "@vue/eslint-config-prettier": "^7.0.0", 49 | "@vue/eslint-config-typescript": "^11.0.0", 50 | "@vue/test-utils": "^2.2.6", 51 | "@vue/tsconfig": "^0.1.3", 52 | "commitizen": "^4.2.1", 53 | "conventional-changelog-cli": "^2.2.2", 54 | "cz-conventional-changelog": "^3.3.0", 55 | "eslint": "^8.22.0", 56 | "eslint-config-airbnb-base": "^15.0.0", 57 | "eslint-config-prettier": "^8.5.0", 58 | "eslint-plugin-import": "^2.25.2", 59 | "eslint-plugin-prettier": "^4.2.1", 60 | "eslint-plugin-vue": "^9.3.0", 61 | "husky": "7.0.1", 62 | "jsdom": "^20.0.3", 63 | "less": "^4.1.3", 64 | "lint-staged": "11.0.1", 65 | "mockjs": "^1.1.0", 66 | "npm-run-all": "^4.1.5", 67 | "prettier": "^2.7.1", 68 | "rollup": "2.77.4-1", 69 | "rollup-plugin-visualizer": "^5.8.3", 70 | "typescript": "~4.7.4", 71 | "unplugin-vue-components": "^0.22.12", 72 | "vite": "^4.0.0", 73 | "vite-plugin-mock": "^2.9.6", 74 | "vite-plugin-pages": "^0.28.0", 75 | "vite-plugin-vue-setup-extend": "^0.4.0", 76 | "vitest": "^0.25.6", 77 | "vue-tsc": "^1.0.12" 78 | }, 79 | "lint-staged": { 80 | "src/**/*.{jsx,txs,ts,js,json,vue,md}": [ 81 | "pnpm lint:check" 82 | ] 83 | }, 84 | "browserslist": [ 85 | "> 1%", 86 | "last 2 versions", 87 | "not dead" 88 | ], 89 | "keywords": [ 90 | "vue", 91 | "vue3", 92 | "typescript", 93 | "base", 94 | "template", 95 | "ant-design", 96 | "ant-design-vue", 97 | "vite" 98 | ], 99 | "license": "MIT", 100 | "repository": { 101 | "type": "git", 102 | "url": "https://github.com/VuePlusOrg/vue3-base" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/hooks/useRequest/interceptor/response.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError, AxiosInstance, AxiosResponse } from 'axios' 2 | import { useUserStore } from '@/stores' 3 | 4 | export default class ResponseInterceptor { 5 | /** 6 | * Add response interceptor 7 | * @param axiosInstance Axios instance 8 | */ 9 | public static load(axiosInstance: AxiosInstance) { 10 | axiosInstance.interceptors.response.use( 11 | ResponseInterceptor.onSuccess, 12 | ResponseInterceptor.onFail 13 | ) 14 | } 15 | 16 | /** 17 | * Add response success interceptor 18 | * @param response Axios response object 19 | * @returns Promise 20 | */ 21 | private static onSuccess(response: AxiosResponse) { 22 | const IS_REAL_SUCCESS = response.status === 200 23 | 24 | if (IS_REAL_SUCCESS) { 25 | return Promise.resolve(response) 26 | } 27 | return Promise.reject( 28 | new Error(ResponseInterceptor.errorCodeToText(response)) 29 | ) 30 | } 31 | 32 | /** 33 | * Add response fail interceptor 34 | * @param error Axios error object 35 | * @returns Promise 36 | */ 37 | private static onFail(error: AxiosError) { 38 | const userStore = useUserStore() 39 | const httpStatusCode = error?.response?.status as number 40 | 41 | /** 42 | * the error message returned by the backend 43 | */ 44 | const serviceErrorMessage: string | undefined = ( 45 | error?.response?.data as any 46 | )?.message 47 | 48 | /** 49 | * Axios internal error information, this field is mainly used for request timeout judgment 50 | */ 51 | const axiosErrorMessage = error.message 52 | 53 | let errorMessage: string = serviceErrorMessage || 'Internal Server Error' 54 | 55 | const IS_TIMEOUT = axiosErrorMessage.indexOf('timeout') !== -1 56 | const IS_UNAUTHORIZED = httpStatusCode === 401 || httpStatusCode === 403 57 | 58 | if (IS_TIMEOUT) { 59 | errorMessage = 'Request timed out, please try again later' 60 | } 61 | 62 | if (IS_UNAUTHORIZED) { 63 | if (serviceErrorMessage === undefined) { 64 | errorMessage = 65 | 'Unexpected login failure, please contact your system administrator' 66 | } 67 | userStore.logout() 68 | } 69 | 70 | return Promise.reject(new Error(errorMessage)) 71 | } 72 | 73 | /** 74 | * HTTP error code to error message text 75 | * @param response Axios response object 76 | * @returns error text 77 | */ 78 | private static errorCodeToText(response: AxiosResponse) { 79 | /** http status code */ 80 | const code = response.status 81 | /** notice text */ 82 | let message = 'Request Error' 83 | switch (code) { 84 | case 400: 85 | message = 'Request Error' 86 | break 87 | case 401: 88 | message = 'Unauthorized' 89 | break 90 | case 403: 91 | message = 'Access Denied' 92 | break 93 | case 404: 94 | message = 'Resource Does Not Exist' 95 | break 96 | case 408: 97 | message = 'Request Time Out' 98 | break 99 | case 500: 100 | message = 'Internal Server Error' 101 | break 102 | case 501: 103 | message = 'Bearer Service Not Implemented' 104 | break 105 | case 502: 106 | message = 'Gateway Error' 107 | break 108 | case 503: 109 | message = 'Service Available' 110 | break 111 | case 504: 112 | message = 'Gateway Timeout' 113 | break 114 | case 505: 115 | message = 'Unsupported HTTP Version' 116 | break 117 | default: 118 | message = 'Internal Server Error' 119 | } 120 | return message 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # vue3-base 2 | 3 | [English](./README.md) | 简体中文 4 | 5 | ## 简介 6 | 该项目是一个基于 Vue3 完成的前端基础架构体系。基于该架构体系进行开发,前端开发人员可以更快、更工程化地投入业务开发。 7 | 8 | ## 包管理工具 9 | 目前项目采用 `pnpm` 作为包管理工具。 10 | 11 | 基于内部 Hard Link 机制,综合业务场景下,`pnpm` 比 `npm`/`yarn`提速将近 2 倍,根本上保障业务团队开发效率。 12 | 13 | ## 主要技术栈 14 | ### 项目基础框架 15 | - [Vue 3](github.com/vuejs/core) 16 | - [Vue Router](github.com/vuejs/router) v4 17 | - [Pinia](github.com/vuejs/pinia) v2 18 | - [Vite](github.com/vitejs/vite) v4 19 | - [Typescript](github.com/Microsoft/TypeScript) v4.7.4 20 | 21 | ### UI 样式相关 22 | - [Ant Design Vue](https://github.com/vueComponent/ant-design-vue) v3 23 | - [Tailwind](https://github.com/tailwindlabs/tailwindcss) v3 24 | - LESS 25 | - [postcss-px-to-viewport](https://github.com/evrone/postcss-px-to-viewport) 26 | 27 | ### HTTP 请求库 28 | - [Axios](https://github.com/axios/axios) 29 | 30 | ### Git 提交规范管控 31 | - [commitizen](https://github.com/commitizen-tools/commitizen) 32 | - [husky](https://github.com/typicode/husky) 33 | 34 | ### 代码规范管控 35 | - [ESLint](https://github.com/eslint/eslint) 36 | - [Airbnb](github.com/airbnb/javascript) 37 | - [Prettier](github.com/prettier/prettier) 38 | - [Lint Staged](github.com/okonet/lint-staged) 39 | - [Editor Config](https://github.com/editorconfig/editorconfig-core-js) 40 | 41 | ### 包分析工具 42 | - [rollup-plugin-visualizer](github.com/btd/rollup-plugin-visualizer) 43 | 44 | ### 多语言解决方案 45 | - [vue-i18n](github.com/intlify/vue-i18n-next) v9 46 | 47 | ### 自动加载配置 48 | - 路由自动化加载 49 | - [vite-plugin-pages](github.com/hannoeru/vite-plugin-pages) 50 | - 组件自动化加载 51 | - [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) 52 | 53 | ### 环境变量管控 54 | - dotenv 55 | 56 | ### Mock 57 | - [Mock.js](github.com/nuysoft/Mock) 58 | 59 | ## 架构说明 60 | ### 项目目录结构概述 61 | ``` 62 | |-- .husky/ 👉 Git 钩子配置目录 63 | | 64 | |-- .vscode/ 👉 VSCode 配置目录 65 | | 66 | |-- mock/ 👉 Mock 数据配置目录 67 | | 68 | |-- public/ 👉 静态文件放置目录 69 | | 70 | |-- src/ 👉 静态文件放置目录 71 | | 72 | |-- asset/ 👉 资源文件目录 73 | | 74 | |-- components/ 👉 公共视图组件目录 75 | | 76 | |-- decorators/ 👉 公共装饰器目录 77 | | 78 | |-- directive/ 👉 公共自定义指令目录 79 | | 80 | |-- hooks/ 👉 公共 Hook 目录 81 | | 82 | |-- i18n/ 👉 公共翻译文件目录 83 | | 84 | |-- plugins/ 👉 插件配置目录 85 | | 86 | |-- router/ 👉 路由配置目录 87 | | 88 | |-- stores/ 👉 数据仓库配置目录 89 | | 90 | |-- styles/ 👉 公共样式配置目录 91 | | 92 | |-- base/ 👉 基础全局样式配置 93 | | 94 | |-- landscape/ 👉 横屏支持相关全局样式配置 95 | | 96 | |-- tailwind/ 👉 Tailwind 相关全局样式配置 97 | | 98 | |-- vant/ 👉 Vant 相关全局样式配置 99 | | 100 | |-- index.less 👉 公共样式入口文件 101 | | 102 | |-- views/ 👉 视图文件目录 103 | | 104 | |-- App.vue 👉 视图文件全局入口 105 | | 106 | |-- components.d.ts 👉 组件 Types 配置 107 | | 108 | |-- main.ts 👉 项目入口文件 109 | | 110 | |-- shims-vue.d.ts 👉 Vue 文件 Types 配置 111 | | 112 | |-- commitlintrc.cjs 👉 Commit Lint 配置文件 113 | | 114 | |-- .czrc 👉 Git Commit向导文本配置文件 115 | | 116 | |-- .editorconfig 👉 EditorConfig 配置文件 117 | | 118 | |-- .env 👉 公共环境变量 119 | | 120 | |-- .env.development 👉 开发环境变量 121 | | 122 | |-- .env.preview 👉 测试环境变量 123 | | 124 | |-- .env.production 👉 生产环境变量 125 | | 126 | |-- .eslintrc.cjs 👉 ESLint 配置文件 127 | | 128 | |-- .gitgnore 👉 Git 文件排除配置文件 129 | | 130 | |-- .gitlab-ci.yml 👉 GitLab CI 配置文件 131 | | 132 | |-- .prettierrc.json 👉 Prettier 配置文件 133 | | 134 | |-- .env.d.ts 👉 环境配置 Types 配置文件 135 | | 136 | |-- index.html 👉 项目 HTML 入口 137 | | 138 | |-- package.json 👉 NPM 配置文件 139 | | 140 | |-- pnpm-lock.yaml 👉 PNPM 锁文件 141 | | 142 | |-- tailwind.config.js 👉 Tailwind 配置文件 143 | | 144 | |-- tsconfig.app.json 👉 项目主体 TypeScript 相关配置 145 | | 146 | |-- tsconfig.config.json 👉 配置文件 TypeScript 相关配置 147 | | 148 | |-- tsconfig.json 👉 TypeScript 配置文件入口 149 | | 150 | |-- tsconfig.vitest.json 👉 公共 TypeScript 相关配置 151 | | 152 | |-- vite.config.ts 👉 Vite配置文件 153 | ``` 154 | 155 | ### 路由配置 156 | 在本套框架体系中,您不需要关注任何的路由配置信息。您只需要将对应的页面放置于对应层级和名称的`@/views`文件夹中,框架便会进行自动化的路由构建和解析。 157 | 158 | #### 路径解析 159 | 下面便是一个案例的目录结构所会被框架解析为的路由路径。 160 | 161 | ``` 162 | |-- views/ 163 | |-- home/ 164 | | |-- index.vue 👉 / 165 | |-- pageA/ 166 | | |-- index.vue 👉 /pageA 167 | | |-- sub1.vue 👉 /pageA/sub1 168 | | |-- sub2/ 169 | | | |-- index.vue 👉 /pageA/sub2 170 | |-- pageB/ 171 | | |-- index.vue 👉 /pageB 172 | | |-- sub1.vue 👉 /pageB/sub1 173 | | |-- sub2/ 174 | | | |-- index.vue 👉 /pageB/sub2 175 | |-- pageC/ 176 | | |-- [id]/ 177 | | | |-- index.vue 👉 /pageC/:id (e.g. /pageC/6) 178 | | | |-- detail.vue 👉 /pageC/:id/detail (e.g. /pageC/6/detail) 179 | ``` 180 | 181 | #### 参数配置 182 | 本套自动化的路由体系同样支持完善的路由参数配置。您只需要在指定页面的最外层添加自定义标签``即可。 183 | 184 | ```html 185 | 186 | name: name-override 187 | meta: 188 | requiresAuth: true 189 | 190 | ``` 191 | 192 | route标签内支持JSON方式的配置项传入,但我们依旧建议您使用YAML进行路由的配置,因为我们认为这将会比JSON更加简洁明了。 193 | 194 | ### 组件化 195 | 组件区分全局组件和局部组件两种类型。若您需要创建全局组件,则仅需要将组件放置至 `@/components` 文件夹中,按照大驼峰规范进行正确命名,该组件则将会自动注册至全局。 196 | 197 | 部分仅涉及到单一功能模块的局部组件,您需要务必将组件放置于对应 `view` 视图中的平行目录下的 `./components` 文件夹中(若不存在则需要您自行创建),放置于该文件夹中的组件不会被路由自动解析。 198 | 199 | 此外,参照该规则 [vue/multi-word-component-names](https://eslint.vuejs.org/rules/multi-word-component-names.html),我们不建议开发者使用单个词作为组件名,即使我们已在 ESLint 配置中禁用了该规则。 200 | 201 | ### 静态资源 202 | 我们建议您,将所有的静态资源放置于 `@/assets` 中。 203 | 204 | 需注意的是,静态文件的放置请遵循分类原则,即类似字体文件需放置至`fonts`子目录下,图片文件需放置于`images`子目录下等。 205 | 206 | ### Hooks 207 | > Hook Function 是 React 于 v16.8 中提出的概念,目前 Vue 生态也逐步接纳该设计理念。若需要了解详情,可参考[Introducing Hooks](https://reactjs.org/docs/hooks-intro.html)。 208 | 209 | 该文件夹内放置的为全局可复用的 `Hook`。项目中若有涉及到 `util` 相关的函数,则需要统一 hook 化。且开发者若已创建了一个新的 hook,则需要开发者自行于 `index.ts` 中将该 hook 进行注册。且我们建议外部调用 hook 应仅引入 `index.ts` 文件,而非该 hook 文件本身。 210 | 211 | #### useRequest 212 | 该 hook 用于进行 HTTP 请求。若需配置相应拦截器,请于 `interceptor` 文件夹中进行对应拦截器配置。 213 | 214 | ### 国际化 215 | 框架支持多语言化的相关配置。关于对应语言包的编写,我们分为全局语言包和局部语言包两种。 216 | 217 | 全局语言包内部放置的是常用语的语言翻译信息,该语言包您应该按照规范放置于 `@/i18n` 文件夹内。我们在框架内部给到了中英两种语言的语言包导入的案例。若您需要新增语言,则需要按照语言缩写于该文件夹内创建新的文件,并于该文件夹内的 `index.ts` 文件中进行语言包文件的注册。 218 | 219 | 局部语言包我们建议放置于对应 `view` 或 `component`的同级目录下,并通过 `` 标签进行导入。 220 | 221 | ```html 222 | 223 | 224 | 225 | ``` 226 | 227 | ### 状态管理 228 | 框架同样提供了状态管理的相关能力,我们选用了 [Pinia](https://pinia.vuejs.org/)(Vue 官方目前推荐的携带完整类型系统的数据仓库解决方案)作为了我们的中心化状态管理实现方案。具体使用细节请参考 `Pinia` 官方文档。 229 | 230 | Pinia 目前提供了 `Option Store` & `Setup Store` 两种 API 以供用户进行业务开发。**但我们依旧建议我们的框架使用者能使用 `Setup Store` 进行相关业务开发,就像我们案例中提供的那样。** 231 | 232 | ### 插件 233 | 为了保证 `main.ts` 文件的清洁和高可维护性,我们创立了 `plugin` 的概念。即涉及到需要导入至 `main.ts` 文件的部分,我们要求统一以 `plugin` 的方式进行实现。最后再以创建的 `plugin` 进行导入。 234 | 235 | 如你所见,我们现已内建了 3 个 `plugin`,这三个 `plugin` 和项目基建挂钩,我们不建议您轻易进行编辑或删除。 236 | 237 | ### Mock 238 | 239 | 框架提供了Mock数据请求的相关配置。Mock数据可以使前后端完全分离,并且通过随机数据,可以有效提高单元测试的真实性。 240 | 241 | Mock请求的数据配置文件应放置于`/mock`文件夹中。并且,若后端项目采用微服务架构,我们建议您相同服务的接口放置于一个文件内。 242 | 243 | ### 全局样式 244 | 我们建议您将全局的样式文件放置于 `@/styles` 文件夹目录下,并分类保存至对应文件夹中,并在放置完成后,导入至 `index.less` 以注册它。我们不建议您直接将文件导入至 `main.ts` 文件中。这在项目规模逐渐增大后,可能会带来一定的项目管控成本。 245 | 246 | ### TypeScript 配置 247 | 如您所见,我们将项目的 `tsconfig.json` 文件拆分成了 4 个文件,这四个文件所作用到的文件作用域都是不一样的,所作用域如下: 248 | 249 | - tsconfig.json 250 | - 配置入口文件 251 | - tsconfig.app.json 252 | - 作用于 `src` 目录下的所有文件,供业务开发使用 253 | - tsconfig.vitest.json 254 | - 作用于 `vite.config.ts` 文件,用于 `vite` 的相关配置使用 255 | - tsconfig.config.json 256 | - 同上 257 | 258 | ### 代码提交 259 | 为了统一 git commit 的提交文本的规范,因此,我们采用了 `git-cz` 作为统一的 commit 生成工具。您仅需运行以下命令即可使用流程化的 commit 提交。 260 | 261 | ``` 262 | pnpm commit 263 | ``` 264 | 265 | 除此之外,我们已经于 `.czrc` 文件中配置了相关的流程可选项,我们认为,我们给到的配置项已经可以基本覆盖开发过程中的全部流程。若您对建议的配置项依旧不满意,您也可以按需修改 `.czrc` 文件以达成目的。 266 | 267 | ### 代码质量和风格管控 268 | 项目代码质量和风格管控我们选用了 4 个渠道的统一性。分别是 `ESLint`、`Prettier`、 `EditorConfig`、`.vscode`文件夹。这其中包含了代码质量、代码风格、编辑器配置等多维度的统一性,以保障团队成员的代码规范一致性。 269 | 270 | 开发人员在遇到上述工具的规范性报错,或配置的开发方式不符合个人习惯时,我们非常不建议直接修改相关配置项。您在遇到上述问题后,请于项目技术管理人员联系和协商,若最终评定合理,则需要全团队统一修改相关规范和配置文件。若自行修改相关配置文件,可能会导致个人代码不符合团队开发规范。 271 | 272 | ### 环境变量配置 273 | 当前项目我们采用 `dotenv` 作为环境变量配置工具。且目前项目中存在的 `.env` 存在三种类型,具体使用场景如下。 274 | 275 | | 文件名 | 是否可上传至 GIT | 是否可以包含敏感数据 | 是否作用于开发环境 | 是否作用于测试环境 | 是否作用于生产环境 | 276 | | ---------------------- | --------------- | ----------------- | --------------- | --------------- | --------------- | 277 | | .env | ⭕️ | ❌ | ⭕️ | ⭕️ | ⭕️ | 278 | | .env.local | ❌ | ⭕️ | ⭕️ | ⭕️ | ⭕️ | 279 | | .env.development | ⭕️ | ❌ | ⭕️ | ❌ | ❌ | 280 | | .env.development.local | ❌ | ⭕️ | ⭕️ | ❌ | ❌ | 281 | | .env.preview | ⭕️ | ❌ | ❌ | ⭕️ | ❌ | 282 | | .env.preview.local | ❌ | ⭕️ | ❌ | ⭕️ | ❌ | 283 | | .env.production | ⭕️ | ❌ | ❌ | ❌ | ⭕️ | 284 | | .env.production.local | ❌ | ⭕️ | ❌ | ❌ | ⭕️ | 285 | 286 | ### CI/CD 287 | [ ] GitHub Action 288 | [ ] GitLab CI 289 | [ ] Travis 290 | 291 | ## 项目指令 292 | ### 依赖安装 293 | ``` 294 | pnpm install 295 | ``` 296 | 297 | ### 服务运行 298 | #### 开发环境 299 | > 以下指令二选一即可 300 | ``` 301 | pnpm serve 302 | pnpm serve:develop 303 | ``` 304 | 305 | #### 测试环境 306 | ``` 307 | pnpm serve:preview 308 | ``` 309 | 310 | #### 生产环境 311 | ``` 312 | pnpm serve:product 313 | ``` 314 | 315 | ### 服务构建 316 | #### 开发环境 317 | > 以下指令二选一即可 318 | ``` 319 | pnpm build 320 | pnpm build:develop 321 | ``` 322 | 323 | #### 测试环境 324 | ``` 325 | pnpm build:preview 326 | ``` 327 | 328 | #### 生产环境 329 | ``` 330 | pnpm build:product 331 | ``` 332 | 333 | ### 代码 Lint 334 | #### Lint 修复 335 | ``` 336 | pnpm lint 337 | ``` 338 | 339 | #### Lint 检查 340 | ``` 341 | pnpm lint:check 342 | ``` 343 | 344 | ### 代码提交 345 | ``` 346 | pnpm commit 347 | ``` 348 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue3-base 2 | 3 | English | [简体中文](./README-zh_CN.md) 4 | 5 | ## Introduction 6 | This project is a front-end infrastructure based on Vue3. Based on this architecture, front-end developers can be faster and more engineered into business development. 7 | 8 | ## Package Management Tools 9 | The project currently uses `pnpm` as a package management tool. 10 | 11 | Based on the internal Hard Link mechanism, `pnpm` is almost 2 times faster than `npm`/`yarn` in integrated business scenarios, which fundamentally guarantees the development efficiency of business teams. 12 | 13 | ## Technology Stack 14 | ### Project Base Framework 15 | - [Vue 3](github.com/vuejs/core) 16 | - [Vue Router](github.com/vuejs/router) v4 17 | - [Pinia](github.com/vuejs/pinia) v2 18 | - [Vite](github.com/vitejs/vite) v4 19 | - [Typescript](github.com/Microsoft/TypeScript) v4.7.4 20 | 21 | ### UI Style Related 22 | - [Ant Design Vue](https://github.com/vueComponent/ant-design-vue) v3 23 | - [Tailwind](https://github.com/tailwindlabs/tailwindcss) v3 24 | - LESS 25 | - [postcss-px-to-viewport](https://github.com/evrone/postcss-px-to-viewport) 26 | 27 | ### HTTP Request Library 28 | - [Axios](https://github.com/axios/axios) 29 | 30 | ### Git Commit Control 31 | - [commitizen](https://github.com/commitizen-tools/commitizen) 32 | - [husky](https://github.com/typicode/husky) 33 | 34 | ### Code Specification Control 35 | - [ESLint](https://github.com/eslint/eslint) 36 | - [Airbnb](github.com/airbnb/javascript) 37 | - [Prettier](github.com/prettier/prettier) 38 | - [Lint Staged](github.com/okonet/lint-staged) 39 | - [Editor Config](https://github.com/editorconfig/editorconfig-core-js) 40 | 41 | ### Package Analysis Tools 42 | - [rollup-plugin-visualizer](github.com/btd/rollup-plugin-visualizer) 43 | 44 | ### Multi-language Solutions 45 | - [vue-i18n](github.com/intlify/vue-i18n-next) v9 46 | 47 | ### Automated Configuration Loading 48 | - Routing automation loading 49 | - [vite-plugin-pages](github.com/hannoeru/vite-plugin-pages) 50 | - Component automation loading 51 | - [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) 52 | 53 | ### Environment Variable Control 54 | - dotenv 55 | 56 | ### Mock 57 | - [Mock.js](github.com/nuysoft/Mock) 58 | 59 | ### Architecture Description 60 | #### Project Directory Structure Overview 61 | ``` 62 | |-- .husky/ 👉 Git Hook configuration directory 63 | | 64 | |-- .vscode/ 👉 VSCode configuration directory 65 | | 66 | |-- mock/ 👉 Mock data configuration directory 67 | | 68 | |-- public/ 👉 Static file placement directory 69 | | 70 | |-- src/ 👉 Project main files 71 | | 72 | |-- asset/ 👉 Resource file directory 73 | | 74 | |-- components/ 👉 Public view components directory 75 | | 76 | |-- decorators/ 👉 Public decorators directory 77 | | 78 | |-- directive/ 👉 Public directives directory 79 | | 80 | |-- hooks/ 👉 Public hook directory 81 | | 82 | |-- i18n/ 👉 Public translation file directory 83 | | 84 | |-- plugins/ 👉 Plugin configuration directory 85 | | 86 | |-- router/ 👉 Router configuration directory 87 | | 88 | |-- stores/ 👉 Data store configuration directory 89 | | 90 | |-- styles/ 👉 Public styles configuration directory 91 | | 92 | |-- base/ 👉 Base global style configuration 93 | | 94 | |-- landscape/ 👉 Horizontal support related global style configuration 95 | | 96 | |-- tailwind/ 👉 Tailwind related global style configuration 97 | | 98 | |-- vant/ 👉 Vant related global style configuration 99 | | 100 | |-- index.less 👉 Public style entry file 101 | | 102 | |-- views/ 👉 View file directory 103 | | 104 | |-- App.vue 👉 View file global entry 105 | | 106 | |-- components.d.ts 👉 Component types configuration 107 | | 108 | |-- main.ts 👉 Project entry file 109 | | 110 | |-- shims-vue.d.ts 👉 Vue file types configuration 111 | | 112 | |-- commitlintrc.cjs 👉 Commit lint configuration 113 | | 114 | |-- .czrc 👉 Git Commit wizard text configuration file 115 | | 116 | |-- .editorconfig 👉 EditorConfig configuration file 117 | | 118 | |-- .env 👉 Public environment variables 119 | | 120 | |-- .env.development 👉 Development environment variables 121 | | 122 | |-- .env.preview 👉 Test environment variables 123 | | 124 | |-- .env.production 👉 Production environment variables 125 | | 126 | |-- .eslintrc.cjs 👉 ESLint configuration file 127 | | 128 | |-- .gitgnore 👉 Git file exclusion configuration file 129 | | 130 | |-- .gitlab-ci.yml 👉 GitLab CI configuration file 131 | | 132 | |-- .prettierrc.json 👉 Prettier configuration file 133 | | 134 | |-- .env.d.ts 👉 Types configuration file for environment configuration 135 | | 136 | |-- index.html 👉 Project HTML entry 137 | | 138 | |-- package.json 👉 NPM configuration file 139 | | 140 | |-- pnpm-lock.yaml 👉 PNPM lock file 141 | | 142 | |-- tailwind.config.js 👉 Tailwind configuration file 143 | | 144 | |-- tsconfig.app.json 👉 TypeScript configuration for project main file 145 | | 146 | |-- tsconfig.config.json 👉 TypeScript configuration for project configuration file 147 | | 148 | |-- tsconfig.json 👉 TypeScript configuration file entry 149 | | 150 | |-- tsconfig.vitest.json 👉 Public TypeScript configuration 151 | | 152 | |-- vite.config.ts 👉 Vite configuration file 153 | ``` 154 | 155 | ### Routing configuration 156 | In this framework system, you don't need to pay attention to any routing configuration information. You just need to place the corresponding page in the `@/views` folder of the corresponding level and name, and the framework will do the automated route building and parsing. 157 | 158 | #### Route Resolution 159 | The following is the route path that will be parsed by the framework for the directory structure of a case. 160 | 161 | ``` 162 | |-- views/ 163 | |-- home/ 164 | | |-- index.vue 👉 / 165 | |-- pageA/ 166 | | |-- index.vue 👉 /pageA 167 | | |-- sub1.vue 👉 /pageA/sub1 168 | | |-- sub2/ 169 | | | |-- index.vue 👉 /pageA/sub2 170 | |-- pageB/ 171 | | |-- index.vue 👉 /pageB 172 | | |-- sub1.vue 👉 /pageB/sub1 173 | | |-- sub2/ 174 | | | |-- index.vue 👉 /pageB/sub2 175 | |-- pageC/ 176 | | |-- [id]/ 177 | | | |-- index.vue 👉 /pageC/:id (e.g. /pageC/6) 178 | | | |-- detail.vue 👉 /pageC/:id/detail (e.g. /pageC/6/detail) 179 | ``` 180 | 181 | #### Parameters Configuration 182 | This automated routing system also supports full configuration of routing parameters. All you need to do is add the custom tag ``` to the outermost part of the specified page. 183 | 184 | ```html 185 | 186 | name: name-override 187 | meta: 188 | requiresAuth: true 189 | 190 | ``` 191 | 192 | The route tag supports incoming JSON configuration items, but we still recommend that you use YAML for route configuration as we think it will be more concise and clear than JSON. 193 | 194 | ### Componentization 195 | There are two types of components, global and local. If you need to create a global component, you only need to place the component in the `@/components` folder, name it correctly according to the Big Hump specification, and it will be automatically registered to the global. 196 | 197 | For local components that involve only a single functional module, you need to make sure to place the component in the `./components` folder in the parallel directory of the corresponding `view` view (the /components` folder in the parallel directory of the corresponding `view` view (if it does not exist, you will have to create it yourself), as components placed in this folder will not be automatically resolved by the route. 198 | 199 | Also, referring to the rule [vue/multi-word-component-names](https://eslint.vuejs.org/rules/multi-word-component-names.html), we do not recommend that developers use a single word as a component name, even if we have disabled it in the ESLint configuration to disable this rule. 200 | 201 | ### Static Resources 202 | We recommend that you place all static resources in `@/assets`. 203 | 204 | Note that the placement of static files should follow the categorization principle, i.e. fonts should be placed in the `fonts` subdirectory, images should be placed in the `images` subdirectory, etc. 205 | 206 | ### Hooks 207 | > Hook Function is a concept introduced by React in v16.8, and is being gradually adopted by the Vue ecosystem. For more information, see [Introducing Hooks](https://reactjs.org/docs/hooks-intro.html). 208 | 209 | This folder contains globally reusable `Hooks`. If there are functions related to `util` in the project, they need to be hooked uniformly. If a new hook has been created by the developer, the developer has to register the hook in `index.ts`. It is recommended that external calls to the hook should only bring in the `index.ts` file and not the hook itself. 210 | 211 | #### useRequest 212 | This hook is used to make HTTP requests. If you need to configure the corresponding interceptor, please do so in the `interceptor` folder. 213 | 214 | ### Internationalization 215 | The framework supports configuration for multilingualism. we have two types of packages: global packages and local packages. 216 | 217 | The global language package is placed inside the common language translation information, the language package you should follow the specifications placed in the `@/i18n` folder. We have given examples of importing language packs for both English and Chinese languages inside the framework. If you need to add a new language, you need to create a new file in that folder with the language abbreviation and register the language pack file in the `index.ts` file in that folder. 218 | 219 | Local language packs are recommended to be placed in the same directory as `view` or `component` and imported via the `` tag. 220 | 221 | ```html 222 | 223 | 224 | 225 | ``` 226 | 227 | ### State Management 228 | The framework also provides state management capabilities, and we have chosen [Pinia](https://pinia.vuejs.org/) (the official Vue recommended data warehouse solution that carries the full type of system) as our centralized state management implementation. Please refer to the official `Pinia` documentation for details. 229 | 230 | Pinia currently provides both `Option Store` & `Setup Store` APIs for business development. ** However, we still recommend our framework users to use the `Setup Store` for business development, as provided in our case. ** 231 | 232 | ### Plugins 233 | In order to keep the `main.ts` file clean and highly maintainable, we created the concept of `plugin`. That is, when it comes to the parts that need to be imported into the `main.ts` file, we require that they be implemented uniformly as `plugin`. Finally, the import will be done with the `plugin` created. 234 | 235 | As you can see, we have 3 `plugin`s built in, which are tied to the project infrastructure and we don't recommend you to edit or delete them easily. 236 | 237 | ### Mock 238 | The framework provides configuration for Mock data requests, which allows complete separation of front and back ends and improves unit test fidelity by randomizing data. 239 | 240 | The data configuration file for Mock requests should be placed in the `/mock` folder. And, if the back-end project adopts microservice architecture, we recommend that you place the interfaces of the same service in one file. 241 | 242 | ### Global styles 243 | We recommend that you place the global style files in the `@/styles` folder, save them in the corresponding folder, and import them to `index.less` to register them once they are placed. We do not recommend that you import the files directly into the `main.ts` file. This may incur some project control costs as the project grows in size. 244 | 245 | ### TypeScript configuration 246 | As you can see, we split the project's `tsconfig.json` file into four files, all of which have different file scopes, as follows. 247 | 248 | - tsconfig.json 249 | - configuration entry file 250 | - tsconfig.app.json 251 | - acts on all files in the `src` directory for business development 252 | - tsconfig.vitest.json 253 | - The `vite.config.ts` file, used for `vite` related configuration 254 | - tsconfig.config.json 255 | - as above 256 | 257 | ### Code Commits 258 | In order to standardize the commit text of git commits, we have adopted `git-cz` as a uniform commit generation tool. You can use a process-based commit by simply running the following command. 259 | 260 | ``` 261 | pnpm commit 262 | ``` 263 | 264 | In addition to this, we have configured the process options in the `.czrc` file, and we believe that we have given you enough configuration items to basically cover the entire process of development. If you are still not satisfied with the proposed configuration, you can modify the `.czrc` file as needed to achieve your goal. 265 | 266 | ### Code Quality and Style Control 267 | For project code quality and style control we have chosen 4 channels of uniformity. They are `ESLint`, `Prettier`, `EditorConfig`, and `.vscode` folders. This contains multi-dimensional uniformity in code quality, code style, editor configuration, etc. to guarantee the consistency of code specification for team members. 268 | 269 | Developers are highly discouraged from directly modifying the relevant configuration items when they encounter normative errors reported by the above tools, or when the configured development style does not match their personal habits. If you encounter the above problem, please contact and negotiate with the project technical management, and if the final assessment is reasonable, you will need to modify the relevant specification and configuration file uniformly for the whole team. If you modify the configuration file by yourself, it may lead to individual code not conforming to the team development specification. 270 | 271 | ### Environment Variable Configuration 272 | For the current project, we use `dotenv` as the environment variable configuration tool. There are three types of `.env` in the project, and the specific usage scenarios are as follows. 273 | 274 | | File Name | Can Be Uploaded To GIT | Can Contain Sensitive Data | Works In Development Environment | Works In Test Environment | Works In Production Environment | 275 | | ---------------------- | ---------------------- | -------------------------- | -------------------------------- | ------------------------- | ------------------------------- | 276 | | .env | ⭕️ | ❌ | ⭕️ | ⭕️ | ⭕️ | 277 | | .env.local | ❌ | ⭕️ | ⭕️ | ⭕️ | ⭕️ | 278 | | .env.development | ⭕️ | ❌ | ⭕️ | ❌ | ❌ | 279 | | .env.development.local | ❌ | ⭕️ | ⭕️ | ❌ | ❌ | 280 | | .env.preview | ⭕️ | ❌ | ❌ | ⭕️ | ❌ | 281 | | .env.preview.local | ❌ | ⭕️ | ❌ | ⭕️ | ❌ | 282 | | .env.production | ⭕️ | ❌ | ❌ | ❌ | ⭕️ | 283 | | .env.production.local | ❌ | ⭕️ | ❌ | ❌ | ⭕️ | 284 | 285 | ### CI/CD 286 | [ ] GitHub Action 287 | [ ] GitLab CI 288 | [ ] Travis 289 | 290 | ## Project Directives 291 | ### Dependency Installation 292 | ``` 293 | pnpm install 294 | ``` 295 | 296 | ### Service Run 297 | #### Development Environment 298 | > Just choose one of the following commands 299 | ``` 300 | pnpm serve 301 | pnpm serve:develop 302 | ``` 303 | 304 | #### Test Environment 305 | ``` 306 | pnpm serve:preview 307 | ``` 308 | 309 | #### Production Environment 310 | ``` 311 | pnpm serve:product 312 | ``` 313 | 314 | ### Service Build 315 | #### Development Environment 316 | > Just choose one of the following commands 317 | ``` 318 | pnpm build 319 | pnpm build:develop 320 | ``` 321 | 322 | #### Test Environment 323 | ``` 324 | pnpm build:preview 325 | ``` 326 | 327 | #### Production Environment 328 | ``` 329 | pnpm build:product 330 | ``` 331 | 332 | ### Code Lint 333 | #### Lint Fixes 334 | ``` 335 | pnpm lint 336 | ``` 337 | 338 | #### Lint Check 339 | ``` 340 | pnpm lint:check 341 | ``` 342 | 343 | ### Code Commit 344 | ``` 345 | pnpm commit 346 | ``` 347 | --------------------------------------------------------------------------------