LoginSignup
0

posted at

updated at

Organization

【Vue-CLI+Typescript+Element UI】でナビゲーションとモーダルダイアログを作成してみる

はじめに

初心者エンジニアです。初めてコードに触れて数ヶ月が経ちました。
勉強のために、Vue3とTypeScriptとelement-plusを用いて新しくページを作成してみました。

内容は以下の通りです。データは固定です。

  • 環境構築
  • Vue Routerとelement-plusでナビゲーションタブを実装(クリックアクション)
  • element-plusでモーダルダイアログを実装(クリックアクション)

対処したエラー:

  • TS2339 : Property 'XXX' does not exist on type '{ openDialog(row?: any): Promise<void>; closeDialog(): void; onCancel(): void; onSubmit(): void; initForm(): void; }'.
  • Component name "XXX" should always be multi-word
  • An empty interface is equivalent to `{}`. (no-empty-interface)

完成形

image.gif

本記事はARISE analytics Advent Calender 2022の9日目です。

環境構築

環境:
yarn 1.22.19
@vue/cli 5.0.8
element-plus
vue-router@4

  1. まずyarnをインストールします。
$ npm install --global yarn

2. Vue-CLIをインストールします。

$ yarn global add @vue/cli
$ vue --version

3. 任意の名前でプロジェクトを作成します。今回はsampleという名前で作成しました。インストールを開始するとpresetの選択画面が表示されるので、”Manually select features”を選択してください。その後の選択肢はをこちらを参考にしました。TypeScriptも忘れずに選択してください。

$ vue create sample

4. Vueを起動してみます。

$ cd sample
$ yarn serve

起動できたら、ブラウザでローカルにアクセスしてみます。Vue.js + TypeScriptの文字が表示されていたらOKです。

5. あとはelement-plusとvue-routerもインストールしてください。

$ yarn add element-plus
$ npm install vue-router@4

これで環境構築は完了です。

Vue Routerとelement-plusでナビゲーションタブを実装

  1. まず、main.tsです。使用するelement-plusやrouterを入れていきます。

ディレクトリ構成は最後に載せています。

main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from "./router";
import App from './App.vue'

createApp(App)
.use(ElementPlus)
.use(router)
.mount('#app')

2. ルーティングを実装していきます。
今回作るタブのは3つで、ホーム、ギフト、教室のタブを作ります。
srcの下にrouterディレクトリを作成し、index.tsも作成します。
中身は以下のようにします。

補足:こちらの記事を参考にさせていただきました。vueプロジェクトを作成する際からVue Routerを入れる方法もあるようです。

index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/components/Home.vue'
import Gift from '@/components/Gift.vue'
import Lesson from '@/components/Lesson.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/gift',
    name: 'gift',
    component: Gift
  },
  {
    path: '/lesson',
    name: 'lesson',
    component: Lesson
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

2. タブを選択したときに表示する内容のコンポーネントを作成します。
componentsディレクトリにGift.vue, Home.vue, Lesson.vueを次のように作成します。ここら辺は自由に作成してください。

Gift.vue
<template>
    <h1>gift</h1>
</template>
Home.vue
<template>
    <h1>home</h1>
</template>
Lesson.vue
<template>
    <h1>lesson</h1>
</template>

3. ナビゲーションバーを設置するのにもう一つコンポーネントを用意しました。
el-menuを使っています。

エラー: TS2339 : Property 'XXX' does not exist on type '{ openDialog(row?: any): Promise<void>; closeDialog(): void; onCancel(): void; onSubmit(): void; initForm(): void; }'.

環境構築の際に選択した設定で、defineComponentをつけないといけなくなっています。最初つけていなくて1日動かなかった、、

NavBarIndex.vue
<template>
  <div class="navbar">
    <el-menu
      :router="true" 
      :default-active="$route.path"
      class="el-menu"
      mode="horizontal"
      background-color="#545c64"
      text-color="#fff"
      active-text-color="#ffd04b"
    >
      <el-menu-item index="1" :route="{ name:'home' }">ホーム</el-menu-item>
      <el-menu-item index="2" :route="{ name:'gift' }">ギフト</el-menu-item>
      <el-menu-item index="3" :route="{ name:'lesson' }">教室</el-menu-item>
    </el-menu>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'NavBarIndex',
});

</script>

4. App.vueを変更します。
router-viewとnavBarIndexはここで入れてます。スタイルはほぼデフォルトです。

App.vue
<template>
  <NavBarIndex />
  <router-view />
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import NavBarIndex from './components/NavBarIndex.vue';

export default defineComponent({
  name: 'App',
  components: {
    NavBarIndex
  }
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 0;
}
</style>

ここまでで大体終わりなのですが、エラーが出ました。

エラー: Component name "XXX" should always be multi-word

調べてみるとeslintのエラーで、命名規則を制御するエラーのようなので、.eslintrc.jsのrules:の中に以下を追記して解消しました。

.eslintrc.js
'vue/multi-word-component-names': 0 

これで動くはずです。

element-plusでモーダルダイアログを実装

Gift.vueを書き換えて、ボタンをクリックすると、モーダルダイアログが出現するようにしました。el-dialogを使用しています。

ダイアログの中身にはel-formを使用して入力フォームを作ってみました。

Gift.vue
<template>
    <div class="app-container">
        <h1>gift</h1>
    <el-row>
      <el-button round :icon="Edit" @click="openDialog">コメント</el-button>
    </el-row>
    
  <el-dialog v-model="dialogVisible" title="コメント" width="80%">
        <el-form
        :model="form"
        :rules="rules" 
        >    
        <el-form-item label="ジャンル">
          <el-select
            v-model="form.genre"
            placeholder="ジャンル"
          >
            <el-option label="ご意見" value="issue" />
            <el-option label="ご要望" value="request" />
          </el-select>
        </el-form-item>
          <el-form-item label="内容">
            <el-input v-model="form.content" type="textarea" />
          </el-form-item>
      </el-form>
        <template #footer>
          <span class="dialog-footer">
            <el-button
              type="primary"
              @click="onCancel"
              >Cancel
            </el-button>
            <el-button
              type="primary"
              @click="onSubmit"
              >送信
            </el-button>
          </span>
        </template>
      </el-dialog>
    </div>
</template>

<script lang="ts">
import { reactive } from 'vue';
import { defineComponent } from 'vue';

interface ReactiveStateType {
    dialogVisible: boolean;
    form: {
      genre?: string;
      content?: string;
    };
    rules: {
      [name: string]: {
        [name: string]: any;
      }
    }
}


export default defineComponent({
    name: 'Gift',
    components: {
    },
    data() {
        const reactiveTarget: ReactiveStateType = {
        dialogVisible: false,
        form: {
          genre: undefined,
          content: '',
        },
        rules: {},
    };
        const state: any = reactive(reactiveTarget);
        return {
            form: {
                genre: undefined,
                content: '',
            },
            ...state,
        };
    },
    methods: {

      async openDialog(row?: any) {
        this.initForm();
        this.dialogVisible = true;
      },
      closeDialog(){
        this.dialogVisible = false;
      },
      onCancel() {
        this.closeDialog();
        this.initForm();
      },
      onSubmit() {
        this.closeDialog();
      },
      initForm() {
        this.form.genre = undefined;
        this.form.content = '';
      },
    }
})
</script>

エラー: An empty interface is equivalent to `{}`. (no-empty-interface)

作成している途中、空のinterfaceを設定したらエラーが出て怒られました。.eslintrc.jsのrules:の中に以下を追記して解消しました。

.eslintrc.js
'@typescript-eslint/no-empty-interface': 'off' 

ディレクトリ構成

作成したsampleディレクトリ配下の構成です。

├── public
│   ├── favicon.ico
│   └── index.html
│── src
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   ├── Gift.vue //追加
│   │   ├── Home.vue //編集
│   │   ├── Lesson.vue //追加
│   │   └── NavBarIndex.vue //追加
│   ├── router
│   │   └── index.vue //編集
│   ├── App.vue //編集
│   ├── main.ts //編集
│   └── shims-vue.d.ts
├── .browserslistrc
├── .eslintrc.js  //設定を追加
├── .gitignore
├── babel.config.js
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
├── vue.config.js
├── yarn.lock
├── node_modules //中身は省略

おわりに

el-menuのサンプルを検索してもrouterとどう動くのかわからず苦戦しました、、エラーがTS2339とかで出るからわからない、、
が、なんとか目標にしてたところまでできました。
今後この入力フォームに入れたデータをどうにかしたりしていきたいです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0