- 03-5820-1777平日10:00〜18:00
- お問い合わせ
今回はVue.jsのバージョン3で使用可能なComposition Apiを使用して一覧、登録画面を作成してみたいと思います。
他の同じ形式の画面を作成する際のベースにも使えると思います。
サーバ側も簡単なアプリケーションをSpringBootで作成し、Ajax通信を行っています。
今回は画面側の説明、次回はサーバ側の説明をしていきたいと思います。
機能一覧
画面 | 機能 |
---|---|
商品一覧 | 一覧表示 |
検索 | |
ソート | |
商品編集・登録 | 登録編集 |
入力チェック |
動作イメージ
プロジェクト作成コマンド
//プロジェクト作成
vue create [プロジェクト名]
プロジェクトタイプ選択(Manually select featuresを選択)
//プロジェクトタイプ選択 Manually select featuresを選択
? Please pick a preset:
router-sample ([Vue 2] babel, router, eslint)
aa ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features
プロジェクトの特徴を選択(RouterとVuexを追加。TypeScriptは今回使用してません)
? Check the features needed for your project:
(*) Choose Vue version
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
>(*) Vuex
( ) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing
ディレクトリ構成
プロジェクト名/
├─ src/
├─ App.vue ←編集
├─ main.js
├─ api/ ←追加
├─ checker.js
├─ serveraccess.js
├─ composable/ ←追加
├─ GoodsRepository.js
├─ GoodsFilter.js
├─ GoodsSort.js
├─ GoodsValidator.js
├─ router/
├─ index.js ←編集
├─ store/
├─ index.js ←編集
├─ views/
├─ GoodsList ←追加
├─ EditGoods ←追加
構成図
モジュール | 機能説明 |
---|---|
App.vue | メインのモジュール。router-viewタグを記述 |
Router | Vue.js提供の画面ルーティング機能 |
Vuex | Vue.js提供の状態共有機能 |
GoodsList.vue | 商品リスト画面用コンポーネント |
EditGoods.vue | 商品登録、編集画面用コンポーネント |
GoodsFilter.js | 商品名検索用モジュール |
GoodsSort.js | 商品リストソート用モジュール |
GoodsRepository.js | 商品取得、登録、編集用モジュール |
GoodsValidator.js | 商品登録入力チェックモジュール |
serveraccess.js | サーバへのアクセスモジュール |
checker.js | 値チェック用モジュール |
・App.vue
<template>
<router-view/>
</template>
<style>
省略
</style>
商品一覧画面
・GoodsList.vue
商品リスト画面用コンポーネント
<template>
<div style="text-align:left;">
<input type="text" v-model="searchQuery">
<div class="table">
<table>
<thead>
<tr>
<th @click="sort('code')">コード</th>
<th @click="sort('name')">名前</th>
<th @click="sort('category')">カテゴリ</th>
<th @click="sort('price')">価格</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(data) in goods" :key="data.code">
<td>{{ data.code }}</td>
<td class="tdname">{{ data.name }}</td>
<td>{{ categoryDatas.filter( function (value){ return value.id === data.category })[0].name }}</td>
<td>{{ data.price }}</td>
<td><router-link :to="{ name: 'editGoods', params:{id:data.code}}">編集</router-link></td>
</tr>
</tbody>
</table>
</div>
<input type="button" @click="addGoods" value="新規登録">
</div>
</template>
<script>
import { onUnmounted } from 'vue'
import goodsRepository from '../composables/GoodsRepository'
import goodsFilter from '../composables/GoodsFilter'
import goodsSort from '../composables/GoodsSort'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
export default ({
name: 'GoodsList',
setup(){
//GoodsRepositoryモジュールからの変数、メソッドを宣言
//カテゴリ用データ、商品リスト、商品取得メソッド
const { categoryDatas, goodsList, getGoods} = goodsRepository()
//GoodsFilterモジュールからの変数、メソッドを宣言
//検索のマッチした商品リスト、検索文字列
const { matchGoods, searchQuery } = goodsFilter( goodsList )
//GoodsSortモジュールからの変数、メソッドを宣言
//ソート用メソッド
const { sort } = goodsSort( goodsList )
//ルータ使用変数宣言
const router = useRouter()
//Vuex使用変数宣言
const store = useStore()
//新規作成ボタン押下時の処理
const addGoods = () => {
router.push("/editGoods")
}
//データ取得時のコールバック( GoodsRepositoryのgetCodesメソッドに渡す)
const callback = ( result )=>{
if( result != "success"){
alert( result )
}
}
//Vuexから検索文字列を取得
searchQuery.value = store.state.searchQuery
//商品情報取得
getGoods( callback )
//Unmounted時(別画面への遷移など)、Vuexに検索文字列設定
onUnmounted(()=>{
store.dispatch('setSearchQuery', searchQuery.value)
})
//template上で使用する変数を宣言
return{
goods:matchGoods,
addGoods,
searchQuery,
getGoods,
categoryDatas,
sort
}
}
})
</script>
<style>
省略
</style>
・GoodsRepository.js
商品取得、登録、編集用モジュール
※このモジュールは商品一覧、商品登録画面両方から使用される。
import { ref } from 'vue'
import { fetchGoods, fetchGoodsById, sendGoods } from '../api/serveraccess'
//商品情報の取得、登録、編集などの機能を提供する
export default function (){
//商品一覧用変数の宣言、ref(リアクティブな値)として宣言
let goodsList = ref([])
//商品登録、編集用変数の宣言、refとして宣言
let goods = ref({code:'',name:'',category:'001',price:''})
//カテゴリのマスタを宣言、refとして宣言
const categoryDatas = ref([])
categoryDatas.value.push({id:'001', name:'本'})
categoryDatas.value.push({id:'002', name:'ゲーム'})
//商品取得メソッド宣言、非同期メソッドとして宣言
//sereraccess.jsのメソッドをコール
const getGoods = async( callback ) => {
try{
goodsList.value = await fetchGoods()
callback("success")
}catch(e){
callback(e)
}
}
//商品をIDで指定して取得するメソッド宣言、非同期メソッドとして宣言
//sereraccess.jsのメソッドをコール
const getGoodsById = async( id, callback ) =>{
try{
goods.value = await fetchGoodsById(id)
callback("success")
}catch(e){
callback(e)
}
}
//商品登録、更新メソッド宣言、非同期メソッドとして宣言
//sereraccess.jsのメソッドをコール
const registGoods = async( regGoods, callback ) => {
try{
await sendGoods( regGoods )
callback("success")
}catch(e){
callback(e)
}
}
//当該モジュールが提供する変数、メソッドを宣言
return{
goodsList,
goods,
getGoods,
getGoodsById,
registGoods,
categoryDatas
}
}
・GoodsFilter.js
商品名検索用モジュール
import { ref, computed } from 'vue'
//商品リストを渡してもらう
export default function ( goodsList ){
//検索文字列を宣言、refとして宣言
//GoodsList.vueのinput項目と関連付けている
const searchQuery = ref('')
//searchQuery変更時に常に当該ロジックが起動、Vue.jsのcomputed機能を使用する
//検索で絞られた値を格納する変数をreturnで宣言し外部で使用する事が出来る
const matchGoods = computed(() => {
return goodsList.value.filter(goods => {
return goods.name.includes(searchQuery.value)
})
})
//当該モジュールが提供する変数、メソッドを宣言
return{
searchQuery,
matchGoods
}
}
・GoodsSort.js
商品リストソート用モジュール
//商品リストを渡してもらう
export default function ( goodsList ){
let orderColumn = ''
let orderBy = ''
//ソートメソッドを宣言
//引数のカラムのソート処理を行う。
//現在が昇順の場合は降順に、降順の場合は昇順にソート
const sort = ( column ) =>{
if( orderColumn != column ){
orderColumn = column
orderBy = "asc"
}else{
if( orderBy == "asc"){
orderBy = "desc"
}else{
orderBy = "asc"
}
}
goodsList.value.sort(
(a, b)=>{
let res1 = 1;
let res2 = -1
if( orderBy == "desc"){
res1 = -1
res2 = 1
}
if( a[column] > b[column] ){
return res1
}else{
return res2
}
}
)
}
//当該モジュールが提供する変数、メソッドを宣言
return{
sort
}
}
商品登録・編集画面
・EditGoods.vue
商品登録、編集画面用コンポーネント
<template>
<div>
<div class="inputItemDiv"><div class="inputTitleDiv">コード</div><input readonly class="inputread" type="text" v-model="goods.code"></div>
<div class="inputItemDiv"><div class="inputTitleDiv">商品名</div><input class="input" type="text" v-model="goods.name"></div>
<div class="inputItemDiv" v-show="!resNameVal"><div class="inputTitleDiv"></div><span class="validateError">入力必須。50桁以内で入力して下さい。</span></div>
<div class="inputItemDiv"><div class="inputTitleDiv">カテゴリ</div>
<select class="select" name="category" v-model="goods.category">
<option v-for="categoryData in categoryDatas" :value="categoryData.id" :key="categoryData.id">
{{ categoryData.name }}
</option>
</select>
</div>
<div class="inputItemDiv"><div class="inputTitleDiv">価格</div><input class="input" type="text" v-model="goods.price"></div>
<div class="inputItemDiv" v-show="!resPriceVal"><div class="inputTitleDiv"></div><span class="validateError">入力必須。半角数字7桁以内で入力して下さい。</span></div>
<div class="inputItemDiv" style="text-align:right">
<button type="button" class="button" @click='cancel'>キャンセル</button>
<button :disabled="resAllVal" type="button" class="button" @click='regist'>登録</button>
</div>
</div>
</template>
<script>
import goodsValidator from '../composables/GoodsValidator'
import goodsRepository from '../composables/GoodsRepository'
import { useRoute, useRouter } from 'vue-router'
export default ({
name: 'GoodsList',
setup(){
//GoodsRepositoryモジュールからの変数、メソッドを宣言
const { categoryDatas, goods, getGoodsById, registGoods } = goodsRepository()
//ルータ使用変数宣言
const route = useRoute()
const router = useRouter()
//パラメータでIDが渡された際は、商品情報を取得する
if( route.params.id != null ){
getGoodsById( route.params.id, getCallback )
}
//データ取得時のコールバック( GoodsRepositoryのgetGoodsByIdメソッドに渡す)
const getCallback = ( result ) =>{
if( result != "success"){
alert( result );
}
}
//GoodsValidatorモジュールからの変数、メソッドを宣言
//resName:名称入力チェック結果
//resPricceVal:価格入力チェック結果
//resAllVal:全ての項目の入力チェック結果
const{ resNameVal, resPriceVal, resAllVal } = goodsValidator( goods )
//登録ボタン押下時の処理
const regist = () => {
registGoods( goods.value, regCallback )
}
//登録ボタン押下時のコールバック( GoodsRepositoryのregistGoodsメソッドに渡す)
const regCallback = ( result ) =>{
if( result == "success"){
alert("登録完了");
router.push("/")
}else{
alert( result );
}
}
//キャンセル処理 商品一覧画面へ戻る
const cancel = () => {
router.push("/")
}
//template上で使用する変数を宣言
return{
categoryDatas,
goods,
regist,
cancel,
resNameVal,
resPriceVal,
resAllVal,
}
}
})
</script>
<style>
省略
</style>
import { computed } from 'vue'
import { validLength, validInteger } from '../api/checker'
export default function ( goods ){
//商品名用チェック
const nameValidator = () =>{
return validLength(1, 10, goods.value.name)
}
//価格用チェック
const priceValidator = () =>{
return validInteger(1,7, goods.value.price)
}
//全てのチェック
const allValidator = () =>{
var res = ( computed( nameValidator ).value && computed( priceValidator ).value )
console.log( res )
if( res ){
return false
}else{
return true
}
}
//computedに設定
//商品名、価格の入力が変化した際に処理が起動
//全チェックは登録ボタン押下可否に使用する
const resNameVal = computed( nameValidator )
const resPriceVal = computed( priceValidator )
const resAllVal = computed( allValidator )
//当該モジュールが提供する変数、メソッドを宣言
return{
resNameVal,
resPriceVal,
resAllVal
}
}
その他
・ckecker.js
値チェック用モジュール
//桁数チェック
let validLength = (min, max, value) => {
if( value.length >= min && value.length <= max ){
return true
}
return false
}
//数値桁数チェック
let validInteger = (minLength, maxLength, value) => {
var pattern = '^[0-9]{' + minLength + ',' + maxLength + '}$';
console.log( pattern )
return value.match( pattern )
}
//必須チェック
let validRequire = ( value) => {
if( value == null || value.length == 0 ) return false
return true
}
export{ validLength, validInteger, validRequire }
・serveraccess.js
サーバへのアクセスモジュール
import * as axios from 'axios'
//商品全権取得
let fetchGoods = ( ) => {
return new Promise((resolve, reject) => {
axios
.get('http://localhost:8080/goods/list')
.then(response => {
resolve(response.data)
}).catch(error => {
reject(error)
})
}).catch(() => {
throw 'サーバアクセスエラー'
})
}
//商品をID指定で取得
let fetchGoodsById = (id) => {
return new Promise((resolve, reject) => {
axios
.get('http://localhost:8080/goods/byId?id=' + id)
.then(response => {
resolve(response.data)
}).catch(error => {
reject(error)
})
}).catch(() => {
throw 'サーバアクセスエラー'
})
}
//商品登録、更新
let sendGoods = (goods) => {
return new Promise((resolve, reject) => {
axios
.post('http://localhost:8080/goods/regist', goods )
.then(response => {
resolve(response.data)
}).catch(error => {
reject(error)
})
}).catch(() => {
throw 'サーバアクセスエラー'
})
}
export{ fetchGoods, fetchGoodsById, sendGoods }
以上、画面側の説明でした。
Composition Apiになってコードをモジュール事に小分けする事が可能になりました。
これにより、モジュール事に役割が明確になり実装が用意かつ管理しやすくなったのが分かります。
コードは此方にアップしてあります。
サーバ側は次回に簡単に説明したいと思います。