見出し画像

Vue.js勉強記録その34 Slack投稿アプリを作ろうver2

こちらの書籍で勉強中です。

が、今回は少しテキストから離れてSlack投稿アプリを作ったので、備忘録的に作り方を残します。

大まかな流れは以下

1.slackのAPIから、アプリを登録し、ボットで投稿できるトークンを取得
2.そのアプリを、該当のSlackのチャンネルに追加
3.axiosを使ってSlackにアクセスし、投稿する仕組みを作る
4.routerを使って、各クラスのSlackに投稿出来る様にする

1、2は、ver1を参照

■必要なパッケージのインストール

今回は、router、vuex、axiosを使う予定なので、各々インストールします。

routerをインストール

npm install vue-router@4


vuexをインストール

npm install vuex@next


axiosをインストール

npm install axios


■必要なファイルを準備

今回のファイル構成は以下の感じです。(主たるもののみ記載)

index.html
┗src
    ┗main.js
    ┗router.js
    ┗store.js
    ┗App.vue
    ┗components
        ┗Slack.vue


index.htmlにbootstrapを準備

index.html

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <link rel="icon" href="/favicon.ico" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Slack</title>
 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
   integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
 <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
   integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
   crossorigin="anonymous"></script>
 <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
   integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
   crossorigin="anonymous"></script>
 <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
   integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
   crossorigin="anonymous"></script>
 <script type="module" src="/020/slack/_assets/index.d98cf061.js"></script>
 <link rel="stylesheet" href="/020/slack/_assets/style.0b11845e.css">
</head>

<body>
 <div class="container">
   <div id="app"></div>
 </div>

</body>

</html>


router.jsとstore.jsの準備

router.js

import { createRouter, createWebHistory } from 'vue-router'
import Slack from "./components/Slack.vue";

export const router = createRouter({
   history: createWebHistory(),
   routes: [
       {
           path: '/path/:param',
           name: 'Slack',
           component: Slack,
           props: true
       },
   ]
})

path:'/path/:param'の、pathの部分は実際にアップロードする先のパスが入ります。

store.js

import { createStore } from 'vuex'
export const store = createStore({
   state() {
       return {
           key: '',
           keys: {
               'test': 'xoxb-aaaaaaaaaaaaaaaaa',
               'class1': 'xoxb-bbbbbbbbbbbbbbb',
               'class2': 'xoxb-ccccccccccccccccc',
               'class3': 'xoxb-dddddddddddddddddddd'
           },
           proxy: '/path/slack_post.php',
           success: false,
       }
   },
   mutations: {
       setKey: (state, targetClass) => {
           state.key = state.keys[targetClass];
       },
       postSuccess: (state) => {
           state.success = true
           setInterval(() => {
               state.success = false
           }, 3000);
       }
   }
})

xoxb-aaaaaaaaaaaaaこの部分は、slackのトークンが入ります。
各クラス用にslackのトークンは用意しています。

また、proxy: '/path/slack_post.php'は、axiosでアクセスして、slackにpostする為に用意するphpです。

router.jsとstore.jsを用意したら、main.jsを用意します。

main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import { router } from './router'
import { store } from './store.js'

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

これでrouterとstoreが使えます。

use(router).use(store)

この書き方ってあってるのだろうか。。。まぁ動いたからよしとしよう。


■処理の流れ

Slack.vueにテキストを入力するtextareaや、setupなどを書いていきます。

<template>
 <div v-if="data.active" class="alert alert-primary">
   <div class="form-group">
     <label for="qa">質問を投稿する</label>
     <textarea
       class="form-control"
       id="qa"
       rows="3"
       v-model="data.msg"
     ></textarea>
     <button class="btn btn-primary mt-3" @click="btnClick">投稿</button>
   </div>
   <transition name="success">
     <p v-if="$store.state.success" class="alert alert-secondary" role="alert">
       質問を投稿しました
     </p>
   </transition>
 </div>
 <div v-else class="alert alert-danger">
   <p class="h1 m-0">urlを確認してください</p>
 </div>
</template>


<script>
import axios from "axios";
import { onMounted, reactive } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";

export default {
 name: "Slack",
 props: {
   param: String,
 },
 setup(props, context) {
   const store = useStore();
   const data = reactive({
     msg: "",
     active: false,
   });

   const btnClick = () => {
     let params = new URLSearchParams();
     params.append("keys", store.state.key);
     params.append("mes", data.msg);

     axios
       .post("/020/proxy/slack_post.php", params)
       .then((response) => {
         store.commit("postSuccess");
         data.msg = "";
       })
       .catch((e) => {
         alert("データの送信に失敗しました");
       });
   };

   onMounted(() => {
     const route = useRoute();
     data.active = route.params.param in store.state.keys ? true : false;
     store.commit("setKey", route.params.param);
   });
   return { data, btnClick, store };
 },
};
</script>

<style>
.success-enter-active,
.success-leave-active {
 transition: 0.5s;
}
.success-enter-from {
 opacity: 0;
}
.success-enter-to {
 opacity: 1;
}
.success-leave-from {
 opacity: 1;
}
.success-leave-to {
 opacity: 0;
}
</style>

templateの箇所は、textareaとボタンがあります。

textareaには、msgプロパティが結び付けられ、ボタンにはクリックイベントでbtnClickが設定されています。


onMountedで、URLのパラメータを調べています。

onMounted(() => {
     const route = useRoute();
     data.active = route.params.param in store.state.keys ? true : false;
     store.commit("setKey", route.params.param);
});

route.params.paramは、router.jsの/path/:paramのparamの部分です。

この値がstoreに書かれている、store.state.keysの中にあるかを調べています。あればactiveプロパティにtrueを、なければfalseを代入します。このactiveプロパティは、テンプレート内で表示を切り替える為に使っています。
store.state.keysのキーの中に、URLのパラメーターの値があればテキストエリア等を表示し、無ければエラー画面が出る様にしました。

ミューテーションsetKeyを呼び出し、パラメーターを引数で渡しています。

setKey: (state, targetClass) => {
    state.key = state.keys[targetClass];
}

setKeyの内容は、keyプロパティに、オブジェクトであるkeysプロパティから送られてきた引数で値を参照し代入しています。


ボタンをクリックすると、btnClickメソッドが呼び出されます。

const btnClick = () => {
 let params = new URLSearchParams();
 params.append("keys", store.state.key);
 params.append("mes", data.msg);

 axios
   .post(store.state.proxy, params)
   .then((response) => {
     store.commit("postSuccess");
     data.msg = "";
   })
   .catch((e) => {
     alert("データの送信に失敗しました");
   });
};

内容は、slackのトークンと、メッセージをパラメーターとして、phpに渡しつつアクセスしています。

phpの内容はこちら

<?php
$key = $_POST['keys'];
$mes = htmlspecialchars($_POST['mes']);


$headers = [
   'Authorization: Bearer '.$key, 
   'Content-Type: application/json;charset=utf-8'
];

$url = "https://slack.com/api/chat.postMessage"; 

$post_fields = [
   "channel" => "チャンネル名",
   "text" => $mes,
];

$options = [
   CURLOPT_URL => $url,
   CURLOPT_RETURNTRANSFER => true,
   CURLOPT_HTTPHEADER => $headers,
   CURLOPT_POST => true,
   CURLOPT_POSTFIELDS => json_encode($post_fields) 
];

$ch = curl_init();

curl_setopt_array($ch, $options);

$result = curl_exec($ch); 

curl_close($ch);
echo $result;
?>

データをポストで受け取り、slackへテキストを投げる感じ。

btnClickは、同時にミューテーションpostSuccessも呼び出しています。

postSuccess: (state) => {
   state.success = true
   setInterval(() => {
       state.success = false
   }, 3000);
}

内容は、successプロパティをtrueにし、3秒後にfalseに戻しています。

この値は、テンプレート内で、「質問を投稿しました」のテキストを表示させるのに使っています。


■アップロードとhtaccess

ビルドした後のhtmlを見ると、jsとcssへのパスが、ルートパスになっている。

これだと、今回アップロードする、

/000/slack/

ここにアップした時に、パスが通らないので、ビルド後のhtmlのパスを書き直して、パスが通る様にした。

この辺、設定で変えられるっぽかったけど、うまく動いてくれなかった。。。ここは次回までの課題ってことで。

また、ただ単にアップした場合うまくrouterの設定が動かなかった。
こちらは、.htaccessを使うと良いらしい。

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteBase /
 RewriteRule ^index\.html$ - [L]
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule . /020/slack/index.html [L]
</IfModule>

これを、ビルドしたHTMLたちと一緒にアップロード。

これで無事に動きました。


■参考サイト

今回サイトを作るのに、色々参考にさせていただいたサイトです。色々調べながら作成しました。
ver1に書いたものも含めて、もう一度まとめておきます。


■まとめ

ひとまず出来上がって良かったです。今度は画像の投稿機能とかつけられたらなぁと思っています。
もっともっと、便利にしていきたいですね。

今回、主にビルド時の設定?などがうまくいかずに強行突破的に直しちゃったところが多いので、この辺が課題かなと思います。

個人的には、vuexが便利だなぁと感じます。必要なデータを、一つにまとめておけるのは、色々とっちらからなくて良いですね。

色々作りたいものが多くて、時間が足りません><

まだまだがんばります。

この記事が気に入ったらサポートをしてみませんか?