作成日:2021/04/12 更新日:2022/01/27

#1 WebAPIを自作してデータをJSON形式で受信

WebAPIについて

APIApplication Programming Interfaceとは ユーザー側が欲しい情報を公開されたプログラムに対しリクエスト、 データベースのデータを取得したり操作できる仕組みのことです。Webで使われる場合は特にWebAPIと呼ばれます。

ユーザーのリクエストに対し、WebサーバにあるAPI(ここではphpファイル)が実行され、データベースからデータがJSON形式で戻って来たものをHTMLで表示させます。
もちろんデータベースのどんな情報にも勝手にアクセスできるわけではなく、APIを作った人の意図するデータだけを取得できる仕組みです。
今回はその仕組を作っていきましょう。

REST APIとは

またAPIを調べるとREST APIというものに出くわすとおもいます。 RESTRepresentational State Transfer(訳:理解しやすい通信)とは ブラウザとサーバ間のデータのやり取りの設計思想のことです。今回は小規模なので使っていません。隠しAPIのようなものを作ります。
ちなみにRESTではURIにhttps://cbc-study.com?name=大橋太郎&dataX=123のように クエリ文字、パラメーター(変数)と呼ばれる文字列をくっつけ、変数名:nameに値:大橋太郎、 変数名:dataXに値:123というデータを乗せて、サーバに送信します。
サーバ側ではユーザーからの操作の状態を POSTポスト(新規作成)、 Getゲット(読み込み)、 PUTプット(更新)、 DELETEデリート(削除) という手法(HTTPメソッド)で受信、結果をJSONでレスポンスを返す、などのルールがあります。

アプリをvue化させる方法

#1でコマンドラインを使ってvueのテンプレートを作りました。今回も同じ手法でテンプレートを作り、 #4で作った「名前移動アプリ」を完全にvueだけで作ってみたいと思います。
その際、PHPで作ったinputとupdateのロジックをAPI化し、jQueryで作ったドラッグアンドドロップ(以下、DnD)やajax機能を、vueのライブラリで実装します。

ファイル構成


ファイル構成はこのように変わります。
機能を色別に分けてみると、コンポーネントに役割が集中しているのがわかりますね。

jQueryライブラリを変更する

jQueryで利用していて変更するものとして、$.ajax()の代わりに非同期通信させるために axiosアクシオスライブラリを使います。 また、jQueryの.draggable()メソッドの代わりに、vue-draggable-resizableライブラリを使います。 これらのライブラリはすべて、npmコマンドでインストールしていきます。

vueテンプレート作成

$ brew updateは必要に応じて行いnode.jsやVUE-CLIはグローバルにインストール済みであるものとします。
job/stg/vue/内に02-moveという名前のプロジェクト名で始めましょう。
初期のファイル構成・内容は前ページ、#2 静的なページをvue.jsを使って構築すると同じです。

ターミナルで作成


$ cd ~/job/stg/vue/
$ vue create 02-move
> Default ([Vue 2] babel, eslint)   ←を選択

インストール完了後に移動
$ cd 02-move

$ npm i vue-router ress
$ npm i -D sass-loader@10 sass
エンターを押して必要なプラグインをインストールします。vue-routerとress、sass-loaderとsassをインストールします。
sass-loaderのバージョンを10にしているのはwebpackのバージョンが4であるためです。
※sassではなくnode-sassをインストールすよう解説しているものもありますが、現在node-sassは非推奨なので、 sass(Dart-Sass)を使いましょう。
またfibersをインストールする場合はnode.jsのバージョンが15以下の場合のみですので、16以上の場合は利用できません。

47 vulnerabilities (19 moderate, 28 high)
このようなエラーが出ますが、一旦無視して進めてください。

src/main.jsにrouterとscssの追記


/* main.js */
import Vue from 'vue'
import App from './App.vue'
import router from './router'     /* ←追加 */

import 'ress'                     /* ←追加 */
import '@/assets/scss/main.scss'  /* ←追加 */

Vue.config.productionTip = false

new Vue({
  router,              /* ←追加 */
  render: h => h(App),
}).$mount('#app')

router/index.jsを作成し、routerの設定を記述


/* router/index.js */
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/views/Home.vue'
Vue.use(VueRouter)

const routes = [
  { path: '/', component: Home }
]
const router = new VueRouter({
  mode: 'history',
  routes
})
export default router
routerフォルダを作成し中にindex.jsを作ります。

vue.config.jsの作成


/* vue.config.js */
module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/test/' : '/',

  productionSourceMap: process.env.NODE_ENV === 'production' ? false : true,

  devServer: {
    port: 8085,   /* この場合、http://localhost:8085 で表示されます */
    https: false
  },

  css: {
    loaderOptions: {
      scss: {
        additionalData: `@import "@/assets/scss/_variables.scss";`
      }
    }
  }
}
ポートは好きに変えてかまいません。

App.vueの編集


<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
1ページしかないので、上記のようにまるごと差し替えます。

SCSSファイルの作成

src/assets内に、scssディレクトリを作り、以下の2つのファイルを入れます。
_variables.scss

$breakpoints: (
  'sm': 340,
  'md': 768,
  'lg': 1215,
) !default;
@mixin mq($mq, $bp1:lg, $bp2:lg){
  $w1    : map-get($breakpoints, $bp1);
  $w2    : map-get($breakpoints, $bp2);
  $min1  : 'min-width: #{($w1+1)}px';
  $max1  : 'max-width: #{($w1)}px';
  $min2  : 'min-width: #{($w1+1)}px';
  $max2  : 'max-width: #{($w2)}px';

  @if $mq == min {
    @media screen and ($min1) {
      @content;
    }
  }
  @else if $mq == max {
    @media screen and ($max1) {
      @content;
    }
  }
  @else if $mq == min-max {
    @media screen and ($min2) and ($max2) {
      @content;
    }
  }
}

$mainColor:#7BC2BA;
main.scss

body,div,p{margin:0;}
.clearfix:after {
    content:"";
    display:block;
    clear:both;
}
body {
  height:100%;
  color:#ccc;
  font-size:10px;
}

views/Home.vueの作成


<template>
  <div id="wrapper">
  </div>
</template>

<script>
export default {
  name: 'Home',
}
</script>

<style lang="scss" scoped>
#wrapper {
  border: 2px dashed #ccc;
  margin:15px;
  border-radius: 6px;
  height: 600px;
}
</style>
src内に、viewsフォルダを作成し、Home.vueを作成

確認

ここまでで、ブラウザで確認してみましょう。

$ npm run serve

データは受信していないので、このような表示になればOKです。

APIファイルを作成

publicディレクトリにAPIファイルを作成

APIファイルは公開用に用意されているpublicディレクトリに配置します。

├── api
│   └── v1
│       ├── Controller
│       │   ├── AppController.php
│       │   └── Connect.php
│       └── user
│           └── index.php
├── favicon.ico
└── index.html
public/api/v1/ とディレクトリを作成します。v1とはバージョン1のことです。APIは機能をバージョンで管理していくので、 今回も便宜上v1としておきます。
Controller内のDBに接続するためのクラスファイルAppController.phpConnect.php はSortable4と同じものを使用します。Sortable4からフォルダごとコピーして入れておきます。
user/index.phpはこのような内容です。

<?php
require_once('../Controller/Connect.php');

try{
  $sql = '
    SELECT
      t1.*,
      genders.gender
    FROM
      sortable AS t1
    LEFT JOIN `genders` ON t1.gender_id = genders.id
    ';
  $select = new SelectData();
  $result = $select->select($sql);
  $json_data = json_encode( $result, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_NUMERIC_CHECK);

  header("Content-Type: application/json; charset=utf-8");
  header("Access-Control-Allow-Origin: http://localhost:8085");
  echo $json_data;

} catch (PDOException $e) {
  echo $e->getMessage();
}
解説
sortable4のindex.phpでは以下のように記述されていました。

<?php
$sql = '
    SELECT
      t1.*,
      genders.gender
    FROM
      sortable AS t1
    LEFT JOIN `genders` ON t1.gender_id = genders.id
    ';
$result = $select->select($sql);
$json_data = json_encode( $result, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
SQLのSELECT文や、$json_data変数を作るまでは同じです。
新たにheader()関数が追加されています。

header("Content-Type: application/json; charset=utf-8");
このファイルが「jsonテキストデータですよ」という宣言になります。

header("Access-Control-Allow-Origin: http://localhost:8085");
通常ではこのJSONファイルに、他のドメインからアクセスしようとした場合エラーが出ます。 第二引数に指定したアドレス(vueアプリ)からのアクセスを許可する場合に必要となります。

$json_data = json_encode(JSON_NUMERIC_CHECK);
json_encodeに引数JSON_NUMERIC_CHECKを追加しました。JSONデータで数字の部分をvueで使うときにNumberフォーマットにするためです。

echo $json_data;
このファイルにアクセスすると、JSONデータを出力するAPIの完成です。
ただし、ローカル環境でAPIのテストをする場合、vueアプリのポート8085ではpublicフォルダは表示されません。 (http://localhost:8085/api/v1/user/ では表示されない)
そこで新たに、MAMPでvueのpublicフォルダに8086ポートを設定してアクセスできるようにします。

Listen 8086
<virtualhost *:8086>
  DocumentRoot "/Users/ユーザ名/job/stg/vue/02-move/public/"
</virtualhost>
httpd-vhosts.confを開いて、上記を追記したあと、MAMPでサーバを再起動します。
するとこのAPIのアドレスはhttp://localhost:8086/api/v1/user/で開けるようになります。

このようにJSONデータが表示されればOKです。

非同期通信を行う「axios」のインストール

コンポーネントでAPIと非同期通信をしたい場合にはaxiosを使うと便利です。

$ npm i axios
インストール

import axios from 'axios'
Vue.prototype.$axios = axios
axiosを使うためmain.jsにこの2行を追記します。 HTTPリクエストを簡単に書くことができます。

APIからコンポーネントでデータを読み込む(GET)

コンポーネント「AppDrag.vue」の作成


<template>
<div id="drag-area">
  <div
    class="drag"
    v-for="item in users"
    :key="item.id"
    :class="'gender' + item.gender_id"
    :data-num="item.id"
    :style="'left:' + item.left_x + 'px; top:' + item.top_y + 'px;'"
  >
    <p><span class="name">{{item.id}} {{item.name}} ({{item.gender}})</span></p>
  </div>
</div>
</template>

<script>
export default {
  name: 'AppDrag',
  data() {
    return {
      users: []
    }
  },
  mounted: function() {
    this.$axios.get('http://localhost:8086/api/v1/user/')
        .then(response => (this.users = response.data))
        .catch(error => console.log(error))
  },
}
</script>

<style lang="scss" scoped>
#drag-area {
  position: relative;
  height: 500px;
  width:980px;
  margin: 5px auto;
  border: 1px solid #333;
  border-radius: 6px;
  .vdr.active:before {
    outline: none;
  }
  .drag {
    width:auto;
    height:auto;
    position: absolute;
    padding: 1em;
    color: #666;
    border-radius: 5px;
    background: #efefef;
    border: 3px solid #ccc;
  }
  .name {
    font-size:12px;
  }
  .gender1{
    border: 2px solid #00F;
  }
  .gender2{
    border: 2px solid #F00;
  }
}
</style>

<template>
<div id="drag-area">
  <div
    class="drag"
    v-for="item in users"
    :key="item.id"
    :class="'gender' + item.gender_id"
    :data-num="item.id"
    :style="'left:' + item.left_x + 'px; top:' + item.top_y + 'px;'"
  >
    <p><span class="name">{{item.id}} {{item.name}} ({{item.gender}})</span></p>
  </div>
</div>
</template>
基本的には前回のsertable4と同様です。
JSONデータをjsonDataからusersに変更しました。 また、v-bindは省略できるため省略しています。
script data()の解説

<script>
export default {
  name: 'AppDrag',
  data() {
    return {
      users: []
    }
  },
  mounted: function() {
    this.$axios.get('http://localhost:8086/api/v1/user/')
        .then(response => (this.users = response.data))
        .catch(error => console.log(error))
  },
}
</script>
重要な設定はdata関数created関数です。
これらはライフサイクルフックと呼ばれ、vueファイルが読み込まれHTMLファイルが(DOMが)描画されるタイミングごとに、 処理を設定していきます。
詳しくはvueのマニュアル を見てみましょう。

usersという配列データを使うので、空のusersを用意しておきます。
dataはオブジェクト形式ではなく、関数形式で記述する必要があります。

/* オブジェクト形式 */
data: {
  users: []
}

/* 関数形式 */
data() {
  return {
    users: []
  }
}
new Vue()を使ってインスタンスを作る場合はオブジェクト形式で、
コンポーネントの定義のときには、初期データオブジェクトを返す関数として宣言する必要があります。
script axiosの解説

mounted: function() {
  this.$axios.get('http://localhost:8086/api/v1/user/')
      .then(response => (this.users = response.data))
      .catch(error => console.log(error))
}
mountedフックはDOM生成後に実行されます。 このコンポーネントでaxiosをimportした場合はaxios.get()で指定しますが、 main.jsにインポートしているのでどこでもaxiosを使えます。その場合は例のようにthis.$axios.get()で指定します。

this.$axios.get('http://localhost:8086/api/v1/user/')
this.$axiosに続き、getメソッドget('APIアドレス')でjSONデータを取得します。
postメソッドの場合はpost('APIアドレス')になります。

APIアクセスが成功した場合、失敗した場合

.then(response => (this.users = response.data))
.catch(error => console.log(error))
.then()メソッドで成功したときの処理を、.catch()メソッドで失敗したときの処理を記述します。

getリクエストが成功した場合、データ*1が戻ってきます(コールバック)のでresponseという名前で保存します。 いろんなデータが取得できますがresponse.dataで中身のデータが取り出せますので、 data()で設定したthis.usersに代入します。

*1 このデータはPromiseプロミスオブジェクトと呼ばれます。 Promiseデータは、then()メソッドを使うことで`正常データ`を取り出します。`エラー情報`はcatch()メソッドで取得できます。

Home.vueでコンポーネントファイルAppDrag.vueの読み込み設定


<template>
  <div id="wrapper">
    <AppDrag />
  </div>
</template>

<script>
import AppDrag from '@/components/AppDrag.vue'
export default {
  name: 'Home',
  components: {
    AppDrag
  }
}
</script>
import でコンポーネントファイルを読み込みます。
components オプションに使うコンポーネント名を登録。
template 内で使うコンポーネント(タグ)を指定します。

APIからJSONデータを読み込み、内容が表示されればOKです。
まだドラッグ機能は追加していないので要素は動きません。

DnD機能の実装

vue-draggable-resizableのインストール


$ npm i vue-draggable-resizable
npmでインストールします。

コンポーネントAppDrag.vueを編集

JavaScriptの設定

<script>
import VueDraggableResizable from 'vue-draggable-resizable'

export default {
  name: 'AppDrag',
  components: {
    VueDraggableResizable
  },
  data() {
  ・・・
DnDライブラリvue-draggable-resizableをインポートします。
components: でこのライブラリのVueDraggableResizableコンポーネントを登録します。
templateの設定

<template>
  <div id="drag-area">
    <VueDraggableResizable
      v-for="item in users"
      :class="'drag gender' + item.gender_id"
      :key="item.id"
      :data-num="item.id"

      :parent="true"
      :resizable="false"
      :x="item.left_x"
      :y="item.top_y"
      :w="150"
      :h="38"
    >
      <p><span class="name">{{item.id}} {{item.name}} ({{item.gender}})</span></p>
    </VueDraggableResizable>
  </div>
</template>
解説
前回までは、ただの<div>タグ内でv-for="item in jsonData"として、データをループ表示させていました。
今回は、DnDライブラリで設定されているVueDraggableResizableをAppDrag.vueの子コンポーネントとして読み込みます。

VueDraggableResizableで設定できるv-bindを設定して、動的にデータをやりとりできるようにします。

:parent="true"       /* 親要素ないでDnDできるようにする */
:resizable="false"   /* リサイズ機能は外しておきます */
:x="item.left_x"     /* style属性のx座標データが動的に入ります */
:y="item.top_y"      /* style属性のy座標データが動的に入ります */
:w="150"             /* 要素の幅 */
:h="38"              /* 要素の高さ */
x,y座標や要素の幅、高さをバインドします。

:class="'drag gender' + item.gender_id"
class="drag"は:class="'gender' + item.gender_id"に統合しました。

:style="'left:' + item.left_x + 'px; top:' + item.top_y + 'px;'"
またv-bind:styleで設定していた内容は、VueDraggableResizableに同様の機能としてtransformでx,y座標を保存・表示させるため、削除しました。
DnDはできるようになりましたが、座標データが保存されていないので、リロードすると要素はもとに戻ってしまいます。

DnDのあとに座標データをAPIに送信する(POST)

ドラッグ終了時のカスタムイベントの設定


@dragstop="onDragStop"
テンプレートの<VueDraggableResizable>タグにDnDの終了時の処理を入れます。
@dragstopはv-on:dragstopのことです。 dragstopはDnDライブラリVueDraggableResizable で定義されているカスタムイベントです。 dragが終了したらxy座標の情報を取得するように作られています。
onDragStopはmethods:{}内で定義したメソッド名です。どんな文字列でも構いません。

ただし、idデータがなければ、どの要素の座標かわからないので役に立ちません。DnDライブラリはidを取得するようには設計されていませんので、 カスタムイベントを以下のように改定しておきます。

@dragstop="(x, y) => onDragStop(x, y, item.id)"
本来の引数は2つですが、idも取得できるようにできました。
カスタムイベントについて
カスタムイベント
このようにテンプレート上で@:カスタムイベント名='メソッド名'のように設定できます。 子コンポーネント側ではthis.$emit('カスタムイベント名')と設定しておき、親側のメソッドにデータを渡します。 親のメソッドにはメソッド名: function(){}でイベントが実行されときの処理を設定しておきます。

vueでは他のコンポーネントのデータ・メソッドに直接アクセスできない仕組みになっており、 コンポーネント同士でデータをやりとりする場合はpropsプロパティproperty)、 $emitエミット発する)を使います。
カスタムイベントにおけるpropsとemitの図解
こんなイメージで良いかと思います。
vueでは子側から親のデータの書き換えをできなようにすることでデータの保守を行っています。
詳しくは疎結合そけつごう密結合みつけつごうで調べてみましょう。
$emitは生成したデータを渡しているだけで、親のデータを書き換えているわけではありません。

このアプリで言えば、
① DnDしたときの移動中の値が入る
② 受け取る
③ マウスを離すとカスタムイベント@dragstopが発生。propsのデータを固定。
④ 子側から親のonDragStopメソッドの引数にデータがemitされる
⑤ メソッドが実行される(APIにデータを送信するなど)

DnDライブラリのドラッグ終了時にaxiosでデータ送信

終了時のメソッドonDragStopを設定します。

methods: {
  onDragStop: function(x, y, id) {
    this.dragging = false

    let params = new URLSearchParams();
        params.append('left', x);  /* params.append('変数名', 値); */
        params.append('top', y);
        params.append('id', id);

    this.$axios.post('http://localhost:8086/api/v1/user/', params)
        .catch(error => console.log(error))
  }
}


this.dragging = false
ドラッグを止めていることを定義します。


let params = new URLSearchParams();

axiosが通信するときのContent-type: ファイルの情報分類application/json形式で送信されますが、 APIではPHPで受信するので、PHPで受けれるContent-type:であるapplication/x-www-form-urlencoded形式でaxios送信するためのクラスです。
※PHP側をjsonに対応することでも対処できます。


params.append('left', x);
params変数にappendで値を追加していきます。params.append('変数名', 値);という形です。
Content-type確認方法
データはJSON形式{'left':0, 'top':0, 'id':20}ではなく
クエリパラメータ形式left=0&top=0&id=20という形で送信されます。
ChromeであればNetworkのXHRタブ、Nameかuser/のpostデータ、Headersを参照すると確認できます。

APIでデータの受信とDB登録

public/api/v1/user/index.phpに以下を追記する

<?php
require_once('../Controller/AppController.php');
・・・省略・・・

if(!empty($_POST['left'])){
  try{
    $sql  = ' 
      UPDATE
        sortable
      SET
        left_x = :LEFT,
        top_y  = :TOP
      WHERE
        id = :NUMBER
      ';

    $obj = new AppController();
    $obj->update_sortable($sql, $_POST['left'], $_POST['top'], $_POST['id']);

  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}
axiosを経由してPOSTで受信した「座標、id」データを受けて、DBに登録。以前作成したアプリと同じものです。 リロードしても座標が変わらないことが確認できます。

新規登録コンポーネントの作成

コンポーネント「AppImport.vue」の作成


<template>
  <div id="input_form">
    <input v-model="name" type="text" name="inputName" placeholder="新メンバー名を入力">
    <input v-model="gender" type="radio" name="inputGender" value="1">男性
    <input v-model="gender" type="radio" name="inputGender" value="2">女性
    <button v-on:click="createUser">送信</button>
  </div>
</template>

<script>
export default {
  name: 'AppImport',
  data() {
    return {
      name: '',
      gender: 2,
    }
  },
  methods: {
    createUser: function(){
      let params = new URLSearchParams();
          params.append('inputName', this.name);
          params.append('inputGender', this.gender);

      this.$axios.post('http://localhost:8086/api/v1/user/', params)
          .then(location.reload())
          .catch(error => console.log(error))
    }
  }
}
</script>

<style  lang="scss" scoped>
#input_form {
  padding:20px;
  background:#efefef;
  input{
    margin:0 3px;
  }
  input[type="text"] {
    width: 200px;
    margin-right:15px;
    padding: 6px 12px;
    border:none;
    font-size: 16px;
    border-radius: 6px;
    background-color:#fff;
  }
  button {
    padding: 7px 20px;
    font-size: 12px;
    border:none;
    border-radius: 6px;
    background: #75d1dc;
    color:#333;
  }
}
</style>
解説

<template>
  <div id="input_form">
    <input v-model="name" type="text" name="inputName" placeholder="新メンバー名を入力">
    <input v-model="gender" type="radio" name="inputGender" value="1">男性
    <input v-model="gender" type="radio" name="inputGender" value="2">女性
    <button v-on:click="createUser">送信</button>
  </div>
</template>
inputの値をデータバインディングするため、それぞれのinputにv-modelを追加します。
v-model="name"とすることで、データを取得できます。
<input type="submit" value="登録"><button v-on:click="createUser">送信</button>に変更しました。 クリックされると、methods:で定義したcreateUserメソッドが起動します。

<script>
export default {
  name: 'AppImport',
  data() {
    return {
      name: '',
      gender: 2,
    }
  },
  methods: {
    createUser: function(){
      let params = new URLSearchParams();
          params.append('inputName', this.name);
          params.append('inputGender', this.gender);

      this.$axios.post('http://localhost:8086/api/v1/user/', params)
          .then(location.reload())
          .catch(error => console.log(error))
    }
  }
}
</script>
dataオブジェクトにnameの初期設定、genderの初期値を入れることでcheckedの役割をもたせています。1を設定すると男性が初期にチェックされます。
v-on:clickで呼び出されるcreateUserメソッドでは、送るデータを整理してaxiosでAPIに送ります。
paramsデータの作り方はDnDの座標を送る手順と同様です。名前と性別データを飛ばします。
成功した場合.thenに記述した処理が起動します。登録が完了したらlocation.reload()して、ブラウザをリロードします。

input{
  margin:0 3px;
}
input[type="text"] {
  background-color:#fff;
}
button {
  color:#333;
}
前回から追加したscssは上記の3点です。

Home.vueにコンポーネントAppImport.vueを設定


<template>
  <div id="wrapper">
    <AppImport />
    <AppDrag />
  </div>
</template>

<script>
import AppImport from '@/components/AppImport.vue'
import AppDrag from '@/components/AppDrag.vue'

export default {
  name: 'Home',
  components: {
    AppImport,
    AppDrag
  }
}
</script>
AppImport.vueをimportして、components:に登録、<AppImport />を追加します。

登録画面コンポーネントが追加されました。

新規登録データをAPIで受信しDB登録

APIファイルに新規登録ロジックを追記

public/api/v1/user/index.phpに新規登録したらDBに登録するロジックを追記します。
といっても、以前PHPで作った内容そのままです。

/* axiosを経由してPOSTで受信した「名前、性別」データを受けて、DBに登録 */
if(!empty($_POST['inputName'])){
  try{
    $sql = '
      INSERT INTO sortable(
        name,
        gender_id
      )
      VALUES(
        :ONAMAE,
        :GENDER
      )
      ';

    $obj = new AppController();
    $obj->insert_sortable($sql, $_POST['inputName'], $_POST['inputGender']);
    /* file_put_contents('aaa.txt', $_POST);  //データ取得確認用  */

  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}

座標が登録できるようになったため、リロードしても残ります。
これで、PHPで作ったアプリを、vue.jsに完全移植することに成功しました。
ここまでのコードはこちら よりダウンロードできます。

Webサーバーにデプロイする

レンタルサーバのxserverを例に、Webサーバにデプロイする方法です。
サーバにアップする場合に変更するヶ所は以下のとおりです

const DB_NAME ='cri_sortable';
const HOST    ='mysql****.xserver.jp';
const UTF     ='utf8';
const USER    ='mysqlユーザ名';
const PASS    ='mysqlパスワード';
api/v1/Controller/Connect.php
レンタルサーバの情報を入れます。

mountedに書いてある
this.$axios.get('https://******/.com/test/api/v1/user/')

methodsに書いてある
this.$axios.post('https://******.com/test/api/v1/user/', params)
AppDrag.vue

header("Access-Control-Allow-Origin: http://localhost:8085")  /* ローカルの場合の設定 */
api/v1/user/index.php
同じドメインからアクセスするのであれば削除する。

サブディレクトリ「test」でデプロイしたい場合

publicPath: process.env.NODE_ENV === 'production' ? '/test/' : './',
vue.config.js

/* { path: '/', component: Home } */
{ path: '/test', component: Home }
router/index.js
サブディレクトリを指定すると(https://******.com/test)でアクセスできます。

使い方はわかったので、みなさんの考えたアプリをどんどん作っていきましょう!