Вы знаете о табличном компоненте?

Vue.js
Вы знаете о табличном компоненте?

предисловие

Эта статья написана днем, ветерок и солнце, это очень удобно 🏖.
Тогда без лишних слов перейдем сразу к теме.Сегодня мы в основном объяснимМасштабируемые столбцы, фиксированные столбцы, многоуровневые заголовкиИ несколько общих вопросов о формах✍️, полных галантереи 😯.

Напоминание: эта статья является продолжением предыдущейТабличный компонент понять?Она была написана после этой статьи, поэтому советую сначала прочитать предыдущую статью👀. Конечно, можно смотреть и прямо вниз, потому что здесь основная идея — рассказать об идее, а кода не так много 😁.

масштабируемый столбец

Масштабируемые столбцы, как следует из названия, мы можем перетаскивать заголовокborderЧтобы добиться размера ширины столбца, посмотрите на следующую картинку и вы все поймете👇:Смысл вышеприведенной картинки должен быть вполне ясен, а теперь кратко рассмотрим конкретные шаги реализации👇:

первый шаг:

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

Шаг 2:

в заголовкеheaderи телоbodyДобавить один на том же уровнеdivЧтобы представить пунктирную линию на приведенном выше рисунке, она по умолчанию скрыта, как и на следующем рисунке:

третий шаг:

Следите за событиями мыши в красной части: при нажатии мыши отображается пунктирная линия, и пунктирная линия изменяется в реальном времени.leftзначение, когда мышь поднята, пунктирная линия скрывается, а ширина столбца после перетаскивания вычисляется, а затем изменяетсяcolumnsсоответствующий столбецwidthТо есть ширина столбцов заголовка и тела будет меняться синхронно. Конечно, не забудьте отвязать, когда мышь поднята.mousemoveа такжеmouseupсобытия, это хорошая привычка.
Вот как реализован масштабируемый столбец.

фиксированный столбец

Далее давайте посмотрим, как реализованы фиксированные столбцы. Во-первых, это дизайн API, о котором должно быть легко думать, мы находимся вcolumnsДобавьте столбцы, которые необходимо зафиксировать внутриfixedсвойство, его значение имеет два варианта (leftилиright), следующим образом:

columns: [
    {
      title: '姓名',
      key: 'name',
      width: 100,
      fixed: 'left'
    }
    ...
]

Чтобы упростить задачу, здесь мы рассматриваем только фиксацию левой колонки, потому что фиксация правой колонки такая же.
Вот предложение, объясняющее принцип 😁: нужно визуализировать еще одну таблицу и абсолютно расположить ее на исходной таблице. Следующая картинка должна помочь вам понять👇:По сути вышеприведенная картинка уже является ядром реализации, так что поговорим непосредственно о конкретном методе реализации😎:

Шаг 1: Обработайте табличные данные

Поскольку мы хотим поместить столбцы, которые необходимо зафиксировать, слева, в начале нам нужно иметь дело с табличными данными.fixed="left"Атрибуты перечислены первыми.

Шаг 2: визуализируйте две таблицы

Следующим шагом является нормальная визуализация двух таблиц (A и B. Фактически, таблица A и таблица B абсолютно одинаковы, за исключением того, что таблица B имеет некоторые дополнительные свойства, такие как абсолютное позиционирование в левом верхнем углу и фиксированная ширина ( ширинаfixed="left"Сумма ширины столбцов атрибута), скрытое переполнение и т.д. Конкретная структура кода показана на следующем рисунке:Кроме того, для формы Аfixedчасть, которую мы можем установитьvisibility: hidden, потому что его не нужно отображать, а Element так и пишется; аналогично для не-fixedЧасть также может быть установленаvisibility: hidden.
В это время у меня вдруг возник вопрос 🤔, т.е.Почему он должен быть установлен наvisibility: hiddenне установленоdisplay: noneШерстяная ткань?display: noneРазве нельзя было бы сделать меньше dom? установлен вvisibilityКаково значение ?Это хороший вопрос, и я предлагаю вам подумать над ним. . . Смотрите дальше вниз 😁.

С сомнениями я взглянул на структуру dom iView и Ant Design и обнаружил, что iView также используетvisibility: hiddenчтобы с этим справиться, а Ant Design не рендерит напрямую, что очень странно! Поэтому я поставил iView и Element ofvisibility: hiddenзаменитьdisplay: noneПопробовал и обнаружил, что вроде нормально, отображение таблицы тоже правильное, проблем нет, так почему? На самом деле основная причина в том, чтоvisibility: hiddenзаменитьdisplay: noneвызоветсмещение линииЭта проблема.
Что это значит? То есть, если мы установимdisplay: none, высота строки в таблице A не фиксирована, но в это время таблица B не отображает содержимое тела таблицы в таблице A, поэтому таблица B не может синхронизировать высоту в таблице A; и если мы зададим ее какvisibility: hiddenЕсли это так, то таблица A и таблица B на самом деле содержат все данные, но они не видны визуально, так что высоту их строк можно оставить точно такой же, хотя это приведет к избыточным элементам dom.
Так почему же Ant Design это делает? На самом деле у Ant Design тоже есть эта проблема.Хотя он не отображает избыточный dom, он заранее рассчитает высоту строки таблицы A, а затем синхронно установит высоту строки фиксированного столбца.Кроме того, когда размер таблицы и изменение ширины столбца Также для синхронизации. Это два разных решения, давайте попробуем сами🙌.
Конечно, здесь мы используем схему Element и iView.

Шаг 3: Синхронизированная прокрутка

Что не так с приведенной выше реализацией 🤔?Наиболее очевидная проблема заключается в том, что форма A и форма B разделены.Поэтому, когда одна из таблиц прокручивается, другая таблица не будет реагировать соответствующим образом. На самом деле, заголовок и тело каждой таблицы также разделены, так что теперь нам нужно прокручивать синхронно: когда мы прокручиваем тело таблицы А по горизонтали, нам нужно прокручивать часть заголовка таблицы А синхронно; когда мы прокрутка по вертикали При прокрутке тела формы A нам необходимо синхронно прокручивать тело формы B.
Вот тело таблицы в форме A (A__body) прокрутив в качестве примера, кратко опишите конкретный метод:A__bodyПри боковой прокрутке: получитьA__bodyизscrollLeftvalue, а затем синхронизировать значение с заголовком таблицы A;A__bodyПри вертикальной прокрутке: получитьA__bodyизscrollTopvalue, а затем синхронизируйте значение с телом таблицы B. Конечно, прокрутка — это высокочастотное действие, поэтому мы можем сделать обработку против тряски, также можно попробовать использоватьtransformзаменитьscrollTopа такжеscrollLeftпроматывать.

Шаг 4: Синхронизируйте наведение

Как и на третьем шаге, когда мыhoverКаждая строка также должна быть синхронизирована, как прокрутка, то естьhoverСинхронизация стилей.
Так же мы используемhoverВозьмите в качестве примера тело формы А🌰, следите за строкойmouseenterа такжеmouseleaveСобытие, когда мышь перемещается в строку, вы можете получить информацию о строке, а затем синхронизироваться с соответствующей строкой фиксированного столбца; аналогичноhoverКогда речь идет о фиксированных столбцах, их также следует синхронизировать с таблицей A, которая здесь повторяться не будет.

многоуровневый заголовок

заголовок

Во-первых, это дизайн API, мы надеемсяcolumnsдобавитьchildrenЭтого можно добиться, например, следующим образом (кстати, посмотрите, как выглядит многоуровневый заголовок):

columns: [
    {
      title: '日期',
      key: 'date',
      width: 200
    },
    {
      title: '配送信息',
      children: [
        {
          title: '姓名',
          key: 'name',
          width: 200
        },
        {
          title: '地址',
          key: 'addr'
        }
      ]
    }
]

Из приведенного выше рисунка видно, что многоуровневый заголовок на самом деле рендерится построчно, и каждая строка может быть разделена на столбцы и столбцы для рендеринга, что на самом деле представляет собой двумерный массив, дваv-forЦикл, давайте разрежем код iView, чтобы увидеть общую структуру:Итак, первое, что мы должны сделать, этоcolumnsотформатируйте его так, чтобы он стал Это выглядит так (двумерный массив):Каждый из узлов данных дастlevel,rowspanа такжеcolspan, последние два используются для объединения ячеек, напримерcolumnsдата первого элемента, егоcolspanдолжно быть такchildrenобщее количество, если нетchildrenравно 1; егоrowspanДолжно быть равно максимальному количеству слоев - текущее количество слоев + 1, если естьchildren1.
Здесь может быть небольшая загвоздка, так что нужно остановиться и подумать🤔, но на самом деле суть в преобразовании структур данных, поэтому особого акцента на том, как его преобразовать, нет, так что давайте попробуем себя 😊.

тело

Рендеринг тела прост, но мы также должныcolumnsВыполним некоторую обработку, посмотрим, как выглядит отсортированный столбец:На самом деле новыйcolumnsпросто пройди старыйcolumns,имеютchildrenПродолжайте движение, чтобы получить подстолбцы, нетchildrenПросто выньте столбец напрямую, аналогично получению столбца всех листовых узлов, этот новый столбец заменит исходный.columnsДля рендеринга, поскольку это преобразование структуры данных проще, я разместил код:

const getAllColumns = (cols, forTableHead = false) => {
    const columns = deepCopy(cols);
    const result = [];
    columns.forEach((column) => {
        if (column.children) {
            if (forTableHead) result.push(column);
            result.push.apply(result, getAllColumns(column.children, forTableHead));
        } else {
            result.push(column);
        }
    });
    return result;
};

На самом деле, это все еще проблема преобразования данных.Вы можете взглянуть на следующие две картинки, чтобы углубить свое понимание👇: На данный момент мы закончили реализацию масштабируемых колонок, фиксированных колонок и многоуровневых заголовков, ха-ха 😁😁😁.

Дополнение к вопросу

1, ширина столбца непостоянна

На самом деле ширина столбцов нашей таблицы и тела таблицы на самом деле несовместимы, например, когда тело таблицы имеет полосу прокрутки, а заголовок - нет, поэтому будет разрыв в ширине полосы прокрутки. время обычная практика заключается в прокрутке по вертикали Когда полоса существует, добавьте столбец в конце заголовка, чтобы компенсировать эту разницу в ширине, обычно мы называем этоgutter. что этоgutterКаково значение ширины? По сути, это ширина полосы прокрутки, как рассчитать ширину полосы прокрутки? Вероятно, принцип состоит в том, чтобы создатьdiv, затем используйте (нетoverflow: scrollатрибутdivширина) - (даoverflow: scrollизdivwidth), которая является шириной полосы прокрутки.

2. Вывих

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

this.$nextTick(() => {
    this.$refs['table'] && this.$refs['table'].doLayout()
})

3. Катон

Если стол заполненtooltipа такжеinputОн застрянет, ведь обработчиков событий и dom-структур больше, поэтому такой ситуации следует избегать. Например, он отображается только тогда, когда строка находится в состоянии редактирования.input;tooltipПерейдите на рендеринг в реальном времени, не добавляйте все в началеtooltip.

4. ключевое значение

v-forне использовать, когдаindexсвязыватьkeyценность, потому чтоindexОн может измениться, поэтому он ненадежен, попробуйте использоватьidсвязывать.

5. Рендеринг больших данных

Для тысяч строк рендеринга данных нормально, если есть зависание.В настоящее время, если данные вашей таблицы используются только для чистого отображения, вы можете добавить их в компонентfunctional: trueСделайте его функциональным компонентом, что уменьшит накладные расходы на рендеринг. Другой - виртуальный список.Хотя существуют тысячи табличных данных, в видимой области содержится ограниченное количество данных, а область действия настолько велика.Нам всегда нужно визуализировать только видимую область и часть данных вокруг верхней и нижние части видимой области. , остальные рендерить не надо, а потом вычислять текущий интервал данных для отображения по количеству скролла при скролле.Общая идея такова, конечно, проще сказать чем сделано 😂.

резюме

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