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

Концепция

Вся система будет работать довольно просто, для отображения нужного нам диалогового окна надо будет всего лишь изменить один query параметр в адресной строке браузера, для примера назовем этот параметр ‘dialog’. Соответственно для закрытия окна надо будет только убрать параметр dialog.

Само окно будет состоять из четырех блоков:

  1. ‘Dialog-control-panel’ в нем будут располагаться кнопки управления

  2. “Dialog-content” в этой области будут динамически появляться vue компоненты с нужным нам контентом

  3. “Dialog-left-btn” и “Dialog-right-btn” кнопки которые можно вызывать по мере надобности, нужны для переключения контента

Начало работы

Создадим нужные нам компоненты:

  1. “TheDialog.vue” - главный компонент в котором будет все происходить

  2. “DialogAbout.vue” и “DialogContacts.vue” - компоненты с контентом для нашего диалогового окна

Необходимое в vuex

В папке проекта “store” создадим еще одну директорию “dialog”, в ней уже на понадобятся 3 файла “state.js”, “mutations.js” и “index.js”.

Как уже стало понятно, файл “state.js” нужен нам для хранения состояний диалогового окна, в нем я пропишу несколько объектов с полями active и to.

Если в active стоит значение “true”, то данный элемент будет отображаться в диалоговом окне.

Свойство to должно хранить в себе ссылку или же пустую строку.

export default function () {
  return {
    leftBtn: {
      active: false,
      to: "",
    },
    rightBtn: {
      active: false,
      to: "",
    },
    downloadBtn: {
      active: false,
      to: "",
    },
    backBtn: {
      active: false,
      to: "",
    },
  };
}

Файл “mutations.js” содержит в себе мутации для изменения состояний нашего диалогового окна.

export function changeStatusLeftBtn(state, active = false, to = "") {
  state.leftBtn = { active, to };
}
 
export function changeStatusRightBtn(state, active = false, to = "") {
  state.rightBtn = { active, to };
}

export function changeStatusDownloadBtn(state, active = false, to = "") {
  state.downloadBtn = { active, to };
}
 
export function changeStatusBackBtn(state, active = false, to = "") {
  state.backBtn = { active, to };
}

“index.js”  нам нужен для соединения всех файлов в один модуль

import state from './state'
import * as mutations from './mutations'
 
export default {
  state,
  mutations,
}

После данных манипуляций мы можем добавить модуль dialog в store.

import dialog from "./dialog";
 
  const Store = createStore({
    modules: {
      dialog,
    },
  });

Наконец мы подошли к главному компоненту “TheDialog.vue”

Разметка

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

<template>
  <div class="dialog-bg">
    <div class="dialog-control-panel">
      <div class="panel">
        <div class="btn" v-ripple v-if="backBtn.active">
          <q-icon name="eva-arrow-back-outline" />
        </div>
      </div>
 
      <div class="panel">
        <div class="btn" v-ripple v-if="downloadBtn.active">
          <q-icon name="eva-cloud-download-outline" />
        </div>
 
        <div class="btn" v-ripple>
          <q-icon name="eva-close-outline" />
        </div>
      </div>
    </div>
 
    <div class="btn right" v-ripple v-if="rightBtn.active">
      <q-icon name="eva-arrow-ios-forward-outline" />
    </div>
 
    <div class="btn left" v-ripple v-if="leftBtn.active">
      <q-icon name="eva-arrow-ios-back-outline" />
    </div>
 
    <div class="dialog-content">
      <component :is="requiredModule" />
    </div>
  </div>
</template>

Логика

<script>
import DialogAbout from "components/dialog/DialogAbout";
import DialogContacts from "components/dialog/DialogContacts";
import { defineComponent, computed } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";
 
export default defineComponent({
  name: "TheDialog",
 
  setup() {
    const $store = useStore();
    const $route = useRoute();
 
    const requiredModule = computed(() => {
      const dialog = $route.query.dialog;
 
      if (dialog === "about") return DialogAbout;
      if (dialog === "contacts") return DialogContacts;
 
      return false;
    });
 
    const backBtn = $store.state.dialog.backBtn;
    const downloadBtn = $store.state.dialog.downloadBtn;
    const leftBtn = $store.state.dialog.leftBtn;
    const rightBtn = $store.state.dialog.rightBtn;
 
    return {
      requiredModule,
      backBtn,
      downloadBtn,
      leftBtn,
      rightBtn,
    };
  },
});
</script>

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

Добавим немного стилей

<style lang="scss" scoped>
.dialog-bg {
  width: 100%;
  min-height: 100vh;
  background-color: rgba(30, 30, 30, 0.9);
  position: fixed;
  left: 0;
  top: 0;
  z-index: 10;
  padding: 100px 0;
 
  .btn {
    width: 80px;
    height: 80px;
    color: rgba(255, 255, 255, 0.637);
    font-size: 1.8rem;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    position: relative;
    transition: background-color 0.2s;
 
    &:hover {
      background-color: rgba(0, 0, 0, 0.2);
    }
  }
 
  .dialog-control-panel {
    width: 100%;
    display: flex;
    justify-content: space-between;
    position: fixed;
    top: 0;
    left: 0;
 
    .panel {
      display: flex;
    }
  }
 
  .right,
  .left {
    position: fixed;
    height: calc(100vh - 160px);
    top: 80px;
  }
 
  .right {
    right: 0;
  }
 
  .left {
    left: 0;
  }
 
  .dialog-content {
    max-width: 732px;
    margin: auto;
  }
}
</style>

Результат

Все! Наше диалоговое окно готово.

Комментарии (8)


  1. korsetlr473
    04.12.2021 16:38

    автор вкурсе про новую вичу vue3 телепорт? )))

    подгружаемся дальше

    автор вкурсе про новую вичу html summary? где не нужно будет js вообще )))


  1. vovchisko
    04.12.2021 17:25
    +1

    С телепортом я бы пересмотрел подход: https://v3.ru.vuejs.org/ru/guide/teleport.html
    Идеальное окно... все через это проходят ????


    1. levnikrot Автор
      04.12.2021 18:12

      Конечно, можно было бы реализовать все это через teleport, но описанное решение в этой статье мне нравиться больше. Там нет ничего лишнего, все находиться в одном месте, добавлять новый контент довольно просто + легко вызывать окно, для этого нужно всего лишь изменить query параметр dialog.


      1. vovchisko
        04.12.2021 19:15

        Там нет ничего лишнего

        Роутер меня немного смущает.


  1. js_n00b
    04.12.2021 19:09
    +2

    Мне понравилась идея. Только небольшое замечание по семантике.
    Диалог подразумевает обратную связь с пользователем, то что реализовано в статье просто модальное окно.


  1. js_n00b
    04.12.2021 20:51

    Вот тут есть хук, возвращающий реактивные query-параметры. Вдруг пригодится.


    1. levnikrot Автор
      04.12.2021 21:59

      Полезная вещь, может даже эффективнее, чем тянуть route, но в данном примере я не использую VueUse.


  1. darkxanter
    05.12.2021 09:46
    +5

    Ожидал увидеть что-то подобное как в Quasar:

      const $q = useQuasar()
    
      $q.dialog({
        component: CustomComponent,
        // props forwarded to your custom component
        componentProps: {
          text: 'something',
          // ...more..props...
        }
      }).onOk(() => {
        console.log('OK')
      }).onCancel(() => {
        console.log('Cancel')
      }).onDismiss(() => {
        console.log('Called on OK or Cancel')
      })

    А оказались просто модальные окна.