您现在的位置是:首页 >其他 >vite+vue3+TypeScript 搭建项目基本框架【未整合优化】网站首页其他

vite+vue3+TypeScript 搭建项目基本框架【未整合优化】

西木Qi 2023-06-04 16:00:04
简介vite+vue3+TypeScript 搭建项目基本框架【未整合优化】

1 参考文档

参考:vite+vue3+ts 手把手教你创建一个vue3项目

2 整体步骤

  1. 创建vue3项目
  2. 安装less/scss
  3. 自动导入
  4. 安装 router
  5. 安装pinia【vue-devtools插件、数据持久化插件 这两个先不装】
  6. 安装axios
  7. 配置@别名
  8. 自定义组件名 setup 语法糖
  9. 安装element-plus 组件库
  10. 安装 Ant Design Vue 组件库
  11. 安装与使用Echarts

3 创建vue3项目

NPM方式

npm create vite@latest 
  1. 填写项目名称
  2. 选择前端框架【选Vue】
  3. 选择开发语言【TypeScript】

4 安装less/scss

由于是使用vite,vite它提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持,但必须安装相应的预处理器依赖;
国内一般只使用 less 或 scss,所以我只写这两个安装。

Sass、SCSS、Less介绍
Sass (Syntactically Awesome StyleSheets):后缀名为.sass,是由ruby语言编写的一款css预处理语言。
SCSS (Sassy CSS):后缀名为 .scss。SCSS 是 Sass 3 引入新的语法,与原来的语法兼容,只是用{ }替代了原来的缩进。SCSS语法完全兼容 CSS3,并且继承了 Sass 的强大功能。
Less:后缀名为 .less。与Sass类似,也是一款css预处理语言。与Sass不同的是,Less是基于Javascript,是在客户端处理的。Less 既可以在客户端上运行(支持IE 6+,Webkit,Firefox),也可以运行在 Node 服务端。

区别

  • Scss功能较Less强大
    1、scss有变量和作用域
    2、scss有函数的概念
    3、进程控制
    4、数据结构
  • Scss和Less处理机制不一样
    1、前者是通过服务端处理的,后者是通过客户端处理,相比较之下前者解析会比后者快一点。
  • Scss和Less在变量中的唯一区别就是Scss用$,Less用@

安装scss或less即可,只安装一个,如有需要再安装另一个。

  1. 安装scss依赖【推荐】
npm add -D scss
  1. 安装less依赖【推荐】
 npm add -D less 
  1. 安装sass 依赖【不推荐】
npm add -D sass

5 自动导入

这个是我非常推荐的两个插件,不过用过element、naiveui等的人一般也会知道,尤大推荐

npm install -D unplugin-vue-components unplugin-auto-import
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

// 自动导入vue中hook reactive ref等
import AutoImport from "unplugin-auto-import/vite"
//自动导入ui-组件 比如说ant-design-vue  element-plus等
import Components from 'unplugin-vue-components/vite';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        AutoImport({
            //安装两行后你会发现在组件中不用再导入ref,reactive等
            imports: ['vue', 'vue-router'],
            //存放的位置
            dts: "src/auto-import.d.ts",
        }),
        Components({
            // 引入组件的,包括自定义组件
            // 存放的位置
            dts: "src/components.d.ts",
        }),
    ],

})

6 安装配置 router

  1. npm方式
 npm install vue-router@4 
  1. src下创建一个 routes 文件夹,再创建一个 index.ts 文件。
    注意:相关页面需自己创建,比如views/homeView.vue页面。
import {createRouter, createWebHistory} from "vue-router";

let routes = [
    {
        path: '/',
        name: 'home',
        //使用import可以路由懒加载,如果不使用,太多组件一起加载会造成白屏
        component: () => import('../views/homeView.vue')
    },
    //{
    //配置404页面
    //path: '/:catchAll(.*)',
    //name: '404',
    //component: () => import(''),
    //}
]
// 路由
const router = createRouter({
    history: createWebHistory(),
    routes
})
// 导出
export default router
  1. 在src的main.ts文件引入。
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'

//routes
import router from "./routes/index";

const app = createApp(App)

//routes
app.use(router)
app.mount('#app')

7 安装Pinia

因为是vue3+ts,安装Pinia更好点,vuex拥抱ts没有Pinia好。

Pinia介绍
参考:什么是Pinia?
Pinia和Vuex一样都是是vue的全局状态管理器。其实Pinia就是Vuex5,只不过为了尊重原作者的贡献就沿用了这个看起来很甜的名字Pinia。


  1. npm方式安装
npm install pinia
  1. 在src的 main.ts引入
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'

//routes
import router from "./routes/index";
//pinia
import { createPinia } from 'pinia'
const pinia = createPinia()

const app = createApp(App)

//routes
app.use(router)
//pinia
app.use(pinia)
app.mount('#app')
  1. 在src下创建一个 store 文件夹,再创建一个 home.ts 文件

其它名也可以,因为pinia它有一个根文件,会把 defineStore 第一个参数当id值,相当于vuex中的 module 自动引入,也会在Vue.js devtools 插件中以第一个参数名展示(下面展示)
注意:defineStore第一个参数很重要,而且是唯一值。它的命名在devtools 插件能方便找到这个文件的数据,方便调试。

import {defineStore} from 'pinia'
// useMain  可以是 useUser、useCart 之类的名字
// defineStore('main',{..}) 在devtools 就使用 main 这个名
export const useMain = defineStore('main', {
    // 相当于data
    state: () => {
        return {
            // 所有这些属性都将自动推断其类型,如果推断失败可以试下 as xxx
            counter: 0,
            name: 'Eduardo',
        }
    },
    // 相当于计算属性
    getters: {
        doubleCount: (state) => {
            return state.counter * 2
        },
    },
    // 相当于vuex的 mutation + action,可以同时写同步和异步的代码
    actions: {
        increment() {
            this.counter++
        },
        randomizeCounter() {
            setTimeout(() => {
                this.counter = Math.round(100 * Math.random())
            }, 0);
        },
    },
})
  1. 在要使用pinia的组件
    示例
<template>
    <div>counter:{{counter}}</div>
    <div>doubleCount:{{doubleCount}}</div>
    <a-button @click="main.randomizeCounter()">counter(round)</a-button>
    <a-button type="primary" @click="main.increment()">counter++</a-button>
 
    <div>{{name}}</div>
    <a-button @click="amend()">修改</a-button>
</template>
<script setup lang='ts'>
 
//引入想要的pinia文件 {} 里面就是对应导出的名字
import { useMain } from '../store/home'
import { storeToRefs } from 'pinia';
 
const main = useMain()
// 解构main里面的state和getters的数据,
// 使用storeToRefs解构才有响应式,响应式可以直接修改数据,不过这我只用来渲染
const { counter,name ,doubleCount } = storeToRefs(main)
 
//(常用方法三种)
//常用方法一: 使用数据
console.log( counter );
//使用方法(方法目前不能解构)
main.increment()
 
 
// 常用方法二:修改数据
counter = 9999
 
//常用方法三:
//进阶使用$patch,多个修改
const amend=()=>{
    main.$patch((state) => {
        state.counter += 10;
        state.name = '张三'
    })
}
 
</script>

8 安装配置axios

  1. npm方式安装
npm install axios
  1. 封装request
    src 下创建一个 request 文件夹,并添加一个 request.ts 文件
import axios from 'axios'
// 创建axios实例
const request = axios.create({
    baseURL: '',// 所有的请求地址前缀部分(没有后端请求不用写)
    timeout: 80000, // 请求超时时间(毫秒)
    withCredentials: true,// 异步请求携带cookie
    // headers: {
    // 设置后端需要的传参类型
    // 'Content-Type': 'application/json',
    // 'token': x-auth-token',//一开始就要token
    // 'X-Requested-With': 'XMLHttpRequest',
    // },
})

// request拦截器
request.interceptors.request.use(
    config => {
        // 如果你要去localStor获取token
        let token = localStorage.getItem("x-auth-token");
        if (token) {
            //添加请求头
            config.headers["Authorization"] = "Bearer " + token
        }
        return config
    },
    error => {
        // 对请求错误做些什么
        Promise.reject(error)
    }
)

// response 拦截器
request.interceptors.response.use(
    response => {
        // 对响应数据做点什么
        return response.data
    },
    error => {
        // 对响应错误做点什么
        return Promise.reject(error)
    }
)
export default request
  1. 创建调用的接口
    request 文件夹,再添加一个 api.ts 文件

定义接口格式:
export const 自定义接口名 = (形参:请求类型):返回类型 => instance.方法(路径,后端要的参数);

import instance from "./request";

//一般情况下,接口类型会放到一个文件
// 下面两个TS接口,表示要传的参数
interface ReqLogin {
    name: string
    paw: string
}

interface ReqStatus {
    id: string
    navStatus: string
}


// Res是返回的参数,T是泛型,需要自己定义,返回对数统一管理***
type Res<T> = Promise<ItypeAPI<T>>;
// 一般情况下响应数据返回的这三个参数,
// 但不排除后端返回其它的可能性,
interface ItypeAPI<T> {
    data: T,//请求的数据,用泛型
    msg: string | null // 返回状态码的信息,如请求成功等
    code: number //返回后端自定义的200,404,500这种状态码
}


// post请求 ,没参数
export const LogoutAPI = (): Res<null> => instance.post("/admin/logout");

// post请求,有参数,如传用户名和密码
export const loginAPI = (data: ReqLogin): Res<string> =>
    instance.post("/admin/login", data);

// post请求 ,没参数,但要路径传参
export const StatusAPI = (data: ReqStatus): Res<null> =>
    instance.post(`/productCategory?ids=${data.id}&navStatus=${data.navStatus}`);


//  get请求,没参数,
export const FlashSessionListApi = (): Res<null> =>
    instance.get("/flashSession/list");

// get请求,有参数,路径也要传参  (也可能直接在这写类型,不过不建议,大点的项目会维护一麻烦)
export const ProductCategoryApi = (params: { parentId: number }): Res<any> =>
    instance.get(`/productCategory/list/${params.parentId}`, {params});

// get请求,有参数,(如果你不会写类型也可以使用any,不过不建议,因为用了之后 和没写TS一样)
export const AdminListAPI = (params: any): Res<any> => instance.get("/admin/list", {params}); 
  1. 在要请求的组件上使用
  • 方式一:使用.then
<script setup lang="ts">
import { returnApplyListAPi} from "../../request/api";
 
const logout = () => {
    returnApplyListAPi({
        ...val,
    }).then((res) => {
         console.log('***',res );
        let { list, pageNum, pageSize, total } = res.data
    })
 
};
</script>
  • 方式二:直接使用(和vue2在cretae上用一样,setup自带async,await在顶层可以直接使用)
<script setup lang="ts">
import { indexAPI} from "../../request/api";
    //直接使用,一般用在进入页面入请求数据的接口
    let res = await indexAPI()
    console.log( "***" ,res);
}
</script>
  • 方式三:使用 async / await,(setup虽然自带async,但单独用await只能在顶层使用,如果在函数下还是要async / await一起写)
<script setup lang="ts">
import { returnApplyListAPi } from "../../request/api";
 
const search = async(val: IUseTableParam) => {
    let res = await returnApplyListAPi({
        ...val,
    })
    console.log( "***" ,res);
    let { list, pageNum, pageSize, total } = res.data
    console.log(list, pageNum, pageSize, total);
}
</script>
  1. 代理

需要代理才写

request.ts 文件

const request = axios.create({
    //这时你要代理
    //填写后端统一的前缀,
    //如:123.xx.xx.xx:456/api/...
    //这个/api是每一个接口都有的,就写它
    //如果没有,也写,下面会讲区别
    baseURL: '/api',
})

vite.config.ts 文件

只需添加server部分即可。

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        //...
    ],
    server: {
        proxy: {
            '/api': { // 匹配请求路径,
                target: '你要代理的地址', // 代理的目标地址
                 // 开发模式,默认的127.0.0.1,开启后代理服务会把origin修改为目标地址
                changeOrigin: true,
                // secure: true, // 是否https接口
                // ws: true, // 是否代理websockets
 
                // 路径重写,**** 如果你的后端有统一前缀(如:/api),就不开启;没有就开启
                //简单来说,就是是否改路径 加某些东西
                rewrite: (path) => path.replace(/^/api/, '') 
            }
        }
    }
})

9 配置@别名

在vite配置@别名,这能在开发时对路径看些来直观点。

  1. 打开 vite.config.ts 文件
  • 导入 path 模块
  • 加入 解析配置
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

//1、 自动导入vue中hook reactive ref等
import AutoImport from "unplugin-auto-import/vite"
//2、自动导入ui-组件 比如说ant-design-vue  element-plus等
import Components from 'unplugin-vue-components/vite';

//3、 导入 path 模块,帮助我们解析路径
import {resolve} from "path";

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        AutoImport({
            //安装两行后你会发现在组件中不用再导入ref,reactive等
            imports: ['vue', 'vue-router'],
            //存放的位置
            dts: "src/auto-import.d.ts",
        }),
        Components({
            // 引入组件的,包括自定义组件
            // 存放的位置
            dts: "src/components.d.ts",
        }),
    ],

    // ↓解析配置
    resolve: {
        // ↓路径别名
        alias: {
            "@": resolve(__dirname, "./src")
        }
    }
})
  1. 打开 tsconfig.json 文件

配置 baseUrl,paths 参数

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": [
      "ES2020",
      "DOM",
      "DOM.Iterable"
    ],
    "skipLibCheck": true,
    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    // 配置@别名
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ],
  "references": [
    {
      "path": "./tsconfig.node.json"
    }
  ]
}
  1. 使用方式

重新运行一遍即可

10 自定义组件名 setup 语法糖

在 vue 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。也就是说,除非你想换名,并且又不想写两个 script 标签,就可以通过下面的链接去做。

  1. 插件名称:vite-plugin-vue-setup-extend
  2. 安装
npm i vite-plugin-vue-setup-extend -D
  1. 配置 ( vite.config.ts )
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

//1、 自动导入vue中hook reactive ref等
import AutoImport from "unplugin-auto-import/vite"
//2、自动导入ui-组件 比如说ant-design-vue  element-plus等
import Components from 'unplugin-vue-components/vite';

//3、 导入 path 模块,帮助我们解析路径
import {resolve} from "path";

//4、vue3语法糖
import VueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        AutoImport({
            //安装两行后你会发现在组件中不用再导入ref,reactive等
            imports: ['vue', 'vue-router'],
            //存放的位置
            dts: "src/auto-import.d.ts",
        }),
        Components({
            // 引入组件的,包括自定义组件
            // 存放的位置
            dts: "src/components.d.ts",
        }),
        VueSetupExtend(),
    ],

    // ↓解析配置
    resolve: {
        // ↓路径别名
        alias: {
            "@": resolve(__dirname, "./src")
        }
    }
})
  1. 使用
<script lang="ts" setup name="home">
 
</script>

11 安装element-plus组件库

element-plusAnt Design Vue都是组件库,可以只安装其中一个,也可以两个都装,相辅相成。

  1. 安装element-plus组件库
    NPM方式
npm install element-plus --save
  1. 安装element-icon图标
npm install @element-plus/icons-vue
  1. main.ts引入
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'

//1、routes
import router from "./routes/index";
//2、pinia
import {createPinia} from 'pinia'

//3、element-plus
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

const pinia = createPinia()

const app = createApp(App)

//1、routes
app.use(router)
//2、pinia
app.use(pinia)
//3、element-plus
app.use(ElementPlus)
app.mount('#app')

  1. 使用
    在.vue页面引入所需组件即可。

  2. ElMessageElLoading 找不到的问题

如果项目使用时出现问题找不到模块 element-plus 或其相应的类型声明的问题需要。

1 在src根目录创建Element-puls.d.ts文件

//文件内容
export {}
declare global {
    const ElMessage: typeof import('element-plus')['ElMessage']
    const ElLoading: typeof import('element-plus')['ElLoading']
}

2 然后在 tsconfig.json 文件添加一行代码

  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    //添加这行
    "Element-puls.d.ts"
  ],

完整代码:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": [
      "ES2020",
      "DOM",
      "DOM.Iterable"
    ],
    "skipLibCheck": true,
    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    // 配置@别名
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    //添加这行
    "Element-puls.d.ts"
  ],
  "references": [
    {
      "path": "./tsconfig.node.json"
    }
  ]
}

12 安装 Ant Design Vue 组件库

  1. 安装ant-design-vue
npm install ant-design-vue --save
  1. 安装ant-design图标
npm install --save @ant-design/icons-vue
  1. main.ts引入
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'

//1、routes
import router from "./routes/index";
//2、pinia
import {createPinia} from 'pinia'

//3、element-plus
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

//4、ant-design-vue
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

const pinia = createPinia()

const app = createApp(App)

//1、routes
app.use(router)
//2、pinia
app.use(pinia)
//3、element-plus
app.use(ElementPlus)
//4、ant-design-vue
app.use(Antd);
app.mount('#app')
  1. 使用
    直接使用即可。

13 安装与使用Echarts

参考:vue3 + ts 在 setup 下使用Echarts

  1. 安装Echarts
npm install echarts --save
  1. main.ts引入
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'

//1、routes
import router from "./routes/index";
//2、pinia
import {createPinia} from 'pinia'

//3、element-plus
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';

//4、ant-design-vue
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

//5、引入echarts
import * as echarts from 'echarts';

const pinia = createPinia()

const app = createApp(App)

//1、routes
app.use(router)
//2、pinia
app.use(pinia)
//3、element-plus
app.use(ElementPlus)
//4、ant-design-vue
app.use(Antd)
//5、放入全局
app.config.globalProperties.$echarts = echarts
app.mount('#app')
  1. html准备一个容器
<template>
  <div id='showorders' style='width:300px; height:300px;position: absolute;'></div>
</template>
  1. js代码(重点用***标明,可以直接使用,效果图在下面)
<script setup lang='ts'>
import { onMounted, reactive} from 'vue';
 
//***引入
import * as echarts from 'echarts';
 
let data = reactive({
  mobile:200,//移动端
  computer:100,//电脑端
})
 
const showorders =()=>{
  //  基于准备好的dom,初始化echarts实例
  let myChart = echarts.init(document.getElementById('showorders')!);
  // 指定图表的配置项和数据
  let option = {
  series: [
    {//外圈
      type: 'gauge',
      min: 0,
      max: 1,
      center: ['50%','50%'],
      radius: '98%',
      animation :false,
      axisLine: {//外圈大小
        lineStyle: {
          color:[[1, '#8956e2']],
          width: 2,
        }
      },
      progress:{show:false},
      pointer: {show:false},
      axisTick: {show:false},
      splitLine: {show:false},
      axisLabel: {show:false}
    },
    {//内圈
      type: 'gauge',
      min: 0,
      max: 1,
      center: ['50%','50%'],
      radius: '70%',
      animation :false,
      axisLine: {
        lineStyle: {
          color:[[1, '#8956e2']],
          width: 2,
        }
      },
      progress:{show:false},
      pointer: {show:false},
      axisTick: {show:false},
      splitLine: {show:false},
      axisLabel: {show:false}
    },
    {//主要内容
      type: 'gauge',//测量仪器
      animation :false,//是否开启动画
      startAngle: 225,//仪表盘起始角度(左180,右0)
      endAngle: -45,//仪表盘结束角度。
      center: ['50%', '50%'],//的中心(圆心)坐标
      radius: '90%',//仪表盘半径,(百分比是相对容器)
      min: 0,//最小的数据值
      max: 1,//最大的数据值
      splitNumber: 1,//仪表盘刻度的分割段数
      axisLine: {//仪表盘轴线样式
        lineStyle: {
          width: 20,//轴线与刻度的距离
        }
      },
      color:{//向渐变,前三个参数分别是圆心 x, y 和半径,取值同线性渐变
        type: 'radial',
        x: 0,
        y: 0,
        r: 2,
        colorStops: [{
            offset: 0, color: '#9a3bdb' // 0% 处的颜色
        }, {
            offset: 1, color: '#43c7ff' // 100% 处的颜色
        }],
        global: false // 缺省为 false
      },
      progress:{//展示当前进度(代替仪表盘轴线样式,它没有渐变)
        show:true,
        overlap: false,
        width:20,
      },
      pointer: {//仪表盘指针不显示
        show:false,
      },
      axisTick: {//刻度样式 (小的)
        length: 16,
        lineStyle: {
          color: '#43c7ff',
          width: 1
        }
      },
      splitLine: {//分隔线样式 (大的)
        show:false
      },
      axisLabel: {//刻度标签
        show:false,
      },
      detail: {//仪表盘详情,用于显示数据(value的样式)
        fontSize: 44,
        lineHeight:50,
        height:200,
        offsetCenter: [0, '50%'],//位置偏移
        valueAnimation: true,
        color: 'inherit',
        formatter: function (value: number) {
          const str =  '{user|
当前用户数量}';
          return `${Math.round(value * 1)}${str}`;
        },
        rich: {//富文本样式
            user: {//与formatter 中的 | 左侧 文本一样名
                color: '#0e5387',
                lineHeight: 10,
                borderColor:"#0e5387",
                borderWidth:1,
                borderType:'solid',
                borderRadius:3,
                padding:6,
            },
        }
      },
      title:{//name的样式
        offsetCenter :[0, '80%'],
        color:'#464646',
        fontSize:16,
        lineHeight:24
      },
      data: [//表盘的数据
        {
          value: data.mobile + data.computer,
          name: ` 移动端用户数:${data.mobile} 
电脑端用户数:${data.computer}`,
        }
      ]
    }
  ]
};
  option && myChart.setOption(option);
}
 
//*** 挂载Echarts
onMounted(()=>{
  showorders()
})
 
</script>
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。