SpringBoot3 + Vue3 + Uniapp + uView + Elementによる動的二段階カテゴリーリストとその管理機能実装

  1. 実装効果

1.1 フロントエンド表示効果

1.2 バックエンド一級カテゴリ管理

1.3 バックエンド二級カテゴリ管理

一級カテゴリをクリックすると二級カテゴリ管理画面に遷移

  1. サーバーサイドコード

2.1 GoodsCategoryController.java

package com.zhx.app.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zhx.app.model.goods.GoodsCategory;
import com.zhx.app.model.PagePram;
import com.zhx.app.model.goods.GoodsCategorySon;
import com.zhx.app.service.GoodsCategoryService;
import com.zhx.app.service.GoodsCategorySonService;
import com.zhx.app.utils.ResultUtils;
import com.zhx.app.utils.ResultVo;
import io.micrometer.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;


/**
 * @ClassName : GoodsCategoryController
 * @Description : 商品カテゴリ
 * @Author : zhx
 * @Date: 2024-03-31 10:50
 */
@RestController
@RequestMapping("/api/goodsCategory")
public class GoodsCategoryController {
    @Autowired
    private GoodsCategoryService goodsCategoryService;
    @Autowired
    private GoodsCategorySonService goodsCategorySonService;

    /**
     * 商品カテゴリリスト取得
     *
     * @param pagePram
     * @return
     */
    @GetMapping("/getList")
    public ResultVo getList(PagePram pagePram) {
        // ページングクエリ条件構築
        QueryWrapper<GoodsCategory> query = new QueryWrapper<>();
        query.lambda().like(StringUtils.isNotBlank(pagePram.getSearchName()), GoodsCategory::getCategoryName, pagePram.getSearchName()).orderByDesc(GoodsCategory::getOrderNum);
        // ページオブジェクト作成
        IPage<GoodsCategory> page = new Page<>(pagePram.getCurrentPage(), pagePram.getPageSize());
        // クエリ実行
        IPage<GoodsCategory> list = goodsCategoryService.page(page, query);
        return ResultUtils.success("取得成功!", list);
    }

    /**
     * 二級カテゴリデータ取得
     * @param categoryFatherId
     * @param pagePram
     * @return
     */
    @GetMapping("/getInfo/{categoryFatherId}")
    public ResultVo getListInfo(@PathVariable String categoryFatherId, PagePram pagePram) {
        // ページングクエリ条件構築
        QueryWrapper<GoodsCategorySon> query = new QueryWrapper<>();
        query.lambda().like(StringUtils.isNotBlank(categoryFatherId), GoodsCategorySon::getCategoryFatherId,categoryFatherId).orderByDesc(GoodsCategorySon::getOrderNum);
        // ページオブジェクト作成
        IPage<GoodsCategorySon> page = new Page<>(pagePram.getCurrentPage(), pagePram.getPageSize());
        // クエリ実行
        IPage<GoodsCategorySon> list = goodsCategorySonService.page(page, query);
        return ResultUtils.success("取得成功!", list);
    }
    /**
     * 商品カテゴリ追加
     *
     * @param goodsCategory
     * @return
     */
    @PostMapping
    public ResultVo add(@RequestBody GoodsCategory goodsCategory) {
        if (goodsCategoryService.save(goodsCategory)) {
            return ResultUtils.success("追加成功!");
        } else {
            return ResultUtils.error("追加失敗!");
        }
    }

    /**
     * 商品カテゴリ追加
     *
     * @param goodsCategorySon
     * @return
     */
    @PostMapping("/son")
    public ResultVo addSon(@RequestBody GoodsCategorySon goodsCategorySon) {
        if (goodsCategorySonService.save(goodsCategorySon)) {
            return ResultUtils.success("追加成功!");
        } else {
            return ResultUtils.error("追加失敗!");
        }
    }

    /**
     * 商品カテゴリ削除
     *
     * @param goodsCategoryId
     * @return
     */
    @DeleteMapping("/{categoryId}")
    public ResultVo delete(@PathVariable("categoryId") Long goodsCategoryId) {
        if (goodsCategoryService.removeById(goodsCategoryId)) {
            return ResultUtils.success("削除成功!");
        } else {
            return ResultUtils.error("削除失敗!");
        }
    }
    /**
     * 商品カテゴリ削除
     *
     * @param goodsCategoryId
     * @return
     */
    @DeleteMapping("/son/{categoryId}")
    public ResultVo deleteSon(@PathVariable("categoryId") Long goodsCategoryId) {
        System.out.println(goodsCategoryId);
        if (goodsCategorySonService.removeById(goodsCategoryId)) {
            return ResultUtils.success("削除成功!");
        } else {
            return ResultUtils.error("削除失敗!");
        }
    }

    /**
     * 商品カテゴリ編集
     *
     * @param goodsCategorySon
     * @return
     */
    @PutMapping("/son")
    public ResultVo edit(@RequestBody GoodsCategorySon goodsCategorySon) {
        if (goodsCategorySonService.updateById(goodsCategorySon)) {
            return ResultUtils.success("編集成功!");
        } else {
            return ResultUtils.error("編集失敗!");
        }
    }

    /**
     * 子商品カテゴリ編集
     *
     * @param goodsCategory
     * @return
     */
    @PutMapping
    public ResultVo edit(@RequestBody GoodsCategory goodsCategory) {
        if (goodsCategoryService.updateById(goodsCategory)) {
            return ResultUtils.success("編集成功!");
        } else {
            return ResultUtils.error("編集失敗!");
        }
    }

    /**
     * 前端u-pickerコンポーネント描画用の選択リスト取得
     * @return
     */
    @GetMapping("/getSelectList")
    public ResultVo getSelectList() {
        List<Object> categoryList = goodsCategorySonService.getSelectLists();
        return ResultUtils.success("取得成功!", categoryList);
    }

    /**
     * 二級カテゴリIDでカテゴリ詳細を取得
     * @param id
     * @return
     */
    @GetMapping("/{categoryId}")
    public ResultVo getCategoryListById(@PathVariable("categoryId") String id) {
        List<String> categoryList = goodsCategorySonService.getCategoryListById(id);
        return ResultUtils.success("取得成功!", categoryList);
    }
}


2.2.1 GoodsCategoryMapper.java

package com.zhx.app.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhx.app.model.goods.GoodsCategory;

/**
 * @ClassName : GoodsCategoryMapper
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:47
 */
public interface GoodsCategoryMapper extends BaseMapper<GoodsCategory> {
}


2.2.2 GoodsCategorySonMapper.java

package com.zhx.app.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhx.app.model.goods.GoodsCategory;
import com.zhx.app.model.goods.GoodsCategorySon;

/**
 * @ClassName : GoodsCategorySonMapper
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:47
 */
public interface GoodsCategorySonMapper extends BaseMapper<GoodsCategorySon> {
}


2.3.1 GoodsCategory.java

package com.zhx.app.model.goods;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * @ClassName : GoodsCategory
 * @Description : 商品一級カテゴリ
 * @Author : zhx
 * @Date: 2024-03-31 10:44
 */
@Data
@TableName("goods_category")
public class GoodsCategory {
    @TableId(type = IdType.AUTO)
    private Long categoryId;

    private String categoryName;
    private Integer orderNum;
}


2.3.2 GoodsCategorySon.java

package com.zhx.app.model.goods;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * @ClassName : GoodsCategorySon
 * @Description : 商品二級カテゴリ
 * @Author : zhx
 * @Date: 2024-03-31 10:44
 */
@Data
@TableName("goods_category_son")
public class GoodsCategorySon {
    @TableId(type = IdType.AUTO)
    private Long categoryId;

    private String categoryName;
    private Integer orderNum;
    private Long categoryFatherId;
}


2.4 PageParm.java

package com.zhx.app.model;

import lombok.Data;

/**
 * @ClassName : PageParm
 * @Description : ページング
 * @Author : zhx
 * @Date: 2024-03-30 11:00
 */
@Data
public class PagePram {
    private Long currentPage;
    private Long pageSize;
    private String searchName;
}


2.5.1 GoodsCategoryService .java

package com.zhx.app.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zhx.app.model.goods.GoodsCategory;

/**
 * @ClassName : GoodsCategoryService
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:48
 */

public interface GoodsCategoryService extends IService<GoodsCategory> {
}

2.5.2 GoodsCategorySonService.java

package com.zhx.app.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zhx.app.model.goods.GoodsCategorySon;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName : GoodsCategoryService
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:48
 */

public interface GoodsCategorySonService extends IService<GoodsCategorySon> {

    List<Object> getSelectLists();

    List<String> getCategoryListById(String id);
}


2.6.1 GoodsCategoryServiceImpl .java

package com.zhx.app.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhx.app.mapper.GoodsCategoryMapper;
import com.zhx.app.model.goods.GoodsCategory;
import com.zhx.app.model.goods.GoodsCategorySon;
import com.zhx.app.service.GoodsCategoryService;
import org.springframework.stereotype.Service;

/**
 * @ClassName : GoodsCategoryServiceImpl
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:49
 */
@Service
public class GoodsCategoryServiceImpl extends ServiceImpl<GoodsCategoryMapper, GoodsCategory> implements GoodsCategoryService{
}


2.6.2 GoodsCategoryServiceSonImpl.java

package com.zhx.app.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhx.app.mapper.GoodsCategoryMapper;
import com.zhx.app.mapper.GoodsCategorySonMapper;
import com.zhx.app.model.goods.GoodsCategory;
import com.zhx.app.model.goods.GoodsCategorySon;
import com.zhx.app.service.GoodsCategorySonService;
import io.micrometer.common.util.StringUtils;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * @ClassName : GoodsCategoryServiceSonImpl
 * @Description :
 * @Author : zhx
 * @Date: 2024-03-31 10:49
 */
@Service
public class GoodsCategoryServiceSonImpl extends ServiceImpl<GoodsCategorySonMapper, GoodsCategorySon> implements GoodsCategorySonService{
    @Autowired
    private GoodsCategoryMapper goodsCategoryMapper;
    @Autowired
    private GoodsCategorySonMapper goodsCategorySonMapper;

    /**
     * 一級カテゴリと二級カテゴリリストのフォーマット化
     * @return
     */
    @Override
    public List<Object> getSelectLists() {
        @Data
        class SelectType {
            private Long id;
            private String name;
        }
        @Data
        class SelectTypeSon {
            private Long id;
            private String name;
            private Long categoryFatherId;
        }
        // カテゴリリスト取得
        // クエリ構築
        QueryWrapper<GoodsCategory> query = new QueryWrapper<>();
        // クエリ条件
        query.lambda().orderByDesc(GoodsCategory::getCategoryId);
        // クエリ結果取得

        List<GoodsCategory> list = goodsCategoryMapper.selectList(query);

        // クエリ構築
        QueryWrapper<GoodsCategorySon> querySon = new QueryWrapper<>();
        // クエリ条件
        querySon.lambda().orderByDesc(GoodsCategorySon::getOrderNum);
        // クエリ結果取得
        List<GoodsCategorySon> listSon = goodsCategorySonMapper.selectList(querySon);

        // 必要な型を格納
        ArrayList<SelectType> selectList = new ArrayList<>();
        ArrayList<SelectTypeSon> selectListSon = new ArrayList<>();
        List<Object> category = new ArrayList<>();
        // 必要な型を構築
        Optional.ofNullable(list).orElse(new ArrayList<>())
                .stream()
                .forEach(x -> {
                    SelectType type = new SelectType();
                    type.setId(x.getCategoryId());
                    type.setName(x.getCategoryName());
                    selectList.add(type);
                });

        Optional.ofNullable(listSon).orElse(new ArrayList<>())
                .stream()
                .forEach(x -> {
                    SelectTypeSon type = new SelectTypeSon();
                    type.setId(x.getCategoryId());
                    type.setName(x.getCategoryName());
                    type.setCategoryFatherId(x.getCategoryFatherId());
                    selectListSon.add(type);
                });
        category.add(selectList);
        category.add(selectListSon);
        return category;
    }

    /**
     * 二級カテゴリIDで一級カテゴリと二級カテゴリ名を取得
     * @param id
     * @return
     */
    @Override
    public List<String> getCategoryListById(String id) {
        // 一級カテゴリと二級カテゴリ名を格納するコンテナ
        List<String> nameList = new ArrayList<>();
        // カテゴリリスト取得
        // クエリ構築
        QueryWrapper<GoodsCategorySon> query = new QueryWrapper<>();
        query.lambda().like(StringUtils.isNotBlank(id),GoodsCategorySon::getCategoryId, id);
        GoodsCategorySon son = goodsCategorySonMapper.selectOne(query);
        // 二級カテゴリをコンテナに追加
        nameList.add(son.getCategoryName());
        // 二級カテゴリの親IDから一級カテゴリ名を取得
        Long categoryFatherId = son.getCategoryFatherId();
        QueryWrapper<GoodsCategory> queryFather = new QueryWrapper<>();
        queryFather.lambda().like(StringUtils.isNotBlank(String.valueOf(categoryFatherId)),GoodsCategory::getCategoryId, categoryFatherId);
        GoodsCategory father = goodsCategoryMapper.selectOne(queryFather);
        nameList.add(father.getCategoryName());
        Collections.reverse(nameList);
        return nameList;
    }
}


  1. uniappコード

3.1 GoodsCategory.vue

			<!-- 商品カテゴリ -->
			<view class="foot line-border" @tap="showShopType=true;">
				<view>商品カテゴリ</view>
				<view class="foot-right">
					<u-tag v-if="productData.type==''" @click="showShopType=true"
						:text="productData.type==''?'カテゴリを選択してください':items" :type="productData.newColor" shape="circle"
						size="mini" style="margin-left: 10rpx;"></u-tag>
					<template v-for="(items, index) in productData.type" :key="index">
						<u-tag @click="showShopType=true" :text="items" :type="productData.type!=''?'success':''"
							shape="circle" size="mini" style="margin-left: 10rpx;"></u-tag>
					</template>

					<u-icon name="arrow-right"></u-icon>
				</view>
			</view>
			<!-- 商品カテゴリプルダウン -->
			<view>
				<u-picker :show="showShopType" ref="uPicker" :columns="typeList.columns" @confirm="confirm"
					@change="changeHandler" confirmColor="green" immediateChange @cancel="showShopType=false"
					keyName="name" :defaultIndex="[Math.trunc(typeList.columns[0].length / 2),1]"></u-picker>
			</view>


<script setup>
	import {
		reactive,
		ref
	} from 'vue';
	import UpLoadFile from "@/utils/uploadFlie/UpLoadFile.vue"
	import MapOpen from "@/utils/mapOpen/MapOpen.vue"
	import {
		getSelectList
	} from '../../api/shopcategory';
	const urlLists = ref([]);
	const urlList = (data) => {
		urlLists.value = data;
	}
	const showShopType = ref(false);
	
	const realyType = ref("");
	// 商品発売
	const publishProducts = () => {
		// 商品データ構築
		let data = {
			profile: productData.value.profile,
			url: '',
			address: "商品位置",
			money: productData.value.money,
			type: realyType.value,
			oldMoney: productData.value.oldMoney,
			new: productData.value.new,
			trading: productData.value.trading,
			contact: productData.value.contact,
			contactType: productData.value.contactType
		}
		urlLists.value.forEach(x => data.url += x + ",")
		data.url = data.url.slice(0, -1)

		console.log(data);
		// API呼び出しで商品データを保存
	}

	// 商品データ
	const productData = ref({
		tips: "ブランド・モデル・商品の詳細を明確に記述することで、より速く売却できます~",
		profile: "", // 商品説明
		address: "", // 商品場所
		money: "", // 商品価格
		oldMoney: "", // 商品元価格
		type: "",
		new: "新旧状態を選択してください", // 新旧状態
		newColor: "", // 新旧色
		trading: "取引方法を選択してください", // 取引方法
		contact: "", // 連絡先
		contactType: "" // 連絡先タイプ
	})
	// 商品カテゴリ
	const typeList = reactive({
		columns: [
			['服飾', 'デジタル']
		],
		columnData: []
	})
	// データ取得
	import {
		onShow
	} from '@dcloudio/uni-app'

	onShow(() => {
		getList();
	})
		const getList = () => {
		let res = getSelectList();
		res.then((result) => {
			// 一級カテゴリのデフォルト選択設定
			let num = Math.trunc(result.data[0].length/2);
			// 一級カテゴリ設定
			typeList.columns = [result.data[0]];
			// 二級カテゴリ構築
			// categoryFatherIdに基づいてグループ化するMapオブジェクトを使用
			const groupedData = new Map();

			result.data[1].forEach(item => {
				const {
					categoryFatherId
				} = item;
				if (!groupedData.has(categoryFatherId)) {
					// Mapに該当のcategoryFatherIdキーが存在しない場合、新しい配列を作成
					groupedData.set(categoryFatherId, [item]);
				} else {
					// Mapに該当のcategoryFatherIdキーが既に存在する場合、対応する配列にオブジェクトを追加
					groupedData.get(categoryFatherId).push(item);
				}
			});

			// Mapの値(配列)を最終結果配列に変換
			const resultArray = Array.from(groupedData.values());
			typeList.columnData = resultArray;
			// categoryFatherIdに基づいてソートする比較関数を定義
			const compareByCategoryFatherId = (a, b) => {
				const categoryA = a[0].categoryFatherId;
				const categoryB = b[0].categoryFatherId;
				if (categoryA < categoryB) {
					return 1;
				}
				if (categoryA > categoryB) {
					return -1;
				}
				return 0;
			};
			// 二級カテゴリをソートして一級カテゴリと対応させる(一級カテゴリ一つに対応する二級カテゴリ配列)
			typeList.columnData.sort(compareByCategoryFatherId);
			// 二級カテゴリ列を一級カテゴリに追加し、初期表示時に一級カテゴリと二級カテゴリが表示されるようにする
			typeList.columns.push(typeList.columnData[num])
		});
	}

	const changeHandler = (e) => {
		const {
			columnIndex,
			value,
			values, // valuesは現在変更された列の配列内容
			index,
			picker = this.$refs.uPicker
		} = e;
		// 第一列の値が変更された場合、第二列(次の列)の選択肢を変更
		if (columnIndex === 0) {
			// pickerはセレクタthisインスタンスで、第二列の選択肢を変更
			picker.setColumnValues(1, typeList.columnData[index])
		}
	}
	// コールバックパラメータはcolumnIndex、value、valuesを含む
	const confirm = (e) => {
		let type = [];
		e.value.forEach(x => type.push(x.name));
		productData.value.type = type;
		showShopType.value = false
		realyType.value = e.value[1].id;
	}
</script>


3.2 getSelectList.js

import http from "../../utils/httpRequest/http";

// 全カテゴリ取得
export const getSelectList = () =>{
	return http.get(`/api/goodsCategory/getSelectList`);
} 

3.3 http.js

const baseUrl = 'http://localhost:9999';
const http = (options = {}) => {
	return new Promise((resolve, reject) => {
		uni.request({
			url: baseUrl + options.url || '',
			method: options.type || 'GET',
			data: options.data || {},
			header: options.header || {}
		}).then((response) => {
			// console.log(response);
			if (response.data && response.data.code == 200) {
				resolve(response.data);
			} else {
				uni.showToast({
					icon: 'none',
					title: response.data.msg,
					duration: 2000
				});
			}
		}).catch(error => {
			reject(error);
		})
	});
}
/**
 * getリクエストカスタム
 */
const get = (url, data, options = {}) => {
	options.type = 'get';
	options.data = data;
	options.url = url;
	return http(options);
}

/**
 * postリクエストカスタム
 */
const post = (url, data, options = {}) => {
	options.type = 'post';
	options.data = data;
	options.url = url;
	return http(options);
}

/**
 * putリクエストカスタム
 */
const put = (url, data, options = {}) => {
	options.type = 'put';
	options.data = data;
	options.url = url;
	return http(options);
}

/**
 * upLoadアップロード
 * 
 */
const upLoad = (parm) => {
	return new Promise((resolve, reject) => {
		uni.uploadFile({
			url: baseUrl + parm.url,
			filePath: parm.filePath,
			name: 'file',
			formData: {
				openid: uni.getStorageSync("openid")
			},
			header: {
				// Authorization: uni.getStorageSync("token")
			},
			success: (res) => {
				resolve(res.data);
			},
			fail: (error) => {
				reject(error);
			}
		})
	})
}

export default {
	get,
	post,
	put,
	upLoad,
	baseUrl
}

  1. バックエンド管理ページコード

4.1 GoodsType.vue

<!--
 * @Date: 2024-04-11 18:15:17
 * @LastEditors: zhong
 * @LastEditTime: 2024-04-11 17:25:33
 * @FilePath: \app-admin\src\views\goods\GoodsType.vue
-->
<template>
    <el-main>
        <!-- 検索バー -->
        <el-form :model="searchParm" :inline="true" size="default">
            <el-form-item>
                <el-input v-model="searchParm.searchName" placeholder="カテゴリ名を入力してください"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button icon="search" @click="searchBtn()">検索</el-button>
                <el-button icon="closeBold" type="danger" @click="resetBtn()">リセット</el-button>
                <el-button icon="plus" type="primary" @click="addBtn()">新規</el-button>
                <el-button v-if="isShowBack" icon="RefreshRight" type="success" @click="back()"
                    style="justify-content: flex-end;">戻る</el-button>
            </el-form-item>
        </el-form>

        <!-- テーブル -->
        <el-table :height="tableHeight" :data="tableList" border stripe>
            <el-table-column label="番号" align="center">
                <template #default="scope">
                    <div @click="lookInfo(scope.row.categoryId)"> <el-tag>{{ scope.$index + 1 }}</el-tag></div>
                </template>
            </el-table-column>

            <el-table-column label="カテゴリ" align="center">
                <template #default="scope">
                    <div @click="lookInfo(scope.row.categoryId)"> <el-tag>{{ scope.row.categoryName }}</el-tag></div>
                </template>
            </el-table-column>
            <!-- 操作 -->
            <el-table-column prop="status" label="操作" align="center" width="220">
                <template #default="scope">
                    <el-button type="primary" icon="edit" size="default" @click="editBtn(scope.row)">編集</el-button>
                    <el-button type="danger" icon="delete" size="default" @click="deleteBtn(scope.row)">削除</el-button>
                </template>
            </el-table-column>
        </el-table>
        <div class="tips">ヒント: 現在は一級カテゴリです。二級カテゴリを見るにはクリックしてください。</div>
        <!-- ページング -->
        <div class="page-helper">
            <el-pagination @size-change="sizeChange" @current-change="currentChange"
                :current-page.sync="searchParm.currentPage" :page-sizes="[10, 20, 40, 80, 100]"
                :page-size="searchParm.pageSize" layout="total, sizes, prev, pager, next, jumper"
                :total="searchParm.total" background>
            </el-pagination>
        </div>

        <!-- 新規 -->
        <SystemDialog :title="dialog.title" :height="dialog.height" :width="dialog.width" :visible="dialog.visible"
            @on-close="onClose" @on-confirm="commit">
            <template v-slot:content>
                <!-- 新規内容フォーム -->
                <el-form :model="addGoodsTypePram" ref="addRef" :rules="rules" label-width="80px" :inline="false"
                    size="default">
                    <el-form-item prop="categoryName" label="名前:">
                        <el-input v-model="addGoodsTypePram.categoryName"></el-input>
                    </el-form-item>
                    <el-form-item prop="orderNum" label="優先度:">
                        <el-input type="number" v-model="addGoodsTypePram.orderNum"></el-input>
                    </el-form-item>
                </el-form>

            </template>
        </SystemDialog>
    </el-main>

</template>

<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue';
import useDialog from '@/hooks/useDialog';
import { Title } from '@/type/BaseEnum';
import { ElMessage, FormInstance } from 'element-plus';
import { getGoodsTypeListApi, addGoodsTypeApi, editGoodsTypeApi, deleteGoodsTypeApi, getGoodsTypeSonListApi, deleteGoodsTypeSonApi, addGoodsTypeSonApi, editGoodsTypeSonApi } from '@/api/goods'
import SystemDialog from '@/components/SystemDialog/SystemDialog.vue';
import { GoodsType } from '@/api/goods/GoodsType';
import useInstance from '@/hooks/useInstance';
// 戻るボタン表示フラグ
const isShowBack = ref(false);
// 一級カテゴリに戻る
const back = () => {
    isShowBack.value = false;
    getList();
}
// 現在クリックされたオブジェクトのID取得
const id = ref<string>("0");
// グローバルプロパティ取得
const { golbal } = useInstance();
// ダイアログプロパティ取得
const { dialog, onClose } = useDialog();
// フォームref属性
const addRef = ref<FormInstance>();
// 検索バインドオブジェクト リストクエリパラメータ
const searchParm = reactive({
    currentPage: 1,
    pageSize: 10,
    searchName: "",
    total: 0
})
// 提出操作か修正操作か識別
const tags = ref();
// 新規ボタン
const addBtn = () => {
    tags.value = '0';
    // ダイアログタイトル設定
    dialog.title = Title.ADD
    dialog.height = 120;
    dialog.visible = true;

    // フォームクリア
    addRef.value?.resetFields()
}
// クリックしてカテゴリ詳細に遷移
const lookInfo = async (id_: string) => {
    if (!isShowBack.value) {
        id.value = id_;

        let res = await getGoodsTypeSonListApi(searchParm, id_);
        if (res && res.code == 200) {
            // res.data.records.forEach((x: { status: number; }) => x.status == 0 ? "true" : "false")
            tableList.value = res.data.records;
            searchParm.total = res.data.total;
            isShowBack.value = true;
        }
    }

}

// 検索
const searchBtn = () => {
    getList();
}
// リセット
const resetBtn = () => {
    searchParm.searchName = '';
    getList();
}
// テーブルデータ
const tableList = ref([]);
// テーブル高さ
const tableHeight = ref(0);
// リストクエリ
const getList = async () => {
    let res = await getGoodsTypeListApi(searchParm);
    if (res && res.code == 200) {
        // res.data.records.forEach((x: { status: number; }) => x.status == 0 ? "true" : "false")
        tableList.value = res.data.records;
        searchParm.total = res.data.total;
    }
}
// 新規フォーム内容
const addGoodsTypePram = reactive({
    categoryId: "",
    categoryName: "",
    orderNum: ""
})
// 新規フォーム内容
const addGoodsTypeSonPram = reactive({
    categoryId: "",
    categoryName: "",
    orderNum: "",
    categoryFatherId: ""
})


// フォーム検証ルール
const rules = {
    categoryName: [
        { required: true, message: '商品カテゴリを入力してください', trigger: 'blur' },
        { min: 0, max: 12, message: '長さは0〜12である必要があります', trigger: 'blur' },
    ],
    orderNum: [{ required: true, message: 'カテゴリ番号を入力してください', trigger: 'blur' }],
}
// 提出フォーム
const commit = () => {
    addRef.value?.validate(async (valid) => {
        let res = null;
        if (valid) {
            addGoodsTypeSonPram.categoryId = addGoodsTypePram.categoryId;
            addGoodsTypeSonPram.categoryName = addGoodsTypePram.categoryName;
            addGoodsTypeSonPram.orderNum = addGoodsTypePram.orderNum;
            addGoodsTypeSonPram.categoryFatherId = id.value;
            if (tags.value == '0') {
                // データ送信
                if (isShowBack.value) {
                    res = await addGoodsTypeSonApi(addGoodsTypeSonPram);
                } else {
                    res = await addGoodsTypeApi(addGoodsTypePram);
                }
            } else {
                // データ送信
                if (!isShowBack.value) {
                    res = await editGoodsTypeApi(addGoodsTypePram);
                }
                else {
                    res = await editGoodsTypeSonApi(addGoodsTypeSonPram);
                }
            }
            if (res && res.code == 200) {
                // データ再取得
                if (isShowBack.value) {
                    isShowBack.value = false;
                    lookInfo(id.value);
                    isShowBack.value = true;
                } else {
                    getList();
                }
                // 情報通知
                ElMessage.success(res.msg);
                // 提出成功 ダイアログ閉じる
                dialog.visible = false;
            }
        }
    });
}
// 編集
const editBtn = (row: GoodsType) => {
    tags.value = '1';
    console.log(row);
    // ダイアログタイトル設定
    dialog.title = Title.EDIT
    dialog.height = 320;
    dialog.visible = true;

    // データ表示
    nextTick(() => {
        Object.assign(addGoodsTypePram, row);
    })

}
// 削除
const deleteBtn = async (row: GoodsType) => {
    console.log(row);
    const confirm = await golbal.$myConfirm("このデータを削除してもよろしいですか?")
    if (confirm) {
        let res;
        if (!isShowBack.value) {
            res = await deleteGoodsTypeApi(row.categoryId);
        } else {
            res = await deleteGoodsTypeSonApi(row.categoryId);
            isShowBack.value = false;
        }
        if (res && res.code == 200) {
            // データ再取得
            getList();
            // 情報通知
            ElMessage.success(res.msg);
        }
        // データ表示
        nextTick(() => {
            Object.assign(addGoodsTypePram, row);
        })
    }
}
// ページサイズ変更時
const sizeChange = (size: number) => {
    searchParm.pageSize = size;
    getList();
}
// ページ番号変更時
const currentChange = (page: number) => {
    searchParm.currentPage = page;
    getList();
}
onMounted(() => {
    tableHeight.value = window.innerHeight - 200;
    getList();
})
</script>

<style lang="scss" scoped>
.tips {
    float: left;
    color: red;
    z-index: 99999;
}

.page-helper {
    margin-top: 20px;
    display: flex;
    justify-content: flex-end;
}
</style>

4.2 GoodsType.ts

/*
 * @Date: 2024-03-31 13:09:13
 * @LastEditors: zhong
 * @LastEditTime: 2024-03-31 13:10:56
 * @FilePath: \app-admin\src\api\goods\GoodsType.ts
 */
// 商品カテゴリデータ定義
export type GoodsType = {
    categoryId: string,
    categoryName: string,
    orderNum: string,
}
// 二級商品カテゴリデータ
export type GoodsTypeSon = {
    categoryId: string,
    categoryName: string,
    orderNum: string,
    categoryFatherId: string
}

4.3 goods\index.ts

/*
 * @Date: 2024-03-31 13:02:30
 * @LastEditors: zhong
 * @LastEditTime: 2024-04-11 17:23:15
 * @FilePath: \app-admin\src\api\goods\index.ts
 */
import http from "@/http";
import { PageQueryPram } from "../PaginationQueryModel";
import { GoodsType, GoodsTypeSon } from './GoodsType'

// 新規追加
export const addGoodsTypeApi = (parm: GoodsType) => {
    return http.post("/api/goodsCategory", parm);
}
// 全て取得
export const getGoodsTypeListApi = (parm: PageQueryPram) => {
    return http.get("/api/goodsCategory/getList", parm);
}
// 編集
export const editGoodsTypeApi = (parm: GoodsType) => {
    return http.put("/api/goodsCategory", parm);
}
// 削除
export const deleteGoodsTypeApi = (categoryId: string) => {
    return http.delete(`/api/goodsCategory/${categoryId}`);
}


// 全ての子カテゴリ取得
export const getGoodsTypeSonListApi = (parm: PageQueryPram, id: string) => {
    return http.get(`/api/goodsCategory/getInfo/${id}`, parm);
}
// 子カテゴリ削除
export const deleteGoodsTypeSonApi = (categoryId: string) => {
    return http.delete(`/api/goodsCategory/son/${categoryId}`);
}
// 子カテゴリ新規追加
export const addGoodsTypeSonApi = (parm: GoodsTypeSon) => {
    return http.post("/api/goodsCategory/son", parm);
}

// 編集
export const editGoodsTypeSonApi = (parm: GoodsTypeSon) => {
    console.log(parm);
    
    return http.put("/api/goodsCategory/son", parm);
}


4.4 PaginationQueryModel.ts

/*
 * @Date: 2024-03-30 13:16:03
 * @LastEditors: zhong
 * @LastEditTime: 2024-03-31 13:31:27
 * @FilePath: \app-admin\src\api\PaginationQueryModel.ts
 */
// ユーザーデータ型定義

export type PageQueryPram = {
    currentPage: number,
    pageSize: number,
    searchName: string,
    total?: number
}

タグ: SpringBoot Vue3 UniApp uView Element

7月18日 00:39 投稿