实现仿若依后台管理主要功能
1.简述后台管理系统2.主要实现功能3.涉及到的前端技术这一步的具体实现思路是,自己编写国际化的文本,如这个项目只需中英切换,所以只需创建两个js文件,并导出其中的文本对象,国际化的思路就是通过切换目标文本源来更改文本,代码示例如下然后在再建一个全局注入国际化文本的js文件目录结构如下注入文件代码如下全局注册完成之后就可以使用了接下来完成语言切换功能这里使用的也是element-plus的国际化组
前言
1.简述后台管理系统
后台管理系统 (手动编程除外)是 内容管理系统 Content Manage System(简称CMS)的一个子集。
WMS是Web Management System 的简写,简单的说:WMS是一个网站管理系统。
一个网站管理系统是把一个网站的内容(文字,图片,等等)与网站的组件分离开来,可以将各个页面连接到一起,可以控制页面的显示。
2.主要实现功能
由于后台管理的功能实际上都差别不大,所以这次只总结若依后台管理的主要功能,分别是主题切换,i18n国际化,动态路由,excel导入导出等。
3.涉及到的前端技术
1.vue
一套用于构建用户界面的渐进式框架
2.vue-router
Vue.js官方的路由插件
3.vuex
Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组件之间的数据的共享。
4.md5
md5的全称是md5信息摘要算法(英文:MD5 Message-Digest Algorithm ),一种被广泛使用的密码散列函数
5.i18n
使产品或软件具有不同国际市场的普遍适应性,从而无需重新设计就可适应多种语言
6.scss
层叠样式表
7.xlsx
用于解析xlsx,xls格式的文件
8.element-Plus
基于vue3的一款前端UI框架
9.axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
主要功能
i18n语言切换
这一步的具体实现思路是,自己编写国际化的文本,如这个项目只需中英切换,所以只需创建两个js文件,并导出其中的文本对象,国际化的思路就是通过切换目标文本源来更改文本,
代码示例如下
//中文文本
export default {
route: {
profile: '个人中心',
user: '用户',
excelImport: 'Excel导入',
userManage: '员工管理',
userInfo: '员工信息',
roleList: '角色列表',
permissionList: '权限列表',
article: '文章',
articleRanking: '文章排名',
articleCreate: '创建文章',
articleDetail: '文章详情',
articleEditor: '文章编辑'
},
}
//英文文本
export default {
route: {
profile: 'Profile',
user: 'user',
excelImport: 'ExcelImport',
userManage: 'EmployeeManage',
userInfo: 'UserInfo',
roleList: 'RoleList',
permissionList: 'PermissionList',
article: 'article',
articleRanking: 'ArticleRanking',
articleCreate: 'ArticleCreate',
articleDetail: 'ArticleDetail',
articleEditor: 'ArticleEditor'
},
}
然后在再建一个全局注入国际化文本的js文件
目录结构如下
注入文件代码如下
//引入i18n
import { createI18n } from 'vue-i18n'
//引入中文文本
import mZhLocale from './lang/zh'
//引入英文文本
import mEnLocale from './lang/en'
//引入vuex 用来存储切换后的文本文件
import store from '@/store'
const messages = {
en: {
msg: {
...mEnLocale
}
},
zh: {
msg: {
...mZhLocale
}
}
}
//返回当前语言的函数
function getLanguage() {
return store && store.state && store.state.language
}
const i18n = createI18n({
// 使用 Composition API 模式,则需要将其设置为false
legacy: false,
// 全局注入 $t 函数
globalInjection: true,
locale: store.state.language,
messages
})
export default i18n
全局注册完成之后就可以使用了
//使用前需要先引入
import { useI18n } from "vue-i18n";
//声明
const i18n = useI18n();
//script标签中使用
i18n.t("msg.elcel.importSuccess")
//dom可以直接使用$t
<el-button @click="closed">{{ $t("msg.excel.close") }}</el-button>
接下来完成语言切换功能
这里使用的也是element-plus的国际化组件
需要用这个组件包裹住整个router-view标签
<el-config-provider :locale="$store.state.language == 'en' ? en : zhCn">
<router-view />
</el-config-provider>
切换语言是我使用element封装的一个组件,直接上代码
<template>
<div class="lang">
<el-col :span="8">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
<div class="language">
<div class="cn">中</div>
<div class="en">A</div>
</div>
</span>
<template #dropdown>
//通过下拉菜单来切换语言
<el-dropdown-menu>
<el-dropdown-item
:disabled="$store.state.language === 'zh'"
command="zh"
>中文</el-dropdown-item
>
<el-dropdown-item
:disabled="$store.state.language === 'en'"
command="en"
>English</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
</div>
</template>
<script setup>
const emit = defineEmits(["changeMenus"])
import { useI18n } from "vue-i18n";
import { useStore } from "vuex";
let store = useStore();
const i18n = useI18n();
const handleCommand = (e) => {
//先在这里修改当前语言
i18n.locale.value = e;
//再通过vuex存入本地 下次进入页面时会先读取本地语言
store.commit("setLanguage", e);
emit('changeMenus')
};
</script>
<style lang="less" scoped>
.block-col-2 .demonstration {
display: block;
color: var(--el-text-color-secondary);
font-size: 14px;
margin-bottom: 20px;
}
.language {
width: 35px;
height: 35px;
background-color: #fff;
border-radius: 5px;
box-shadow: 1px 1px 5px 1px #000;
position: relative;
cursor: pointer;
.cn {
width: 18px;
height: 18px;
text-align: center;
line-height: 18px;
border-radius: 2px;
font-size: 14px;
font-weight: 700;
border: 0.5px solid #999;
position: absolute;
top: 3px;
left: 3px;
z-index: 100;
background-color: #fff;
}
.en {
width: 18px;
height: 18px;
text-align: center;
line-height: 18px;
border-radius: 2px;
font-size: 14px;
font-weight: 900;
background-color: rgb(82, 82, 82);
color: white;
position: absolute;
right: 3px;
bottom: 3px;
}
}
</style>
组件效果图如下
主题切换
这里我只是简单完成了主题切换功能,并没有使用element-plus的主题色
首先需要通过element-plus的颜色选择器来获取将要修改的主题颜色
这个组件有一个回调函数可以获取到当前颜色的16进制的值,可以通过这个值存入vuex来进行主题的切换,比如
:background-color="$store.state.mainColor"
:style="$store.state.mainColor != ''? 'border-color:' + $store.state.mainColor: false "
切换/取消全屏显示
这里是使用H5新增的api来完成的 ,不同浏览器的api不相同,考虑到兼容的问题,所以都写上了
if (document.documentElement.RequestFullScreen) {
document.documentElement.RequestFullScreen();
}
//兼容火狐
console.log(document.documentElement.mozRequestFullScreen);
if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
}
//兼容谷歌等可以webkitRequestFullScreen也可以webkitRequestFullscreen
if (document.documentElement.webkitRequestFullScreen) {
document.documentElement.webkitRequestFullScreen();
}
//兼容IE,只能写msRequestFullscreen
if (document.documentElement.msRequestFullscreen) {
document.documentElement.msRequestFullscreen();
}
f11Flag.value = true;
} else {
if (document.exitFullScreen) {
document.exitFullscreen();
}
//兼容火狐
console.log(document.mozExitFullScreen);
if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
}
//兼容谷歌等
if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
//兼容IE
if (document.msExitFullscreen) {
document.msExitFullscreen();
}
excel导入导出
这里的导入有两种方式,分别为按钮上传和拖拽上传,这里需要使用xlsx插件,他的作用是用来解析xlsx文件
先分析一下两种上传文件的方式,上传之后的文件解析是完全一样的逻辑,所以放到最后说
先分析下点击上传
导入
按钮导入
//上传的按钮
<el-button type="primary" :loading="loading" @click="handleUpload">{{
$t("msg.uploadExcel.upload")
}}</el-button>
//上传文件的input
<input
//type为文件上传类型
type="file"
//ref为后边触发点击事件做准备
ref="excelUploadInput"
class="excel-upload-input"
//限制上传文件的格式
accept=".xlsx,xlx"
//选择文件后触发的函数
@change="handelChange"
/>
在点击上传之后触发上传文本事件
const handleUpload = () => {
excelUploadInput.value.click();
};
在选择文件之后就会触发input的change事件
//e就是当前上传的文件
const handelChange = (e) => {
//因为这里一次只上传一个文件 所以可以直接通过下标0来获取
const rawFile = e.target.files[0];
//如果该文件则return
if (!rawFile) return;
//如果有的话则触发upload函数
uoload(rawFile);
};
拖拽导入
upload函数代码如下
<div
class="drop"
@drop.stop.prevent="handleDorp"
@dragover.stop.prevent="handelDragover"
@dragenter.stop.prevent="handelDragover"
>
<i class="el-icon-upload"></i>
<span>{{ $t("msg.uploadExcel.drop") }}</span>
</div>
这里提一下拖拽事件
ondragenter 当被鼠标拖动的对象进入其容器范围内时触发此事件
ondragover 当被拖动的对象在另一对象容器范围内拖动时触发此事件
ondragleave 当被鼠标拖动的对象离开其容器范围内时触发此事件
ondrop 在一个拖动过程中,释放鼠标键时触发此事件
//触发上传事件
const uoload = (rawFile) => {
//先将input的value.value置为空
excelUploadInput.value.value = null;
//如果用户没有指定上传前回调的话
if (!props.beforeUpload) {
//执行解析函数
readerData(rawFile);
return;
}
//如果用户指定了上传前回调,那么只有返回为true的时候才会执行解析函数
const before = props.beforeUpload(rawFile);
if (before) {
readerData(rawFile);
}
};
拖拽上传
//拖拽上传
const handleDorp = (e) => {
//如果当前文件正在上传中 return掉
if (loading) return;
// 获取当前上传的文件
const files = e.dataTansfer.files;
// 判断文件数量
if (files.length !== 1) {
ElMessage.error("必须要有一个文件");
return;
}
// 因为获得的是一个数组 所以需要通过下标0取其中第一个
const rawFile = files[0];
// 判断文件格式 这里是自己封装的方法 代码在下边
if (!isExcel(rawFile)) {
ElMessage.error("文件必须是.xlsx,xls,csv格式");
return;
}
// 解析
upload(rawFile);
};
判断文件格式封装,也是通过正则实现的
export const isExcel = file => {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
readerData函数代码如下
//读取数据
const readerData = (rawFile) => {
loading.value = true;
return new Promise((resolve, reject) => {
const reader = new FileReader();
// 读取操作完成时
reader.onload = (e) => {
//1获取解析后的数据
const data = e.target.result;
// console.log(data);
//2利用xslx解析数据
const workbook = XLSX.read(data, { type: "array" });
//3获取第一张表格
const firstSheetName = workbook.SheetNames[0];
// //4读取第一张biogenic的数据
const workSheet = workbook.Sheets[firstSheetName];
// // 5解析数据表头 这一步由于都是一摸一样的 百度一搜一大把 就不自己写了
const header = getHeaderRow(workSheet);
// //6解析数据体
const results = XLSX.utils.sheet_to_json(workSheet);
// // 7传输解析之后的数据
console.log(header, results);
generateData({ header, results });
// //8处理loading
loading.value = false;
// 9成功回调
resolve();
};
reader.readAsArrayBuffer(rawFile);
});
};
//通过回调将处理之后的数据返回给父组件
const generateData = (excelData) => {
props.onSuccess && props.onSuccess(excelData);
};
解析表头封装
import XLSX from 'xlsx'
/**
* 获取表头(通用方式)
*/
export const getHeaderRow = sheet => {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) {
/* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
}
export const isExcel = file => {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
解析完成后向父组件传递事件
const props = defineProps({
beforeUpload: Function,
onSuccess: Function,
});
父组件中直接使用传递过来的onSuccess来接收
//dom
<uploadExcel :onSuccess="onSuccess"></uploadExcel>
//js
const onSuccess = async ({ header, results }) => {
//由于上传文件之后的key是中文的 所以需要通过函数来将key设置为可供上传的key
const upDateData = generateData(results)
await userBatchImport(upDateData);
};
const generateData = (results) => {
const arr = [];
results.forEach((item) => {
const userInfo = [];
Object.keys(item).forEach((key) => {
//USER_RELATIONS是自己导入的方法,需要根据文件的key替换上传所需要的key 代码在下方
userInfo[USER_RELATIONS[key]] = item[key];
});
arr.push(userInfo);
});
return arr;
};
export const USER_RELATIONS = {
姓名: 'username',
联系方式: 'mobile',
角色: 'role',
开通时间: 'openTime'
}
到这里 导入就完成了
excel导出
导出这里呢需要先弹出一个dialog,输入导出文件的名字
在点击导出按钮之后的逻辑处理如下
const onComfirm = async () => {
//先将按钮加载置为true
loading.value = true;
//声明变量接收所有角色列表
const allUser = (await getUserManageAllList()).data.list;
//这里接收一个封装的方法,也是可以百度到的 下方赋代码1
const excel = await import("@/utils/Export2Excel");
//这里的formatJson是处理excel的表头,将后台的数据改为对应表格的表头
const data = formatJson(USER_RELATIONS, allUser);
//这里是将数据处理为可以被excel打开的格式,下方赋代码2
excel.export_json_to_excel({
header: Object.keys(USER_RELATIONS),
data,
filename:excelName.value || exportDfauleName
});
};
附代码1
/* eslint-disable */
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'
function datenum(v, date1904) {
if (date1904) v += 1462
var epoch = Date.parse(v)
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {}
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
}
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R
if (range.s.c > C) range.s.c = C
if (range.e.r < R) range.e.r = R
if (range.e.c < C) range.e.c = C
var cell = {
v: data[R][C]
}
if (cell.v == null) continue
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
})
if (typeof cell.v === 'number') cell.t = 'n'
else if (typeof cell.v === 'boolean') cell.t = 'b'
else if (cell.v instanceof Date) {
cell.t = 'n'
cell.z = XLSX.SSF._table[14]
cell.v = datenum(cell.v)
} else cell.t = 's'
ws[cell_ref] = cell
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
return ws
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook()
this.SheetNames = []
this.Sheets = {}
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
}
export const export_json_to_excel = ({
multiHeader = [],
header,
data,
filename,
merges = [],
autoWidth = true,
bookType = 'xlsx'
} = {}) => {
// 1. 设置文件名称
filename = filename || 'excel-list'
// 2. 把数据解析为数组,并把表头添加到数组的头部
data = [...data]
data.unshift(header)
// 3. 解析多表头,把多表头的数据添加到数组头部(二维数组)
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
// 4. 设置 Excel 表工作簿(第一张表格)名称
var ws_name = 'SheetJS'
// 5. 生成工作簿对象
var wb = new Workbook()
// 6. 将 data 数组(json格式)转化为 Excel 数据格式
var ws = sheet_from_array_of_arrays(data)
// 7. 合并单元格相关(['A1:A2', 'B1:D1', 'E1:E2'])
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = []
merges.forEach((item) => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
// 8. 单元格宽度相关
if (autoWidth) {
/*设置 worksheet 每列的最大宽度*/
const colWidth = data.map((row) =>
row.map((val) => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
wch: 10
}
} else if (val.toString().charCodeAt(0) > 255) {
/*再判断是否为中文*/
return {
wch: val.toString().length * 2
}
} else {
return {
wch: val.toString().length
}
}
})
)
/*以第一行为初始值*/
let result = colWidth[0]
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch']
}
}
}
ws['!cols'] = result
}
// 9. 添加工作表(解析后的 excel 数据)到工作簿
wb.SheetNames.push(ws_name)
wb.Sheets[ws_name] = ws
// 10. 写入数据
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
})
// 11. 下载数据
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
`${filename}.${bookType}`
)
}
赋代码2
export const USER_RELATIONS = {
姓名:'username',
电话:'mobile',
角色:'role',
开通时间:'openTime',
}
这个时候就可以将文件下载到本地了
更多推荐
所有评论(0)