widget-icon-btn.vue 6.9 KB
<template>
  <div class="icon-btn" @click="onClick" :style="btnStyle">
    <i class="iconfont" :class="iconClass" :style="iconStyle"></i>
    <p v-if="viewText" class="icon-btn-text" :style="textStyle">
      <span class="view-text">{{viewText}}</span>
      <span class="placeholder-text">{{placText}}</span>
    </p>
  </div>
</template>

<script>
  import {forEach, get, fill} from 'lodash';
  import {createNamespacedHelpers} from 'vuex';
  const {mapActions} = createNamespacedHelpers('user');

  const classMap = {
    fav: {
      default: 'icon-zan',
      selected: 'icon-zan-fill'
    },
    star: {
      default: 'icon-star',
      selected: 'icon-star-fill'
    },
    share: {
      default: 'icon-share'
    },
    msg: {
      default: 'icon-msg'
    }
  };

  const defaultOption = {
    canSelect: true,                  // 是否支持选中
    selected: false,                  // 初始选中状态(不受是否支持选中控制)
    color: '#444',                    // btn字体颜色
    selectedColor: '#d90025',         // btn选中状态字体颜色(不设置默认与非选中一致)
    iconFontSize: 48,                 // icon字号(单位px)
    textSelectedColor: '#444',        // text选中状态字体颜色(不设置默认与selectedColor一致)
    textFontSize: 20,                 // text字号(单位px)
    textAlign: 'top',                 // text位置, 默认normal(支持normal, top, bottom)
    textZoom: 0.9,                    // text缩放
    textAutoChange: true,            // text自动增减,只支持number类型(受是否支持选中控制)
    emitName: ''                      // 点击触发事件名称
  };

  export default {
    name: 'WidgetIconBtn',
    props: {
      type: {
        type: String,
        default: 'fav'
      },
      text: {
        type: [String, Number],
        default() {
          return '';
        }
      },
      articleId: Number,
      commentId: Number,
      option: {
        type: Object,
        default() {
          return defaultOption;
        }
      }
    },
    data() {
      return {
        viewOption: {},
        btnSelected: false,
        actionClass: '',
        editText: null
      }
    },
    created() {
      forEach(defaultOption, (value, key) => {
        if (!this.viewOption.hasOwnProperty(key)) {
          this.viewOption[key] = this.option.hasOwnProperty(key) ? this.option[key] : defaultOption[key];
        }
      });
      this.viewOption = {...this.viewOption};
    },
    computed: {
      btnStyle() {
        let color = this.viewOption.color || defaultOption.color;

        return `color: ${this.btnSelected ? (this.viewOption.selectedColor || color) : color};`;
      },
      iconClass() {
        if (this.actionClass) {
          return this.actionClass;
        }

        if (!this._icon) {
          this._type = classMap[this.type] ? this.type : 'fav'
          this._icon = classMap[this._type];
        }

        if (this.viewOption.selected) {
          this.btnSelected = true;

          return this._icon.selected || this._icon.default;
        }

        return this._icon.default;
      },
      iconStyle() {
        return `font-size: ${this.pxToRem(this.viewOption.iconFontSize)};`;
      },
      textStyle() {
        let style = `font-size: ${this.pxToRem(this.viewOption.textFontSize)};`;

        let textAlign = this.viewOption.textAlign;

        if (['top', 'bottom'].indexOf(textAlign) >= 0) {
          style += ` vertical-align: ${textAlign};`;
        }

        let textZoom = this.viewOption.textZoom;

        if (Number(textZoom) !== NaN) {
          style += ` transform: scale(${textZoom}, ${textZoom});`
        }

        if (this.viewOption.textSelectedColor) {
          style += ` color: ${this.viewOption.textSelectedColor};`
        }

        return style;
      },
      viewText() {
        return `${(this.editText === null ? this.text : this.editText)}`;
      },
      placText() {
        if (!isNaN(Number(this.text)) && this.viewText.length) {
          return fill(Array(this.viewText.length), 0).join('');
        } else  {
          return `${this.text}`;
        }
      }
    },
    methods: {
      ...mapActions(['followArticle', 'praiseArticle', 'praiseComment']),
      pxToRem(px) {
        const rootValue = 40;

        if (typeof px !== 'number') {
          px = parseInt(`0${px}`);
        }

        if (px > 2) {
          return (px / rootValue).toFixed(2) + 'rem';
        } else {
          return px + 'px';
        }
      },
      changeBtnStatus() {
        this.btnSelected = !this.btnSelected;

        if (this.viewOption.textAutoChange) {
          let _text = this.editText || this.text;

          if (!isNaN(Number(this.viewText))) {
            this.editText = Number(this.viewText) + (this.btnSelected ? 1 : -1);
          }
        }

        if (this._icon.selected) {
          this.actionClass = this.btnSelected ? this._icon.selected : this._icon.default;
        }
      },
      syncService(type, data) {
        if (typeof this[type] === 'function') {
          return this[type](data);
        } else {
          return Promise.resolve({code: 404});
        }
      },
      onClick(evt) {
        if (this.syncing) {
          return;
        }

        if (this.viewOption.canSelect) {
          this.changeBtnStatus();

          let syncFnName = '';

          if (this.articleId) {
            if (this._type === 'fav') {
              syncFnName = 'praiseArticle';
            } else if (this._type === 'star') {
              syncFnName = 'followArticle';
            }
          } else if (this.commentId && this._type === 'fav') {
            syncFnName = 'praiseComment';
          }

          if (syncFnName && this.$auth()) {

            const backFn = (res) => {
              this.syncing = false;

              if (res.code !== 200) {
                this.changeBtnStatus();

                if (res.code === 401) {
                  // TODO go_login
                } else {
                  this.$createToast && this.$createToast({
                    txt: res.message || '服务器开小差了',
                    type: 'warn',
                    time: 1000
                  }).show();
                }
              }
            };

            this.syncing = true;

            this.syncService(syncFnName, {
              articleId: this.articleId,
              commentId: this.commentId,
              status: this.btnSelected
            }).then(backFn).catch(backFn);
          }
        }

        this.viewOption.emitName && this.$emit(this.viewOption.emitName, evt);
      }
    },
  };
</script>

<style type="scss">
  .icon-btn {
    display: inline-block;
    line-height: 1;
    vertical-align: middle;

    > * {
      display: inline-block;
      vertical-align: middle;
    }

    .icon-btn-text {
      position: relative;

      .view-text {
        position: absolute;
      }

      .placeholder-text {
        opacity: 0;
      }
    }
  }
</style>