创建项目
基于webpack
vue create xxx
基于vite
npm init vue@latest
配置项目环境(TS专属)
安装插件
vue language features
typescript vue plugin
vue文件类型声明
//env.d.ts
<reference type="vite/client"/>
//declare 声明文件
declare module '*.vue'{
import {DefineComponent} from 'vue'
const component:DefineComponent
export default component
}
配置项目的icon
复制.icon文件到public文件夹
配置项目的标题
index.html中修改title
配置项目别名
//tsconfig.json
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["src/**/*", "src/**/*.vue", "env.d.ts", "auto-imports.d.ts", "components.d.ts"],
"exclude": ["commitlint.config.js"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// "types": ["element-plus/global"]
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
//jsconfig.json
//复制粘贴:更好的代码提示
{
"compilerOptions": {
// "target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"jsx":"preserve",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
// "vueCompilerOptions": {
"experimentalDisableTemplateSupport": true
// }
}
editorconfig配置
为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格
VSCode需要安装一个插件:EditorConfig for VS Code
//.editorconfig
# http://editorconfig.org
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
prettier工具
npm install prettier -D
代码格式化工具
VSCode需要安装prettier-code formatter的插件
VSCod中的配置
- settings =>format on save => 勾选上
- settings => editor default formatter => 选择 prettier
在package.json中配置一个scripts
“prettier”: “prettier --write .”
npm run prettier
{
"useTabs": false,//使用tab缩进还是空格缩进
"tabWidth": 2,//tab是空格的情况下,是几个空格
"printWidth": 80,//当行字符的长度
"singleQuote": true,//使用单引号还是双引号
"trailingComma": "none",//在多行输入的尾逗号是否添加,
"semi": false//语句末尾是否要加分号
}
.prettierignore忽略文件
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
ESLint检测
对代码进行检测并报错
VSCode需要安装ESLint插件
解决eslint和prettier冲突的问题
安装插件:(vue在创建项目时,如果选择prettier,那么这两个插件会自动安装)
npm install eslint-plugin-prettier eslint-config-prettier -D
//添加prettier插件
// .eslintrc.cjs
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint",
'plugin:prettier/recommended'
],
rules:{}
目录结构
├── src # 源代码目录
│ ├── assets # 主题 字体,CSS等静态资源
│ ├── components # 全局公用组件
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── service # 网络请求
│ ├── utils # 全局公用方法
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
│ └── settings.js # 配置文件
css样式重置
npm install normalize.css
npm install less -D
normalize.css
//main.ts
import "normalize.css"
import './assets/css/index.less'
reset.less
/* reset.css样式重置文件 */
/* margin/padding重置 */
body, h1, h2, h3, ul, ol, li, p, dl, dt, dd {
padding: 0;
margin: 0;
}
/* a元素重置 */
a {
text-decoration: none;
color: #333;
}
/* img的vertical-align重置 */
img {
vertical-align: top;
}
/* ul, ol, li重置 */
ul, ol, li {
list-style: none;
}
/* 对斜体元素重置 */
i, em {
font-style: normal;
}
common.less
:root {
// --el-button-size: 50px !important;
}
body{
font-size:14px;
}
index.css
//src/assets/css/index.less
@import './reset.less';
@import './common.less';
//main.js
import "./assets/css/index.css"
}
宽高铺满
//App.vue
.app{
height:100vh;
width:100vw;
background-color:blue;
}
配置路由
npm install vue-router
ctrl+t打开某个文件
初始化
// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
// 映射关系: path => component
routes: []
})
export default router
//main.ts
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
新建页面
//views/main/Main.vue
<template>
<div class="main">
<h2>
Main
</h2>
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.main{}
</style>
新建NotFound页面
<template>
<div class="not-found">
<h2>您输入的路径地址不正确, 请联系管理员~</h2>
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped>
.not-found {
color: purple;
}
</style>
配置路由关系
routes:[
{
path:'/',
redirect:'/main'
},
{
path:'/login',
components:()=>import('../views/login/Login.vue')
},
{
path:'/main',
components:()=>import('../views/main/Main.vue')
},
{
path: '/:pathMatch(.*)',
component: () => import('../views/not-found/NotFound.vue')
}
]
测试
//App.vue
<template>
<div class="app">
<h2>App</h2>
<router-link to="/main">主页</router-link>
<router-link to="/login">登录</router-link>
<router-view/>
</div>
</template>
配置pinia
npm install pinia
初始化
// store/index.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
//main.ts
import pinia from './store'
app.use(pinia)
新建store
//store/login/login.ts
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counter', {
state: () => ({
counter: 100
}),
getters: {
doubleCounter(state) {
return state.counter * 2
}
},
actions: {
changeCounterAction(newCounter: number) {
this.counter = newCounter
}
}
})
export default useCounterStore
适用store
//Main.vue
<h2>main:{{counterStore.counter}}-{{counterStore.doubleCounter}}</h2>
<button @click="changeCounter">修改counter</button>
import useCounterStore from '@/store/counter'
const counterStore = useCounterStore()
function changeCounter(){
counterStore.changeCounterAction(999)
}
配置axios(JS)
新建目录结构
├── request
│ ├── ├── index.js
│ ├── ├── config.js
├── module
│ ├── ├── city.js
config.js
//services/request/config.js
export const BASE_URL = "http://123.207.32.32:1888/api"
// export const BASE_URL = "http://codercba.com:1888/api"
export const TIMEOUT = 10000
index.js
// services/request/index.js
import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config'
class HYRequest {
constructor(baseURL, timeout=10000) {
this.instance = axios.create({
baseURL,
timeout
})
}
request(config) {
return new Promise((resolve, reject) => {
this.instance.request(config).then(res => {
resolve(res.data)
}).catch(err => {
reject(err)
})
})
}
get(config) {
return this.request({ ...config, method: "get" })
}
post(config) {
return this.request({ ...config, method: "post" })
}
}
export default new HYRequest(BASE_URL, TIMEOUT)
city.js
services/modules/city.js
import hyRequest from '../request'
export function getCityAll() {
return hyRequest.get({
url: "/city/all"
})
}
使用
import { getCityAll } from "@/services/modules/city"
// 网络请求: 请求城市的数据
const allCity = ref({})
getCityAll().then(res => {
allCity.value = res.data
console.log(res)
})
配置axios(TS)
npm install axios
新建目录结构
├── service
│ ├── config
│ ├── ├── index.ts
│ ├── login
│ ├── main
│ ├── request
│ ├── ├── index.ts
│ ├── ├── type.ts
│ ├── index.ts
config文件
export const BASE_URL = "http://codercba.com:8000"
export const TIME_OUT = 10000
request文件
//index.ts
import axios from "axios"
import type { AxiosInstance, } from "axios"
import type { HYRequestConfig } from "./type"
/**
* 两个难点:
* 1.拦截器进行精细控制
* > 全局拦截器
* > 实例拦截器
* > 单次请求拦截器
*
* 2.响应结果的类型处理(泛型)
*/
class HYRequest {
instance: AxiosInstance
// request实例 => axios的实例
constructor(config: HYRequestConfig) {
this.instance = axios.create(config)
// 每个instance实例都添加拦截器
this.instance.interceptors.request.use(config => {
// loading/token
return config
}, err => {
return err
})
this.instance.interceptors.response.use(res => {
return res.data
}, err => {
return err
})
// 针对特定的hyRequest实例添加拦截器
this.instance.interceptors.request.use(
config.interceptors?.requestSuccessFn,
config.interceptors?.requestFailureFn
)
this.instance.interceptors.response.use(
config.interceptors?.responseSuccessFn,
config.interceptors?.responseFailureFn
)
}
// 封装网络请求的方法
// T => IHomeData axios->AxiosResponse->IHomeData
request<T = any>(config: HYRequestConfig<T>) {
// 单次请求的成功拦截处理
if (config.interceptors?.requestSuccessFn) {
config = config.interceptors.requestSuccessFn(config)
}
// 返回Promise
return new Promise<T>((resolve, reject) => {
this.instance.request<any, T>(config).then(res => {
// 单词响应的成功拦截处理
if (config.interceptors?.responseSuccessFn) {
res = config.interceptors.responseSuccessFn(res)
}
//resolve(res as any as T)
resolve(res)
}).catch(err => {
reject(err)
})
})
}
get<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "GET" })
}
post<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "POST" })
}
delete<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "DELETE" })
}
patch<T = any>(config: HYRequestConfig<T>) {
return this.request({ ...config, method: "PATCH" })
}
}
export default HYRequest
//type.ts
import type { AxiosRequestConfig, AxiosResponse } from "axios"
// 针对AxiosRequestConfig配置进行扩展
export interface HYInterceptors<T = AxiosResponse> {
requestSuccessFn?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestFailureFn?: (err: any) => any
responseSuccessFn?: (res: T) => T
responseFailureFn?: (err: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYInterceptors<T>
}
index.ts
import HYRequest from "./request";
import { BASE_URL, TIME_OUT } from "./config";
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT
})
export default hyRequest
使用
//Login.vue
hyRequest.get({
url:'/home/multidata'
})
.then((res)=>{
console.log(res)
})
开发和生产环境
第一种
// config/index.ts
let BASE_URL = ''
if (import.meta.env.PROD) {//应用是否运行在生产环境
// 生产环境
BASE_URL = 'http://152.136.185.210:4000'
} else {
// 开发环境
BASE_URL = 'http://152.136.185.210:5000'
}
export {BASE_URL}
第二种
│ ├── .env
│ ├── .env.development
│ ├── .env.production
//.env.development
VITE_BASE_URL = "http://codercba.com/dev:1888"
VITE_TIME_OUT = 10000
引入element-plus
npm install element-plus
按需引入
npm install -D unplugin-vue-components unplugin-auto-import
vite
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
Webpack
// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
// ...
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
//tsconfig.json
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["src/**/*", "src/**/*.vue", "env.d.ts", "auto-imports.d.ts", "components.d.ts"],
"exclude": ["commitlint.config.js"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// "types": ["element-plus/global"]
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
}
针对ElMessage和ElLoading等反馈组件样式引入
npm i -D vite-plugin-style-import
npm install consola -D
//vite.config.ts
import {
createStyleImportPlugin,
ElementPlusResolve
} from 'vite-plugin-style-import'
plugins: [
...
createStyleImportPlugin({
resolves: [ElementPlusResolve()],
libs: [
{
libraryName: 'element-plus',
esModule: true,
resolveStyle: (name: string) => {
return `element-plus/theme-chalk/${name}.css`
}
}
]
})
]
icon 图标
npm install @element-plus/icons-vue
//global/register-icons.ts
import type { App } from 'vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
function registerIcons(app: App<Element>) {
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
}
export default registerIcons
//main.ts
import icons from './global/register-icons'
app.use(icons)
引入Vant
npm i unplugin-vue-components -D
vite
//vite.config.js
import vue from '@vitejs/plugin-vue';
+ import Components from 'unplugin-vue-components/vite';
+ import { VantResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
vue(),
+ Components({
resolvers: [VantResolver()],
}),
],
};
webpack
//vue.config.js
const { VantResolver } = require('unplugin-vue-components/resolvers');
const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = {
configureWebpack: {
plugins: [
ComponentsPlugin({
resolvers: [VantResolver()],
}),
],
},
};
git 提交
Husky
对git commit进行校验,符合eslint规范
npx husky-init ‘&&’ npm install(有git仓库 git init)
// .husky/pre-commit
...
npm run lint
Commitizen
编写规范commit message的工具,不再使用git commit -m “”
使用npx cz
npm install commitizen -D
npx commitizen init cz-conventional-changelog --save-dev --save-exact
阻止commit提交
npm i @commitlint/config-conventional @commitlint/cli -D
npx husky add .husky/commit-msg “npx --no-install commitlint --edit $1”
//commitlint.config.js
module.exports={
extends:['@commitlint/config-conventional']
}
命令重置
//package.json
"script":{
"commit":'cz'
}
npm run commit