Vueでモーダルウインドウを作成

![スクリーンショット 2025-01-10 174906.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3957418/1b16a4dd-450c-c0b6-409d-cf483b21cdb0.jpeg)


# モーダルウインドウの実装例
## /src/views/SideBar.vue

### `<li>タグを右クリックすると現れるモーダルウインドウを実装`
`@contextmenu.prevent="handleRightClick(record, $event)"`と書いて右クリックイベント実装
  
```
<template>
  <div>
    <label>サイドバー</label>
    <!-- リストの表示範囲を制限してスクロール可能にする -->
    <ul class="max-h-[calc(100dvh_-_50px)] overflow-y-auto space-y-2 bg-white shadow-md rounded-md custom-scrollbar">
      <li
        v-for="(record) in filteredRecords"
        :key="record.data_id"
        class="border-b border-gray-200 last:border-b-0 flex items-center cursor-pointer"
        @click="handleClick(record)"
        @contextmenu.prevent="handleRightClick(record, $event)"
      >
        <!-- 動的に画像パスをバインド -->
        <img :src="`./thumbnail/${record.img_path}`" alt="record image">
      </li>
    </ul>

    <!-- モーダルウィンドウ -->
    <div
      v-if="isModalVisible"
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
    >
      <div class="bg-white p-4 rounded shadow-md w-96">
        <h3 class="text-lg font-semibold mb-2">Record Details</h3>
        <p><strong>Data ID:</strong> {{ selectedRecord?.data_id }}</p>
        <p><strong>Image Path:</strong> {{ selectedRecord?.img_path }}</p>
        <button
          @click="closeModal"
          class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          Close
        </button>

        <button
          @click="OpenPic_btn"
          class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          画像を開く
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { ref, reactive, computed, watch, onMounted, toRefs,getCurrentInstance } from 'vue';
import { useUserStore } from "../stores/userStore";

export default {
  name: "SideBar",
  setup() {
    const filteredRecords = ref([]);
    const userStore = useUserStore();
    const isModalVisible = ref(false); // モーダルの表示状態
    const selectedRecord = ref(null); // 選択されたレコード

    // フィルタリングロジック
    const updateFilteredRecords = () => {
      if (!userStore.records || !Array.isArray(userStore.records)) {
        filteredRecords.value = [];
        return;
      }
      filteredRecords.value = userStore.records;
    };

    // `userStore.records` の変更を監視
    watch(
      () => userStore.records,
      (newRecords, oldRecords) => {
        console.log("userStore.recordsが変更されました:", {
          newRecords,
          oldRecords,
        });
        updateFilteredRecords();
      },
      { deep: true, immediate: true }
    );

    // 左クリックイベントの処理
    const handleClick = (record) => {
      console.log("クリックされたレコード:", record);
      userStore.setSelectItem(record);
    };

    // 右クリックイベントの処理
    const handleRightClick = (record, event) => {
      console.log("右クリックされたレコード:", record);
      event.preventDefault(); // デフォルトのコンテキストメニューを防止
      selectedRecord.value = record; // 選択されたレコードを保存
      isModalVisible.value = true; // モーダルを表示
    };

    // 新しいタブでリンクを開く
    const OpenPic_btn = () => {
      if (selectedRecord.value && selectedRecord.value.img_path) {
        const link = `./images/${selectedRecord.value.img_path}`;
        window.open(link, "_blank"); // 新しいタブでリンクを開く
      }
      isModalVisible.value = false;
      selectedRecord.value = null;
    };

    // モーダルを閉じる
    const closeModal = () => {
      isModalVisible.value = false;
      selectedRecord.value = null;
    };

    const { proxy } = getCurrentInstance();
    const showSuccessToast = () => {
      if (!proxy || !proxy.$toast) {
        console.error("Toast instance is undefined. Ensure VueToast is properly registered.");
        return;
      }
      proxy.$toast.open({
        message: "操作が成功しました！",
        type: "success",
        duration: 5000,
      });
    };

    return {
      //----------------------------------//
      showSuccessToast,
      //---------------------------------//
      filteredRecords,
      userStore,
      handleClick,
      handleRightClick,
      isModalVisible,
      selectedRecord,
      closeModal,
      OpenPic_btn,
    };
  },
};
</script>

<style>
/* カスタムスクロールバーのデザイン */
.custom-scrollbar {
  scrollbar-width: thin;
  scrollbar-color: #888 #f0f0f0;
}
.custom-scrollbar::-webkit-scrollbar {
  width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: #888;
  border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: #555;
}

/* モーダルスタイル */
.fixed {
  position: fixed;
}

.inset-0 {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.bg-opacity-50 {
  background-color: rgba(0, 0, 0, 0.5);
}

.z-50 {
  z-index: 50;
}

.flex {
  display: flex;
}

.items-center {
  align-items: center;
}

.justify-center {
  justify-content: center;
}

.shadow-md {
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>
```

## モーダルウインドウ本体部分
```
    <!-- モーダルウィンドウ -->
    <div
      v-if="isModalVisible"
      class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
    >
      <div class="bg-white p-4 rounded shadow-md w-96">
        <h3 class="text-lg font-semibold mb-2">Record Details</h3>
        <p><strong>Data ID:</strong> {{ selectedRecord?.data_id }}</p>
        <p><strong>Image Path:</strong> {{ selectedRecord?.img_path }}</p>
        <button
          @click="closeModal"
          class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          Close
        </button>

        <button
          @click="OpenPic_btn"
          class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          画像を開く
        </button>
      </div>
    </div>
```