vue3+Pinia+element-plus 后台管理系统项目实战

发布于:2025-05-28 ⋅ 阅读:(24) ⋅ 点赞:(0)

vue3+Pinia+element-plus 后台管理系统项目实战记录
参考项目:https://www.bilibili.com/video/BV1L24y1n7tB

全局api

provide、inject

vue2

import api from'@/api'
vue.propotype.$api = api

this.$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.js

import { 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.path

watch(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,			// 组合式 API
  	globalInjection: true,	// 全局生效$t
  	locale: 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)

网站公告

今日签到

点亮在社区的每一天
去签到