vite2 主要变化
- 配置项变化:
vue特有选项、创建选项、css选项、jsx选项 别名用法变化:不再需要 开始/结尾 处的斜线了vue 支持:通过@vitejs/plugin-vue插件支持- react 支持
- HMR API变化
- 清单格式变化
插件API重新设计
vue支持
和其他框架一视同仁,Vue的整合通过插件:@vitejs/plugin-vue实现
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()]
})
SFC定义默认使用setup script
<script setup>
import { ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
别名定义
不再需要像vite1一样在别名前后加上/
//vite.config.js
resolve:{
alias: {
'@': path.resolve(__dirname, 'src'),
'comps': path.resolve(__dirname, 'src/components')
}
}
App.vue中用一下试试
<script setup>
import HelloWorld from 'comps/HelloWorld.vue'
</script>
- vue3 setup script
defineProps和defineEmits:声明props和emits- defineExpose:暴露在当前
<script setup>中声明的绑定,以便通过模板 ref 或者$parent链获取到的组件的公开实例可以访问// HelloWorld.vue defineProps({ msg: String }) const emit = defineEmits(['myclick']) defineExpose({ someMethod(){ console.log('some message from helloworld') } }) // App.vue <HelloWorld msg="Hello Vue 3 + Vite" ref="hw" @myclick="onmyclick" /> <script setup> import HelloWorld from 'comps/HelloWorld.vue' import { ref } from 'vue' const hw = ref(null) const onmyclick = () => { console.log('myclick from helloworld') // HelloWorld中defineExpose 暴露 hw.value.someMethod() } </script>
插件API重新设计
Vite 2 使用了一套完全重定义的,扩展了 Rollup 插件的接口。 插件开发指南
jsx支持
Vue 3 JSX 支持:@vitejs/plugin-vue-jsx
npm i @vitejs/plugin-vue-jsx -D
仅限于单文件组件中解析
jsx和 后缀名为jsxtsx的解析不是一回事
//vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vue(),vueJsx()]
})
// Comp.vue
<script lang="jsx">
import { ref } from "vue"
export default{
setup(){
const counter = ref(0)
const onclick = ()=>{
counter.value++
}
return () => (
<>
<div> Comp </div>
<p onClick={onclick}>{counter.value}</p>
</>
)
}
}
</script>
Mock插件应用
- 插件安装
npm i mockjs -Snpm i vite-plugin-mock -D- 用到环境变量 :npm i cross-env
"dev": "cross-env NODE_ENV=development vite"
- 配置
vite.config.js
// vite.config.js
import { viteMockServe } from 'vite-plugin-mock'
export default {
plugins: [ viteMockServe({ supportTs: false }) ]
}
项目基础架构
路由 vue-router 4.x
- 安装
vue-router 4.xnpm install vue-router@4
- 路由配置,
router/index.js
import { createRouter, createWebHashHistory } from "vue-router";
// 工厂函数来创建router实例
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
component: () => import('views/home.vue')
}
]
});
export default router
- 引入,
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
记得创建
homg.vue,并修改App.vue
路由用法略有变化 ,vue-router v4.x
状态管理 vuex@next (vuex4.x)
- 安装
vuex@next(vuex4.x)npm install vuex@next --save
- store配置,
store/index.js
import { createStore } from 'vuex';
export default createStore({
state: {
counter: 0
},
mutations:{
add(state){
state.counter++
}
}
});
- 引入,
main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(router).use(store).mount('#app')
用法和以前基本一样,vuex 4.x
样式组织
- 安装sass
npm i sass -D
styles目录保存各种样式index.scss作为出口组织这些样式,同时编写一些全局样式- 最后在
main.js导入
// vite.config.js 添加别名 styles
// 全局样式
import 'styles/index.scss'
UI库
- 安装
npm i element3 -S
- 完整引入,
main.js
import Element3 from 'element3'
import 'element3/lib/theme-chalk/index.css'
createApp(App).use(Element3)
- 按需引入,
main.js
import "element3/lib/theme-chalk/button.css";
import { ElButton } from "element3"
createApp(App).use(ElButton)
- 抽取成插件,
plugins/element3.js
// 完整引入element3
// import Element3 from 'element3'
// import 'element3/lib/theme-chalk/index.css'
// 按需加载
import { ElButton,ElInput } from "element3"
import 'element3/lib/theme-chalk/button.css'
import 'element3/lib/theme-chalk/input.css'
export default function(app){
// 完整
// app.use(Element3)
// 按需引入
app.use(ElButton)
app.use(ElInput)
}
基础布局
基础布局页,将来每个页面以布局页为父页面即可:
布局页面
layouts/index.vue
- 侧边栏
layouts/components/Sidebar/index.vue- 右侧内容容器
- 顶部导航栏
layouts/components/Navbar.vue
- 面包屑
layouts/components/Breadcrumb.vue- 右侧下拉菜单
- 内容区
layouts/components/AppMain.vue
动态导航
侧边导航 sidebar
根据路由表动态生成侧边导航菜单
- 创建侧边栏组件,递归输出
routes中配置的多级菜单
<template>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
mode="vertical"
>
<sidebar-item
v-for="route in routes"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
</el-scrollbar>
</template>
<script setup>
import variables from "styles/variables.module.scss";
import SidebarItem from "./SidebarItem.vue";
import { computed } from "@vue/runtime-core";
import { useRoute } from "vue-router";
import { routes } from "@/router";
const activeMenu = computed(() => {
const route = useRoute();
console.log(route);
const { meta, path } = route;
if (meta.activeMenu) {
return meta.activeMenu;
}
return path;
});
</script>
sass文件导出变量解析需要用到css module,因此variables文件要加上module中缀,variables.module.scss 导出以便可以js访问变量值
-
相关样式文件:
styles/variables.module.scssstyles/sidebar.scssstyles/index.scss中引入
-
- 父菜单(含子路由)
- 导航链接
面包屑
通过路由匹配数组可以动态生成面包屑。
面包屑组件,src\layouts\components\Breadcrumb.vue
数据封装
统一封装数据请求服务,有利于解决一下问题:
- 统一配置请求
- 请求、响应统一处理
准备工作:
- 安装
axios:npm i axios -S - 添加配置文件:环境变量
.env.developmentVITE_BASE_API=/api - 请求封装,
utils/request.js
import axios from "axios";
import { Message, Msgbox } from "element3";
import store from "@/store";
// 创建axios实例
const service = axios.create({
// 在请求地址前面加上baseURL
baseURL: import.meta.env.VITE_BASE_API,
// 当发送跨域请求时携带cookie
// withCredentials: true,
timeout: 5000,
});
// 请求拦截
service.interceptors.request.use(
(config) => {
// 指定请求令牌
// if (store.getters.token) {
// // 自定义令牌的字段名为X-Token,根据咱们后台再做修改
// config.headers["X-Token"] = store.getters.token;
// }
config.headers["X-Token"] = "my token";
return config;
},
(error) => {
// 请求错误的统一处理
console.log(error); // for debug
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* 通过判断状态码统一处理响应,根据情况修改
* 同时也可以通过HTTP状态码判断请求结果
*/
(response) => {
const res = response.data;
// 如果状态码不是20000则认为有错误
if (res.code !== 20000) {
Message.error({
message: res.message || "Error",
duration: 5 * 1000,
});
// 50008: 非法令牌; 50012: 其他客户端已登入; 50014: 令牌过期;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// 重新登录
Msgbox.confirm("您已登出, 请重新登录", "确认", {
confirmButtonText: "重新登录",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
store.dispatch("user/resetToken").then(() => {
location.reload();
});
});
}
return Promise.reject(new Error(res.message || "Error"));
} else {
return res;
}
},
(error) => {
console.log("err" + error); // for debug
Message({
message: error.message,
type: "error",
duration: 5 * 1000,
});
return Promise.reject(error);
}
);
export default service;
业务处理
结构化数据展示
使用el-table展示结构化数据,配合el-pagination做数据分页。
文件组织结构如下:
list.vue展示列表edit.vue和create.vue编辑或创建- 内部复用
detail.vue处理 model中负责数据业务处理
list.vue中的数据展示
<el-table
v-loading="loading"
:data="list"
border
fit
highlight-current-row
style="width: 100%"
>
<el-table-column align="center" label="ID" prop="id"></el-table-column>
<el-table-column align="center" label="账户名" prop="name">
</el-table-column>
<el-table-column align="center" label="年龄" prop="age">
</el-table-column>
</el-table>
list和loading数据的获取逻辑,可以使用compsition-api提取到userModel.js
export function useList() {
// 列表数据
const state = reactive({
loading: true, // 加载状态
list: [], // 列表数据
total: 0,
listQuery: {
page: 1,
limit: 5,
},
});
// 获取列表
function getList() {
state.loading = true;
return request({
url: "/getUsers",
method: "get",
params: state.listQuery,
})
.then(({ data, total }) => {
// 设置列表数据
state.list = data;
state.total = total;
})
.finally(() => {
state.loading = false;
});
}
// 删除项
function delItem(id) {
state.loading = true;
return request({
url: "/deleteUser",
method: "get",
params: { id },
}).finally(() => {
state.loading = false;
});
}
// 首次获取数据
getList();
return { state, getList, delItem };
}
list.vue中使用:
import { useList } from "./model/userModel";
const { state, getList, delItem } = useList();
分页处理,list.vue
<pagination
v-show="total > 0"
:total="total"
v-model:page="listQuery.page"
v-model:limit="listQuery.limit"
@pagination="getList"
></pagination>
分页需要的数据也在userModel中处理
const state = reactive({
total: 0,
listQuery: {
page: 1,
limit: 5,
},
});
表单处理
用户数据新增、编辑使用el-form处理
用一个组件detail.vue来处理,区别在于初始化时是否获取信息回填到表单。
detail.vue
<el-form ref="form" :model="model" :rules="rules">
<el-form-item prop="name" label="用户名">
<el-input v-model="model.name"></el-input>
</el-form-item>
<el-form-item prop="age" label="用户年龄">
<el-input v-model.number="model.age"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="submitForm" type="primary">提交</el-button>
</el-form-item>
</el-form>
数据处理同样可以提取到userModel中处理。
export function useItem(isEdit, id) {
const model = ref(Object.assign({}, defaultData));
// 初始化时,根据isEdit判定是否需要获取玩家详情
onMounted(() => {
if (isEdit && id) {
// 获取玩家详情
request({
url: "/getUser",
method: "get",
params: { id },
}).then(({ data }) => {
model.value = data;
});
}
});
const updateUser = () => {
return request({
url: "/updateUser",
method: "post",
data: model.value,
});
};
const addUser = () => {
return request({
url: "/addUser",
method: "post",
data: model.value,
});
};
return { model, updateUser, addUser };
}