[Чехол каштан] Несколько точек для повышения универсальности компонентов

JavaScript Vue.js

Написание кода иногда похоже на обработку пружины. Казалось бы, простой предмет может оказаться непростым в обработке

1. Введение

Когда я недавно работал над проектом, я увидел, что есть два требования, которые имеют одинаковые функции, но разные взаимодействия и стили. Чтобы облегчить обслуживание, я инкапсулировал компоненты и нашел, казалось бы, простой компонент. На самом деле есть много вещей, которые нужно рассмотреть возможность.

Эта статья — лишь пример того, с чего начать, чтобы повысить универсальность компонентов. И предложим несколько идей упаковки. Упомянутые компоненты по-прежнему во многом связаны с требованиями проекта, это почти заказная разработка под проект, в других проектах они могут быть недоступны из коробки, если вы хотите их использовать, вам нужно модифицировать компоненты.

2. Сначала посмотрите на компоненты

Этот компонент выглядит очень просто, просто напишите его

Из-за нехватки места CSS и некоторые несвязанные коды js не предоставляются.Для перемещения необходимо посмотреть исходный код: Исходный код примера статьи:HandleButtonOld, полный код проекта:код проекта

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"
    >
      <ec-text v-if="item.fileType==='text'" :src="item.fileUrl"></ec-text>
      <video :src="item.fileUrl" v-if="item.fileType==='video'"></video>
      <audio :src="item.fileUrl" controls="controls" v-if="item.fileType==='audio'"></audio>
      <ec-image :src="item.fileUrl" v-if="item.fileType==='image'" />
      <ul class="customer-form-view-action-box">
        <li class="iconfont icon-icon-cus-edit" @click.stop="handleEvent('edit',index)"></li>
        <li
          class="iconfont icon-icon-cus-up"
          @click.stop="handleEvent('up',index)"
          v-if="index!==0"
        ></li>
        <li
          class="iconfont icon-icon-cus-down"
          @click.stop="handleEvent('down',index)"
          v-if="index!==value.length-1"
        ></li>
        <li class="iconfont icon-icon-cus-del" @click.stop="handleEvent('delete',index)"></li>
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  name: 'HandleButton',
  componentName: 'HandleButton',
  props: {
    value: {
      type: Array,
      default () {
        return []
      }
    }
  },
  data () {
    return {
      nowClickIndex: ''
    }
  },
  mounted () {},
  methods: {
    handleEvent (type, index) {
      let _list = JSON.parse(JSON.stringify(this.value))
      let _nowItem = _list[index]
      switch (type) {
        case 'up':
          this.nowClickIndex--
          _list.splice(index, 1)
          _list.splice(index - 1, 0, _nowItem)
          break
        case 'down':
          this.nowClickIndex++
          _list.splice(index, 1)
          _list.splice(index + 1, 0, _nowItem)
          break
        case 'delete':
          _list.splice(index, 1)
      }
      this.$emit('input', _list)
      this.$emit(type, _nowItem, index)
    },
    switchCur (item, index) {
      this.nowClickIndex = index
    }
  }
}
</script>
<style lang="scss" scoped>
// 略
</style>

3. Улучшить оптимизацию

Компоненты также очень просты в использовании, и получается простая строка кода.

<handle-button-old v-model="sortData"/>

sortData

sortData: [
    {
      fileType: 'text',
      content: '前端开发',
      index: 2,
      size: 12
    },
    {
      fileNmae: '251bb6d882024b11a6051d604ac51fc3.jpeg',
      fileType: 'image',
      fileUrl:
        'https://file-cdn-china.wechatify.net/marketing/sms/mms_material/53ce422f14e516af0eb9a5c7251cc1ca.jpeg',
      index: 3,
      size: 101109,
      fileName: '53ce422f14e516af0eb9a5c7251cc1ca.jpeg'
    },
    {
      fileType: 'text',
      content: '守候',
      index: 5,
      size: 12
    }
 ]

Но если на странице есть такое требование, функция та же, но стиль и макет разные, например, на следующем рисунке.

Тогда компонент нельзя будет использовать.

В настоящее время вместо копирования файла, изменения стиля и написания компонента мы можем только изменить исходный компонент, сделав его более общим, который может удовлетворить больше потребностей.

При выполнении такого требования крайне не рекомендуется копировать файл и писать другой компонент. Если это произойдет в следующий раз, вам придется скопировать другой файл и написать другой компонент. Это может привести к большому количеству файлов компонентов, что повлияет на обслуживание.

Чтобы сделать компоненты более общими и подходящими для большего количества потребностей, главное — извлечь факторы, которые часто меняются, и предоставить их пользователям для настройки.Что же можно улучшить и оптимизировать? Ниже приведен краткий список

3-1. Поддержка пользовательского контента

На главной странице, увидев два требования, стиль макета и поля отображения отличаются. Я не знаю третьего и четвертого стилей верстки в будущем, и я не знаю, какие поля будут отображаться. Поэтому здесь лучше всего извлечь кнопку операции и инкапсулировать ее как компонент Что касается макета и полей для отображения, независимо от того, что представляет собой компонент, ему нужно только предоставить слот для настройки пользователем.

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(index)"
    >
      <!-- 提供slot -->
      <slot :data="item"></slot>
      
      <ul class="customer-form-view-action-box">
        <!--重复代码略-->
      </ul>
    </div>
  </div>
</template>

пейджинговый вызов

<handle-button-old v-model="sortData">
<!--提供 slot-scope 需要什么字段以及排版可以自定义-->
  <div slot-scope="item" class="view-item">
    <span v-if="item.data.fileType==='text'">{{item.data.content}}123</span>
    <video :src="item.data.fileUrl" v-if="item.data.fileType==='video'"></video>
    <audio :src="item.data.fileUrl" controls="controls" v-if="item.data.fileType==='audio'"></audio>
    <img :src="item.data.fileUrl" v-if="item.data.fileType==='image'" />
  </div>
</handle-button-old>

3-2. Поддержка пользовательского стиля выбора

Зайди сюда и посмотри на выбранный эффект,

В дополнение к отображению нескольких кнопок управления, есть также синяя линия границы, но эффект выделения может быть разным для разных нужд.Увеличьте поля на 30px и так далее. Невозможно угадать, каким будет выбранный стиль при следующем использовании этого компонента. Таким образом, выбранный стиль не может быть жестко запрограммирован или оценен внутри кнопки-ручки, а может быть настроен только пользователем. Все, что мы можем предоставить, — это поле, сообщающее пользователю, какой элемент выбран в данный момент.

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"
    >
      <!--对 item 进行封装-->
      <slot :data="getItem(item,index)"></slot>
      //代码略 
    </div>
  </div>
</template>
<script>
export default {
  //代码略
  methods: {
    getItem (item, index) {
      // 把索引($index) 和 当前是否选中($select) 字段合并到 item 里面
      //这里是顺便把索引传过去了,是为了以后的不时之需,这里不展开讲
      return Object.assign({}, item, { $index: index, $select: this.nowClickIndex === index })
    }
    //代码略
  }
}
</script>

пейджинговый вызов

<!--根据 $select 判断是否添加 cur 这个 class-->
<handle-button-old v-model="sortData">
  <div slot-scope="item" class="view-item" :class="{'cur':item.data.$select}">
    //代码略
  </div>
</handle-button-old>
<style lang="scss">
.view-item {
    padding: 10px;
    border:4px dashed transparent;
    &.cur{
      border:4px double #ccc;
    }
}
</style>

Это позволяет пользователю настроить выбранный стиль

3-2. Установите положение дисплея и ориентацию кнопок управления

Посмотрите еще раз на стили двух требований

Сначала обратите внимание, что положение и ориентация кнопок разные. Положению кнопки может быть присвоено значение по умолчанию, но оно также должно быть настроено пользователем. Чтобы определить положение кнопки, компонент handle-button-old должен предоставить верхний, правый, нижний, левый и четыре параметра. Чтобы облегчить позиционирование, в дополнение к установке определенных пикселей и процентов, он также поддерживает пользовательский ввод «центра», что удобно для пользователей, чтобы установить вертикальное или горизонтальное центрирование.

Направление кнопки должно предоставлять параметр направления.Пользователь вводит горизонтальное для отображения по вертикали и ввод по вертикали для отображения по горизонтали.

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot>
      <!--绑定style,以及根据direction 设置 class,设置ul的样式-->
      <ul class="customer-form-view-action-box"
          :style="ulPosition"
          :class="{'handle-vertical':direction==='vertical'}"
      >
        //代码略
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  //代码略
  props: {
    //代码略
    top: {
      type: [String, Number],
      default: '0'
    },
    bottom: {
      type: [String, Number],
      default: 'auto'
    },
    left: {
      type: [String, Number],
      default: 'auto'
    },
    right: {
      type: [String, Number],
      default: '-26px'
    },
    direction: {
      type: String,
      default: 'horizontal'
    }
  },
  computed: {
    ulPosition () {
      let obj = {
        left: this.left,
        right: this.right,
        top: this.top,
        bottom: this.bottom
      }
      let _x = '0'
      let _y = '0'
      if (this.top === 'center' || this.bottom === 'center') {
        obj.top = '50%'
        obj.bottom = 'auto'
        _y = '-50%'
        obj.transform = `translate(${_x},${_y})`
      }
      if (this.left === 'center' || this.right === 'center') {
        obj.left = '50%'
        obj.right = 'auto'
        _x = '-50%'
        obj.transform = `translate(${_x},${_y})`
      }
      return obj
    }
  }
}
</script>
<style lang="scss" scoped>
.ec-handle--item {
  position: relative;
  ul {
    position: absolute;
    right: -26px;
    top: 0;
    display: none;
    line-height: 24px;
    &.handle-vertical {
      li {
        display: inline-block;
        vertical-align: top;
      }
    }
  }
}
</style>

пейджинговый вызов

<!--设置按钮的位置和方向-->
<handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      bottom="center"
    >
  <div slot-scope="item" class="handle-item">
    //代码略
  </div>
</handle-button-old>
export default {
  data () {
    return {
      iconByFileType: {
        text: 'icon-wenben',
        image: 'icon-tupian1',
        video: 'icon-shipin',
        audio: 'icon-yinpin',
        link: 'icon-duanlian'
      },
      //代码略
    }
  },
  //代码略
  methods: {
    formatSize (val) {
      if (val === 0) {
        return '0B'
      }
      let sizeObj = {
        MB: 1048576,
        KB: 1024,
        B: 1
      }
      val = +val
      for (let key in sizeObj) {
        if (val >= sizeObj[key]) {
          return +(val / sizeObj[key]).toFixed(2) + key
        }
      }
    },
    //代码略
  }
}
</script>
<style lang="scss" scoped>
//代码略
</style>

Этот эффект достигается

3-3. Установите режим отображения кнопок управления

Вы, должно быть, видели проблему, [3-2] Последний результат, который вы видите, это то, что только у одного из них есть кнопка управления, и [3-2] В начале требование, которое вы видите, состоит в том, что все результаты должны отображаться . Затем нам нужно установить здесь свойство отображения, чтобы установить метод отображения кнопки операции. В настоящее время предусмотрено три значения: «по умолчанию» — отображаются выбранные элементы, «видимый» — отображаются все элементы и «нет» — не отображаются.

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot>
      <ul class="customer-form-view-action-box"
          :style="ulPosition"
          v-if="display!=='none'"
          :class="{'handle-vertical':direction==='vertical'}"
      >
        //代码略
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    display: {
      type: [String],
      default: 'default'
    },
    //代码略
  },
  //代码略
}
</script>

пейджинговый вызов

<handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      display="visible"
      bottom="center"
    >
    //代码略
</handle-button-old>

Это заставит это случиться

3-4. Инициировать действие перед нажатием кнопки действия

Многие люди столкнутся с некоторыми требованиями в процессе разработки, особенно перед выполнением «опасных операций», таких как удаление, очистка и т. д., чтобы отображалось всплывающее окно или другие напоминания, позволяющие пользователям действовать осторожно. Кнопка управления этого компонента также может быть «опасной операцией». Поэтому необходимо разрешить пользователям настраивать обратный вызов перед операцией.

Возьмем в качестве примера компонент handle-button-old, упомянутый в статье. Если требуется напомнить всплывающее окно перед кнопкой «удалить», другими кнопками можно управлять напрямую.

handle-button-old

<template>
    <!--代码略-->
</template>
<script>
export default {
  props: {
    beforeDelete: {
      type: Function
    },
    beforeUp: {
      type: Function
    },
    beforeDown: {
      type: Function
    },
    beforeEdit: {
      type: Function
    }
  },
  data () {
    return {
      nowClickIndex: '',
      eventType: '',
      curHandleIndex: ''
    }
  },
  methods: {
    /**
     * @description 执行事件
     */
    handle () {
      let _list = this.value
      let _nowItem = _list[this.curHandleIndex]
      switch (this.eventType) {
        case 'up':
          this.nowClickIndex--
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex - 1, 0, _nowItem)
          break
        case 'down':
          this.nowClickIndex++
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex + 1, 0, _nowItem)
          break
        case 'delete':
          _list.splice(this.curHandleIndex, 1)
      }
      this.$emit('input', _list)
      this.$forceUpdate()
      this.$emit(this.eventType, _nowItem, this.curHandleIndex)
    },
    /**
     * @description 处理事件
     */
    handleEvent (eventType, item, index) {
      // 记录事件类型
      this.eventType = eventType
      // 记录当前操作项的索引
      this.curHandleIndex = index
      let _type = eventType.substr(0, 1).toUpperCase() + eventType.substr(1)
      if (typeof this[`before${_type}`] === 'function') {
        // 把当前操作的函数,当前项,索引作为参数,传给调用函数
        this[`before${_type}`](this.handle, item, index)
      } else {
        this.handle()
      }
    },
  }
  // 代码略
}
</script>

пейджинговый вызов

<template>
    <handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      display="visible"
      bottom="center"
      :beforeDelete="handleBefore"
    >
      <!--代码略-->
    </handle-button-old>
</template>
<script>
methods: {
    /**
     * @description 操作前的回调
     * @augments done - 用于执行操作
     * @augments item - 当前项
     * @augments index - 当前索引
     */
    handleBefore (done, item, index) {
      // 点击确认才进行操作,点击取消不做处理
      this.$confirm('确认进行删除操作?')
        .then(() => {
          done()
        })
        .catch(() => {})
    }
  }
</script>

3-5. Переключите триггерное действие выбранного элемента

Например, если есть необходимость, при нажатии на переключение для выбора нужно взять в качестве параметра запроса данные текущего элемента. Чтобы выполнить это требование, вам нужно только предоставить пользовательское событие в компоненте handle-button-old.

handle-button-old

methods:{
    switchCur (item, index) {
      this.nowClickIndex = index
      //触发自定义事件
      this.$emit('change', item, index)
    }
}

пейджинговый вызов

<handle-button-old
  style="margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  right="6px"
  display="visible"
  bottom="center"
  @change="handleChange"
>
  
</handle-button-old>

3-6. Снимите флажок

Может быть, все рано обнаружили эту проблему.Если элемент выбран, возникает следующая ситуация.

Но что, если требуется снять флажок? Этого нельзя сделать. Логически говоря, пока он выбран, должен быть выбран один элемент, и отменить его невозможно. Поэтому в функции switchCur 3-5 нужно судить, если нажат текущий элемент, снять с него галочку

handle-button-old

methods:{
    switchCur (item, index) {
      
      if (this.display === 'visible') {
        return
      }
      // 如果点击的是当前项,就取消选中
      this.nowClickIndex = this.nowClickIndex !== index ? index : ''
      this.$emit('change', item, index)
    }
}

3-7. Кнопочный складной дисплей

Как вы можете видеть на изображении выше, кнопки расположены либо горизонтально, либо вертикально. Если в один прекрасный день вы почувствуете, что кнопка занимает слишком много места, и вам нужно свернуть и отобразить кнопку, это также очень просто для совместимости: добавьте параметр типа к handle-button-old, чтобы определить, как ее отображать.

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot>
      <!--如果不是 dropdown 类型以及 dispaly 不为 none-->
      <ul
        class="customer-form-view-action-box"
        :style="ulPosition"
        v-if="type!=='dropdown'&&display!=='none'"
        :class="{'handle-vertical':direction==='vertical'}"
      >
        <!--代码略-->
      </ul>
      <!--如果是dropdown类型-->
      <el-dropdown v-else-if="type==='dropdown'" class="customer-form-view-action-box" :style="ulPosition" style="position:absolute;">
        <span class="el-dropdown-link">
          操作<i class="el-icon-arrow-down el-icon--right"></i>
        </span>
        <el-dropdown-menu>
          <el-dropdown-item><div @click.stop="handleEvent('edit',item,index)">编辑</div></el-dropdown-item>
          <el-dropdown-item v-if="index!==0"><div @click.stop="handleEvent('up',item,index)">上移</div></el-dropdown-item>
          <el-dropdown-item v-if="index!==value.length-1"><div @click.stop="handleEvent('down',item,index)">下移</div></el-dropdown-item>
          <el-dropdown-item><div @click.stop="handleEvent('delete',item,index)">删除</div></el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
  </div>
</template>
export default {
  name: 'HandleButton',
  componentName: 'HandleButton',
  props: {
    type: {
      type: String
    }
    // 代码略
  }
  // 代码略
}

пейджинговый вызов

После type="dropdown" параметр направления работать не будет.

<!--type="dropdown" 折叠显示操作按钮,否则为平铺并列显示-->
<handle-button-old
  style="margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  type="dropdown"
  right="6px"
  display="visible"
  bottom="center"
  :beforeDelete="handleBefore"
>
  <div slot-scope="item" class="handle-item">
    <div class="message-item___box">
      <span class="message-item___icon iconfont" :class="iconByFileType[item.data.fileType]"></span>
    </div>
    <div class="message-item___info">
      <p v-if="item.data.fileType==='text'">
        <span>{{item.data.content}}</span>
      </p>
      <p v-else>{{item.data.fileName}}</p>
      <span class="message-item___info_size">{{formatSize(item.data.size)}}</span>
    </div>
  </div>
</handle-button-old>

3-8 Режим отображения кнопок

Возвращаясь к этой сцене, вы, возможно, уже думали об этом во время разработки.Чтобы кнопка операции появилась, вы должны нажать на определенный элемент, чтобы он появился. Однако во многих случаях операционная кнопка должна отображаться при размещении мыши, и нет необходимости щелкать. Для этого просто добавьте параметр триггера, триггер по умолчанию «щелчок» — щелкните, чтобы появиться, «наведите курсор» — наведите указатель мыши, чтобы появиться.

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index,'click')"
      @mouseenter="switchCur(item,index,'hover')"
      @mouseleave="handleMouseLeave"
    >
        <!--代码略-->
    </div>
  </div>
</template>
<script>
export default {
  props: {
    // 代码略
    triggle: {
      type: String,
      default: 'click'
    }
  },
  methods: {
    // 加上 eventType 参数区分当前触发的事件
    switchCur (item, index, eventType) {
      if (this.display === 'visible') {
        return
      }
      //如果当前触发事件与 triggle 不同,则不执行操作
      if (eventType !== this.triggle) {
        return
      }
      this.nowClickIndex = this.nowClickIndex !== index ? index : ''
      this.$emit('change', item, index)
    },
    handleMouseLeave () {
        // 如果triggle 为 hover ,鼠标移除的时候,取消选中当前项
      if (this.triggle === 'hover') {
        this.nowClickIndex = ''
      }
    }
  }
  // 代码略
}
</script>

пейджинговый вызов

<handle-button-old v-model="sortData" triggle="hover">
  <!--代码略-->
</handle-button-old>

3-9.О других

Возьмем в качестве примера handle-button-old впервые и перечислим некоторые улучшенные и оптимизированные функции. Если вы хотите выбросить, есть еще много функций, которые можно выбросить, например стиль кнопки (цвет значка, форма, цвет фона, размер и т. д.), интервал, настраиваемые кнопки и т. д. А бросать или нет, зависит от того, нужно ли требование, от конкретной ситуации, от конкретного анализа. Эта статья все еще является очень простым компонентом, если это сложный компонент, может быть больше моментов, которые необходимо оптимизировать.

4. Резюме

При упаковке компонентов, если требования к компонентам вначале относительно просты или время относительно неотложное, вы также можете упаковать компонент с базовыми функциями, которые могут удовлетворить потребности. Позже, если обнаружится, что компоненты не соответствуют бизнес-требованиям, еще не поздно внести улучшения и оптимизации.

------------------------- Великолепная разделительная линия --------------------

Если вы хотите узнать больше и пообщаться со мной, добавьте меня в WeChat. Или обратите внимание на мой паблик WeChat: В ожидании книжного магазина