项目组件整理 #
最近看了一个新的项目,分析一下里面的基础组件
iconfont Icon 基础组件 #
- 只需要type就可以用iconfont
- 支持chrome小于12px
- 不足:jsx里面用了class,这里没必要用render,直接用template就行了吧
- 不足:点击事件这样写不如把所有事件、属性都弄过来:
v-on="$listeners"
v-bind="$attrs"
<script>
export default {
name: 'Icon',
props: {
size: {
type: [Number, String],
default: 16
},
type: {
type: String,
required: true
},
color: {
type: String,
default: 'primary'
},
hoverColor: {
type: String,
default: ''
}
},
methods: {
getIconCls() {
// let cls = `iconfont el-icon-${this.type} icon-${this.type}`;
let cls = `iconfont icon-${this.type}`
if (this.color)
cls += ` icon-color-${this.color}`
if (this.hoverColor)
cls += ` icon-hover-color-${this.hoverColor}`
return cls
},
onClick(e) {
this.$emit('click', e)
},
getIconStyle() {
const chromeMinSize = 12
// 支持小于12px
const retStyle = { fontSize: `${this.size}px`, width: `${this.size}px`, height: `${this.size}px` }
if (this.size < chromeMinSize) {
const ratio = this.size / chromeMinSize
retStyle.transform = `scale(${ratio})`
}
return retStyle
}
},
render() {
return (
<i
onClick={this.onClick}
class={`iconfont icon-component ${this.getIconCls()}`}
style={this.getIconStyle()}
/>
)
}
}
</script>
<script>
export default {
name: 'Icon',
props: {
size: {
type: [Number, String],
default: 16
},
type: {
type: String,
required: true
},
color: {
type: String,
default: 'primary'
},
hoverColor: {
type: String,
default: ''
}
},
methods: {
getIconCls() {
// let cls = `iconfont el-icon-${this.type} icon-${this.type}`;
let cls = `iconfont icon-${this.type}`
if (this.color)
cls += ` icon-color-${this.color}`
if (this.hoverColor)
cls += ` icon-hover-color-${this.hoverColor}`
return cls
},
onClick(e) {
this.$emit('click', e)
},
getIconStyle() {
const chromeMinSize = 12
// 支持小于12px
const retStyle = { fontSize: `${this.size}px`, width: `${this.size}px`, height: `${this.size}px` }
if (this.size < chromeMinSize) {
const ratio = this.size / chromeMinSize
retStyle.transform = `scale(${ratio})`
}
return retStyle
}
},
render() {
return (
<i
onClick={this.onClick}
class={`iconfont icon-component ${this.getIconCls()}`}
style={this.getIconStyle()}
/>
)
}
}
</script>
带上分页 #
- 可以不用在外面写分页,并且需要提供查询的api,让分页自动查询
- 不足:只要参数变化就会查询,这是否合理?(如果搜索参数绑定了输入框,是不是只能通过change事件,也就是说搜索参数最好不要绑定到参数上)是否应该是只有分页变化的时候才会触发查询,其他的提供一个函数,让开发者自己去控制?
<script>
export default {
props: {
pagaPosition: {
type: String,
default: 'end'
},
getData: {
type: Function,
required: false
},
limit: {
type: Number,
default: 10
},
getDataParams: {
type: Object,
default: () => ({})
},
total: {
type: Number,
default: 0
},
pageSizes: {
type: Array,
default: () => [5, 10, 15, 25]
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
loading: {
type: Boolean,
default: false
},
immediate: {
type: Boolean,
default: true
},
pageIndex: { // 针对前端分页
type: Number,
default: 1
}
},
data() {
return {
currentPage: 1,
pageSize: 10
}
},
watch: {
getDataParams: {
deep: true,
handler(nVal) {
if (!nVal.currentPage)
this.currentPage = 1
this.onPageChange()
}
},
pageIndex(val) {
this.currentPage = val
}
},
created() {
this.immediate && this.getData && this.onPageChange()
this.pageSize = this.limit
},
methods: {
async onPageChange() {
this.$emit('update:loading', true)
const params = { ...this.getDataParams }
delete params.currentPage
for (const i in params) {
if (params[i] === '' || params[i] === undefined || params[i] === null)
delete params[i]
}
try {
const result = await this.getData({
pageSize: this.pageSize,
pageIndex: this.currentPage,
...params
}).finally(() => {
this.$emit('update:loading', false)
})
this.$emit('getDataSuccess', result)
}
catch (error) {
this.$emit('getDataError', error)
}
}
}
}
</script>
<template>
<div class="k-frame-pagination-wrap">
<slot name="toolbar" />
<slot />
<div class="pagination mt10 px5 py10" :class="[`jc-${pagaPosition}`]">
<el-pagination
v-model:page-size="pageSize"
v-bind="$attrs"
v-model:current-page="currentPage"
class="k-frame-pagination"
:total="total"
:page-sizes="pageSizes"
:layout="layout"
v-on="$listeners"
@current-change="onPageChange"
@size-change="onPageChange"
/>
</div>
</div>
</template>
<script>
export default {
props: {
pagaPosition: {
type: String,
default: 'end'
},
getData: {
type: Function,
required: false
},
limit: {
type: Number,
default: 10
},
getDataParams: {
type: Object,
default: () => ({})
},
total: {
type: Number,
default: 0
},
pageSizes: {
type: Array,
default: () => [5, 10, 15, 25]
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
loading: {
type: Boolean,
default: false
},
immediate: {
type: Boolean,
default: true
},
pageIndex: { // 针对前端分页
type: Number,
default: 1
}
},
data() {
return {
currentPage: 1,
pageSize: 10
}
},
watch: {
getDataParams: {
deep: true,
handler(nVal) {
if (!nVal.currentPage)
this.currentPage = 1
this.onPageChange()
}
},
pageIndex(val) {
this.currentPage = val
}
},
created() {
this.immediate && this.getData && this.onPageChange()
this.pageSize = this.limit
},
methods: {
async onPageChange() {
this.$emit('update:loading', true)
const params = { ...this.getDataParams }
delete params.currentPage
for (const i in params) {
if (params[i] === '' || params[i] === undefined || params[i] === null)
delete params[i]
}
try {
const result = await this.getData({
pageSize: this.pageSize,
pageIndex: this.currentPage,
...params
}).finally(() => {
this.$emit('update:loading', false)
})
this.$emit('getDataSuccess', result)
}
catch (error) {
this.$emit('getDataError', error)
}
}
}
}
</script>
<template>
<div class="k-frame-pagination-wrap">
<slot name="toolbar" />
<slot />
<div class="pagination mt10 px5 py10" :class="[`jc-${pagaPosition}`]">
<el-pagination
v-model:page-size="pageSize"
v-bind="$attrs"
v-model:current-page="currentPage"
class="k-frame-pagination"
:total="total"
:page-sizes="pageSizes"
:layout="layout"
v-on="$listeners"
@current-change="onPageChange"
@size-change="onPageChange"
/>
</div>
</div>
</template>
简单的展开组件 #
这个组件是我加上去的,利用elementUI的展开动画
<script>
export default {
name: 'SimpleCollapse',
props: {
title: {
type: String,
default: ''
},
defaultCollapse: {
type: Boolean,
default: true
},
titleClass: {
type: String,
default: ''
},
panelClass: {
type: String,
default: ''
}
},
data() {
return {
isCollapse: true
}
},
created() {
this.isCollapse = this.defaultCollapse
},
methods: {
changeCollapse() {
this.isCollapse = !this.isCollapse
this.$emit('change', this.isCollapse)
}
}
}
</script>
<template>
<div>
<div class="collapse-title" :class="titleClass">
<div class="title" @click="changeCollapse">
<span>{{ title }}</span>
<i class="el-icon-arrow-down" :class="{ 'is-unfold': !isCollapse }" />
</div>
<div v-show="!isCollapse">
<slot name="title-right" />
</div>
</div>
<el-collapse-transition>
<div v-show="!isCollapse" :class="panelClass">
<slot />
</div>
</el-collapse-transition>
</div>
</template>
<style lang="scss" scoped>
.collapse-title {
height: 30px;
display: flex;
align-items: center;
}
.title {
cursor: pointer;
display: flex;
align-items: center;
i {
margin-left: 6px;
transition: 0.3s;
&.is-unfold {
transform: rotateZ(180deg);
}
}
}
</style>
<script>
export default {
name: 'SimpleCollapse',
props: {
title: {
type: String,
default: ''
},
defaultCollapse: {
type: Boolean,
default: true
},
titleClass: {
type: String,
default: ''
},
panelClass: {
type: String,
default: ''
}
},
data() {
return {
isCollapse: true
}
},
created() {
this.isCollapse = this.defaultCollapse
},
methods: {
changeCollapse() {
this.isCollapse = !this.isCollapse
this.$emit('change', this.isCollapse)
}
}
}
</script>
<template>
<div>
<div class="collapse-title" :class="titleClass">
<div class="title" @click="changeCollapse">
<span>{{ title }}</span>
<i class="el-icon-arrow-down" :class="{ 'is-unfold': !isCollapse }" />
</div>
<div v-show="!isCollapse">
<slot name="title-right" />
</div>
</div>
<el-collapse-transition>
<div v-show="!isCollapse" :class="panelClass">
<slot />
</div>
</el-collapse-transition>
</div>
</template>
<style lang="scss" scoped>
.collapse-title {
height: 30px;
display: flex;
align-items: center;
}
.title {
cursor: pointer;
display: flex;
align-items: center;
i {
margin-left: 6px;
transition: 0.3s;
&.is-unfold {
transform: rotateZ(180deg);
}
}
}
</style>
img-icon #
有点疑惑这个组件存在意义在哪
<script>
export default {
props: {
url: {
type: String,
required: true
},
size: {
type: [String, Number],
default: 16
}
},
computed: {
imgStyle() {
const styles = {
width: `${this.size}px`,
height: `${this.size}px`,
display: 'inline-block',
cursor: 'pointer',
verticalAlign: 'middle'
}
return styles
}
}
}
</script>
<template>
<div class="icon-img">
<img :style="imgStyle" style="width:100%;height:100%;" :src="url" alt="">
</div>
</template>
<style lang="scss" scoped>
.icon-img {
border-radius: 50%;
height: 100%;
display: inline-block;
vertical-align: middle;
}
/* .icon-img:hover{
background-color: #1890FF;
}*/
</style>
<script>
export default {
props: {
url: {
type: String,
required: true
},
size: {
type: [String, Number],
default: 16
}
},
computed: {
imgStyle() {
const styles = {
width: `${this.size}px`,
height: `${this.size}px`,
display: 'inline-block',
cursor: 'pointer',
verticalAlign: 'middle'
}
return styles
}
}
}
</script>
<template>
<div class="icon-img">
<img :style="imgStyle" style="width:100%;height:100%;" :src="url" alt="">
</div>
</template>
<style lang="scss" scoped>
.icon-img {
border-radius: 50%;
height: 100%;
display: inline-block;
vertical-align: middle;
}
/* .icon-img:hover{
background-color: #1890FF;
}*/
</style>
directive - click outside
import Vue from 'vue'
const isServer = Vue.prototype.$isServer
/* istanbul ignore next */
export const on = (function () {
if (!isServer && document.addEventListener) {
return function (element, event, handler) {
if (element && event && handler)
element.addEventListener(event, handler, false)
}
}
else {
return function (element, event, handler) {
if (element && event && handler)
element.attachEvent(`on${event}`, handler)
}
}
})()
const nodeList = []
const ctx = '@@clickoutsideContext'
let startClick
let seed = 0
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e))
!Vue.prototype.$isServer && on(document, 'mouseup', (e) => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick))
})
function createDocumentHandler(el, binding, vnode) {
return function (mouseup = {}, mousedown = {}) {
if (!vnode
|| !vnode.context
|| !mouseup.target
|| !mousedown.target
|| el.contains(mouseup.target)
|| el.contains(mousedown.target)
|| el === mouseup.target
|| (vnode.context.popperElm
&& (vnode.context.popperElm.contains(mouseup.target)
|| vnode.context.popperElm.contains(mousedown.target))))
return
if (binding.expression
&& el[ctx].methodName
&& vnode.context[el[ctx].methodName])
vnode.context[el[ctx].methodName]()
else
el[ctx].bindingFn && el[ctx].bindingFn()
}
}
/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose"></div>
* ```
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el)
const id = seed++
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
}
},
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode)
el[ctx].methodName = binding.expression
el[ctx].bindingFn = binding.value
},
unbind(el) {
const len = nodeList.length
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1)
break
}
}
delete el[ctx]
}
}
import Vue from 'vue'
const isServer = Vue.prototype.$isServer
/* istanbul ignore next */
export const on = (function () {
if (!isServer && document.addEventListener) {
return function (element, event, handler) {
if (element && event && handler)
element.addEventListener(event, handler, false)
}
}
else {
return function (element, event, handler) {
if (element && event && handler)
element.attachEvent(`on${event}`, handler)
}
}
})()
const nodeList = []
const ctx = '@@clickoutsideContext'
let startClick
let seed = 0
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e))
!Vue.prototype.$isServer && on(document, 'mouseup', (e) => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick))
})
function createDocumentHandler(el, binding, vnode) {
return function (mouseup = {}, mousedown = {}) {
if (!vnode
|| !vnode.context
|| !mouseup.target
|| !mousedown.target
|| el.contains(mouseup.target)
|| el.contains(mousedown.target)
|| el === mouseup.target
|| (vnode.context.popperElm
&& (vnode.context.popperElm.contains(mouseup.target)
|| vnode.context.popperElm.contains(mousedown.target))))
return
if (binding.expression
&& el[ctx].methodName
&& vnode.context[el[ctx].methodName])
vnode.context[el[ctx].methodName]()
else
el[ctx].bindingFn && el[ctx].bindingFn()
}
}
/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose"></div>
* ```
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el)
const id = seed++
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
}
},
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode)
el[ctx].methodName = binding.expression
el[ctx].bindingFn = binding.value
},
unbind(el) {
const len = nodeList.length
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1)
break
}
}
delete el[ctx]
}
}
common-h1 #
项目里有好几个类似的组件,都是通用的一些样式,还挺方便的吧
<script>
export default {
props: {
title: {},
subtitle: {},
hasSlot: {
type: Boolean,
default: false
},
outerSlot: {
type: Boolean,
default: false
}
}
}
</script>
<template>
<div class="common-h1" :class="{ 'outer-slot': outerSlot }">
<div>
<h1 class="common-h1-title">
{{ title }}
</h1>
<div class="common-h1-subtitle">
{{ subtitle }}
<div v-if="hasSlot" class="tool-slot k-frame-tool-bar">
<slot />
</div>
</div>
</div>
<div v-if="outerSlot" class="outer-slot">
<slot name="outer" />
</div>
</div>
</template>
<style lang="scss" scoped>
.common-h1 {
font-size: 0;
background-color: #fafafa;
padding-left: 8px;
overflow: hidden;
margin-bottom: 8px;
&.outer-slot {
display: flex;
padding-right: 8px;
align-items: center;
justify-content: space-between;
}
.common-h1-title {
color: #000;
font-size: 20px;
line-height: 48px;
}
.common-h1-subtitle {
font-size: 12px;
color: #333;
line-height: 1;
margin-top: 6px;
margin-bottom: 9px;
display: flex;
justify-content: space-between;
.tool-slot {
transform: translateY(-50%);
}
}
.outer-slot {
float: right;
}
}
</style>
<script>
export default {
props: {
title: {},
subtitle: {},
hasSlot: {
type: Boolean,
default: false
},
outerSlot: {
type: Boolean,
default: false
}
}
}
</script>
<template>
<div class="common-h1" :class="{ 'outer-slot': outerSlot }">
<div>
<h1 class="common-h1-title">
{{ title }}
</h1>
<div class="common-h1-subtitle">
{{ subtitle }}
<div v-if="hasSlot" class="tool-slot k-frame-tool-bar">
<slot />
</div>
</div>
</div>
<div v-if="outerSlot" class="outer-slot">
<slot name="outer" />
</div>
</div>
</template>
<style lang="scss" scoped>
.common-h1 {
font-size: 0;
background-color: #fafafa;
padding-left: 8px;
overflow: hidden;
margin-bottom: 8px;
&.outer-slot {
display: flex;
padding-right: 8px;
align-items: center;
justify-content: space-between;
}
.common-h1-title {
color: #000;
font-size: 20px;
line-height: 48px;
}
.common-h1-subtitle {
font-size: 12px;
color: #333;
line-height: 1;
margin-top: 6px;
margin-bottom: 9px;
display: flex;
justify-content: space-between;
.tool-slot {
transform: translateY(-50%);
}
}
.outer-slot {
float: right;
}
}
</style>