使用组件需先下载 element ui vue2
效果图
组件封装
这段代码是一个封装的分页表格组件。它使用了Vue.js框架,并结合了Element UI中的el-table和el-pagination组件来实现。该组件接受一系列属性作为数据源,包括列表数据(list)、表格列(columns)、操作按钮列(operates)、总数(total)、分页参数(pagination)等。
在模板中,使用el-table组件来展示表格数据,使用el-pagination组件来实现分页功能。el-table-column用来定义每列的样式和展示内容。组件中还包括其他的一些方法和事件处理函数,如handleSizeChange用于切换每页显示的数量,handleIndexChange用于切换页码,handleSelectionChange处理多行选中等。
此外,还定义了一些样式规则来美化表格的显示效果,如设置表头颜色、调整列的样式、设置操作按钮组的布局、设置筛选弹窗和表格操作弹窗的样式等。
最后,使用了scss来编写样式表,通过设置类名来控制样式的展示。
- 在
<template>
标签中,定义了一个表格组件,包括表格本身,列定义,按钮操作组,和分页控件。 - 在
<script>
标签中,导入了必要的依赖,并定义了组件的属性(props)、数据(data)、生命周期钩子(activated、beforeDestroy、deactivated、created、mounted)和方法(methods)。 props
属性用于接受从父组件传递进来的数据,如数据列表list
、列定义columns
、按钮操作operates
、总数total
、分页参数pagination
等。data
数据属性包括一些内部状态,如表格高度height
、监听窗口调整大小的回调函数$_resizeHandler
、当前页码pageIndex
、表格分页信息tableCurrentPagination
、多行选中数据multipleSelection
等。- 在生命周期钩子中,组件监听窗口大小调整事件,自动调整表格高度,并在适当的生命周期中初始化和销毁事件监听器。
methods
包括一系列方法,如计算表头宽度的方法headSpanFit
、初始化事件监听器的方法initListener
、销毁事件监听器的方法destroyListener
、调整表格高度的方法resize
、处理每页显示数量变化的方法handleSizeChange
、处理页码变化的方法handleIndexChange
、处理多行选中的方法handleSelectionChange
等。- 最后,在
<style>
标签中定义了一些样式,包括表格样式、分页样式、按钮样式、筛选和操作弹窗样式等。
<!--region 封装的分页 table-->
<!--region 封装的分页 table-->
<template>
<div class="table">
<el-table
id="iTable"
ref="mutipleTable"
:row-class-name="tableRowClassName"
:header-cell-style="getRowClass"
v-loading="options.loading"
:border="options.border"
:data="list"
:height="noStatic ? customHeight : height"
:max-height="noStatic ? customHeight : height"
v-bind="options"
@selection-change="handleSelectionChange"
@sort-change="sortChange"
>
<!--region 选择框-->
<el-table-column
v-if="options.mutiSelect"
type="selection"
style="width: 60px"
:selectable="options.selectable"
>
</el-table-column>
<!--endregion-->
<!--region 序号-->
<el-table-column
v-if="options.numbers"
width="60"
type="index"
label="序号"
align="center"
></el-table-column>
<!--endregion-->
<!--region 数据列-->
<template v-for="(column, index) in columns">
<el-table-column
v-if="isShowColumn(column)"
:min-width="headSpanFit(column)"
:key="index"
:prop="column.prop"
:label="column.label"
:align="column.align"
:width="column.width"
v-bind="column.el"
>
<template slot-scope="scope">
<template v-if="!column.render">
<template v-if="column.formatter">
<span v-html="column.formatter(scope.row, column)"></span>
</template>
<template v-else>
<span>{{
scope.row[column.prop] === 0 ? 0 : scope.row[column.prop] || "--"
}}</span>
</template>
</template>
<template v-else>
<expand-dom
:column="column"
:row="scope.row"
:render="column.render"
:index="index"
></expand-dom>
</template>
</template>
</el-table-column>
</template>
<!--endregion-->
<!--region 按钮操作组-->
<el-table-column
ref="fixedColumn"
label="操作"
align="center"
:width="operates && operates.width"
:fixed="operates && operates.fixed"
v-if="operates && operates.list.length > 0"
>
<template slot-scope="scope">
<expand-dom
:row="scope.row"
:render="renderOperates"
:index="scope.$index"
></expand-dom>
</template>
</el-table-column>
<!--endregion-->
</el-table>
<div style="height: 12px"></div>
<!--region 分页-->
<el-pagination
style="float: none; text-align: right"
v-if="pagination"
:pager-count="5"
@size-change="handleSizeChange"
@current-change="handleIndexChange"
:page-size="tableCurrentPagination.pageSize"
:page-sizes="this.tableCurrentPagination.pageArray"
:current-page="tableCurrentPagination.pageIndex"
layout="total,sizes, prev, pager, next,jumper"
:total="total"
></el-pagination>
<!--endregion-->
</div>
</template>
<!--endregion-->
<script>
import { debounce } from "@/utils";
import { checkPermi } from "@/utils/permission.js";
const _pageArray = [20, 50, 100]; // 每页展示条数的控制集合
export default {
props: {
list: {
type: Array,
default: [], // prop:表头绑定的地段,label:表头名称,align:每列数据展示形式(left, center, right),width:列宽
}, // 数据列表
columns: {
type: Array,
default: [], // 需要展示的列 === prop:列数据对应的属性,label:列名,align:对齐方式,width:列宽
},
operates: {
type: Object,
defaultt: () => {}, // width:按钮列宽,fixed:是否固定(left,right),按钮集合 === label: 文本,type :类型(primary / success / warning / danger / info / text),show:是否显示,icon:按钮图标,plain:是否朴素按钮,disabled:是否禁用,method:回调方法
},
total: {
type: Number,
default: 0,
}, // 总数
pagination: {
type: Object,
default: null, // 分页参数 === pageSize:每页展示的条数,pageIndex:当前页,pageArray: 每页展示条数的控制集合,默认 _page_array
},
noStatic: false, // 是否计算表格高度
customHeight: {
//与noStatic一起使用
type: Number,
default: 320,
},
otherHeight: {
type: Number,
default: 180,
}, // 计算表格的高度
options: {
type: Object,
default: {
stripe: false, // 是否为斑马纹 table
loading: false, // 是否添加表格loading加载动画
highlightCurrentRow: false, // 是否支持当前行高亮显示
mutiSelect: false, // 是否支持列表项选中功能
border: false, //是否显示边框
selectable: () => {
//是否可以选中
return false;
},
},
}, // table 表格的控制参数
},
components: {
expandDom: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null,
},
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index,
};
if (ctx.props.column) params.column = ctx.props.column;
return ctx.props.render(h, params);
},
},
},
data() {
return {
height: 250,
$_resizeHandler: null,
pageIndex: 1,
tableCurrentPagination: {},
multipleSelection: [], // 多行选中
};
},
activated() {
// 通常是在使用 Vue.js 的 <keep-alive> 包装时,组件会被缓存并在重新激活时调用这个钩子函数。
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener();
}
// when keep-alive chart activated, auto resize
this.resize();
},
//当组件即将被销毁(beforeDestroy)时,会调用这个钩子函数。
beforeDestroy() {
this.destroyListener();
},
//当组件被停用(deactivated),通常也是在 <keep-alive> 包装下,组件会调用这个钩子函数。
deactivated() {
this.destroyListener();
},
created() {},
mounted() {
this.initListener();
if (this.pagination && !this.pagination.pageSizes) {
this.pagination.pageArray = _pageArray; // 每页展示条数控制
}
this.tableCurrentPagination = this.pagination || {
pageSize: this.total,
pageIndex: 1,
}; // 判断是否需要分页
},
methods: {
//计算小列宽
headSpanFit(column) {
let labelLong = column.label.length; // 表头label长度
let size = 20; // 根据需要定义标尺,直接使用字体大小确定就行,也可以根据需要定义
let minWidth = labelLong * size < 100 ? 100 : labelLong * size; // 根据label长度计算该表头最终宽度
return minWidth;
},
// 初始化监听器的方法。
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize();
}, 200);
window.addEventListener("resize", this.$_resizeHandler);
this.$nextTick(() => {
this.resize();
});
},
//这是一个销毁监听器的方法。
destroyListener() {
window.removeEventListener("resize", this.$_resizeHandler);
this.$_resizeHandler = null;
},
//计算表格的高度
resize() {
// 不用计算
if (this.noStatic) {
return;
}
const { mutipleTable } = this.$refs;
let staticHeight =
window.innerHeight - this.$refs.mutipleTable.$el.offsetTop - this.otherHeight;
this.height = staticHeight < 250 ? 250 : staticHeight;
console.log(this.height);
//保表格的布局在高度调整后得以更新。
mutipleTable && mutipleTable.doLayout();
},
// 切换每页显示的数量
handleSizeChange(size) {
if (this.pagination) {
this.tableCurrentPagination = {
pageIndex: 1,
pageSize: size,
};
this.$emit("handleSizeChange", this.tableCurrentPagination);
}
},
// 切换页码
handleIndexChange(currnet) {
if (this.pagination) {
this.tableCurrentPagination.pageIndex = currnet;
this.$emit("handleIndexChange", this.tableCurrentPagination);
}
},
// 多行选中
handleSelectionChange(val) {
this.multipleSelection = val;
this.$emit("handleSelectionChange", val);
},
// 用于渲染操作按钮的方法
renderOperates(h, params) {
const endArr = this.rebuildList(params); //权限验证后最终按钮数组
let outSideBtnArr = endArr.slice(0, 2); //外部按钮 默认前两个
let insideArr = endArr.slice(2); //下拉菜单按钮数组
const buttonArr = []; //最终渲染数组
outSideBtnArr.forEach((item) => {
buttonArr.push(this.renderOutsideButton(h, item, params));
});
if (insideArr.length > 0) {
buttonArr.push(this.renderDropdownButton(h, insideArr, params));
}
return h("div", { attrs: { class: "operate-group" } }, buttonArr);
},
// 渲染外部按钮
renderOutsideButton(h, item, params) {
return h(
"el-button",
{
// 组件的属性(数据)
props: {
type: item.type || "text", //类型(primary / success / warning / danger / info / text)
icon: item.icon || "", //icon:按钮图标
plain: item.plain || false, //plain:是否朴素按钮
size: item.size || "mini", //大小
},
// 组件的属性(html属性)
attrs: {
title: item.label, //label: 文本
},
// 样式
style: {
color: item.label == "删除" ? "#ff4057" : "",
},
// 按钮的点击事件处理函数
on: {
click: () => {
item.method(params.index, params.row);
},
},
},
item.label //label: 文本
);
},
// 渲染下拉按钮
renderDropdownButton(h, insideArr, params) {
return h(
// 创建了一个 "el-dropdown" 组件
"el-dropdown",
// dropdown 样式
{
class: ["custom-dropdown"],
// 组件的属性(数据):
props: {
trigger: "click", //下拉框点击触发
},
},
[
// "el-dropdown" 组件中的button
h(
"el-button",
{
// 按钮样式
style: {
fontSize: "18px",
},
// 按钮类名
class: ["custom-text"],
props: {
type: "text",
plain: false,
size: "mini",
},
},
// 在按钮的内容中,使用了一个 "i" 标签,其 class 属性为 "el-icon-more",显示一个图标。
[h("i", { class: "el-icon-more" })]
),
// 创建了一个 "el-dropdown-menu" 组件
h(
"el-dropdown-menu",
// 传入slot
{
slot: "dropdown",
},
// 对传入的按钮循环渲染出el-dropdown-item"
insideArr.map((item) => {
return h(
"el-dropdown-item",
{
nativeOn: {
click: () => {
item.method(params.index, params.row);
},
},
},
item.label
);
})
),
]
);
},
// 校验权限
rebuildList({ row }) {
// 检查 operates 是否为空对象
if (!this.operates || !this.operates.list || !Array.isArray(this.operates.list)) {
return [];
}
// 过滤操作项
return this.operates.list.filter((item) => {
// 如果定义了 show 函数,则根据该函数的返回值来决定是否显示
if (typeof item.show === "function") {
return item.show(row);
}
// 如果 show 是布尔值且为 true,则显示
if (typeof item.show === "boolean" && item.show) {
return true;
}
// 如果定义了权限要求,则检查权限
if (item.haspermission) {
return checkPermi(item.haspermission);
}
// 默认允许显示
return true;
});
},
// 判断列的数据是否显示
isShowColumn(column) {
console.log(column);
if (column.show === undefined) {
return true;
}
if (typeof column.show !== "function") {
return column.show;
}
return column.show(column);
},
// 排序
sortChange({ column, prop, order }) {
this.$emit("sortChange", { column, prop, order });
},
// 每一行的样式
tableRowClassName({ rowIndex }) {
if (rowIndex % 2 === 0) {
return "warning-row";
} else if (rowIndex % 2 === 1) {
return "success-row";
}
return "";
},
// 表头样式
getRowClass({ rowIndex }) {
if (rowIndex == 0) {
return this.options.headerCellStyle || "";
} else {
return " ";
}
},
},
};
</script>
<style lang="scss">
.table {
height: 100%;
.el-pagination {
float: right;
margin: 20px;
}
.el-table__header-wrapper,
.el-table__fixed-header-wrapper {
thead {
tr {
th {
color: #333333;
}
}
}
}
.el-table-column--selection .cell {
padding: 0;
text-align: center;
}
.el-table__fixed-right {
bottom: 0 !important;
right: 6px !important;
z-index: 1004;
}
.operate-group {
display: flex;
flex-wrap: wrap;
.item {
margin-top: 4px;
margin-bottom: 4px;
display: block;
flex: 0 0 50%;
}
}
.filter-data {
top: e("calc((100% - 100px) / 3)");
background-color: rgba(0, 0, 0, 0.7);
}
.table-action {
top: e("calc((100% - 100px) / 2)");
background-color: rgba(0, 0, 0, 0.7);
}
.fix-right {
position: absolute;
right: 0;
height: 100px;
color: #ffffff;
width: 30px;
display: block;
z-index: 1005;
writing-mode: vertical-rl;
text-align: center;
line-height: 28px;
border-bottom-left-radius: 6px;
border-top-left-radius: 6px;
cursor: pointer;
}
.custom-dropdown {
color: #666;
&:hover {
color: inherit;
}
}
.operate-group {
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100%;
.item {
margin-right: 2px;
display: block;
flex: 0 1 auto;
}
}
.el-button--text.custom-text {
span {
vertical-align: middle;
}
color: #666;
&:hover {
color: #66b1ff;
}
}
}
</style>
parseTime函数
用于页面使用格式化时间对象
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
debounce 函数
用在封装监听窗口变化的节流函数
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
render通用渲染模版
丰富表格的展现形式
// renderUtils.js
// 渲染标签
export function customRender({ h, params, fieldToCheck, textMapping }) {
const fieldValue = params.row[fieldToCheck];
const type = textMapping.hasOwnProperty(fieldValue)
? textMapping[fieldValue].type
: "default"; // 默认类型,可以根据需要修改
const labelText = textMapping.hasOwnProperty(fieldValue)
? textMapping[fieldValue].text
: '未知'; // 默认显示字段值,可以根据需要修改
return h("el-tag", { props: { type } }, labelText);
}
// 渲染按钮
export function renderSwitch({ h, params, size, fieldToCheck }) {
return h("el-switch", {
props: {
size: size || "medium",
value: params.row[fieldToCheck],
},
on: {
change: (events) => {
this.$set(params.row, fieldToCheck, events);
this.changeMsgStatus(events, params);
},
},
});
}
// link
export function renderLink(h, params, title) {
return h(
"el-link",
{
props: {
type: "primary",
underline: false,
},
on: {
click: (e) => {
this.handleDetail(params);
},
},
},
title
);
}
// 头像
export function avatarElement({ h, params, fieldToCheck, size }) {
return h(
"el-avatar",
{
props: {
size: size || 44,
src: params.row[fieldToCheck],
},
// 图片加载失败展示默认图片
on: {
error: (e) => {
return true;
},
},
},
[
h("img", {
attrs: {
src: require("@/assets/images/default_avatar.png"),
},
}),
]
);
}
页面使用
<template>
<div class="table-page">
<!--region table 表格-->
<app-table :list="list" :total="total" :otherHeight="otherHeight" :options="options" :pagination="pagination"
:columns="columns" :operates="operates" @handleSizeChange="handleSizeChange" @handleIndexChange="handleIndexChange"
@handleSelectionChange="handleSelectionChange" @sortChange="sortChange">
</app-table>
<!--endregion-->
</div>
</template>
<script>
import { parseTime } from "@/utils/ruoyi";
import {
customRender,
renderSwitch,
renderLink,
avatarElement,
} from "@/utils/renderUtils.js";
export default {
data() {
return {
total: 0,
list: [
{
id: 1,
title: "标题",
state: 2,
author: "张三",
phone: "12346788901",
email: "[email protected]",
createDate: "2023-04-23 16:11:38",
zero: null,
isOpend: false,
headimgurl: 'https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png'
},
],
otherHeight: 208,
columns: [
{
prop: "id",
label: "编号",
align: "center",
el: {
// element ui的一些props...
sortable: true, //开启排序
},
},
{
prop: "title",
label: "标题",
align: "center",
formatter: (row, column, cellValue) => {
return `<span style="white-space: nowrap;color: dodgerblue;">${row.title}</span>`;
},
},
{
prop: "state",
label: "状态",
align: "center",
width: "160",
render: (h, params) => {
const fieldToCheck = "state";
const textMapping = {
0: { type: "success", text: "上架" },
1: { type: "info", text: "下架" },
2: { type: "danger", text: "审核中" },
};
let data = { h, params, fieldToCheck, textMapping }
return customRender.call(this, data);
},
},
{
prop: "switch",
label: "开关",
align: "center",
width: "160",
render: (h, params) => {
let data = {
h, params,
fieldToCheck: 'isOpend',
size: 'medium'
}
return renderSwitch.call(this, data);
},
},
{
prop: "headimgurl",
label: "头像",
align: "center",
render: (h, params) => {
let data = {
h, params,
fieldToCheck: 'headimgurl',
size: 44
}
return avatarElement.call(this, data);
},
},
{
prop: "author",
label: "作者",
align: "center",
width: 120,
},
{
prop: "phone",
label: "联系方式",
align: "center",
width: 160,
show: false, //控制这一列是否展示
},
{
prop: "zero",
label: "邮箱",
align: "center",
width: 240,
},
{
prop: "link",
label: "查看",
align: "center",
width: "160",
render: (h, params) => {
return renderLink.call(this, h, params, '查看');
},
},
{
prop: "createDate",
label: "发布时间",
align: "center",
width: 180,
formatter: (row, column, cellValue) => {
return parseTime(row.createDate);
},
},
], // 需要展示的列
operates: {
width: 200,
fixed: "right",
list: [
{
label: "编辑",
type: "text",
show: (index, row) => {
return true;
},
icon: "el-icon-edit",
disabled: false,
method: (index, row) => {
this.handleEdit(index, row);
},
},
{
label: "删除",
type: "text",
icon: "el-icon-delete",
show: true,
disabled: (index, row) => {
return false;
},
method: (index, row) => {
this.handleDel(index, row);
},
},
{
label: "测试下拉",
type: "text",
icon: "el-icon-delete",
haspermission: ["agent:del"], //显示权限
show: true,
disabled: (index, row) => {
return false;
},
method: (index, row) => {
this.handleDel(index, row);
},
},
],
}, // 操作按钮组
pagination: {
pageIndex: 1,
pageSize: 20,
}, // 分页参数
options: {
stripe: true, // 是否为斑马纹 table
loading: false, // 是否添加表格loading加载动画
highlightCurrentRow: true, // 是否支持当前行高亮显示
mutiSelect: true, // 是否支持列表项选中功能
border: true, //是否显示边框
numbers: true, //是否显示序号
selectable() {
//禁用选中
return false;
},
headerCellStyle: "background-color:#fff", //表头颜色
}, // table 的参数
};
},
mounted() { },
methods: {
// 切换每页显示的数量
handleSizeChange(pagination) {
console.log("pagination", pagination);
},
// 切换页码
handleIndexChange(pagination) {
console.log("pagination", pagination);
},
// 选中行
handleSelectionChange(val) {
console.log("val:", val);
},
// 编辑
handleEdit(index, row) {
console.log(" index:", index);
console.log(" row:", row);
},
// 删除
handleDel(index, row) {
console.log(" index:", index);
console.log(" row:", row);
},
// 排序
sortChange(data) {
console.log(data);
},
// 开关按钮
changeMsgStatus(ev, params) {
console.log(ev, params);
},
handleDetail(params) {
console.log(params);
},
},
};
</script>
【扩展】vue 函数式组件
函数式组件特点:
- 没有管理任何状态
- 没有监听任何传递给它的状态
- 没有生命周期方法
- 它只是接收一些
prop
的函
我们将这样的组件标记为
functional
:
- 无状态 == 无响应式数据
- 无实例 == 无
this
上下文
函数式组件的优点:
- 渲染开销低,因为函数式组件只是函数;
{
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
}
props
: 提供所有
prop
的对象
children:VNode
子节点的数组
slots
: 一个函数,返回了包含所有插槽的对象
scoptedSlots
:(2.6.0) 一个暴露传入的作用域插槽的对象,也以函数形式暴露普通插槽
data
:传递个组件的整个数据对象,作为
createElement
的第二个参数传入组件
parent
:对父组件的引用
listeners
:(2.3.0+) 一个包含了:所有父组件为当前组件祖册的事件监听器对象,是
data.on
的一个别名
injections
:(2.3.0+) 如果使用了
inject
选项,则改对象包含了:应当被注入的属性;
【扩展】vue中的render函数
一、初步认识render函数
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App)
})
在使用脚手架创建vue项目的过程,我们很容易看到render这个函数,相对于其他标签,我们对于render还是比较陌生的,因此写下这篇文章你我共同理解。
二、为什么使用render函数
VUE推荐在绝大多数情况下使用template来创建我们的HTML。然而在一些场景中,我们真的需要JavaScript的完全编程的能力,这就是render函数,它比template更接近编译器。(这是官方的话)
简单来说,我们为什么要使用render函数呢?? 便是因为我们最经常使用的一个引入。
import Vue from "vue";
这一个引入你看似没有任何问题,但问题恰恰就是出在这。在不同版本的vue中,有vue.js和vue.runtime.xxx.js这两种js文件。其中
(1)vue.js是完整版的vue,包含核心功能+模板解析器。
(2)vue.runtime.xxx.js是运行版vue,只包含核心功能,没有模板解析器。
VUE开发者为了让我们打包的文件能尽可能小一点,在上述引入的是运行版vue。因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,这时候就需要使用render函数去接收到的createElement函数去指定具体内容,创建html模板。
三、render函数的解析
render 函数即渲染函数,它是个函数,它的参数 createElement 也是个函数。
上边的代码中 render: h => h(App) ,这是 ES6的箭头函数的写法,可以把 h 当作 createElement 的别名。所以这段代码其实相当于
render: function (createElement) {
return createElement(App)
}
这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。
createElement 函数的返回值是 VNode(即:虚拟节点)
createElement 函数的3个参数
- 一个 HTML 标签字符串,组件选项对象,或者解析上述任何一种的一个 async 异步函数。类型:String | Object | Function。必需。
- 一个包含模板相关属性的数据对象,你可以在 template 中使用这些特性。类型:Object。可选。
- 子虚拟节点 (VNodes),由 createElement() 构建而成,也可以使用字符串来生成“文本虚拟节点”。类型:String | Array。可选
new Vue({
el: '#app',
render:function (createElement) {
//1.普通用法
// createElement(标签,{属性},[内容])
return createElement("h2",{class:"box"},['hello',createElement("button",["按钮"])])
}
})
同时createElement也可以传进去一个组件,因此
render: h => h(App)
等同于
render:function (createElement) {
return createElement(App)
}
【扩展】添加操作栏显示权限
结构改动
通过函数式组件渲染 操作按钮部分
<el-table-column
ref="fixedColumn"
label="操作"
align="center"
:width="operates.width"
:fixed="operates.fixed"
v-if="operates.list.length > 0"
>
<template slot-scope="scope">
<expand-dom
:row="scope.row"
:render="renderOperates"
:index="scope.$index"
></expand-dom>
</template>
</el-table-column>
逻辑新增
这段代码是一个Vue.js组件方法,用于渲染操作按钮。让我逐步解释代码的主要部分:
renderOperates
方法:这是一个渲染操作按钮的方法。它接受两个参数h
和params
,其中h
是Vue的createElement函数,用于创建虚拟DOM,而params
包含一些参数数据。const endArr = this.rebuildList(params);
:这一行调用了rebuildList
方法,它会根据权限验证后的结果生成最终的按钮数组,然后将这个数组存储在endArr
变量中。let outSideBtnArr = endArr.slice(0, 2);
和let insideArr = endArr.slice(2);
:这两行将endArr
分为两个部分,前两个按钮存储在outSideBtnArr
中,其余的按钮存储在insideArr
中。const buttonArr = [];
:创建一个空数组buttonArr
,用于存储最终要渲染的按钮。outSideBtnArr.forEach((item) => { ... });
:这是一个循环遍历outSideBtnArr
的循环,对每个按钮调用renderOutsideButton
方法进行渲染,然后将渲染结果添加到buttonArr
中。if (insideArr.length > 0) { ... }
:这是一个条件判断,如果insideArr
中有按钮,则调用renderDropdownButton
方法进行渲染,然后将渲染结果添加到buttonArr
中。最后,使用
h("div", { attrs: { class: "operate-group" } }, buttonArr)
创建一个<div>
元素,设置其类名为 "operate-group",并将buttonArr
中的按钮渲染到这个<div>
元素中,最终返回这个<div>
元素的虚拟DOM。
接下来,代码中还包含了两个方法 renderOutsideButton
和 renderDropdownButton
,它们分别用于渲染外部按钮和下拉按钮。这两个方法的主要作用是创建相应的按钮元素,并设置按钮的属性、样式和点击事件处理函数。
最后,代码中还包括一个 rebuildList
方法,用于根据权限验证结果来生成最终的按钮数组。它会遍历操作按钮列表,根据按钮的显示权限和角色身份权限来判断是否允许显示该按钮,然后返回允许显示的按钮数组。
总的来说,这段代码用于动态生成操作按钮组件,根据权限和角色身份权限来控制按钮的显示和行为。
// 用于渲染操作按钮的方法
renderOperates(h, params) {
const endArr = this.rebuildList(params); //权限验证后最终按钮数组
let outSideBtnArr = endArr.slice(0, 2); //外部按钮 默认前两个
let insideArr = endArr.slice(2); //下拉菜单按钮数组
const buttonArr = []; //最终渲染数组
outSideBtnArr.forEach((item) => {
buttonArr.push(this.renderOutsideButton(h, item, params));
});
if (insideArr.length > 0) {
buttonArr.push(this.renderDropdownButton(h, insideArr, params));
}
return h("div", { attrs: { class: "operate-group" } }, buttonArr);
},
// 渲染外部按钮
renderOutsideButton(h, item, params) {
return h(
"el-button",
{
// 组件的属性(数据)
props: {
type: item.type || "text", //类型(primary / success / warning / danger / info / text)
icon: item.icon || "", //icon:按钮图标
plain: item.plain || false, //plain:是否朴素按钮
size: item.size || "mini", //大小
},
// 组件的属性(html属性)
attrs: {
title: item.label, //label: 文本
},
// 样式
style: {
color: item.label == "删除" ? "#ff4057" : "",
},
// 按钮的点击事件处理函数
on: {
click: () => {
item.method(params.index, params.row);
},
},
},
item.label //label: 文本
);
},
// 渲染下拉按钮
renderDropdownButton(h, insideArr, params) {
return h(
// 创建了一个 "el-dropdown" 组件
"el-dropdown",
// dropdown 样式
{
class: ["custom-dropdown"],
// 组件的属性(数据):
props: {
trigger: "click", //下拉框点击触发
},
},
[
// "el-dropdown" 组件中的button
h(
"el-button",
{
// 按钮样式
style: {
fontSize: "18px",
},
// 按钮类名
class: ["custom-text"],
props: {
type: "text",
plain: false,
size: "mini",
},
},
// 在按钮的内容中,使用了一个 "i" 标签,其 class 属性为 "el-icon-more",显示一个图标。
[h("i", { class: "el-icon-more" })]
),
// 创建了一个 "el-dropdown-menu" 组件
h(
"el-dropdown-menu",
// 传入slot
{
slot: "dropdown",
},
// 对传入的按钮循环渲染出el-dropdown-item"
insideArr.map((item) => {
return h(
"el-dropdown-item",
{
nativeOn: {
click: () => {
item.method(params.index, params.row);
},
},
},
item.label
);
})
),
]
);
},
// 校验权限
rebuildList({ row }) {
// 验证是否显示权限和角色身份权限
return this.operates.list.filter((item) => {
if (typeof item.show === "function") {
return item.show(row);
} else if (typeof item.show === "boolean" && item.show) {
return true;
}
// 如果没有显示权限要求,则继续验证角色身份权限
if (item.haspermission) {
return checkPermi(item.haspermission);
}
// 如果没有角色身份权限要求,则默认允许显示
return true;
});
},
组件引入使用
只需添加按钮对应的权限标识符字段即可
operates: {
fixed: "right",
list: [
{
label: "编辑",
type: "text",
haspermission: ["agent:del"],
icon: "el-icon-edit",
plain: false,
disabled: false,
method: (index, row) => {
this.handleEdit(index, row);
},
},
{
label: "删除",
show: true,
plain: false,
disabled: (index, row) => {
return false;
},
method: (index, row) => {
this.handleDel(index, row);
},
},
],
}, // 操作按钮组
本文用的是若依框架 上文中添加权限部分 checkPermi 函数 为若伊提供的校验按钮权限方法(组件引入haspermission字段同理 可以根据业务场景自行修改,传入haspermission权限标识符即可)
【扩展 】 props与attrs
Vue.js是一个流行的前端JavaScript框架,它提供了一种便捷的方式来构建交互性的用户界面。在Vue.js中,
attrs
和
props
是两个重要的概念,它们用于组件之间的数据传递和交互。让我们来探讨一下它们的区别和应用场景。
1、props 要先声明才能取值,attrs 不用先声明
2、props 声明过的属性,attrs 里不会再出现
3、props 不包含事件,attrs 包含
4、props 支持 string 以外的类型,attrs 只有 string 类型
props
(Properties):
- 用途:
props
是用来从父组件向子组件传递数据的一种方式。父组件可以将数据作为属性传递给子组件,子组件可以在其模板中使用这些属性。 - 数据流向: 数据流是单向的,从父组件到子组件。父组件通过属性设置子组件的数据。
- 定义方式: 在子组件中,需要明确声明接收哪些
props
,通常在组件的选项中使用props
属性进行定义。
// 父组件
<template>
<child-component :message="parentMessage"></child-component>
</template>
<script>
export default {
data() {
return {
parentMessage: "Hello from parent!",
};
},
};
</script>
// 子组件
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: ['message'],
};
</script>
attrs
(Attributes):
- 用途:
attrs
用于从父组件向子组件传递HTML属性,而不是组件的数据。这对于将原生HTML属性传递给子组件非常有用。 - 数据流向: 同样是单向的,从父组件到子组件。但是,
attrs
传递的是HTML属性,而不是组件的属性。 - 定义方式:
attrs
不需要在子组件中明确声明,而是在子组件的模板中直接使用。
// 父组件
<template>
<child-component title="Child Component"></child-component>
</template>
// 子组件
<template>
<div :title="title">This is a child component</div>
</template>
attrs的属性
HTML 中的属性可以分为多个类别,以下是一些常见的 HTML 属性及其分类:
- 全局属性 (Global Attributes): 这些属性可以在 HTML 元素中的任何地方使用,它们不受特定元素的限制。例如:-
id
: 元素的唯一标识符-class
: 元素的类名-style
: 元素的 CSS 样式-data-*
: 自定义数据属性-title
: 元素的标题(通常显示为工具提示) - 链接属性 (Link Attributes): 用于链接元素的属性,如
<a>
和<link>
:-href
: 指定链接的目标 URL-target
: 定义链接如何在浏览器中打开-rel
: 定义当前文档与链接资源之间的关系 - 表单属性 (Form Attributes): 用于表单元素(如
<input>
,<form>
)的属性:-name
: 表单元素的名称-type
: 输入字段的类型(文本、密码、单选框等)-value
: 输入字段的默认值-action
: 表单提交的目标 URL-method
: 表单提交的 HTTP 方法(GET 或 POST)-required
: 指示字段是否必填 - 图像属性 (Image Attributes): 用于
<img>
元素的属性:-src
: 图像的源 URL-alt
: 图像的替代文本-width
和height
: 图像的宽度和高度 - 多媒体属性 (Media Attributes): 用于多媒体元素,如
<audio>
和<video>
:-controls
: 显示多媒体控件(播放、暂停、音量等)-autoplay
: 指示多媒体是否自动播放-loop
: 指示多媒体是否循环播放-poster
: 预览图像的 URL - 元信息属性 (Meta Information Attributes): 用于
<meta>
元素,通常用于定义文档的元信息:-charset
: 文档字符编码-name
和content
: 定义各种元信息,如页面描述、关键词、作者等 - 脚本属性 (Script Attributes): 用于
<script>
元素,用于加载和执行脚本:-src
: 脚本文件的源 URL-type
: 指定脚本的 MIME 类型-async
和defer
: 控制脚本的加载和执行方式 - 框架属性 (Frame Attributes): 用于
<iframe>
元素,用于嵌套其他文档:-src
: 嵌套文档的源 URL-width
和height
: 框架的宽度和高度
这只是一些常见的 HTML 属性分类,还有其他特定元素的属性。不同的 HTML 元素可能具有不同的属性,具体取决于元素的类型和用途。
应用场景:
- 使用
props
:- 当你需要在父组件和子组件之间传递数据,以便子组件可以根据这些数据渲染不同的内容或执行不同的操作时,使用props
是最常见的做法。 - 使用
attrs
:- 当你需要将原生HTML属性传递给子组件,例如title
、class
、style
等,而不需要在子组件中显式声明它们时,可以使用attrs
。- 也可以用于将自定义属性传递给子组件,这些属性在子组件内部没有定义props
,但仍然需要在子组件的模板中使用。
总之,
props
用于传递组件之间的数据,而
attrs
用于传递HTML属性。根据你的需求,选择适当的方式来实现组件之间的通信。
版权归原作者 前端 贾公子 所有, 如有侵权,请联系我们删除。