手把手,完整的從0搭建vite-vue3-ts項目框架:配置less+svg+pinia+vant+axios
- 2022 年 11 月 17 日
- 筆記
- typescript, VUE
項目同步git://gitee.com/lixin_ajax/vue3-vite-ts-pinia-vant-less.git
覺得有幫助的小夥伴請點下小心心哦
為避免贅述,過於基礎的點會直接省略或貼圖,比如創建文件夾/文件的路徑/路由一類
配置相應功能,也盡量只貼相關程式碼,並不代表整個文件內容
我會盡量把每一步都記錄下來,讓跟隨文檔操作的朋友也能還原項目
項目不盡完美,但是主體功能應該都可以有所參考
一.本地初始環境
二.使用vite腳手架,創建vue3+ts
yarn create vite
輸入項目名,回車確認
選擇Vue和TypeScript
文件目錄如下,項目創建成功!
三.啟動項目:根據提示進入項目運行項目,或自行使用編碼器輸入指令進行啟動
yarn // 安裝依賴
yarn dev // 運行項目
瀏覽器打開地址,運行成功!
四.必備依賴
This package contains type definitions for Node.js (//nodejs.org/)
yarn add @types/node -S -D
五.配置路徑別名@
1.位置:直接改寫vite.config.ts — 順便就添加alias
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
function pathResolve(dir: string) {
return resolve(process.cwd(), ".", dir);
}
// //vitejs.dev/config/
export default () => {
return {
resolve: {
alias: [
{
find: "@",
replacement: pathResolve("src"),
},
{
find: "views",
replacement: pathResolve("src/views"),
},
],
},
plugins: [vue()],
};
};
2.位置:tsconfig.json — 修改baseUrl及paths
// tsconfig.json
六.配置vue-router
yarn add vue-router -S
推薦一個很好的插件nprogress【進度條】
yarn add @types/nprogress nprogress -D
main.ts引入router
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from "./router";
const app = createApp(App as any);
app.use(router)
app.mount('#app')
src下創建router文件夾,項目往往需要模組化,所以程式碼盡量不要雜糅在一起
/router/index.ts
/router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
const modules: any = import.meta.glob("./modules/**/*.ts", { eager: true });
const routes: Array<RouteRecordRaw> = [];
for (const key in modules) {
routes.push(...modules[key].default);
}
const router = createRouter({
history: createWebHashHistory(), // history 模式則使用 createWebHistory()
routes,
});
router.beforeEach(async (_to, _from, next) => {
NProgress.start();
next();
});
router.afterEach((_to) => {
NProgress.done();
});
export default router;
/router/typings.d.ts 路由meta格式受控
/router/typing.d.ts
import "vue-router";
declare module "vue-router" {
interface RouteMeta {
// options
title?: string;
// every route must declare
show?: boolean; //
}
}
然後就是test下隨便創建一個路由,對應的,views下創建相應的vue文件,App.vue給上router-view
test/index.ts
app.vue
test/index.vue
查看頁面
頁面正確顯示,路由部署成功!
七.配置css預處理:less
yarn add less less-loader -D
為了配置全局的less樣式文件,同時引入fs模組
yarn add fs -D
進入項目根目錄的配置文件
位置:vite.config.ts — css
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import fs from 'fs'
function pathResolve(dir: string) {
return resolve(process.cwd(), ".", dir);
}
// //vitejs.dev/config/
export default () => {
const lessResources: Array<String> = []
fs.readdirSync('src/assets/styles').map((dirname) => {
if (fs.statSync(`src/assets/styles/${dirname}`).isFile()) {
lessResources.push(`@import "src/assets/styles/${dirname}";`)
}
})
return {
......,
// css
css: {
preprocessorOptions: {
less: {
charset: false,
additionalData: lessResources.join(''),
modifyVars: {
'primary-color': '#0080FF',
'link-color': '#0080FF',
'border-radius-base': '4px',
},
javascriptEnabled: true,
},
},
},
};
};
這裡配置的公共less文件路徑為src/assets/styles,創建styles文件夾和index.less文件
進入index.less聲明全局樣式,測試less配置是否成功
進入test/index.vue使用聲明
查看頁面
盒子背景顏色改變,less及全局less配置成功!
八.配置svg
yarn add vite-plugin-svg-icons -D
yarn add fast-glob -D
vite.config.ts引入插件
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import path from "path";
// //vitejs.dev/config/
export default () => {
return {
......,
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要快取的圖標文件夾
iconDirs: [path.resolve(process.cwd(), "src/assets/icons")],
// 指定symbolId格式
symbolId: "icon-[dir]-[name]",
}),
],
};
};
根據config配置創建存放svg的目錄文件,並創建SvgIcon組件
SvgIcon組件
// SvgIcon/index.vue
<template>
<svg aria-hidden="true" class="svg-icon">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script lang="ts" setup>
import { computed } from "vue";
const props = defineProps({
prefix: {
type: String,
default: "icon",
},
name: {
type: String,
required: true,
},
color: {
type: String,
default: "#333",
},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
</script>
<style lang="less" scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: v-bind(color);
vertical-align: middle;
color: v-bind(color);
}
</style>
在main.ts中註冊SvgIcon為全局組件
// main.ts
import { createApp } from 'vue'
import './style.css'
import "virtual:svg-icons-register";
import SvgIcon from "@/components/SvgIcon/index.vue";
import App from './App.vue'
import router from "./router";
const app = createApp(App as any);
app.use(router)
app.mount('#app')
app.component("SvgIcon", SvgIcon);
在test/index.vue中引入組件
// test/index.vue
<svg-icon name="test-vue" />
查看頁面,測試是否成功
頁面顯示svg圖標,svg組件配置成功!
九.配置pinia
pinia: 類似vuex的倉庫
pinia-use-persist: 持久加密快取pinia數據
yarn add pinia pinia-use-persist -S
main.ts中引入pinia
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { usePersist } from 'pinia-use-persist'
import App from './App.vue'
const app = createApp(App as any);
const pinia = createPinia()
pinia.use(usePersist)
app.use(pinia)
src下創建store目錄存放相關文件
/store/modules下存放項目不同模組需要通過pinia通訊的數據,假裝項目有一個test模組,存放了一個數據number
// store/modules/test/index.ts
import { defineStore } from "pinia";
interface stateType {
number: number;
}
const useTestStore = defineStore("user", {
state: (): stateType => ({
number: 0,
}),
getters: {},
actions: {
setNumber(number: number): void {
this.number = number;
},
},
persist: {
enabled: true,
encryptionKey: "vueTest",
},
});
export { useTestStore };
store/index.ts引入各模組
// store/index.ts
import { createPinia } from "pinia";
import { useTestStore } from "./modules/test";
const pinia = createPinia();
export { useTestStore };
export default pinia;
回到test/index.vue,測試pinia配置是否成功
// test/index.vue
<template>
<!-- 測試pinia -->
<button @click="number += 1">{{ number }}</button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useTestStore } from '@/store'
const store = useTestStore()
const number = computed<number>({
get() {
return store.number
},
set(value) {
store.setNumber(value)
},
})
</script>
點擊按鈕,查看頁面是否變化
頁面數據沒有初始化,pinia配置成功!
十.配置vant ui
vant ui://vant-contrib.gitee.io/vant/v4/#/zh-CN/home
yarn add vant
這裡再推薦一個插件,unplugin-vue-components【自動引入】,引入ui可以省去很多麻煩
yarn add unplugin-vue-components -D
vite.config.ts中引入vant ui
// vite.config.ts
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()],
}),
],
};
回到test/index.vue測試vant ui引入是否成功
// test/index.vue
<!-- 測試vant ui -->
<div>
<van-button type="primary">vant button</van-button>
</div>
刷新頁面查看
按鈕有樣式,vant ui引入成功!
但是官方描述:Vant 中有個別組件是以函數的形式提供的,包括 Toast,Dialog,Notify 和 ImagePreview 組件。在使用函數組件時,unplugin-vue-components 無法自動引入對應的樣式,因此需要手動引入樣式。
所以,這幾個組件需要使用的話需要在main.ts中引入樣式
// main.ts
// Toast
import 'vant/es/toast/style';
// Dialog
import 'vant/es/dialog/style';
// Notify
import 'vant/es/notify/style';
// ImagePreview
import 'vant/es/image-preview/style';
回到test/index.vue測試,示例toast
// test/index.vue
import { Toast } from 'vant'
Toast('使用vant')
查看頁面
toast有樣式,vant ui引入成功!
但是,使用vant ui多是移動端,所以還要做移動端做以下適配
參考: //vant-contrib.gitee.io/vant/v4/#/zh-CN/advanced-usage
1.適配安全距離
根據vant ui提供,在根文件index.html修改
// index.html
<!-- 在 head 標籤中添加 meta 標籤,並設置 viewport-fit=cover 值 -->
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<!-- 開啟頂部安全區適配 -->
<van-nav-bar safe-area-inset-top />
<!-- 開啟底部安全區適配 -->
<van-number-keyboard safe-area-inset-bottom />
2.Viewport 布局
postcss-px-to-viewport-8-plugin:postcss-px-to-viewport-8-plugin 是一款 PostCSS 插件,用於將 px 單位轉化為 vw/vh 單位。
yarn add postcss-px-to-viewport-8-plugin -D
vite.config.ts中更改配置
// vite.config.ts
import pxtovw from 'postcss-px-to-viewport-8-plugin'
const loder_pxtovw = pxtovw({
//這裡是設計稿寬度 自己修改
viewportWidth: 375,
viewportUnit: 'vw'
})
export default defineConfig({
......,
css: {
postcss: {
plugins: [loder_pxtovw]
}
}
})
創建一個types/index.d.ts,用於處理包括postcss-px-to-viewport-8-plugin一類的沒有聲明文件的依賴
// src/types/index.d.ts
declare module "postcss-px-to-viewport-8-plugin"
刷新頁面,F12查看樣式
px已被轉換,vant ui 及 移動端配置成功!
十一.配置axios
yarn add axios
// tsconfig.json
{
"compilerOptions": {
......,
"types": ["vite/client", "vite-plugin-svg-icons/client"]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"*.ts",
],
"exclude": ["node_modules", "dist"],
"references": [{ "path": "./tsconfig.node.json" }]
}
src下創建axios請求文件
創建axios
// utils/http/axios/index.ts
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
AxiosError,
} from "axios";
import { IResponse } from "./type";
// 如果請求超過 `timeout` 的時間,請求將被中斷
axios.defaults.timeout = 5000;
const axiosInstance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_API_BASEURL + "",
});
// axios實例攔截請求
axiosInstance.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 配置headers
config.headers = {
...config.headers,
};
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// axios實例攔截響應
axiosInstance.interceptors.response.use(
// 請求成功
(response: AxiosResponse) => {
return response;
},
// 請求失敗
(error: AxiosError) => {
const { response } = error;
console.error(response, "response error");
if (response) {
return Promise.reject(response.data);
}
}
);
const request = <T = any>(config: AxiosRequestConfig): Promise<T> => {
const conf = config;
return new Promise((resolve) => {
axiosInstance
.request<any, AxiosResponse<IResponse>>(conf)
.then((res: AxiosResponse<IResponse>) => {
const {
data: { result },
} = res;
resolve(result as T);
});
});
};
export function get<T = any>(config: AxiosRequestConfig): Promise<T> {
return request({ ...config, method: "GET" });
}
export function post<T = any>(config: AxiosRequestConfig): Promise<T> {
return request({ ...config, method: "POST" });
}
export default request;
export type { AxiosInstance, AxiosResponse };
// utils/http/axios/type.ts
export interface RequestOptions {
// Whether to process the request result
isTransformResponse?: boolean;
}
// 返回res.data的interface
export interface IResponse<T = any> {
code: number | string;
result: T;
data: T;
message: string;
status: string | number;
}
根目錄創建.env.development配置開發請求地址
// .env.development
# 開發環境
VITE_APP_API_BASEURL = 你的請求地址
NODE_ENV = development
vite.config.ts配置server
// vite.config.ts
server: {
hmr: { overlay: false }, // 禁用或配置 HMR 連接 設置 server.hmr.overlay 為 false 可以禁用伺服器錯誤遮罩層
// 服務配置
port: 3030, // 類型: number 指定伺服器埠;
open: false, // 類型: boolean | string在伺服器啟動時自動在瀏覽器中打開應用程式;
cors: false, // 類型: boolean | CorsOptions 為開發伺服器配置 CORS。默認啟用並允許任何源
host: "0.0.0.0", // IP配置,支援從IP啟動
["/api"]: {
target: process.env.VITE_APP_API_BASEURL,
changeOrigin: true,
rewrite: (path: string) => path.replace(new RegExp("^/api"), ""),
},
},
創建api存放目錄
創建一個api
// api/test/index.ts
import { post } from "@/utils/http/axios";
import { IResponse } from "@/utils/http/axios/type";
export interface LoginData {
username?: string;
password?: string;
}
enum URL {
login = "/api/user_center/testLogin",
}
/**
* @description: 用戶登錄
* @params {LoginData} params
* @return {Promise}
*/
const login = async (data: LoginData) =>
post<IResponse>({ url: URL.login, data });
export { login };
回到test/index.vue調用api測試axios
// test/index.vue
<script setup lang="ts">
import { login } from '@/api/test'
login({})
</script>
回到頁面,查看network
介面請求成功,axios配置成功!
最後,配置一下打包
// vite.config.ts
import { UserConfig, ConfigEnv, loadEnv } from "vite";
// //vitejs.dev/config/
export default ({ command, mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, __dirname);
return {
base: env.NODE_ENV === "development" ? "/" : "./",
build: {
outDir: "dist",
assetsDir: "assets", //指定靜態資源存放路徑
sourcemap: false, //是否構建source map 文件
},
};
};
啟動dist,沒問題!
結語:
項目到此主體功能就已經配置完畢了,細節之處大家多多查看官網和眾多網頁的分享
項目還有很多不完善甚至錯誤的地方,踩坑還會繼續,後續有時間還會繼續優化,實際使用中還有很多地方需要改進
項目同步git://gitee.com/lixin_ajax/vue3-vite-ts-pinia-vant-less.git