一,封装表单组件
1,移动端
<template>
<div class="from">
<van-form>
<van-field
v-for="item in FromLabel"
:key="item.label"
v-model="from[item.model]"
:label="item.label"
:type="item.type"
:placeholder="`请输入${item.label}`"
:rules="[
{
required: true,
trigger: 'onBlur',
message: item.label+'不能为空',
},
{
pattern: item.rule,
message: item.message,
trigger: 'onBlur',
},
]"
/>
</van-form>
<slot></slot>
</div>
</template>
// 传入的数据
FromLabel: [
{
model: "username",
label: "账号",
type: "text",
rule: /^.{6,16}$/,
message: "请填写6到16位字符",
},
{
model: "password",
label: "密码",
type: "password",
rule: /^.{6,16}$/,
message: "请填写6到16位字符",
},
],
from: {
username: "",
password: "",
},
2,vue2PC端
<template>
<el-form ref="form" :model="form" label-width="100px" :inline="inline" >
<el-form-item
v-for="item in formLabel"
:key="item.label"
:label="item.label"
>
<el-input
v-if="item.type == 'input'"
v-model="form[item.model]"
:placeholder="'请输入' + item.label"
></el-input>
<el-switch
v-if="item.type == 'switch'"
v-model="form[item.model]"
></el-switch>
<el-date-picker
v-if="item.type == 'date'"
type="date"
value-format="yyyy-MM-dd"
v-model="form[item.model]"
placeholder="选择日期"
>
</el-date-picker>
<el-select
v-if="item.type === 'select'"
placeholder="请选择"
v-model="form[item.model]"
>
<el-option
v-for="item in item.opts"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<slot></slot>
</el-form-item>
</el-form>
operateFormLabel: [
{
model: "name",
label: "姓名",
type: "input",
},
{
model: "age",
label: "年龄",
type: "input",
},
{
model: "sex",
label: "性别",
type: "select",
opts: [
{
label: "男",
value: 1,
},
{
label: "女",
value: 0,
},
],
},
{
model: "birth",
label: "出生日期",
type: "date",
},
{
model: "addr",
label: "地址",
type: "input",
},
],
operateForm: {
name: "",
addr: "",
age: "",
birth: "",
sex: "",
},
二,封装table组件
<el-table :data="tableData" >
<el-table-column
show-overflow-tooltip
v-for="item in tableLable"
:key="item.prop"
:label="item.label"
:width="item.width ? item.width : 125"
>
<template slot-scope="scope">
{{ scope.row[item.prop] }}
</template>
</el-table-column>
<el-table-column label="操作" min-width="100">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="mini" @click="handleDelete(scope.row)" type="danger"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
tableData: [],
tableLable: [
{
prop: "name",
label: "姓名",
width: 100,
},
{
prop: "age",
label: "年龄",
width: 100,
},
{
prop: "sex",
label: "性别",
width: 100,
},
{
prop: "birth",
label: "出生日期",
width: 100,
},
{
prop: "addr",
label: "地址",
width: 220,
},
],
三,vue3,vite中动态获取图片
// 模板使用
<img :src="getImg()" alt="">
// js
const getImg = ()=> new URL('../assets/images/avter4.webp',import.meta.url).href
四,二次封装axios及axios中mock的灵活使用
1,在src/config/index.js配置不同开发环境下的api和mockApi
/**
* 环境配置文件
* 一般企业级项目有三个环境
* 开发环境
* 测试环境
* 生产环境
* */
// 当前的环境
const env = import.meta.env.MODE || 'prod'
const EnvConfig = {
development:{
baseApi:'/api',
mockApi:'https://www.fastmock.site/mock/f44a3a709364126cbd8dac2abb5fc00a/api'
},
test:{
baseApi:'//test.full.com/api',
mockApi:'https://www.fastmock.site/mock/f44a3a709364126cbd8dac2abb5fc00a/api'
},
prod:{
baseApi:'//full.com/api',
mockApi:'https://www.fastmock.site/mock/f44a3a709364126cbd8dac2abb5fc00a/api'
}
}
export default {
env,
// mock的总开关
mock:true,
// 获取到当前环境的api
...EnvConfig[env]
}
2,在src/api/request.js配置请求,响应拦截器,并封装一个api关键函数
import axios from 'axios'
import {
ElMessage
} from 'element-plus'
import config from '../config/index'
// 网络请求异常消息提示
const NETWORK_Message = '网络请求异常'
// 创建一个axios对象
const http = axios.create({
baseURL: 'config.baseApi'
})
// 在请求之前做一些事情
// http.interceptors.request.use()
// 在响应之后做一些处理
http.interceptors.response.use(
res => {
// 把响应的数据结果出来
console.log(res.data);
const {
code,
data,
msg
} = res.data
console.log(data);
// 视开发情况决定
if (code == 200) {
return data
} else {
// 网络错误
ElMessage.error(msg || NETWORK_Message)
return Promise.reject(msg || NETWORK_Message)
}
}
)
// 封装的接口核心函数
function request(options) {
// 如果是get请求,把data赋值给perams
options.method = options.method || 'get'
if (options.method.toLowerCase() == 'get') {
options.params = options.data
}
// 对mock进行处理
// 获取到mock的总开关
let isMock = config.mock
// 如果接口设置了mock,则把isMock的值变为自己设置的mock值,由接口自己控制是否使用mockApi
if (typeof options.mock != 'undefined') {
isMock = options.mock
}
// 对线上环境做处理
if (config.env == 'prod') {
http.defaults.baseURL = config.baseURL
} else {
http.defaults.baseURL = isMock ? config.mockApi : config.baseApi
}
return http(options)
}
export default request
3,封装接口,并挂载到全局
import request from "./request"
export default {
// 获取home页面的表格数据
getHomeData(params){
return request({
url:'/home/getHomeData',
method:'get',
data:params,
// 自己的开关,是否使用mockAPI
mock:true
})
}
}
import api from './api/api';
app.config.globalProperties.$api = api
// 声明类型(必须)
declare module '@vue/runtime-core'{
export interface ComponentCustomProperties{
$api:Object
}
}
4,在组件中使用axios
import {getCurrentInstance } from "vue"
// 获取当前实例
const app = getCurrentInstance()
const getTableData = async ()=>{
const res =await app?.proxy?.$api.getHomeData();
tableData.value = res
}
五,vue3使用面包屑
1,在pinia添加字段current存储面包屑路由数据
state:()=>{
return {
current:null
}
}
2,跳转路由,把路由的数据添加到pinia
const push = (item:any)=>{
router.push(item.path)
test.$patch({
//如果跳转页面为主页,则值为null,否则值为当前的路由数据
current:item.path=='home'?'null':item
})
}
3,组件中使用面包屑
<el-breadcrumb class="bread" separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item :to="current['path']" v-if="current">{{ current['label'] }}</el-breadcrumb-item>
</el-breadcrumb>
<script>
// 结构pinia中把current结构出来
const {current} = storeToRefs(test)
</script>
六,vue3使用tag标签
1,在pinia中配置tag数据
state: () => {
return {
// 首页tag标签是一直存在的,默认有数据
tableList: [
{
path: 'home',
name: 'home',
label: '首页',
icon: 'House',
url: 'Home/index'
}
]
}
2,当点击菜单栏,添加菜单数据到tableList
const push = (item:any) => {
router.push(item.path)
test.$patch((state)=>{
if(item.name=='home'){
//state.current=null
// 如果点击home页面,不会添加
}else{
//state.current=item
// 如果菜单在tablist不存在,则添加
const bol = state.tableList.some((val)=>val.name===item.name)
if(!bol){
state.tableList.push(item)
}
}
})
}
3,配置components/CommonTab.vue组件
<template>
<div class="tabs">
<el-tag
class="tab"
v-for="(tag,index) in tags"
:key="tag.name"
// 当前路由的name==当前tag的name为drak色,路由跳转到哪,哪个tag变色
:effect="tag.name==route.name?'dark':'plain'"
// 除了home的tag,其他都能删除
:closable="tag.name!='home'"
// 取消动画效果
:disable-transitions="false"
@click="clickTab(tag)"
@close="closeTab(tag,index)"
>
{{ tag.label }}
</el-tag>
</div>
</template>
<script setup lang = 'ts'>
import {useRoute,useRouter} from "vue-router"
import {useStore} from '../store'
const route = useRoute()
const router = useRouter()
const test = useStore()
const tags = test.tableList
const clickTab = (item:any)=>{
// 点击跳转页面
router.push(item.path)
}
// 删除tab标签
const closeTab = (item:any,index:number)=>{
// 当前路由name(点击的tag)不等于要删除tag的name,直接删除
if(route.name!==item.name){
test.tableList.splice(index,1)
//要删除当前路由name(点击的tag),他在最后面,他前面的tag变色
}else if(index===test.tableList.length-1){
test.tableList.pop()
router.push(test.tableList[index-1].path)
// test.tableList[index-1].name==='home'?test.current=null:test.current=test.tableList[index-1]
}else{
//要删除当前路由name(点击的tag),不在最后,他后面的tag变色
test.tableList.splice(index,1)
router.push(test.tableList[index].path)
test.tableList[index-1].name==='home'?test.current=null:test.current=test.tableList[index-1]
}
}
</script>
4,把组件放在指定位置
七,element plus表单提交进行验证、格式化表单数据
1,在form标签上添加ref属性
2,进行验证
//获取当前实例
import { getCurrentInstance } from "vue"
const app = getCurrentInstance()
//进行验证
app?.proxy?.$refs.formRef.validate(async (valid:any) => {
if (valid) {
const res = await app?.proxy?.$api.addUserData(formData)
if(res){
formatForm()
getUserDatas(config)
}
}
3,退出格式化表单
app?.proxy?.$refs.formRef.resetFields()
八,新增,编辑使用同一个弹出框,修改操作的resetFields()格式化表单失效
修改操作获取数据使用$nextTick
const editUser = (row:any): void => {
dialogVisible.value = true
app?.proxy?.$nextTick(()=>{
Object.assign(formData, row)
})
}
九,权限设计,不同用户登录,展示不同的菜单栏,路由
1,设置pinia的方法
import {defineStore} from 'pinia'
export const useStore = defineStore('test', {
state: () => {
return {
loginRouters: []
}
},
actions: {
// 把后端返回的菜单数据添加到pinia
setRoutes(val:any) {
this.loginRouters = val
localStorage.setItem('menu', JSON.stringify(val))
},
// 把菜单数据重置为路由格式,动态添加到router
addMenu(router:any) {
if (!localStorage.getItem("menu")) {
return
}
// 数据持久化
const stateRoutes = JSON.parse(localStorage.getItem('menu'))
this.loginRouters = stateRoutes
let menuRouter = stateRoutes
let menus:any = []
// 对数据做处理
menuRouter.forEach((item:any) => {
if (item.children) {
item.children.map((item:any) => {
let url = `../view/${item.url}.vue`
item.component = () => import(url)
return item
})
menus.push(...item.children)
} else {
let url = `../view/${item.url}.vue`
item.component = () => import(url)
menus.push(item)
}
})
// 动态添加到路由
menus.forEach((item:any) => {
router.addRoute('home1', item)
})
// console.log(router.hasRoute('user'));
}
}
})
2,登录时,调用pinia的方法,动态添加,菜单,路由
const login = ()=>{
app?.proxy?.$refs.loginRef.validate( async (valid:any)=>{
if(valid){
const res = await app.proxy?.$api.login(FormData)
if(res){
test.setRoutes(res.menu)
test.addMenu(router)
router.push('/')
}
}
})
}
3,菜单组件获取菜单
const asyncList:Array<object> = test.loginRouters;
4,页面刷新,页面会丢失,在main.ts调用
const test = useStore()
test.addMenu(router)