Сводка проблем проекта vue

Vue.js

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

1. Поле ввода использует v-модель для привязки значения, но его нельзя изменить.

// 第一种情况
<el-input v-model="form.title"></el-input>
// 初始化
data () {
    return {
        form: {}
    }
}
// 赋值,其中formData为父组件传过来的数据
mounted (){
    this.form.title = this.formData.title
}
// 这里不生效的原因为title没有初始化
data () {
    return {
        form: {title:''}}
}
// 第二种情况
在表单中使用时,用:model=""给输入框赋值
<el-form :model="formData">
   <el-form-item>
      <el-input placeholder="请输入" :model="formData.value" size="small"/>
   </el-form-item>
</el-form>
// 解决方案,改为
<el-input placeholder="请输入" v-model="formData.value" size="small"/>

2. Текстовое содержимое не переводится в html-теги

Я хочу ввести следующее, но после сохранения данных становитсяaaa

Решение: если это html-контент, javascript, xml или другой специальный контент, используйте<xmp></xmp>
Если это простой текст со специальными символами, такими как пробелы и возврат каретки, добавьте<pre></pre>Этикетка

3. Основная функция экспорта

SensitiveOperationExport(params).then(res => {
    if (res.data.code === '200') {
    // 这里是导出
      window.location.href = res.data.data.path
      this.$message({
        message: '导出成功!',
        type: 'success'
      })
    } else {
      this.$message.error({message: '导出失败!'})
    }
  })

4. При использовании таблицы элементов снимите некоторые флажки

<el-table-column type="selection" align="center" fixed :selectable="selectable"></el-table-column>

На официальном сайте есть такая операцияselectable, допустимо только для столбцов с type=selection, тип — Function, возвращаемое значение Function используется для определения возможности проверки флажка в этой строке.

// 判断表格数据是否可选
 selectable(row, index) {
   if (row.IsDefault === 0) {
     return false // 返回false为不可选数据
   } else {
     return true
   }
 }

5. Данные, возвращаемые интерфейсом, имеют тип json, при отображении в таблице их можно преобразовать следующим образом

// 数据结构 content: "{'title': '这是标题'}"
this.title = JSON.parse(content).title

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

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

// main.js 中引入配置
Viewer.setDefaults({
  'zIndex': 9999,
  'inline': false, // 是否默认展示缩略图
  'button': true, // 右上角按钮
  'navbar': true, // 底部缩略图
  'title': true, // 当前图片标题
  'toolbar': true, // 底部工具栏
  'tooltip': true, // 显示缩放百分比
  'movable': false, // 是否可以移动
  'zoomable': true, // 是否可以缩放
  'rotatable': true, // 是否可旋转
  'scalable': true, // 是否可翻转
  'transition': true, // 使用 CSS3 过度
  'fullscreen': true, // 播放时是否全屏
  'keyboard': true, // 是否支持键盘
  'url': 'data-source'
})
// 页面中使用
<viewer>
<img :src="scope.row.content "/>
</viewer>

7. Двигайтесь вверх и двигайтесь вниз

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

/ 上移
moveUp (index, row) {
  if (index > 0) {
    let upDate = this.tableData[index - 1]
    this.tableData.splice(index - 1, 1)
    this.tableData.splice(index, 0, upDate)
  }
},
// 下移
moveDown (index, row) {
  if ((index + 1) === this.tableData.length) {
    console.log('已经是最后一条,不可下移')
  } else {
    let downDate = this.tableData[index + 1]
    this.tableData.splice(index + 1, 1)
    this.tableData.splice(index, 0, downDate)
  }
}

8. Выбор и обращение таблиц

<el-table :data="tableData" border :select-all="allSelect" @selection-change="changeFun" ref="form" height="700"></el-table>
// tableData 是表格数据
<div>
    <el-button @click="toggleSelect(tableData)">全选</el-button>
    <el-button @click="reverseSelect(tableData)">反选</el-button>
</div>
// 全选
    toggleSelect (rows) {
      if (rows) {
        rows.forEach(row => {
          this.$refs.form.toggleRowSelection(row, !this.allSelect)
        })
        this.allSelect = !this.allSelect
      }
    },
    // 反选
    reverseSelect (rows) {
      let checked = this.data
      if (rows) {
        rows.map(row => {
          checked && checked.map(item => {
            if (row.index !== item.index) {
              this.$refs.form.toggleRowSelection(item, false)
              this.$refs.form.toggleRowSelection(row, true)
            } else {
              this.$refs.form.toggleRowSelection(row, false)
            }
          })
        })
        if (checked.length === 0) {
          rows.forEach(row => {
            this.$refs.form.toggleRowSelection(row, true)
          })
        }
      }
    },
    // 获取选择的数据
    changeFun (val) {
      this.data = val
    }

9. Нажмите и удерживайте, чтобы говорить

Эта функция зависит от recorder.js, использование которой было описано в предыдущей статье, поэтому я не буду вдаваться в подробности.

10. Переключатель редактирования и сохранения таблицы

// editColorShow: '' // 设置敏感操作默认显示编辑
// clearEdit: '000' // 替换editColorShow的值
<el-table-column label="操作" align="center"
    width="200">
    <template slot-scope="scope">
      <el-button size="small" v-if="editColorShow !== scope.$index" type="primary" @click="editColor(scope.$index, scope.row)">编辑</el-button>
      <el-button size="small" v-if="editColorShow === scope.$index" type="primary" @click="submitSettingOperation(scope.$index, scope.row)">保存</el-button>
    </template>
 </el-table-column>
 // 方法中这样
editColor (index, row) {
  this.editColorShow = index
},
submitSettingOperation (index, data) {
  this.editColorShow = this.clearEdit
 }

11. Глубокое копирование

Первый:

function copy(arr) {
  var newObj = arr.constructor === Array ? [] : {}
  if (typeof arr === 'object') {
    for (var i in arr) {
      if (typeof arr[i] === 'object') {
        newObj[i] = copy(arr[i])
      }
      newObj[i] = arr[i]
    }
    return newObj
  } else {
    return
  }
}

второй

function copy (obj) {
 var newObj = obj.constructor === Array ? [] : {}
 newObj = JSON.parse(JSON.stringify(obj))
 return newObj
}

12. Проблема сброса формы

Когда я раньше сбрасывал форму, она была такой. одна строка кода.

this.$refs['infoForm'].resetFields() 
// 前提是要重置的输入框必须设置prop属性才可以

13. Есть два способа экспортировать текстовый файл.

Первая чистая фронтальная загрузка

fetch('https://xxxxx.com/' + item.path).then(res => res.blob().then(blob => {
    var a = document.createElement('a')
    var url = window.URL.createObjectURL(blob)
    var filename = 'text.txt'
    a.href = url
    a.download = filename
    a.click()
    window.URL.revokeObjectURL(url)
}))

Второй - скачать после получения содержимого txt

createDownload (fileName, content) {
      var blob = new Blob([content])
      var link = document.createElement('a')
      var bodyEle = document.getElementsByTagName('body')[0]
      link.innerHTML = fileName
      link.download = fileName
      link.href = URL.createObjectURL(blob)
      bodyEle.appendChild(link)
      link.click()
      bodyEle.removeChild(link)
    }

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

14. Экспорт Excel

Существует два метода экспорта формы. Первый зависит от третьей стороны. Во-первых, загрузите три зависимости.
скачатьBlob.js и Export2Excel.jsДва файла, импортированные в файл

// npm install file-saver xlsx script-loader --save
// 导出
    onExportExcel (formName) {
      import('@/vendor/Export2Excel').then(excel => {
// 表格的title
        const tHeader = ['订单编号', '姓名', '员工编号', '手机号', '公司']
// 对应的字段     
        const filterVal = ['sn', 'user_name', 'user_no', 'user_phone', 'user_company']
        const data = this.formatJson(filterVal, this.dataTable)
        excel.export_json_to_excel({
          header: tHeader,
          data,
          filename: `订单列表`
        })
      })
    },
    formatJson (filterVal, jsonData) {
      let arr = jsonData.map(v => filterVal.map(j => v[j]))
      return arr
    }

Второй — через vue-json-excel, подробности см.vue-json-excel

// 安装 npm install vue-json-excel,引入
// vue中使用
<download-excel
    class   = "btn btn-default"
    :data   = "json_data"
    :fields = "json_fields"
    worksheet = "My Worksheet"
    name    = "filename.xls">
</download-excel>

data(){
  return {
// 要导出的字段
    json_fields: {
            'Complete name': 'name',
            'City': 'city',
            'Telephone': 'phone.mobile',
            'Telephone 2' : {
                field: 'phone.landline',
                callback: (value) => {
                    return `Landline Phone - ${value}`;
                }
            },
        },
// 要导出的数据
        json_data: [
            {
                'name': 'Tony Peña',
                'city': 'New York',
                'country': 'United States',
                'birthdate': '1978-03-15',
                'phone': {
                    'mobile': '1-541-754-3010',
                    'landline': '(541) 754-3010'
                }
            },
            {
                'name': 'Thessaloniki',
                'city': 'Athens',
                'country': 'Greece',
                'birthdate': '1987-11-23',
                'phone': {
                    'mobile': '+1 855 275 5071',
                    'landline': '(2741) 2621-244'
                }
            }
        ],
        json_meta: [
            [
                {
                    'key': 'charset',
                    'value': 'utf-8'
                }
            ]
        ]
}
}

15. Используйте iconfont в панели навигации, выберите проблему не меняющегося цвета

Давайте посмотрим на сравнение


Проект разработан на основе element-ui, и использования иконок не избежать, поэтому Али Галерея - хороший выбор.Проблема здесь заключается в том, что после выбора левой панели навигации цвет текста меняется, а иконка остается то же самое.Как правило, существует три способа обращения к галерее Ali: Unicode, класс шрифта, символ; я использую метод символа для ссылки следующим образом

1. Значок имеет формат svg, выберите значок, который будет использоваться, и загрузите формат svg.
2. Создайте папку значков для хранения значков и создайте папку svgIcon для использования значков следующим образом.


3. Причина, по которой иконка здесь не меняет цвет, заключается в том, что сама загруженная иконка цветная, поэтому при получении иконки через символ к пути svg будет добавлен атрибут fill, так что цвет не может быть изменено, а атрибут заполнения в значке можно оставить пустым. , это решает

16. Функция маршрутизации нечеткого поиска vue

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

<template>
  <div>
    <el-select
      ref="headerSearchSelect"
      v-model="search"
      :remote-method="querySearch" // 远程搜索方法
      remote  // 是否为远程搜索
      filterable // 是否可搜索
      default-first-option // 输入框按下回车,返回第一个匹配项,需搭配filterable或remote使用
      placeholder="搜索"
      class="header-search-select"
      @change="change"
    >
      <el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
    </el-select>
  </div>
</template>
<script>
import path from 'path';
export default {
// 获取到当前账户下的所有可见页面路由
  props: {
    routers: {
      type: Array
    }
  },
  data () {
    return {
      search: '',
      options: [],
      searchPool: []
    }
  },
  computed: {
    routes() {
      return this.routers
    }
  },
  mounted () {
    this.searchPool = this.generateRoutes(this.routes)
  },
  methods: {
    // 远程搜索
    querySearch (query) {
      if (query !== '') {
        // 数组去重
        this.options = Array.from(new Set(this.searchQuery(query)))
      } else {
        this.options = []
      }
    },
    // 改变时跳转
    change (val) {
     // 判断当前页与搜索的页面是否一致,如一致则清空不跳转,反之跳转
      if (val.path !== this.$router.history.current.path) {
        this.$router.push(val.path)
        // 跳转路径后清空搜索框内容,也可以不清空
        this.search = ''
        this.options = []
      } else {
        this.search = ''
        this.options = []
      }
    },
    // 筛选符合条件的路由
    searchQuery (query) {
      const list = []
      this.searchPool.map(item => {
        item.title.filter(items => {
          if (items.indexOf(query) > -1) {
            list.push(item)
          }
        })
      })
      return list
    },
    // 处理路由数据
    generateRoutes(routes, basePath = '/', prefixTitle = []) {
      let res = []
      for (const router of routes) {
        if (router.hidden) { continue }
        const data = {
          path: path.resolve(basePath, router.path),
          title: [...prefixTitle]
        }
        if (router.meta && router.meta.title) {
          data.title = [...data.title, router.meta.title]
          if (router.redirect !== 'noRedirect') {
            res.push(data)
          }
        }
        // recursive child routes
        if (router.children) {
          const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
          if (tempRoutes.length >= 1) {
            res = [...res, ...tempRoutes]
          }
        }
      }
      return res
    }
  }
}
</script>

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

// 当页面没有子级菜单时
{
    path: '/log',
    component: Home,
    name: 'Log',
    redirect: '/redirect',
    children: [
      {
        path: 'index',
        name: 'LogIndex',
        component: _import('log/Index'),
        meta: {
          title: '日志管理',
          roles: [RoleName.Super, RoleName.AfterSale],
          icon: 'custom-rizhi'
        }
      }
    ]
  }
// 当页面有子级菜单时
{
    path: '/operation',
    component: Home,
    name: 'Operation',
    redirect: '/redirect',
    meta: { title: '运营管理', icon: 'custom-yunying1' },  // 区别在于这里,有子级的一定要在这加上meta
    children: [
      {
        path: 'payment',
        name: 'OperationPayment',
        component: _import('operation/Payment'),
        meta: {
          title: '支付管理',
          roles: [RoleName.Operator]  // 可通过roles判断当前用户是否有权限查看该页面
        }
      },
      {
        path: 'shop',
        name: 'OperationShop',
        component: _import('operation/Shop'),
        meta: {
          title: '店铺管理',
          roles: [RoleName.Super, RoleName.Operator, RoleName.Staff, RoleName.Marketer]
        }
      },
      {
        path: 'banner',
        name: 'OperationBanner',
        component: _import('operation/Banner'),
        meta: {
          title: '图片管理',
          roles: [RoleName.Super, RoleName.Operator, RoleName.Staff, RoleName.Marketer]
        }
      }
    ]
  }

17. Добавьте несколько небольших вопросов о маршрутизации

  1. <router-link to=""></router-link>: Разобрать в метку и указать путь перехода к
    <router-view>: просмотр компонента рендеринга

  2. Два способа импорта компонентов по маршруту

import app from '@/view/app'
{
  path: '/',
  component: app, // 第一种:直接引入
  component: () => import('@/view/app') // 第二种:懒加载
}
  1. динамическая маршрутизация
{
  path: '/argu:name' // name为动态路由参数
  // 嵌套路由
  path: '/parent',
  children: [
    {
      path: '/child'
    }
  ],
  // 命名路由
  path: '/login',
  name: 'name' // 命名,在router-link中可以:to="{name: 'login'}"跳转
}
  1. Именованные представления, которые могут загружать несколько компонентов
<router-view/>
<router-view name="email"/>
{
  path: 'names',
  components: {
    default: () => import('@/view/app'),
    email: () => import('@/view/message')
  }
}
  1. Три способа переадресации
{
  path: '/email',
  redirect: '/main',  // 第一种
  redirect: {  // 第二种
    name: 'main'
  },
  redirect: to => {  // 第三种
    return {
      name: 'main'
    }
  }
}

6. Псевдоним (доступ к псевдониму эквивалентен доступу к текущему маршруту)

// 例如,访问main_index就相当于访问main
{
  path: '/main',
  alias: '/main_index'
}

7. Прыжок

this.$router.push('/home') // 可直接跳转到home页
// 携带参数跳转一
this.$router.push({
  name: 'home',
  querry: {
    // 参数,显示在地址栏中
  }
})
// 携带参数二
this.$router.push({
  params: {
    // 参数
  }
})
// 携带参数三
this.$router.push({
  // 第三种
  name: '我是参数'
  path:`/home${name}`
})

8. Заменить (разница между push и replace)
Браузер push запишет, вернется на предыдущую страницу, а замена будет рассматривать текущую страницу как родительскую.
Например: текущая страница домашняя, щелкните, чтобы перейти на страницу «О нас», нажмите «Нажать», чтобы перейти, а затем вернуться, предыдущая страница является домашней.
Текущая страница является домашней, нажмите, чтобы перейти на страницу «О нас», нажмите «Заменить», чтобы вернуться, а предыдущая страница посвящена
this.$router.replace({
name: 'home'
})

  1. props может передавать значения в роутер
{
  path: '/about',
// 第一种
  props: {
    foood: 'apple'
  },
// 第二种
  props: router => {
    food: router.query.food   // 参数中传递了food值
  }
}
// 页面中获取
props: {
  foood: {
    type: string,
    default: 'orange'
  }
}
  1. routerСредняя конфигурацияmode: 'history'Адресную строку # можно удалить, но требуется внутренняя поддержка конфигурации
    router.before()Предварительный прыжок, который можно использовать, чтобы определить, нужно ли входить в систему и иметь разрешение перед прыжком.

18. Случайным образом сгенерируйте значение идентификатора --> this._uid

<span>{{index}}</span>
// 初始化定义
index: `index_${this._uid}`

19. Решить проблему с предупреждением браузера проекта vue.

Решить проблему, что проект vue имеет сигнал тревоги при использовании element-ui.Я не знаю, видит ли браузер такую ​​​​информацию при запуске проекта (я использую Google Chrome), хотя это не влияет на работу проекта. , но смотреть не очень удобно, содержание следующее
———————————————
[Нарушение] Добавлен непассивный прослушиватель событий к блокирующему прокрутку событию «колесо мыши». Рассмотрите возможность пометить обработчик события как «пассивный», чтобы сделать страницу более отзывчивой.

Решение выглядит следующим образом
1. Установите зависимости npm i default-passive-events -S
2.main.js представляет импорт «пассивных событий по умолчанию»
3. решено

Решите проблему тревоги, когда проект Vue использует момент, содержание выглядит следующим образом
———————————————
Deprecation warning: value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.

Решение выглядит следующим образом
1. Добавьте строку moment.suppressDeprecationWarnings = true в main.js, чтобы закрыть подсказку.
2. решено

20. Проблема, заключающаяся в том, что дочерний маркер блокируется слоем маски родительского маркера.

Дочернее добавление: append-to-body="true"
Родительское добавление: close-on-click-modal="false" :modal-append-to-body="false"

21. Операции форматирования

Добавьте следующий код в main.js, на странице используется {{time | dateformat}}

// 日期时间格式化
Vue.filter('dateformat', function(dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') {
  return moment(dataStr).format(pattern)
})
Vue.filter('getDate', function(dataStr, pattern = 'YYYY-MM-DD') {
  return moment(dataStr).format(pattern)
})
Vue.filter('getTime', function(dataStr, pattern = 'HH:mm:ss') {
  return moment(dataStr).format(pattern)
})
// 金额格式化
Vue.filter('money', function(val) {
  val = val.toString().replace(/\$|\,/g, '')
  if (isNaN(val)) {
    val = '0'
  }
  const sign = (val === (val = Math.abs(val)))
  val = Math.floor(val * 100 + 0.50000000001)
  let cents = val % 100
  val = Math.floor(val / 100).toString()
  if (cents < 10) {
    cents = '0' + cents
  }
  for (let i = 0; i < Math.floor((val.length - (1 + i)) / 3); i++) {
    val = val.substring(0, val.length - (4 * i + 3)) + ',' + val.substring(val.length - (4 * i + 3))
  }
  return (((sign) ? '' : '¥') + val + '.' + cents)
})
// 银行卡号格式化
Vue.filter('bankCard', function(accNo) {
  let result = ''
  let index = 0
  if (accNo !== undefined && accNo != null) {
    for (let i = 0; i < accNo.length; i++) {
      result += accNo.charAt(i)
      index++
      if (index === 4 && (i + 1 !== accNo.length)) {
        result += ' '
        index = 0
      }
    }
  }
  return result
})

22. Щелкните текст в таблице, чтобы он стал полем ввода и автоматически получил фокус

// input框自动获取焦点,自定义指令,与methods同级, input添加指令v-focus
directives: {
    'focus': {
      // 指令的定义
      inserted(el) {
        if (el.tagName.toLocaleLowerCase() === 'input') {
          el.focus()
        } else {
          if (el.getElementsByTagName('input')) {
            el.getElementsByTagName('input')[0].focus()
          }
        }
      }
    }
  }

23. Фон частиц

插件  vue-particles 安装引入

24. При использовании таблицы элементов, когда таблица отображается динамически, таблица неупорядочена

<el-table>
   <el-table-column v-if="checked" label="旧车价格" width="200" />
</el-table>

Решение состоит в том, чтобы добавить в таблицу атрибут ref и повторно отобразить таблицу в обновленном виде.

updated() {
  this.$refs['table'].doLayout()
}

25. Преобразование массива в составной массив

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

// 级联下拉
<el-cascader
   v-model="ParentID"
   :options="options"
   :props="{ value: 'PowerID', label: 'PowerName'}"
/>

Фоновые данные следующие

const arr1 = [
  { parent: '0', power: '110' },
  { parent: '110', power: '101' },
  { parent: '110', power: '102' },
  { parent: '0', power: '220' },
  { parent: '220', power: '201' },
  { parent: '220', power: '202' },
  { parent: '101', power: '1011' }
]

Наши идеальные данные выглядят так

{ parent: '0', power: '110', children: [
  { parent: '110', power: '101', children: [{ parent: '101', power: '1011' }] },
  { parent: '110', power: '102' }
] },
{{ parent: '0', power: '220' }, children: [
  { parent: '220', power: '201' },
  { parent: '220', power: '202' }
]}

Обработка данных

const render = it => {
  const children = arr1.filter(item => item.parent === it.power).map(render)
     if (children.length) {
       it.children = children
      }
     return it
   }
   this.options = arr1.filter(it => it.parent === '0').map(render)

26. Строки слияния таблиц

<el-table
      :data="data2"
      border
      stripe
      style="width: 100%"
      :height="tableHeight"
      :header-cell-style="{textAlign: 'center',background:'#eef1f6'}"
      :sort-orders="['ascending', 'descending']"
      :default-sort="{prop: 'LineSort', order: 'descending'}"
      :span-method="objectSpanMethod" // 这一行是主要的
    />
// 处理相同数据合并
    setTable() {
      const spanOneArr = []
      const spanTwoArr = []
      var concatOne = 0
      // 判断是否为最后一级
      this.tableData.forEach((item, index) => {
        if (index === 0) {
          spanOneArr.push(1)
          spanTwoArr.push(1)
        } else {
          if (item.LineName === this.tableData[index - 1].LineName) { // 需合并相同内容的判断条件
            spanOneArr[concatOne] += 1
            spanOneArr.push(0)
          } else {
            spanOneArr.push(1)
            concatOne = index
          }
        }
      })
      return {
        one: spanOneArr,
        two: spanTwoArr
      }
    },
    // 合并行
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      if (columnIndex === 0 || columnIndex === 1 || columnIndex === 4 || columnIndex === 5 || columnIndex === 6 || columnIndex === 7) {
        const _row = (this.setTable(this.tableData).one)[rowIndex]
        const _col = _row > 0 ? 1 : 0
        return {
          rowspan: _row,
          colspan: _col
        }
      }
    },

27. Динамически добавлять ряд форм

<el-dialog :title="showForm ? '添加' : '编辑'" :visible.sync="addVisible" width="910px" class="dialog-style" :close-on-click-modal="false">
      <el-form ref="editForm" :model="editForm" label-width="90px" style="width: 90%">
        <el-form-item label="服务费率" prop="ServiceRate">
          <el-input v-model="editForm.ServiceRate" placeholder="0">
            <template slot="append">%</template>
          </el-input>
        </el-form-item>
        <el-form-item label="最低服务费" prop="ServiceCharge">
          <el-input v-model="editForm.ServiceCharge" placeholder="0">
            <template slot="append">元</template>
          </el-input>
        </el-form-item>
        <el-form-item label="贷款期数" prop="LoanPeriod">
          <el-input v-model="editForm.LoanPeriod" placeholder="0">
            <template slot="append">个月</template>
          </el-input>
        </el-form-item>
        <el-form-item label="贷款利率" prop="lendingRate" style="position: relative">
          <el-input v-model="editForm.lendingRate" placeholder="0">
            <template slot="append">%</template>
          </el-input>
          <i class="icon-btn hoverStyle el-icon-circle-plus-outline" @click="addItem" />
        </el-form-item>
        <!-- 动态增加的表单 -->
        <div v-for="(item, index) in editForm.dynamicItem" :key="index" style="display: inline-flex;">
          <el-form-item label="贷款期数" :prop="item.LoanPeriod + '_' + index">
            <el-input v-model="item.LoanPeriod" placeholder="0">
              <template slot="append">个月</template>
            </el-input>
          </el-form-item>
          <el-form-item label="贷款利率" :prop="item.lendingRate + '_' + index" style="position: relative;margin-left: 26px;">
            <el-input v-model="item.lendingRate" placeholder="0">
              <template slot="append">%</template>
            </el-input>
            <i class="icon-btn hoverStyle el-icon-remove-outline" @click="deleteItem(index, item)" />
            <el-button type="text" class="up-btn" :disabled="index === 0" @click="moveUp(index)">上移</el-button>
            <el-button type="text" class="down-btn" :disabled="index === editForm.dynamicItem.length - 1" @click="moveDown(index)">下移</el-button>
          </el-form-item>
        </div>
      </el-form>
      <div class="dialog-footer">
        <el-button class="btn-footer" type="primary" @click="addSaveData">保 存</el-button>
        <el-button class="btn-footer" @click="closeAddDialog('editForm')">取 消</el-button>
      </div>
    </el-dialog>
// methods中
// 添加增加一行表单
    addItem() {
      this.editForm.dynamicItem.push({
        RId: 0, // 新增传0
        BId: 0, // 新增传0
        LoanPeriod: '', // 期数
        lendingRate: '', // 贷款利率
        LoanSort: 0 // 排序按1开始
      })
    },
    // 添加删除一行表单
    deleteItem(index, row) {
      this.editForm.dynamicItem.splice(index, 1)
    },
    // 上移
    moveUp(index) {
      if (index > 0) {
        const upDate = this.editForm.dynamicItem[index - 1]
        this.editForm.dynamicItem.splice(index - 1, 1)
        this.editForm.dynamicItem.splice(index, 0, upDate)
      }
    },
    // 下移
    moveDown(index) {
      const downDate = this.editForm.dynamicItem[index + 1]
      this.editForm.dynamicItem.splice(index + 1, 1)
      this.editForm.dynamicItem.splice(index, 0, downDate)
    }

28. Измените стиль полосы прокрутки таблицы элементов.

// 修改table滚动条
.el-table{
  .el-table__fixed,
    .el-table__fixed-right {
      height: 100% !important;
    }
}
//滚动条的宽度
::-webkit-scrollbar {
  width: 10px;
  height: 8px;
}
//滚动条的滑块
::-webkit-scrollbar-thumb {
  background-color: #dedfe0;
  border-radius: 3px;
}