留学vue2のCDN解説-売上原価プログラムを作ってみた-

vue2のCDN解説-売上原価プログラムを作ってみた-

こんにちは山本です。

今jsに興味がある方はnext.jsやnest.jsをやっているかと思います。
でもこれからreactをやりたいと漠然と考えている方もいるでしょう。

vue2のCDN→vue2 CLI→vue3 nuxt3 → react →next
php→laravel→nest
をお勧めします。

という意味ではvue2 CDNは初学者に必須かなと個人的には思います。

phpがちょっとできて、jqueryくらいならできるよという人
もっともたくさんいるボリュームゾーンの人たちに対してお話ししていきます。

というわけで解説していきましょう。

まずvue.jsを勉強する上で、
いくつかjavascriptの基本を知っていないと
“解説の解説がわからない”と言うことになってしまい
学習難易度が上がってしまうので 先にjavascriptの基本の中でvueを勉強する上で知っておいた方がいいものをお伝えしようと思います。

まずjsonについて、

もしこれから {name : ’yamamoto’ , address: ‘tokyo’} こう言うふうに書いたら、
これを”json型の書き方というものだ”だと思ってください。

そしてときどきこれを

慣習として「jsonオブジェクト」と言ったりする人がいるので 会話の中でオブジェクトといいながら {name : ’yamamoto’ , address: ‘tokyo’} このように書いてくるひとがいます。

phpから入った人は オブジェクトといったら クラスがあって、それをインスタンス化してオブジェクトにして そのオブジェクトから変数や関数にアクセスするという考え方を持つと思います。

例えば、

class Person {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

const yamamoto = new Person('yamamoto', 'tokyo');
yamamoto.greet(); // "Hello, my name is yamamoto."

こう言う感じがオブジェクトだと思っていると思います。

実は、オブジェクトは「オブジェクトリテラル(直接的な記述)」と「クラスからのインスタンス化」の2種類があります。 この今の例はクラスからのインスタンス化です。

オブジェクトリテラルなんて言葉はもう忘れていいんですが、
オブジェクトと言われるものはこういう書き方でも書けるんですよって感じで
const test = {name : ’yamamoto’ , address: ‘tokyo’}

json型でtestオブジェクトを作ってます。 test.nameとかで値を表示できます。

でもphpのクラスと違ってなんか違和感あるんだよなという人に

const person = {
  name: 'yamamoto',
  address: 'tokyo',
  greet: function() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

こう書いたら、なんかそれっぽいなという気持ちになるので これも書いておこうと思います。

ここまでで、 いろいろ言いましたが、 jsにおいてjsonオブジェクトだよ〜 とかいったら {name : ’yamamoto’ , address: ‘tokyo’}みたいな形式だとおもってしまってOKで
このフォーマットは書けるようにしてほしいです、

次に配列

配列は [’yamamoto’ , ‘sugimoto’ , ‘ishiguro’] 0番目にyamamoto 1番目にsugimotoが入ってる


複数のオブジェクトが配列の中にあるよといったら

[{},{},{}]という構造で、0番目に{}、1番目に{}、2番目に{}という感じになるので [{name:’yamamoto’},{name : ‘sugimoto’},{name: ‘ishiguro’}]

という感じになる これを覚えておいてください。


関数の書き方とifの三項演算子とreturn省略になれる

関数の定義は function(){} だが、アロー関数というものを使うと

()⇒{} になる。

const sayHello = () => 'Hello World';

初見だと気持ち悪いです

一つずつ解説していくことで理解できると思います。

// 従来の関数の定義
function sayHello() {
    return 'Hello World';
}

// 関数の呼び出しと出力
console.log(sayHello()); // 出力: Hello World

これはまぁ分かりますね。

// 1. 従来の関数
const sayhello = function sayHello1() {
    return 'Hello World';
}
console.log(sayhello); 

これも分かりますね。 関数のreturnの結果を変数にいれてそれを表示してるだけです。

// アロー関数の定義
const sayHello = () => {
    return 'Hello World';
};

// 関数の呼び出しと出力
console.log(sayHello()); // 出力: Hello World

これがアロー関数です。

ここから、アロー関数は関数本体が1行のときは{}とreturnを省略できます。

// 中括弧とreturnを省略したアロー関数の定義
const sayHello = () => 'Hello World';

// 関数の呼び出しと出力
console.log(sayHello()); // 出力: Hello World

ここまではじめは慣れないかもですが通しで何度も書くとわかるようになります。



次にifの三項演算子省略形

if (name == ‘yamamoto’) {

return ‘yamamotoです’;

} else {

return ‘yamamotoではありません’

} これは、 三項演算子を使うと (name == ‘yamamoto’) ? ‘yamamotoです’ : ‘yamamotoではありません’

改行したとしても

(name == ‘yamamoto’) ? ‘yamamotoです’

: ‘yamamotoではありません’ 
let result;
if (sample == 1) {
    result = 'Sample is 1';
} else if (sample == 2 || sample == 3) {
    result = 'Sample is 2 or 3';
} else if (sample == 4 || sample == 5) {
    result = 'Sample is 4 or 5';
} else {
    result = 'Sample is something else';
}

こういうケースを三項演算子にすることで分かりやすくなるかなと思います。

const result = (sample == 1) 
    ? 'Sample is 1' 
    : (sample == 2 || sample == 3) 
        ? 'Sample is 2 or 3' 
        : (sample == 4 || sample == 5) 
            ? 'Sample is 4 or 5' 
            : 'Sample is something else';

この書き方に慣れていきましょう。

配列、とかjsonとかアロー関数とか三項演算子とか 個別に見るとまだわかるのですが、 例えば配列の中にjson型のオブジェクトを入れてみると

const users = [
  // gender: 1 = 男性, 2 = 女性
  {
    id: 1,
    name: "User1",
    gender: 1,
  },
  {
    id: 2,
    name: "Uses2",
    gender: 1,
  },
  {
    id: 3,
    name: "User3",
    gender: 2,
  },
  {
    id: 4,
    name: "User4",
    gender: 2,
  },
];

一気に難しくなったと思いませんか?
でも一つ一つ読み解いていけば、 所詮は配列の0番目がjson型のオブジェクトってだけです。

バッククオートの便利さを知ろう

‘ ‘だと+で接続しないとだけど バッククオートだと、${変数名}で直接かけるし、改行コードもいらない。

const name = 'yamamoto';
const age = 25;

// バッククォートを使用しない場合
const message1 = 'こんにちは、私の名前は\\n' ' + name + ' です。年齢は ' + age + ' 歳です。';
console.log(message1); 
// 出力: こんにちは、私の名前は 
//       yamamoto です。年齢は 25 歳です。

// バッククォートを使用する場合
const message2 = `こんにちは、私の名前は
 {{name}} です。年齢は {{age}} 歳です。`;
console.log(message2); 
// 出力: こんにちは、私の名前は 
//       yamamoto です。年齢は 25 歳です。

mapとfilterを覚えておきましょう。



const users = [
  // gender: 1 = 男性, 2 = 女性
  {
    id: 1,
    name: "User1",
    gender: 1,
  },
  {
    id: 2,
    name: "Uses2",
    gender: 1,
  },
  {
    id: 3,
    name: "User3",
    gender: 2,
  },
  {
    id: 4,
    name: "User4",
    gender: 2,
  },
];
#userはeachのvalueみたいな感じ。
const userIds = users.map((user) => user.id);
/* userIdsの中身
  [1, 2, 3, 4]
*/

const users = [
  // gender: 1 = 男性, 2 = 女性
  {
    id: 1,
    name: "User1",
    gender: 1,
  },
  {
    id: 2,
    name: "Uses2",
    gender: 1,
  },
  {
    id: 3,
    name: "User3",
    gender: 2,
  },
  {
    id: 4,
    name: "User4",
    gender: 2,
  },
];
#userはeachのvalueみたいな感じ。
const femaleUsers = users.filter((user) => {
  return user.gender === 2;
});
/* femaleUsersの中身
[
  {
    id: 3,
    name: "User3",
    gender: 2,
  },
  {
    id: 4,
    name: "User4",
    gender: 2,
  },
]
*/

アロー関数、json、三項演算子等で混乱しないように

配列はシンプルに書くと

[1,2,3,4]のように書けるが、 オブジェクトが中に入ると [{name:’yamamoto’},{name:’sughimoto’},{name:’ishiguro’}] のようにかけるのは上で書いたが、 実際の開発ではこのように、 要素の中には関数がきたり、さらにオブジェクトがきたりするので、 惑わされないようにする必要がある 例えば、配列の値のオブジェクトの値がさらにオブジェクトなら

[{name:{myouji:'yamamoto',tel:'09000000'}},{name:{myouji:'yamamoto',tel:'09000000'}} , {name:{myouji:'yamamoto',tel:'09000000'}}]

のようになったとしても、構造としては [{x:{y:y1} , {x : {y:y2},{x:{y:y3}} }]となっているだけでよく見ると構造は同じなので惑わされないように。

例えば、このnameの中のオブジェクトの要素にさらに関数が入ったりすると 長くなるので改行の必要は出てくるが、

[
 {
  name:
    {
      myouji:'yamamoto',
         tel:'09000000',
         something: function() {},
     }
  },
  {
   name:
     {
       myouji:'yamamoto',
          tel:'09000000',
          something: function() {},
      }
   }, 
   {
    name:
      {
       myouji:'yamamoto',
           tel:'09000000',
           something: function() {},
      }
   }
 ]

と改行が入り、要素がオブジェクトや関数が入ってくるとややこしく見えることがある。 さらに関数がアロー関数になったりすると

[
 {
  name:
    {
      myouji:'yamamoto',
         tel:'09000000',
         something: ()=>{},
     }
  },
  {
   name:
     {
       myouji:'yamamoto',
          tel:'09000000',
          something: ()=>{},
      }
   }, 
   {
    name:
      {
       myouji:'yamamoto',
           tel:'09000000',
           something:()=>{},
      }
   }
 ]

このようになるし、 さらにその関数が一行のfilterなんかが入ることになったら、

[
 {
  name:
    {
      myouji:'yamamoto',
         tel:'09000000',
         something: this.item.filter(item => item.id != 1),
     }
  },
  {
   name:
     {
       myouji:'yamamoto',
          tel:'09000000',
          something: this.item.filter(item => item.id != 1),
      }
   }, 
   {
    name:
      {
       myouji:'yamamoto',
           tel:'09000000',
           something: this.item.filter(item => item.id != 1),
      }
   }
 ]

のように、 一つ一つ分解すると わかるはずなのに、さまざまな使い方を総合的に書くと

初めのうちは「これなんだっけ」というような感覚になってしまう。

なので、 この前提の書き方を理解し、

()=>{()=>{()=>{(this.item.id=1)?true:false}}}

などと書かれていても、 冷静に、関数の中の関数の中の関数の中に三項演算子でidの値を見てboolianを返してる というように見れる状態になっておく必要がある。

これらがわかることで、 vue2jsを学ぶ基本ができました。 ここで、 3回くらい実際に書いて、 本当に書いて理解したら次に進んでください。

ではvue.jsの基本にいきます

これまでのことは本当に何度も書いて理解してからにしてください。
これからの姿勢で本当はやっていないけど先に進もうとした人や
「なんとなく聞いてなんとなくわかったからそういう設定ねってことで次に進もうとしている人」は
これまで300人以上を教えてきたので、傾向として一人もできるようになりません、
時間の無駄なので絶対に書いて理解してから進んでください。

ではここまでは大丈夫ということで、 まずはこの原型を書けるようになってください。

<html>
<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
</head>
<body>
   <div id="app">
   <h1>{{name}}</h1>
   </div>
   <script>
     var vue = new Vue({
        el: "#app",
        data: function() {
          return {
            name: 'yamamoto',
          }
        }
      });
   </script>
</body>
</html>

これは暗記をするくらい10回くらい書いてください。

変数をいくつか入れて表示することを確認してください。

変数はどのように定義して、 どのように表示するのかがわかったと思います。

変数に配列もいれることができるので、 配列を加えてみるのと、

forとifを入れてみましょう。

<html>

<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1>{{name}}</h1>

    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p>リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          showFlg: true,
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
        }
      }
    });
  </script>
</body>

</html>

ちょっとずつわかってきたのではないかと思います。

さらにクラスと画像とstyleを入れてみます。

<html>

<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }

    .error {
      font-size: 24px;
      color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1 style="background-color:aqua; padding:16px;">{{name}}</h1>

    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <img src="<https://via.placeholder.com/150>">
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p class="error">リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          showFlg: true,
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],

        }
      }
    });
  </script>
</body>

</html>

これをjsを使って書き換えてみます。

<html>

<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1 :style="styleSample">{{name}}</h1>

    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <img src="<https://via.placeholder.com/150>">
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p :class="errorClass">リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          showFlg: true,
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
          errorClass: "error",
          styleSample: "background-color:aqua; padding:16px;",

        }
      }
    });
  </script>
  <style>
    .error {
      font-size: 24px;
      color: red;
    }
  </style>
</body>

</html>

styleSampleは実は

styleSample: {
  backgroundColor: "aqua",
  padding: "16px"
}

のほうが推奨されます。

オブジェクトのようになることでvueのいろんな機能が使えるようになるためです。

次にリアルタイムにフォームの値を取得したり

jsの関数の中で変数を扱ったりすることをしてみます。

いつものおまじないにmethodsというのあがるんだよということと、 @clickでmethodsが実行できること v-modelによってformのvalueがリアルタイムでとれること をやりたいと思います。

<html>

<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1 :style="styleSample2">{{name}}{{count}}</h1>
    <p>都市名<input type="text" v-model="city"></p>
    <p v-if="city">調査都市名「{{city}}」</p>
    <button @click="switchShowFlg">アイテム</button>
    <button @click="increase">カウント</button>
    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <img src="<https://via.placeholder.com/150>">
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p :class="errorClass">リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          city: "",
          showFlg: true,
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
          errorClass: "error",
          styleSample: "background-color:aqua; padding:16px;",
          count: 0,
        }
      },
      methods: {
        switchShowFlg: function () {
          this.showFlg = !this.showFlg;
        },
        increase: function () {
          this.count++;
        },

      }, computed: {
        styleSample2: function () {
          return this.count > 10
            ? { backgroundColor: "aqua", padding: "16px", color: "red" }
            : {};
        }
      }
    });
  </script>
  <style>
    .error {
      font-size: 24px;
      color: red;
    }
  </style>
</body>

</html>

methodsの他にcomputedという機能を使うと computedの関数内で使われた変数が変更されるたびに 関数が実行されるので 一定の条件が発動したら、cssを変更することが可能

ここまでで、 vueのおまじないと、変数の表示方法、v-ifやforの仕様 :class :style などの使い方、関数の実行方法、computedがわかりました。

次に、API通信をして表示をしてみることをしてみます。

<html>

<head>
  <meta charset="utf-8">
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <!--import axios.js -->
  <script src="<https://unpkg.com/axios/dist/axios.min.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1 :style="styleSample2">{{name}}{{count}}</h1>
    <p>都市名<input type="text" v-model="city"></p>
    <p v-if="city">調査都市名「{{city}}」</p>
    <button @click="switchShowFlg">アイテム</button>
    <button @click="increase">カウント</button>
    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <img src="<https://via.placeholder.com/150>">
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
      <ul>
        <li v-for="user in info" :key="user.id">
          {{user.id}}{{user.name }}{{user.createdAt}}
          <img :src="user.avatar"></img>
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p :class="errorClass">リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          city: "",
          showFlg: true,
          info: [],
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
          errorClass: "error",
          styleSample: "background-color:aqua; padding:16px;",
          count: 0,
        }
      },
      methods: {
        switchShowFlg: function () {
          this.showFlg = !this.showFlg;
        },
        increase: function () {
          this.count++;
        },

      }, computed: {
        styleSample2: function () {
          return this.count > 10
            ? { backgroundColor: "aqua", padding: "16px", color: "red" }
            : {};
        }
      }, mounted: function () {
        axios.get("<https://62fe320941165d66bfbabe7e.mockapi.io/apt/v1/users/>").then(
          (resp) => {
            console.log(resp.data);
            this.info = resp.data;
          });
      }
    });
  </script>
  <style>
    .error {
      font-size: 24px;
      color: red;
    }
  </style>
</body>

</html>
本質的には
    axios.get("APIのURI").then();
これで、
axios.get("APIのURI").then((APIの返り値を変数に入れる)=>{
 その変数を使って結果を扱う
});
です。

今度はデータを送信してみよう。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Vue.js POST Example</title>
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
  <!-- axios.js のインポート -->
  <script src="<https://unpkg.com/axios/dist/axios.min.js>"></script>
  <style>
    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
      margin-top: 20px;
    }

    .error {
      font-size: 24px;
      color: red;
    }

    .form-container {
      background-color: #e9e9e9;
      padding: 15px;
      border: 1px solid #aaa;
      margin-top: 20px;
    }

    .form-group {
      margin-bottom: 10px;
    }

    label {
      display: block;
      margin-bottom: 5px;
    }

    input[type="text"] {
      width: 100%;
      padding: 8px;
      box-sizing: border-box;
    }

    button {
      padding: 8px 12px;
      margin-right: 10px;
      cursor: pointer;
    }

    img {
      max-width: 100%;
      height: auto;
    }
  </style>
</head>

<body>
  <div id="app">
    <h1 :style="styleSample2">{{ name }}{{ count }}</h1>
    <p>都市名 <input type="text" v-model="city" placeholder="都市名を入力"></p>
    <p v-if="city">調査都市名「{{ city }}」</p>
    <button @click="switchShowFlg">アイテム</button>
    <button @click="increase">カウント</button>
    <button @click="showPost">データ送信</button>

    <!-- フォームの表示 -->
    <div v-if="showPostFlg" class="form-container">
      <h2>ユーザーデータ送信フォーム</h2>
      <form @submit.prevent="postUserData">
        <div class="form-group">
          <label for="name">名前:</label>
          <input type="text" id="name" v-model="newUser.name" required>
        </div>
        <div class="form-group">
          <label for="avatar">アバター画像URL:</label>
          <input type="text" id="avatar" v-model="newUser.avatar" required>
        </div>
        <button type="submit">送信</button>
        <button type="button" @click="hidePost">キャンセル</button>
      </form>
    </div>

    <div v-if="showFlg" class="highlight">
      <h2>アイテム一覧</h2>
      <img src="<https://via.placeholder.com/150>" alt="Placeholder Image">
      <ul>
        <li v-for="(item, index) in items" :key="index">
          {{ index + 1 }}. {{ item.name }}
        </li>
      </ul>
      <h2>ユーザー情報</h2>
      <ul>
        <li v-for="user in info" :key="user.id">
          <strong>ID:</strong> {{ user.id }}<br>
          <strong>名前:</strong> {{ user.name }}<br>
          <strong>作成日:</strong> {{ user.createdAt }}<br>
          <img :src="user.avatar" alt="User Avatar" width="50">
        </li>
      </ul>
    </div>
    <!-- 条件が偽の場合のメッセージ -->
    <div v-else class="highlight">
      <p :class="errorClass">リストは表示されていません。</p>
    </div>
  </div>

  <script>
    var vue = new Vue({
      el: "#app",
      data: function () {
        return {
          name: 'yamamoto',
          city: "",
          showFlg: true,
          showPostFlg: false,
          info: [],
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
          errorClass: "error",
          styleSample: "background-color:aqua; padding:16px;",
          count: 0,
          newUser: { // フォームデータを管理するオブジェクト
            name: '',
            avatar: ''
          }
        }
      },
      methods: {
        switchShowFlg: function () {
          this.showFlg = !this.showFlg;
        },
        increase: function () {
          this.count++;
        },
        showPost: function () { // フォームを表示するメソッド
          this.showPostFlg = true;
        },
        hidePost: function () { // フォームを非表示にするメソッド
          this.showPostFlg = false;
          this.resetForm();
        },
        postUserData: function () { // フォームデータを送信するメソッド
          // フォームデータのバリデーション(簡易)
          if (!this.newUser.name || !this.newUser.avatar) {
            alert("名前とアバターURLを入力してください。");
            return;
          }

          // POSTリクエストを送信
          axios.post("<https://62fe320941165d66bfbabe7e.mockapi.io/apt/v1/users/>", this.newUser)
            .then((resp) => {
              console.log("POSTレスポンス:", resp.data);
              // 成功時に新しいユーザーをinfoリストに追加
              this.info.push(resp.data);
              alert("ユーザーデータが正常に送信されました。");
              // フォームをリセットして非表示にする
              this.resetForm();
              this.showPostFlg = false;
            })
            .catch((error) => {
              console.error("POSTリクエストに失敗しました:", error);
              alert("データ送信に失敗しました。再度お試しください。");
            });
        },
        resetForm: function () { // フォームデータをリセットするメソッド
          this.newUser.name = '';
          this.newUser.avatar = '';
        }
      },
      computed: {
        styleSample2: function () {
          return this.count > 10
            ? { backgroundColor: "aqua", padding: "16px", color: "red" }
            : {};
        }
      },
      mounted: function () {
        axios.get("<https://62fe320941165d66bfbabe7e.mockapi.io/apt/v1/users/>")
          .then((resp) => {
            console.log("GETレスポンス:", resp.data);
            this.info = resp.data;
          })
          .catch((error) => {
            console.error("APIリクエストに失敗しました:", error);
            // 必要に応じてユーザーにエラーメッセージを表示
          });
      }
    });
  </script>
</body>

</html>

RoutingとSPAについて

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <!--imoirt vue.js-->
  <script src="<https://cdn.jsdelivr.net/npm/vue/dist/vue.js>"></script>
    <script src="<https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js>"></script>
  <style>
  </style>
</head>
<body>
  <div id="app">
    <header>
      <router-link to="/"><h1>HOME</h1></router-link>
      <router-link to="/blog"><h1>Blog</h1></router-link>
    </header>
    <router-view></router-view>
  </div>
  <script>

     const Home = {template : '<div>ホーム</div>'}
     const Blog = {template : '<div>blog</div>'}

    //このroutesを下のVueRouterのroutes:XXの中に食わせることで
    //ルーティングができるようになる
     const routes = [
      {path: '/',component: Home },
      {path: '/blog' , component: Blog },
     ]
     const router = new VueRouter({
       routes: routes
     })
    var vm = new Vue({
      el: '#app',
      router, //上のconst routerのために使う(routerはroutesを含んでるからroutesを書く必要はない)
      data() {
        return {
        }
      },

    })
  </script>
</body>
</html>

ページを作るためには ルーター機能が必要、URLを作って適切なプログラムを実行することをルーティングといいます。 <script src=”https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js“></script> これを読み込んでルーター機能を使えるようにして ページごとに ページのHTMLを持つオブジェクトを作り、 そのHTMLとURLをセットし似た配列をつくり、 それをオブジェクトのバリューとして渡してページを作ります。

これを利用すると

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>SPA Example</title>
  <!-- Vue.js のインポート -->
  <script src="<https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js>"></script>
  <!-- Vue Router のインポート -->
  <script src="<https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js>"></script>
  <!-- Axios のインポート -->
  <script src="<https://unpkg.com/axios/dist/axios.min.js>"></script>
  <style>
    header {
      background-color: #f8f9fa;
      padding: 10px;
    }

    header a {
      margin-right: 20px;
      text-decoration: none;
      color: black;
    }

    header a:hover {
      color: #007BFF;
    }

    .highlight {
      background-color: #f9f9f9;
      padding: 10px;
      border: 1px solid #ccc;
    }

    .error {
      font-size: 24px;
      color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <header>
      <router-link to="/">TOP</router-link>
      <router-link to="/userlist">ユーザ一覧</router-link>
      <router-link to="/userpost">ユーザ投稿</router-link>
    </header>
    <router-view></router-view>
  </div>

  <script>
    // TOPコンポーネント
    const Home = {
      data() {
        return {
          name: 'yamamoto',
          city: "",
          showFlg: true,
          items: [
            { name: 'Tokyo' },
            { name: 'Shanghai' },
            { name: 'Beijing' },
            { name: 'Wuhan' }
          ],
          errorClass: "error",
          styleSample: "background-color:aqua; padding:16px;",
          count: 0
        };
      },
      template: `
        <div>
          <h1 :style="styleSample2">{{ name }}{{ count }}</h1>
          <p>都市名 <input type="text" v-model="city"></p>
          <p v-if="city">調査都市名「{{ city }}」</p>
          <button @click="switchShowFlg">アイテム</button>
          <button @click="increase">カウント</button>
          <div v-if="showFlg" class="highlight">
            <h2>アイテム一覧</h2>
            <img src="<https://via.placeholder.com/150>">
            <ul>
              <li v-for="(item, index) in items" :key="index">
                {{ index + 1 }}. {{ item.name }}
              </li>
            </ul>
          </div>
          <div v-else class="highlight">
            <p :class="errorClass">リストは表示されていません。</p>
          </div>
        </div>
      `,
      computed: {
        styleSample2() {
          return this.count > 10
            ? { backgroundColor: "aqua", padding: "16px", color: "red" }
            : {};
        }
      },
      methods: {
        switchShowFlg() {
          this.showFlg = !this.showFlg;
        },
        increase() {
          this.count++;
        }
      }
    };

    // ユーザ一覧コンポーネント
    const UserList = {
      data() {
        return {
          owners: [],
          loading: true,
          error: null
        };
      },
      template: `
        <div>
          <h2>ユーザ一覧</h2>
          <div v-if="loading">読み込み中...</div>
          <div v-else-if="error">{{ error }}</div>
          <ul v-else>
            <li v-for="(owner, index) in owners" :key="owner.id">
              {{ owner.name }} - {{ owner.city }}
            </li>
          </ul>
        </div>
      `,
      mounted() {
        axios.get("<https://62fe320941165d66bfbabe7e.mockapi.io/apt/v1/users/>")
          .then((response) => {
            this.owners = response.data;
            this.loading = false;
          })
          .catch((error) => {
            this.error = "データの取得に失敗しました。";
            this.loading = false;
          });
      }
    };

    // ユーザ投稿コンポーネント
    const UserPost = {
      data() {
        return {
          newUser: {
            name: '',
            avatar: ''
          },
          submitting: false,
          error: null,
          successMessage: null
        };
      },
      template: `
        <div>
          <h2>ユーザ投稿</h2>
          <form @submit.prevent="postUserData">
            <div>
              <label for="name">名前:</label>
              <input type="text" id="name" v-model="newUser.name" required>
            </div>
            <div>
              <label for="avatar">アバター画像URL:</label>
              <input type="text" id="avatar" v-model="newUser.avatar" required>
            </div>
            <button type="submit" :disabled="submitting">
              {{ submitting ? '送信中...' : '送信' }}
            </button>
            <button type="button" @click="resetForm" :disabled="submitting">リセット</button>
          </form>
          <div v-if="error">{{ error }}</div>
          <div v-if="successMessage">{{ successMessage }}</div>
        </div>
      `,
      methods: {
        postUserData() {
          this.submitting = true;
          this.error = null;
          this.successMessage = null;

          axios.post("<https://62fe320941165d66bfbabe7e.mockapi.io/apt/v1/users/>", this.newUser)
            .then((response) => {
              this.successMessage = "ユーザーデータが正常に送信されました。";
              this.resetForm();
            })
            .catch((error) => {
              this.error = "データ送信に失敗しました。";
            })
            .finally(() => {
              this.submitting = false;
            });
        },
        resetForm() {
          this.newUser.name = '';
          this.newUser.avatar = '';
          this.error = null;
          this.successMessage = null;
        }
      }
    };

    // ルートの定義
    const routes = [
      { path: '/', component: Home },
      { path: '/userlist', component: UserList },
      { path: '/userpost', component: UserPost }
    ];

    // ルーターの作成
    const router = new VueRouter({
      routes
    });

    // Vueインスタンスの作成
    new Vue({
      el: '#app',
      router
    });
  </script>
</body>

</html>

だいたいの構造がわかったのではないかと思います。

ここでこんなこともできますよということで、
売上原価をリアルタイムで表示するコードを公開します。


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive Accounting Tables</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div id="app" class="container-fluid">
        <div class="row">
            <!-- Sales Section -->
            <div class="col-md-6">
                <h3>売上</h3>
                <table class="table">
                    <tr>
                        <th>名目</th>
                        <th>金額</th>
                    </tr>
                    <tr v-for="(item, index) in sales" :key="'sales-' + index">
                        <td><input type="text" class="form-control" v-model="item.name"></td>
                        <td><input type="text" class="form-control" v-model.number="item.amount"></td>
                    </tr>
                </table>
                <button class="btn btn-primary" @click="addRow('sales')">+</button>
                <p>合計: {{ totalSales }}</p>
            </div>

            <!-- Cost Section -->
            <div class="col-md-6">
                <h3>原価</h3>
                <table class="table">
                    <tr>
                        <th>科目名</th>
                        <th>科目ID</th>
                        <th>科目名タイ語</th>
                        <th>金額</th>
                    </tr>
                    <tr v-for="(item, index) in costs" :key="'costs-' + index">
                        <td>
                            <input type="text" class="form-control" v-model="item.name" @input="updateAutocomplete(item, 'name')">
                            <ul class="list-unstyled" v-if="item.matches.length">
                                <li v-for="match in item.matches" :key="match.id" @click="setMatch(item, match)" class="py-1 px-2 hover:bg-gray-200 cursor-pointer">{{ match.name }}</li>
                            </ul>
                        </td>
                        <td><input type="text" class="form-control" v-model="item.id" @input="updateAutocomplete(item, 'id')"></td>
                        <td><input type="text" class="form-control" v-model="item.thai_name" @input="updateAutocomplete(item, 'thai_name')"></td>
                        <td><input type="text" class="form-control" v-model.number="item.amount"></td>
                    </tr>
                </table>
                <button class="btn btn-primary" @click="addRow('costs')">+</button>
                <p>合計: {{ totalCosts }}</p>
            </div>
        </div>
        <p>利益: {{ totalSales - totalCosts }}</p>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                sales: Array(10).fill().map(() => ({ name: '', amount: 0 })),
                costs: Array(10).fill().map(() => ({ name: '', id: '', thai_name: '', amount: 0, matches: [] })),
                accounts: [
       {
        id: "101-1",
        name: "現金",
        description: "手元にある現金",
        thai_name: "เงินสด",
        description_thai: "จำนวนเงินสดที่มีอยู่"
      },
      {
        id: "101-2",
        name: "預金",
        description: "銀行口座に預けられているお金",
        thai_name: "เงินฝากธนาคาร",
        description_thai: "เงินที่ฝากไว้ในบัญชีธนาคาร"
      },
      {
        id: "101-3",
        name: "売掛金",
        description: "商品やサービスを提供した後、まだ入金されていない金額",
        thai_name: "ลูกหนี้การค้า",
        description_thai: "จำนวนเงินที่ค้างรับจากการขายสินค้าหรือบริการ"
      },
      {
        id: "101-4",
        name: "買掛金",
        description: "まだ支払っていない商品やサービスの金額",
        thai_name: "เจ้าหนี้การค้า",
        description_thai: "จำนวนเงินที่ค้างชำระสำหรับสินค้าหรือบริการ"
      },
      {
        id: "101-5",
        name: "棚卸資産",
        description: "販売を目的として保持している商品の価値",
        thai_name: "สินค้าคงเหลือ",
        description_thai: "มูลค่าของสินค้าที่เก็บรักษาไว้เพื่อการขาย"
      },
      {
        id: "101-6",
        name: "固定資産",
        description: "長期間にわたって使用される資産、例えば建物や機械",
        thai_name: "สินทรัพย์ถาวร",
        description_thai: "สินทรัพย์ที่ใช้งานในระยะยาว เช่น อาคารหรือเครื่องจักร"
      },
      {
        id: "101-7",
        name: "車両運搬具",
        description: "企業が所有する車両",
        thai_name: "ยานพาหนะ",
        description_thai: "ยานพาหนะที่บริษัทเป็นเจ้าของ"
      },
      {
        id: "101-8",
        name: "備品",
        description: "事務用品やその他の小規模な装備",
        thai_name: "อุปกรณ์สำนักงาน",
        description_thai: "อุปกรณ์สำนักงานและอุปกรณ์อื่นๆ ขนาดเล็ก"
      },
      {
        id: "101-9",
        name: "事業主貸",
        description: "事業主が事業に貸し付けた金額",
        thai_name: "เงินที่เจ้าของธุรกิจให้กู้ยืม",
        description_thai: "เงินที่เจ้าของธุรกิจให้กู้ยืมแก่ธุรกิจ"
      },
      {
        id: "101-10",
        name: "事業主借",
        description: "事業主が事業から借りた金額",
        thai_name: "เงินที่เจ้าของธุรกิจยืม",
        description_thai: "เงินที่เจ้าของธุรกิจยืมจากธุรกิจ"
      },
      {
        id: "101-11",
        name: "前払金",
        description: "すでに支払われたが、まだサービスや商品が提供されていない金額",
        thai_name: "เงินชำระล่วงหน้า",
        description_thai: "เงินที่จ่ายไปแล้วแต่ยังไม่ได้รับสินค้าหรือบริการ"
      },
      {
        id: "101-12",
        name: "受取利息",
        description: "投資から得られる利息収入",
        thai_name: "ดอกเบี้ยรับ",
        description_thai: "รายได้ดอกเบี้ยที่ได้รับจากการลงทุน"
      },
      {
        id: "101-13",
        name: "未収入金",
        description: "受け取るべきだがまだ受け取っていないその他の収入",
        thai_name: "รายได้ที่ค้างรับ",
        description_thai: "รายได้ที่ควรได้รับแต่ยังไม่ได้รับ"
      },
      {
        id: "101-14",
        name: "未払金",
        description: "支払うべきだがまだ支払っていないその他の費用",
        thai_name: "ค่าใช้จ่ายที่ค้างชำระ",
        description_thai: "ค่าใช้จ่ายที่ควรชำระแต่ยังไม่ได้ชำระ"
      },
      {
        id: "101-15",
        name: "仕入",
        description: "商品の仕入れにかかったコスト",
        thai_name: "ต้นทุนสินค้า",
        description_thai: "ต้นทุนในการซื้อสินค้าเข้ามา"
      },
      {
        id: "101-16",
        name: "給与",
        description: "従業員に支払われる給与",
        thai_name: "เงินเดือน",
        description_thai: "เงินเดือนที่จ่ายให้กับพนักงาน"
      },
      {
        id: "101-17",
        name: "販売費",
        description: "販売活動に直接関連する費用",
        thai_name: "ค่าใช้จ่ายในการขาย",
        description_thai: "ค่าใช้จ่ายที่เกี่ยวข้องโดยตรงกับกิจกรรมการขาย"
      },
      {
        id: "101-18",
        name: "一般管理費",
        description: "企業の日常運営に関わる間接的な費用",
        thai_name: "ค่าใช้จ่ายในการบริหาร",
        description_thai: "ค่าใช้จ่ายทางอ้อมที่เกี่ยวข้องกับการดำเนินงานประจำวันของบริษัท"
      },
      {
        id: "101-19",
        name: "減価償却費",
        description: "固定資産の価値が減少することによる費用",
        thai_name: "ค่าเสื่อมราคา",
        description_thai: "ค่าใช้จ่ายที่เกิดจากการลดค่าของสินทรัพย์ถาวร"
      },
      {
        id: "101-20",
        name: "借入金",
        description: "外部から借り入れた資金の額",
        thai_name: "เงินกู้ยืม",
        description_thai: "จำนวนเงินที่ยืมมาจากภายนอก"
      }
                ]
            },
            methods: {
                addRow(type) {
                    if (type === 'sales') {
                        this.sales.push({ name: '', amount: 0 });
                    } else if (type === 'costs') {
                        this.costs.push({ name: '', id: '', thai_name: '', amount: 0, matches: [] });
                    }
                },
                updateAutocomplete(item, field) {
                    let value = item[field].toLowerCase();
                    item.matches = this.accounts.filter(account =>
                        account.name.toLowerCase().includes(value) ||
                        account.id.toLowerCase().includes(value) ||
                        account.thai_name.toLowerCase().includes(value)
                    );
                },
                setMatch(item, match) {
                    item.name = match.name;
                    item.id = match.id;
                    item.thai_name = match.thai_name;
                    item.matches = [];
                }
            },
            computed: {
                totalSales() {
                    return this.sales.reduce((sum, item) => sum + item.amount, 0);
                },
                totalCosts() {
                    return this.costs.reduce((sum, item) => sum + item.amount, 0);
                }
            }
        });
    </script>
</body>
</html>

http://reezote.com/wp-content/uploads/2024/09/kaikeireezote-2.gif

できました。