最详细的搭建 vue3+ts+vite项目的教程和步骤
项目基于以下相关技术搭建
- vue3
- typescript
- vite
- pnpm
- vue-router
- pinia
- axios
初始化项目
兼容性注意
Vite 需要 Node.js 版本 >= 14.18.0。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
执行命令创建项目
pnpm create vite my-vue-app --template vue-ts
引入eslint prettier stylelint husky
引入eslint
与prettier
以保证代码的质量与代码的统一风格。
这里笔者就把该项目中使用的.eslintrc.js
、.preitterrc.js
与.stylelintrc.js
配置文件放出来
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
// 必须放在最后面
'plugin:prettier/recommended'
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['vue', '@typescript-eslint'],
rules: {
// "off" or 0 - 关闭规则
// "warn" or 1 - 将规则视为一个警告
// "error" or 2 - 将规则视为一个错误
eqeqeq: 2 // 强制使用 === 和 !==
}
}
// .prettierrc.js
module.exports = {
// 一行的字符数,如果超过会进行换行,默认为80
printWidth: 80,
// 一个tab代表几个空格数,默认为80
tabWidth: 2,
// 是否使用tab进行缩进,默认为false,表示用空格进行缩减
useTabs: false,
// 字符串是否使用单引号,默认为false,使用双引号
singleQuote: true,
// 行位是否使用分号,默认为true
semi: false,
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
trailingComma: 'none',
// 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
bracketSpacing: true
}
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-prettier',
'stylelint-config-recommended-less',
'stylelint-config-standard-vue'
],
plugins: ['stylelint-order'],
// 不同格式的文件指定自定义语法
overrides: [
{
files: ['**/*.(less|css|vue|html)'],
customSyntax: 'postcss-less'
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html'
}
],
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml'
],
rules: {
'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep']
}
],
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep']
}
],
// 指定样式的排序
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'justify-content',
'align-items',
'float',
'clear',
'overflow',
'overflow-x',
'overflow-y',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'font-size',
'font-family',
'text-align',
'text-justify',
'text-indent',
'text-overflow',
'text-decoration',
'white-space',
'color',
'background',
'background-position',
'background-repeat',
'background-size',
'background-color',
'background-clip',
'border',
'border-style',
'border-width',
'border-color',
'border-top-style',
'border-top-width',
'border-top-color',
'border-right-style',
'border-right-width',
'border-right-color',
'border-bottom-style',
'border-bottom-width',
'border-bottom-color',
'border-left-style',
'border-left-width',
'border-left-color',
'border-radius',
'opacity',
'filter',
'list-style',
'outline',
'visibility',
'box-shadow',
'text-shadow',
'resize',
'transition'
]
}
}
配置commitlint
校验提交信息
安装依赖
pnpm add @commitlint/cli @commitlint/config-conventional -D
添加commitlint.config.js
配置文件
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
添加commit-msg
githook钩子
cat <<EEE > .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm --no -- commitlint --edit "${1}"
EEE
使钩子可执行
chmod a x .husky/commit-msg
最后
现在提交信息就会进行commit信息的校验
提交信息的规则可以查看官方文档
上面的配置是采用官方的配置(config-conventional
),更多配置可查看官方文档,还可以安装@commitlint/prompt-cli
进行交互式commit信息。
配置别名
在vite.config.ts
中增加一下配置
下面是以@
为例,如果需要添加其他额外的别名,请自行添加即可
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
},
plugins: [vue()]
})
修改tsconfig.json
配置,使vscode可正确提示别名路径
例如:输入@/,vscode就会提示该别名路径下对应的目录与文件,但是在ts文件下不会提示.vue文件,暂时还没找到问题所在todo
如果配置了其他别名,可在tsconfig.json
中的compilerOptions.paths
中增加配置。
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
引入vue-router
安装依赖
pnpm add vue-router
新建src/router/routes/index.ts
路由规则统一在src/router/routes/index.ts
暴露出去。
import type { RouteRecordRaw } from 'vue-router'
// 获取modules中的路由规则
// https://cn.vitejs.dev/guide/features.html#glob-import
const modules = import.meta.globEager('./modules/**/*.ts')
// modules routes
const routes: RouteRecordRaw[] = []
Object.keys(modules).forEach((key) => {
const modulesRoutes = modules[key].default || {}
let modRoutesList = []
if (Array.isArray(modulesRoutes)) {
modRoutesList = [...modulesRoutes]
} else {
modRoutesList = [modulesRoutes]
}
routes.push(...modRoutesList)
})
// 根目录
const rootRoute: RouteRecordRaw = {
path: '/',
name: 'root',
redirect: '/home'
}
// 404页面
const notFoundPage: RouteRecordRaw = {
// vue-router@4的变化,舍弃*通配符
// 官方文档:https://next.router.vuejs.org/zh/guide/migration/index.html#删除了-(星标或通配符)路由
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/views/common/404Page.vue')
}
export default [rootRoute, ...routes, notFoundPage]
新建src/router/routes/modules
该目录下放各个业务模块的路由规则,例如common.ts
就放登录、首页这些页面路由
import type { RouteRecordRaw } from 'vue-router'
// 路由规则
const routes: RouteRecordRaw[] = [
{
path: '/home',
name: 'home',
component: () => import('@/views/HomePage.vue')
},
{
path: '/login',
name: 'login',
component: () => import('@/views/LoginPage.vue')
}
]
export default routes
新建src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import type { App } from 'vue'
import routes from './routes/index'
const router = createRouter({
// vueRouter@3版本的mode改成了history,hash模式配置createWebHashHistory,history模式配置createWebHistory
history: createWebHashHistory(),
routes
})
/**
* 路由初始化函数
* @param app
*/
export const setupRouter = (app: App<Element>) => {
app.use(router)
}
export default router
改造main.ts
,引入router
import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './router'
/**
* 定义启动初始化函数
*/
const bootstrap = () => {
const app = createApp(App)
// 安装初始化路由
setupRouter(app)
app.mount('#app')
}
// 启动
bootstrap()
引入pinia
安装依赖
pnpm add pinia
新建src/store/index.ts
import { createPinia } from 'pinia'
import type { App } from 'vue'
const store = createPinia()
export const setupStore = (app: App<Element>) => {
app.use(store)
}
export default store
在src/main.ts
中安装pinia
import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './router'
import { setupStore } from './store'
/**
* 定义启动初始化函数
*/
const bootstrap = () => {
const app = createApp(App)
// 安装初始化store
setupStore(app)
// 安装初始化路由
setupRouter(app)
app.mount('#app')
}
// 启动
bootstrap()
使用pinia
新建src/types/store.d.ts
export interface UserInfo {
userName: string
userId: string | number
}
增加别名配置
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'#': resolve(__dirname, './src/types')
}
},
plugins: [vue()]
})
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"#/*": ["src/types/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
以user
模块为例
新建src/store/modules/user.ts
import { defineStore } from 'pinia'
import { UserInfo } from '#/store'
interface State {
userInfo: UserInfo
token: string
}
const useUserStore = defineStore('userStore', {
state(): State {
return {
userInfo: {
userName: '',
userId: ''
},
token: ''
}
},
actions: {
changeUserName(userInfo: UserInfo) {
this.userInfo = userInfo
}
}
})
export default useUserStore
在src/components/HelloWorld.vue
中使用
<script setup lang="ts">
import { ref } from 'vue'
import useUserStore from '@/store/modules/user'
defineProps<{ msg: string }>()
const count = ref(0)
const userStore = useUserStore()
userStore.changeUserName({
userName: 'haha',
userId: 123
})
</script>
<template>
<h1>userStore: {{ userStore.userInfo.userName }}</h1>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VS Code</a>
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
</p>
<p>See <code>README.md</code> for more information.</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Docs
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
</p>
<button type="button" @click="count ">count is: {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<style scoped>
a {
color: #42b983;
}
label {
margin: 0 0.5em;
font-weight: bold;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px;
color: #304455;
}
</style>
引入axios
安装依赖
pnpm add axios
配置请求的baseUrl
为环境变量
增加环境变量文件.env.development
与.env.production
定义环境变量VITE_API_BASE_URL
,可根据项目自行修改
.env.development
VITE_API_BASE_URL = "/"
.env.production
VITE_API_BASE_URL = "/"
新增的环境变量声明
在src/env.d.ts
中增加以下声明
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string
}
增加该配置,在使用import.meta.env
会有ts的校验与提示
增加别名配置
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'#': resolve(__dirname, './src/types'),
utils: resolve(__dirname, './src/utils'),
api: resolve(__dirname, './src/api')
}
},
plugins: [vue()]
})
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"#/*": ["src/types/*"],
"utils/*": ["src/utils/*"],
"api/*": ["src/api/*"]
}
}
}
新增src/types/axios.d.ts
声明文件
新增以下接口声明,为axios
响应的data
的结构,各位可根据自己的项目实际情况进行修改
export interface CustomResponseType<T> {
code: number
message: string
data: T
}
配置axios
新建src/utils/http/index.ts
,以下为笔者简单的配置,具体的还是要根据项目的情况来修改配置
import axios, { AxiosRequestConfig, AxiosError } from 'axios'
import type { CustomResponseType } from '#/axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 30 * 1000,
// 请求是否携带cookie
withCredentials: true
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 可以处理token等
return config
},
(err) => {
return Promise.reject(err)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
const { status } = response
if (status < 200 || status >= 300) {
// 统一处理http错误,或者处理后抛到业务代码 TODO
}
return response
},
(err) => {
const { status } = err.response
// 根据返回的http状态码做不同的处理,比如错误提示等 TODO
switch (status) {
case 401:
// 鉴权失败
break
case 403:
// 没有权限
break
case 500:
// 服务端错误
break
default:
break
}
return Promise.reject(err)
}
)
// 封装一层以更好的统一定义接口返回的类型
const request = <T>(
config: AxiosRequestConfig
): Promise<CustomResponseType<T>> => {
return new Promise((resolve, reject) => {
service
.request<CustomResponseType<T>>(config)
.then((res) => {
resolve(res.data)
})
.catch((err: Error | AxiosError) => {
reject(err)
})
})
}
export default request
以请求用户信息为例
新建src/api/user/index.ts
如果该模块的接口比较多,可把枚举
与响应数据的声明
放到单独的文件中维护,即下面的enum Api
与interface UserInfo
使用的时候,在页面中直接引入该方法调用即可
import request from 'utils/http'
// api枚举
enum Api {
Login = '/login'
}
// 用户信息
interface UserInfo {
userName: string
}
/**
* 登录
*/
export const accountLogin = () => {
return request<UserInfo>({
url: Api.Login,
method: 'get'
})
}
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanefic
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01