赞
踩
# npm 6.x
npm init vite@latest my-vue-app --template vue-ts
# npm 7+, 需要额外的双横线
npm init vite@latest my-vue-app -- --template vue-ts
# yarn
yarn create vite my-vue-app --template vue-ts
# pnpm
pnpm create vite my-vue-app -- --template vue-ts
# 全局安装pnpm
npm i pnpm -g
# -D安装到开发环境 -S安装到生产环境
pnpm i element-plus -D
全局引入:main.ts
import { createApp } from 'vue'
import App from './App.vue'
// 引入element-plus
import element from 'element-plus'
import 'element-plus/dist/index.css' // 不引入会导致ui样式不正常
createApp(App).use(element).mount('#app')
pnpm i vue-router@latest -D
配置别名:vite.config.ts
# 使用require需要安装@types/node
npm i @types/node -D
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import * as path from 'path' import { settings } from './src/config/index' export default defineConfig({ plugins: [vue()], base: settings.base, // 生产环境路径 resolve: { alias: { // 配置别名 '@': path.resolve(__dirname, 'src'), 'assets': path.resolve(__dirname, 'src/assets'), 'components': path.resolve(__dirname, 'src/components'), 'config': path.resolve(__dirname, 'src/config'), 'router': path.resolve(__dirname, 'src/router'), 'tools': path.resolve(__dirname, 'src/tools'), 'views': path.resolve(__dirname, 'src/views'), 'plugins': path.resolve(__dirname, 'src/plugins'), 'store': path.resolve(__dirname, 'src/store'), } }, build: { target: 'modules', outDir: 'dist', // 指定输出路径 assetsDir: 'static', // 指定生成静态资源的存放路径 minify: 'terser', // 混淆器,terser构建后文件体积更小 sourcemap: false, // 输出.map文件 terserOptions: { compress: { drop_console: true, // 生产环境移除console drop_debugger: true // 生产环境移除debugger } }, }, server: { // 是否主动唤醒浏览器 open: true, // 占用端口 port: settings.port, // 是否使用https请求 https: settings.https, // 扩展访问端口 // host: settings.host, proxy: settings.proxyFlag ? { '/api': { target: 'http://127.0.0.1:8080', // 后台接口 changeOrigin: true, // 是否允许跨域 // secure: false, // 如果是https接口,需要配置这个参数 rewrite: (path: any) => path.replace(/^\/api/, ''), }, } : {} } })
添加主路由文件:/src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' import { Home } from '../config/constant'; const routes: Array<RouteRecordRaw> = [ { path: '', name: 'index', redirect: '/home', }, { path: '/home', name: 'home', component: Home, meta: { title: '首页' } }, ] const router = createRouter({ history: createWebHistory(), routes }) export default router;
全局路由懒加载文件:/src/config/constant.ts
// 没有的vue文件自行创建引入即可
export const Home = () => import('@/layout/index.vue')
export const Login = () => import('@/views/login/Login.vue')
全局引入:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import element from 'element-plus'
import 'element-plus/dist/index.css'
// 添加router
import router from './router/index'
// 全局引用
createApp(App).use(element).use(router).mount('#app')
在App.vue添加路由渲染
<script setup lang="ts"> </script> <template> <!-- router组件渲染的地方 --> <router-view></router-view> </template> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
pnpm i axios -D
请求函数封装:/src/plugins/request.ts
import axios from 'axios' import cookieService from 'tools/cookie' import { ElMessage } from 'element-plus' import { settings } from 'config/index' axios.defaults.withCredentials = true // 请求超时时间60s axios.defaults.timeout = 1 * 60 * 1000 // get请求头 axios.defaults.headers.get['Content-Type'] = 'application/json' // post请求头 axios.defaults.headers.post['Content-Type'] = 'application/json' // 根请求路径 axios.defaults.baseURL = settings.baseUrl // 请求拦截器 axios.interceptors.request.use( config => { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 // 增加接口时间戳 config.params = { _t: 1000, ...config.params } config.headers = { 'x-csrf-token': "xxx" } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 let timer: any = false axios.interceptors.response.use( response => { cookieService.set('xxx', response.headers['csrftoken']) if (response.status === 200) { return Promise.resolve(response) } else { return Promise.reject(response) } }, error => { if (error.response && error.response.status) { const path = window.location.href switch (error.response.status) { case 302: window.location.href = '' + path break case 401: window.location.href = '' + path break case 403: // 清除token if (!timer) { timer = setTimeout(() => { ElMessage({ message: '登录信息已过期,请重新登录!', type: 'error', }) setTimeout(() => { window.location.href = 'xxx' + path cookieService.set('loginCookie', false, 1) }, 2000) }, 0) } break // 404请求不存在 case 404: ElMessage({ message: '请求不存在', type: 'error', }) break case 500: ElMessage({ message: error.response.statusText, type: 'error', }) break default: ElMessage({ message: error.response.data.message, type: 'error', }) } return Promise.reject(error.response) } } ) /** * get方法,对应get请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */ export function get(url: string, params: any) { return new Promise((resolve, reject) => { axios .get(url, { params: params }) .then(res => { resolve(res.data) }) .catch(err => { reject(err.data) }) }) } /** * post方法,对应post请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */ export function post(url: string, params: any) { return new Promise((resolve, reject) => { axios .post(url, params) .then(res => { resolve(res.data) }) .catch(err => { reject(err.data) }) }) } export default axios
添加全局配置文件:/src/config/index.ts
const BASE_URL = process.env.NODE_ENV === 'development' ? '/api' : 'http://localhost:8080' const settings = { // 请求根路径 baseUrl: BASE_URL, // 是否开启代理,本地需要开,线上环境关闭 proxyFlag: true, // 端口 port: 8081, // 是否开启https https: false, // 扩展端口 // host: 'localhost', // 公共路径 base: './' } export { settings }
添加api请求文件:/src/config/api.ts
import { get, post } from 'plugins/request' // 用户请求 const user = () => { const getUser = (url: string, params: any) => { return get(url, params) } return { getUser } } // 权限请求 const permission = () => { const login = (url: string, params: any) => { return get(url, params) } return { login } } const userService = user() const permissionService = permission() export { userService, permissionService }
添加url路径文件(根据后台接口定):/src/config/url.ts
// 用户url
const userBaseUrl = '/user'
export const userUrl = {
add: userBaseUrl + '/add',
get: userBaseUrl + '',
edit: userBaseUrl + '/edit',
delete: userBaseUrl + '/delete'
}
使用案例:/src/views/Home.vue
<template> <div> {{ state.userName }} </div> </template> <script lang='ts' setup> import { reactive } from 'vue'; import { userService } from 'config/api'; import { userUrl } from 'config/url'; const state = reactive({ userName: '' }) getUser() function getUser() { userService.getUser(userUrl.get, '').then((resp: any) => { console.log(resp) state.userName = resp.data; }) } </script> <style scoped> </style>
pnpm i pinia -D
全局引入:main.ts
import { createApp } from 'vue'
import App from './App.vue'
import element from 'element-plus'
import 'element-plus/dist/index.css'
import router from '@/router'
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App).use(element).use(router).use(pinia).mount('#app')
状态管理案例:/src/store/index.ts
import { defineStore } from 'pinia' /* * 传入2个参数,定义仓库并导出 * 第一个参数唯一不可重复,字符串类型,作为仓库ID以区分仓库 * 第二个参数,以对象形式配置仓库的state、getters、actions * 配置 state getters actions */ export const mainStore = defineStore('main', { /* * 类似于组件的data数据,用来存储全局状态的 * 1、必须是箭头函数 */ state: () => { return { msg: 'hello world!', counter: 0 } }, /* * 类似于组件的计算属性computed的get方法,有缓存的功能 * 不同的是,这里的getters是一个函数,不是一个对象 */ getters: { count10(state) { console.log('count10被调用了') return state.counter + 10 } }, /* * 类似于组件的methods的方法,用来操作state的 * 封装处理数据的函数(业务逻辑):初始化数据、修改数据 */ actions: { updateCounter(value: number) { console.log('updateCounter被调用了') this.counter = value * 1000 } } })
使用案例:/src/views/Home.vue
<template> <div> {{ state.userName }} </div> <el-button @click="handleClick">增加</el-button> <div> {{ counter }} </div> </template> <script lang='ts' setup> import { reactive } from 'vue'; import { userService } from 'config/api'; import { userUrl } from 'config/url'; // 定义一个状态对象 import { mainStore } from 'store/index'; import { storeToRefs } from 'pinia'; // 创建一个该组件的状态对象 const state = reactive({ userName: '' }) // 实例化一个状态对象 const store = mainStore(); // 解构并使数据具有响应式 const { counter } = storeToRefs(store); getUser() function getUser() { userService.getUser(userUrl.get, '').then((resp: any) => { console.log(resp) state.userName = resp.data; }) } function handleClick() { counter.value++; store.updateCounter(counter.value) } </script> <style scoped> </style>
引入持久化插件:pinia-plugin-persist
pnpm i pinia-plugin-persist -D
在main.ts全局引入
import { createApp } from 'vue'
import App from './App.vue'
import element from 'element-plus'
import 'element-plus/dist/index.css'
import router from '@/router'
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPluginPersist)
createApp(App).use(element).use(router).use(pinia).mount('#app')
编写persist配置文件piniaPersist.ts
export const piniaPluginPersist = (key: any) => {
return {
enabled: true, // 开启持久化存储
strategies: [
{
// 修改存储中使用的键名称,默认为当前 Store的id
key: key,
// 修改为 sessionStorage,默认为 localStorage
storage: localStorage,
// []意味着没有状态被持久化(默认为undefined,持久化整个状态)
// paths: [],
}
]
}
}
使用案例
import { defineStore } from 'pinia' import { piniaPluginPersist } from 'plugins/piniaPersist' /* * 传入2个参数,定义仓库并导出 * 第一个参数唯一不可重复,字符串类型,作为仓库ID以区分仓库 * 第二个参数,以对象形式配置仓库的state、getters、actions * 配置 state getters actions */ export const mainStore = defineStore('mainStore', { /* * 类似于组件的data,用来存储全局状态的 * 1、必须是箭头函数 */ state: () => { return { msg: 'hello world!', counter: 0 } }, /* * 类似于组件的计算属性computed,有缓存的功能 * 不同的是,这里的getters是一个函数,不是一个对象 */ getters: { count10(state) { console.log('count10被调用了') return state.counter + 10 } }, /* * 类似于组件的methods,用来操作state的 * 封装处理数据的函数(业务逻辑):同步异步请求,更新数据 */ actions: { updateCounter(value: number) { console.log('updateCounter被调用了') this.counter = value * 1000 } }, /* * 持久化,可选用localStorage或者sessionStorage * */ persist: piniaPluginPersist('mainStore') })
运行命令
pnpm run dev
打包命令(环境自选)
pnpm run build:dev
配置不同的打包环境:package.json
{ "name": "vite-study", "private": true, "version": "0.0.0", "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", "build:dev": "vue-tsc --noEmit && vite build", // 开发环境 "build:prod": "vue-tsc --noEmit && vite build", // 生产环境 "preview": "vite preview" }, "dependencies": { "vue": "^3.2.37" }, "devDependencies": { "@types/node": "^18.0.0", "@vitejs/plugin-vue": "^2.3.3", "axios": "^0.27.2", "element-plus": "^2.2.6", "pinia": "^2.0.14", "typescript": "^4.7.4", "vite": "^2.9.12", "vue-router": "^4.0.16", "vue-tsc": "^0.34.17" } }
由于使用到了vite作为打包工具,在实际使用过程中遇到了问题。webpack打包可以直接指定打包成zip或者其他格式的压缩包,但是在vite中是没有这个配置的,那么遇到流水线部署的时候我们应该怎么办呢?
方法:利用node插件compressing
引入compressing
pnpm i compressing -D
根目录创建:zip.js
const path = require("path"); const { resolve } = require("path"); const fs = require("fs"); const compressing = require("compressing"); const zipPath = resolve("zip"); const zipName = (() => `zip/dist.zip`)(); // 判断是否存在当前zip路径,没有就新增 if (!fs.existsSync(zipPath)) { fs.mkdirSync(zipPath); } // 清空zip目录 const zipDirs = fs.readdirSync("./zip"); if (zipDirs && zipDirs.length > 0) { for (let index = 0; index < zipDirs.length; index++) { const dir = zipDirs[index]; const dirPath = resolve(__dirname, "zip/" + dir) console.log("del ===", dirPath); fs.unlinkSync(dirPath) } } // 文件压缩 compressing.zip .compressDir(resolve("dist/"), resolve(zipName)) .then(() => { console.log(`Tip: 文件压缩成功,已压缩至【${resolve(zipName)}】`); }) .catch(err => { console.log("Tip: 压缩报错"); console.error(err); });
package.json中配置script命令
"build:dev": "vue-tsc --noEmit && vite build && node ./zip.js",
"build:prod": "vue-tsc --noEmit && vite build && node ./zip.js",
输入命令打包
pnpm run build:dev
命令执行完后在zip文件夹会生成dist.zip的压缩包
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。