<template>
<Card class="my-panel" :shadow="shadow" :class="classes" :bodyStyle="cardBodyStyle">
<template v-if="header" v-slot:header>
<div v-if="tabs" class="my-panel__header my-panel__tabs" ref="header" :style="headerStyle">
<Tabs v-model="currentTab">
<TabPane v-for="tab in tabs" :key="tab.name||tab.label" v-bind="tab" lazy>
<slot v-if="$scopedSlots.label" name="label" slot="label" v-bind="tab"></slot>
</TabPane>
</Tabs>
<div v-if="$slots.handle" class="my-panel__handle">
<slot name="handle"></slot>
</div>
</div>
<div v-else class="my-panel__header" ref="header" :style="headerStyle">
<slot name="header">
<my-header :title="title"
:icon="icon"
:theme="titleTheme"
:size="size"
:background="titleBackground">
<template v-slot:title>
<slot name="title"></slot>
</template>
<template v-slot:handle>
<slot name="handle"></slot>
</template>
</my-header>
</slot>
</div>
</template>
<div class="my-panel__body"
:class="{'is-fit':fit}"
:style="mergeBodyStyle">
<slot></slot>
<template v-for="tab in tabs">
<slot v-if="tab.name===currentTab" :name="tab.name" v-bind="tab"></slot>
</template>
</div>
<div v-if="$slots.footer"
ref="footer"
class="my-panel__footer"
:class="[`is-${footerAlign}`, `is-${theme}`]"
:style="footerStyle">
<slot name="footer"></slot>
</div>
<div v-if="actions && actions.length" class="my-panel__actions">
<Action v-for="(action,index) in actions"
:key="index" v-bind="action"
:style="{width:`${100/actions.length}%`}"
@click.native="handleActionClick(action)">
<slot v-if="$scopedSlots.action" name="action" v-bind="action"></slot>
</Action>
</div>
</Card>
</template>
<script>
/**
* 面板容器组件
* @module $ui/components/my-panel
*/
import {Card, Tabs, TabPane} from 'element-ui'
import {MyHeader} from '$ui'
import {addResizeListener, removeResizeListener} from 'element-ui/lib/utils/resize-event'
import {throttle} from '$ui/utils/util'
import Action from './Action'
/**
* 插槽
* @member slots
* @property {string} default 默认插槽,定义容器放置的内容
* @property {string} header 自定义头部内容,如果自定义头部,title 和 handle 插槽将失效
* @property {string} title 定义标题内容
* @property {string} handle 定义右上角的操作区
* @property {string} footer 定义底部内容
* @property {string} label 定义tab的label显示内容
*/
export default {
name: 'MyPanel',
components: {
Card,
MyHeader,
Tabs,
TabPane,
Action
},
/**
* 属性参数
* @member props
* @property {boolean} [header=true] 是否显示头部
* @property {string} [shadow=always] 设置阴影显示时机,可选值:'always', 'hover', 'never'
* @property {string} [title] 标题文本,如果需要设置复制的标题,可以使用title插槽
* @property {string} [icon] 标题左侧的图标
* @property {string} [theme] 风格设置,可选值:'', 'background', 'border-top', 'border-left', 'flag'
* @property {string} [size] 设置尺寸,可选值:'', 'large', 'medium', 'small'
* @property {boolean} [fit=false] 设置自适应父节点的高度
* @property {boolean} [border=true] 设置是否显示边框
* @property {string} [footerAlign=right] 底部对齐方式, 可选值:'left', 'center', 'right'
* @property {Object} [bodyStyle] 主体内容设置样式
* @property {Object} [headerStyle] 头部设置样式
* @property {Object} [footerStyle] 底部设置样式
* @property {Array} [tabs] 选项卡数组,数据项对象 {label, name, disabled}, 如果要定义内容插槽,必须要设置name
* @property {String} [defaultTab] 初始显示的tab 名称
* @property {Array} [actions] 底部操作按钮数组 ,数据项对象 {text, icon}
*/
props: {
// 显示头部
header: {
type: Boolean,
default: true
},
// 设置阴影显示时机
shadow: {
type: String,
default: 'always',
validator(val) {
return ['always', 'hover', 'never'].includes(val)
}
},
// 标题文本
title: String,
// 标题 icon
icon: String,
// 主题风格
theme: {
type: String,
validator() {
return ['', 'background', 'border-top', 'border-left', 'flag']
}
},
// 尺寸
size: String,
// 充满夫容器
fit: Boolean,
// body样式
bodyStyle: Object,
// 底部样式
footerStyle: Object,
// 头部样式
headerStyle: Object,
// 底部对齐方式
footerAlign: {
type: String,
default: 'right',
validator(val) {
return ['left', 'center', 'right'].includes(val)
}
},
// 显示边框
border: {
type: Boolean,
default: true
},
tabs: Array,
defaultTab: String,
// 操作按钮
actions: Array
},
data() {
return {
headerHeight: 0,
footerHeight: 0,
currentTab: this.defaultTab || (this.tabs && this.tabs[0] || {}).name
}
},
computed: {
titleBackground() {
return this.theme === 'background'
},
titleTheme() {
if (this.theme === 'flag') {
return 'bg-down'
}
if (this.theme === 'border-left') {
return 'border-left'
}
return null
},
classes() {
return {
[`my-panel--${this.theme}`]: !!this.theme,
'is-fit': this.fit,
'is-no-border': !this.border,
[`is-${this.size}`]: !!this.size
}
},
mergeBodyStyle() {
if (!this.fit) {
return this.bodyStyle
}
return {
...this.bodyStyle,
height: `calc(100% - ${this.footerHeight}px)`
}
},
cardBodyStyle() {
if (!this.fit) {
return null
}
return {
height: `calc(100% - ${this.headerHeight}px)`
}
}
},
watch: {
currentTab(val) {
/**
* tab切换时触发
* @event tab-change
* @param {string} name 选项卡名称
*/
this.$emit('tab-change', val)
}
},
methods: {
setHeight() {
if (this.$refs.header) {
this.headerHeight = this.$refs.header.getBoundingClientRect().height
} else {
this.headerHeight = 0
}
if (this.$refs.footer) {
this.footerHeight = this.$refs.footer.getBoundingClientRect().height
} else {
this.footerHeight = 0;
}
},
bindResize() {
if (this.$refs.header) {
addResizeListener(this.$refs.header, this.proxyResize)
}
if (this.$refs.footer) {
addResizeListener(this.$refs.footer, this.proxyResize)
}
},
unbindResize() {
if (this.$refs.header) {
removeResizeListener(this.$refs.header, this.proxyResize)
}
if (this.$refs.footer) {
removeResizeListener(this.$refs.footer, this.proxyResize)
}
},
handleActionClick(action) {
/**
* 点击操作按钮时触发
* @event action
* @param {object} action 按钮对象
*/
this.$emit('action', action)
}
},
created() {
this.proxyResize = throttle(this.setHeight, this)
},
updated() {
this.$nextTick(this.proxyResize)
},
mounted() {
this.bindResize()
this.$nextTick(this.proxyResize)
},
beforeDestroy() {
this.unbindResize()
}
}
</script>