vue3+Pinia+element-plus 后台管理系统项目实战
vue3+Pinia+element-plus 后台管理系统项目实战记录
参考项目:https://www.bilibili.com/video/BV1L24y1n7tB
全局api
provide、inject
vue2
import api from'@/api'
vue.propotype.$api = apithis.$api.xxx
vue3
import api from'@/api'
app.provide('$api', api)import { inject } from 'vue'
const $api = inject('$api')
$api.xxx
父子组件传值
defineProps、defineEmits、defineExpose
defineProps
在 script setup 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits ,它们具备完整的类型推断,并且在 script setup 中是直接可用的
<Detail name="李四" :result="1"></Detail>
const props = defineProps({name: {type: String,default: '张三'required: true},result: {type: number,default: '0'required: true}
})
defineEmits
子组件声明并触发自定义事件,父组件监听自定义事件并执行相应的操作
子组件:
<button @click="incrementCounter"></button>import { defineEmits, ref } from 'vue';const emits = defineEmits(['increment']); // 定义方法
const counter = ref(0);function incrementCounter() {emits('increment', counter.value); // 提交方法
}
父组件:
<ChildComponent @increment="handleIncrement" />
<p>Counter: {{ counter }}</p>import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';const counter = ref(0);function handleIncrement(value) {counter.value = value;
}
defineExpose
适用于子组件向父组件暴露方法和属性,父组件通过子组件示例进行调用
const exposeStr = ref("")
defineExpose({ exposeStr })
<Detail ref="detail"></Detail>
const detail = ref(null)detail.value.exposeStr = "exposeStr"
接收父组件数据
props、ref、toRefs
<son :str='str' :num='num' />
const str = ref('abc')
const num = ref('123')
import { toRefs } from'vue'
const props = definProps({str: {type: string,default: '字符串'},num:{type: number,default: 1}
})
const { str, num } = toRefs(props)
向父组件传递数据
emit
const emit = defineEmits(['customEvent'])const clickEvent = (val) => {emit('customEvent', val)
}
<son :str='str' :num='num' @customEvent='customEvent' />const custom = (val) => {...
}
表格删除数据
- 记录当前页码
- 判断是否是当前页的最后一条数据,是的话页码-1
- 根据id删除数据,携带页码请求数据
router相关
{path: '小写',name: '小驼峰',compoment: '大驼峰'
}<router-link to:'xxx/xxx'></router-link>const toXxx = () => {router.push('/xxx/xxx')
}
element 的菜单高亮
<el-menu :default-active="$route.meta.activeMenu || $route.path"></el-menu>
富文本编辑器
wangEditor:https://www.wangeditor.com/v5/getting-started.html
创建一个供用户编辑文本的富文本编辑器
Pinia
npm i pinia
import { defineStore } from 'pinia'const useGoodsStore = defineStore('goods', {state: () => {return {title:'xxx"}},actions: {setRowData(payload){this.title = xxx}}
}export default useGoodsStore
import useGoodsStore from '@/store/GoodsInfo' // 获取仓库的方法
const goodsStore = useGoodsStore(); // 定义常量接收仓库方法goodsStore.setRowData({ title: "xxx" }) // 修改仓库数据
goodsStore.title = "xxx" // 修改仓库数据
注意:考虑页面刷新的场景,哪些数据需要做持久化处理
Pinia持久化
npm i pinia-plugin-persistedstate -s
// main.jsimport { createApp }from "vue";
import App from "./App.vue";
import { createPinia } from 'pinia'
import piniapluginpersistedstate from 'pinia-plugin-persistedstate'const app = createApp(App);
const pinia = createPinia();
pinia.use(piniapluginpersistedstate);
import { definestore } from 'pinia'const useGoodsstore = definestore('goods', {state: () => ({ data: '数据' }),actions: {upData(payload){this.data = payload.data}}// 开启数据持久化persist: true
})export default useGoodsstore
原地深拷贝
let newData = JSON.parse(JSON.stringify(data))
视图不更新
问题:在某些场景下,修改通过reactive声明的响应式数据,修改成功,但是在视图上该数据不更新
-
修改为
ref
声明:data.value = newData -
在原先reactive声明的值上修改,改为一个对象,再将原先的值赋予该对象
const data = reactive([{ id: 1 }, { id: 2 }]}// 改为const data = reactive({result: [{ id: 1 },{ id: 2 }] })data.result = newData
导出Excel
1、安装模块
npm i xlsx file-saver -s
2、模板使用
<el-button type="warning" @click="download">导出</el-button>
<el-table id='table'></el-table>import * as XLXS from "xlsx";
import Filesaver from "file-saver";// 导出名称
const name = ref('替换表名')// 导出excel事件
const download = () => {const table = document.getElementById("table")const wb = XLXs.utils.table_to_book(table, { raw: true });const wbout = XLXS.write(wb, {bookType: "xlsx",bookSST: true,type: "array",});try {FileSaver.saveAs(new Blob([wbout], {//定义文件格式流type: "application/octet-stream"}),name.value + ".xlsx");}catch(e){console.log(e)}return wbout
}
PDF预览下载打印
通过npm官网查找合适的第三方库:http://npm.p2hp.com/
根据第三方库的提供方法进行编写代码
同时要考虑:乱码、格式、排版、大小等
npm i vue3-pdf-app -s
<template><el-button @click="printEvent">打印</el-button><PDF pdf="../../../../public/vitae.pdf" style="height: 100vh"></PDF>
</template><script setup>
import PDF from "vue3-pdf-app";
import "vue3-pdf-app/dist/icons/main.css";
import { ref } from 'vue';
</script>
<el-dialog width="60%"><Pdf/></el-dialog>import Pdf from './Pdf.vue'
Element表单清空
reactive 响应式的变量,需要打点出属性,单独清空,不能使用ElementPlus的resetFields()
ref 响应式的变量,可使用ElementPlus提供的resetFields(),myForm.value.resetFields()
// let formInline = ref({ name: "", date: ""})
let formInline = reactive({ name: "", date: ""})const customEvent = () => {// formInline.name = '';// formInline.date = '';myForm.value.resetFields();
}
Element表格分页英转中
全导入element-plus,在main.js添加配置
import Elementplus from 'element-plus ';
import locale from "element-plus/lib/locale/lang/zh-cn"; // 需要新加的代码
const app = createApp(App);
app.use(ElementPlus,{locale}); // 需要改变的地方,加入locale
按需导入element-plus,在App.vue添加配置
// 在App.vue 的文件中修改即可
<template><el-config-provider :locale="locale"><router-view></router-view></el-config-provider>
</template><script lang="ts" setup>import {reative} from 'vuex'import { ElConfigProvider } from 'element-plus'import zhCn from 'element-plus/lib/locale/lang/zh-cn ' const { locale } = reactive({locale: zhCn});
</script>
Element的提示框
样式:除了需要复制相关代码,还需要单独引入相关的css
层级:由于他跟APP页面是同一级,所以提示框会被后来的组件页面所覆盖
- 在App.vue的
<style>
修改其层级 - 在当前组件新建第二个
<style>
,修改其层级 - 在当前组件减低其他
Dom元素
的层级 - 修改源码,如需修改源码,需要从
node_modules
单独提出来,再重新引入css(不推荐)
Element面包屑
1、编辑好面包屑组价【Breadcrumb.vue】
<template><el-breadcrumb separator="/" class="bread"><el-breadcrumb-item v-for="item in navs" :key="item.path" :to="item.path">{{ item.meta.title }}</el-breadcrumb-item></el-breadcrumb>
</template><script setup>
import { useRoute } from "vue-router";
import { computed } from "vue";const route = useRoute();const navs = computed(() => {let routes = route.matched;// 处理第一个路由的信息routes[0].path = "/";return routes;
})
</script><style lang="scss" scoped>.bread {margin-bottom: 10px;}
</style>
2、配置路由元信息
const routes = [{path:'/',component: Layout,meta:{title:'首页'},children:[{path:'/',name:'home',component: Home,meta:{title:'首页'}},{path: "/product",name: "product",component: Product,redirect: '/product/list', //重定向,保证页面信息展示meta:{title: '产品管理'},children: [{path: "category",name:"category",component: category,meta:{title:'产品分类’}},{path: "list",name:"list",component: List,meta:{title:'产品列表’}}]}]},{path:'/login',name:'login',component: Login}
]
3、main.js引入组件,并全局注册
import Breadcrumb from './components/Breadcrumb/Breadcrumb.vue'
app.compoment('Breadcrumb', Breadcrumb)
4、各个页面顶部使用组件
<Breadcrumb />
5、菜单高亮修改
<el-menu :default-active="activePath"></el-menu>import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'const activePath = ref('')
const route = ref(useRoute())
activePath.value = route.pathwatch(route.value, () => {activePath.value = route.value.fullPath}
)
注意:在发生部分页面发生页面跳转,需要动态配置路由元信息,跳转时传递数据
第三方库
时间处理dayjs
npm i dayjs -s
{{ time }}import dayjs from 'dayjs'const time = dayjs().format('YYYY年MM月DD日 HH:mm:ss')
语言环境Vue i18n
官网:https://kazupon.github.io/vue-i18n/zh/
提醒:注意vue2和vue3的版本,vue3要使用vue-i18n必须要9以上的版本
npm i vue-i18n@9
在src文件下新建lang文件夹,里面建好“cn.js”、“en.js”、 “index.js”文件,分别对应中文、英文、配置
const messages = {menu: {title: '中文',}
}export default messages
const messages = {home: {title: 'English',}
}export default messages
import { createI18n } from 'vue-i18n'
import en from './en'
import cn from './zh'const messages = { en, cn }const localeData = {legacy: true, // 组合式 APIglobalInjection: true, // 全局生效$tlocale: zh, // 默认语言messages
}export function setupI18n (app) {const i18n = createI18n(localeData)app.use(i18n)
}
import i18n from "@/I18n"
App.use(i18n)
{{ $t('home.title') }}
Lodash深拷贝
方式一:JSON.parse( JSON.string( xxx ) ) 会破坏函数,不适合复杂类型
方式二:Lodash.js,官网:www.lodashjs.com
# 安装
npm i lodash -s# 全导 + 单导
improt _ from 'lodash'
improt { cloneDeep } from 'lodash'# 使用
let b = cloneDeep(a)