Wiidede's blog Wiidede's blog
  • 前端
  • Python
  • 算法
  • 生活
  • 其他
  • 分类
  • 标签
  • 归档
  • 关于我
  • 赞赏
  • 我的小站 (opens new window)
GitHub (opens new window)

Wiidede

小的的写前端
  • 前端
  • Python
  • 算法
  • 生活
  • 其他
  • 分类
  • 标签
  • 归档
  • 关于我
  • 赞赏
  • 我的小站 (opens new window)
GitHub (opens new window)
  • 整理一些css样式
  • vue隔代组件层层动态插槽并且附带数据
  • vue判断字符串是否溢出来显示弹窗、解决el-table tooltip 内过多导致无法显示,内容闪烁
  • 整理一些js写法
  • ElementUI timePicker 增加此刻按钮 引发的dom操作的学习
  • 毕业设计(水表识别)前端知识整理
  • html小知识
  • axios请求api然后下载文件
  • vue3+ts根据高度改变元素的透明度
  • vue3 + ElementPlus 换肤方案(Css变量)
  • Moment的一些使用方法
  • echarts基础vue组件
  • element UI el-date-picker 年月日切换组件
  • 可以不选择的el-radio单选框
  • vue的小技巧总结
  • 全局动态权限判断(Vue指令)
  • vue-anchor 探索
  • Deep Dive with Evan You 笔记
  • 前端基础知识查漏补缺
  • WebPack 知识总结
  • 我写的一些可以日后参考的代码
  • 接口变化后,封装接口函数,改变返回内容
  • 项目组件整理
    • iconfont Icon 基础组件
    • 带上分页
    • 简单的展开组件
      • img-icon
    • common-h1
  • 前端框架设计想发
  • 全局进度条
  • 带有token的图片vue组件:authImg,使用axios下载图片
  • 前端npm包推荐
  • 给ElInputNumber添加prefix
  • ElPagination添加页数总数
  • el-tab做成chrome类似的tab样式
  • vue-grid-layout-组件配置
  • 项目数据字典封装
  • 图表组件响应式探索
  • ElementPlus表格table列自动合并composition
  • 简单的curd组件封装
  • ElementPlus表格自定义合计列composition
  • 一些处理表格数据composition api
  • div内容溢出后,内容向左悬浮,vue组件封装
  • 文本数字溢出后,按比例缩小,vue组件封装
  • 表格使用async-validator检验composition
  • ElementPlus Form一些简单的组件整合
  • arco-design快速使用tailwind的颜色、unocss动态颜色
  • 前端
wiidede
2022-05-29

项目组件整理

# 项目组件整理

最近看了一个新的项目,分析一下里面的基础组件

# iconfont Icon 基础组件

  1. 只需要type就可以用iconfont
  2. 支持chrome小于12px
  3. 不足:jsx里面用了class,这里没必要用render,直接用template就行了吧
  4. 不足:点击事件这样写不如把所有事件、属性都弄过来: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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 带上分页

  1. 可以不用在外面写分页,并且需要提供查询的api,让分页自动查询
  2. 不足:只要参数变化就会查询,这是否合理?(如果搜索参数绑定了输入框,是不是只能通过change事件,也就是说搜索参数最好不要绑定到参数上)是否应该是只有分页变化的时候才会触发查询,其他的提供一个函数,让开发者自己去控制?
<template>
 <div class="k-frame-pagination-wrap">
  <slot name="toolbar"></slot>
  <slot></slot>
  <div class="pagination mt10 py10 px5" :class="[`jc-${pagaPosition}`]">
   <el-pagination
    class="k-frame-pagination"
    :page-size.sync="pageSize"
    :total="total"
    :page-sizes="pageSizes"
    v-bind="$attrs"
    v-on="$listeners"
    @current-change="onPageChange"
    @size-change="onPageChange"
    :current-page.sync="currentPage"
    :layout="layout"
   ></el-pagination>
  </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
  };
 },
 created() {
  this.immediate && this.getData && this.onPageChange();
  this.pageSize = this.limit;
 },
 methods: {
  async onPageChange() {
   this.$emit('update:loading', true);
   let params = {...this.getDataParams};
   delete params.currentPage;
   for(let 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);
   }
  }
 },
 watch: {
  getDataParams: {
   deep: true,
   handler(nVal) {
    if(!nVal.currentPage) {
     this.currentPage = 1;
    }
    this.onPageChange();
   }
  },
  pageIndex(val) {
   this.currentPage = val;
  }
 }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

# 简单的展开组件

这个组件是我加上去的,利用elementUI的展开动画

<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}"></i>
      </div>
      <div v-show="!isCollapse">
        <slot name="title-right"></slot>
      </div>
    </div>
    <el-collapse-transition>
      <div v-show="!isCollapse" :class="panelClass">
        <slot/>
      </div>
    </el-collapse-transition>
  </div>
</template>

<script>
export default {
 name: 'simple-collapse',
 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>

<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: .3s;

    &.is-unfold {
      transform: rotateZ(180deg);
    }
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

# img-icon

有点疑惑这个组件存在意义在哪

<template>
 <div class="icon-img">
  <img :style="imgStyle" style="width:100%;height:100%;" :src="url" alt=""/>
 </div>
</template>

<script>
export default {
 props: {
  url: {
   type: String,
   required: true
  },
  size: {
   type: [String, Number],
   default: 16
  }
 },
 computed: {
  imgStyle() {
   let styles = {
    width: `${this.size}px`,
    height: `${this.size}px`,
    display: 'inline-block',
    cursor: 'pointer',
    verticalAlign: 'middle'
   };
   return styles;
  }
 }
};
</script>
<style lang="scss" scoped>
.icon-img {
 border-radius: 50%;
 height: 100%;
 display: inline-block;
 vertical-align: middle;
}

/* .icon-img:hover{
   background-color: #1890FF;
 }*/
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

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) {
  let 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];
 }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

# common-h1

项目里有好几个类似的组件,都是通用的一些样式,还挺方便的吧

<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></slot>
        </div>
      </div>
    </div>
    <div v-if="outerSlot" class="outer-slot">
      <slot name="outer"></slot>
    </div>
  </div>
</template>

<script>
export default {
 props: {
  title: {},
  subtitle: {},
  hasSlot: {
   type: Boolean,
   default: false
  },
  outerSlot: {
   type: Boolean,
   default: false
  }
 }
};
</script>

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#前端#Vue#Vue组件
上次更新: 2023/06/01, 12:40:50

← 接口变化后,封装接口函数,改变返回内容 前端框架设计想发→

Theme by Vdoing | Copyright © 2021-2023 Wiidede | Website use MIT License | Article content & logo use CC-BY-SA-4.0 License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式