diff --git a/msh_crmeb_22/crmeb-admin/src/main/java/com/zbkj/admin/controller/FoodEncyclopediaController.java b/msh_crmeb_22/crmeb-admin/src/main/java/com/zbkj/admin/controller/FoodEncyclopediaController.java new file mode 100644 index 0000000..1522e84 --- /dev/null +++ b/msh_crmeb_22/crmeb-admin/src/main/java/com/zbkj/admin/controller/FoodEncyclopediaController.java @@ -0,0 +1,114 @@ +package com.zbkj.admin.controller; + +import com.zbkj.common.model.tool.V2Food; +import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.PageParamRequest; +import com.zbkj.common.result.CommonResult; +import com.zbkj.service.service.tool.ToolFoodAdminService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 食物百科管理 后台控制器 + * +---------------------------------------------------------------------- + * | 提供食物百科 CRUD 管理接口,含图片维护 + * +---------------------------------------------------------------------- + */ +@Slf4j +@RestController +@RequestMapping("api/admin/tool/food") +@Api(tags = "食物百科管理") +public class FoodEncyclopediaController { + + @Autowired + private ToolFoodAdminService toolFoodAdminService; + + /** + * 分页列表 + */ + @ApiOperation(value = "食物列表") + @GetMapping("/list") + public CommonResult> getList( + @RequestParam(required = false) String keyword, + @RequestParam(required = false) String category, + @RequestParam(required = false) String status, + @Validated PageParamRequest pageParamRequest) { + return CommonResult.success(CommonPage.restPage( + toolFoodAdminService.getAdminList(keyword, category, status, pageParamRequest))); + } + + /** + * 食物详情 + */ + @ApiOperation(value = "食物详情") + @GetMapping("/info/{id}") + public CommonResult info(@PathVariable Long id) { + return CommonResult.success(toolFoodAdminService.getById(id)); + } + + /** + * 新增食物 + */ + @ApiOperation(value = "新增食物") + @PostMapping("/save") + public CommonResult save(@RequestBody @Validated V2Food food) { + if (toolFoodAdminService.create(food)) { + return CommonResult.success(); + } + return CommonResult.failed(); + } + + /** + * 修改食物 + */ + @ApiOperation(value = "修改食物") + @PostMapping("/update/{id}") + public CommonResult update(@PathVariable Long id, + @RequestBody @Validated V2Food food) { + food.setFoodId(id); + if (toolFoodAdminService.updateFood(food)) { + return CommonResult.success(); + } + return CommonResult.failed(); + } + + /** + * 删除食物 + */ + @ApiOperation(value = "删除食物") + @GetMapping("/delete/{id}") + public CommonResult delete(@PathVariable Long id) { + if (toolFoodAdminService.deleteById(id)) { + return CommonResult.success(); + } + return CommonResult.failed(); + } + + /** + * 修改状态(上架/下架) + */ + @ApiOperation(value = "修改上下架状态") + @PostMapping("/changeStatus/{id}") + public CommonResult changeStatus(@PathVariable Long id, + @RequestParam String status) { + if (toolFoodAdminService.changeStatus(id, status)) { + return CommonResult.success(); + } + return CommonResult.failed(); + } + + /** + * 获取分类列表(用于下拉筛选) + */ + @ApiOperation(value = "食物分类列表") + @GetMapping("/categories") + public CommonResult> getCategories() { + return CommonResult.success(toolFoodAdminService.getAllCategories()); + } +} diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodAdminServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodAdminServiceImpl.java new file mode 100644 index 0000000..8622ead --- /dev/null +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolFoodAdminServiceImpl.java @@ -0,0 +1,134 @@ +package com.zbkj.service.service.impl.tool; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.github.pagehelper.PageHelper; +import com.zbkj.common.model.tool.V2Food; +import com.zbkj.common.request.PageParamRequest; +import com.zbkj.service.dao.tool.V2FoodDao; +import com.zbkj.service.service.SystemAttachmentService; +import com.zbkj.service.service.tool.ToolFoodAdminService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 食物百科管理后台服务实现类 + * +---------------------------------------------------------------------- + * | 提供后台 CRUD 操作,图片维护等功能 + * +---------------------------------------------------------------------- + */ +@Slf4j +@Service +public class ToolFoodAdminServiceImpl implements ToolFoodAdminService { + + @Resource + private V2FoodDao v2FoodDao; + + @Resource + private SystemAttachmentService systemAttachmentService; + + @Override + public List getAdminList(String keyword, String category, String status, + PageParamRequest pageParamRequest) { + PageHelper.startPage(pageParamRequest.getPage(), pageParamRequest.getLimit()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 关键词搜索(名称或别名) + if (StrUtil.isNotBlank(keyword)) { + wrapper.and(w -> w.like(V2Food::getName, keyword) + .or().like(V2Food::getAlias, keyword)); + } + // 分类筛选 + if (StrUtil.isNotBlank(category)) { + wrapper.eq(V2Food::getCategory, category); + } + // 状态筛选 + if (StrUtil.isNotBlank(status)) { + wrapper.eq(V2Food::getStatus, status); + } + + wrapper.orderByDesc(V2Food::getSortOrder) + .orderByDesc(V2Food::getUpdatedAt); + + return v2FoodDao.selectList(wrapper); + } + + @Override + public V2Food getById(Long id) { + return v2FoodDao.selectById(id); + } + + @Override + public boolean create(V2Food food) { + Date now = new Date(); + food.setImage(normalizeImage(food.getImage())); + food.setCreatedAt(now); + food.setUpdatedAt(now); + if (food.getStatus() == null) { + food.setStatus("active"); + } + if (food.getSortOrder() == null) { + food.setSortOrder(0); + } + if (food.getViewCount() == null) { + food.setViewCount(0); + } + return v2FoodDao.insert(food) > 0; + } + + @Override + public boolean updateFood(V2Food food) { + food.setImage(normalizeImage(food.getImage())); + food.setUpdatedAt(new Date()); + return v2FoodDao.updateById(food) > 0; + } + + /** + * 规范化图片字段: + * 1. 先 clearPrefix 剥掉单层 CDN 前缀 + * 2. 再用正则去掉残留的「http(s)://xxx/」段,修复历史脏数据中的双前缀 + */ + private String normalizeImage(String image) { + if (StrUtil.isBlank(image)) { + return image; + } + String cleaned = systemAttachmentService.clearPrefix(image); + // 处理历史脏数据:形如 "https://.../crmebimage/..." 或 "https://.../https://.../crmebimage/..." + // 去除 "crmebimage/" 之前的所有 "http(s)://...//?" 段 + return cleaned.replaceAll("^(https?://[^/]+/+)+(?=crmebimage/)", ""); + } + + @Override + public boolean deleteById(Long id) { + return v2FoodDao.deleteById(id) > 0; + } + + @Override + public boolean changeStatus(Long id, String status) { + V2Food food = new V2Food(); + food.setFoodId(id); + food.setStatus(status); + food.setUpdatedAt(new Date()); + return v2FoodDao.updateById(food) > 0; + } + + @Override + public List getAllCategories() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select(V2Food::getCategory) + .isNotNull(V2Food::getCategory) + .ne(V2Food::getCategory, "") + .groupBy(V2Food::getCategory); + return v2FoodDao.selectList(wrapper).stream() + .map(V2Food::getCategory) + .filter(StrUtil::isNotBlank) + .distinct() + .sorted() + .collect(Collectors.toList()); + } +} diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolFoodAdminService.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolFoodAdminService.java new file mode 100644 index 0000000..a397246 --- /dev/null +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/tool/ToolFoodAdminService.java @@ -0,0 +1,51 @@ +package com.zbkj.service.service.tool; + +import com.zbkj.common.model.tool.V2Food; +import com.zbkj.common.request.PageParamRequest; + +import java.util.List; + +/** + * 食物百科管理后台服务接口 + * +---------------------------------------------------------------------- + * | 提供后台 CRUD 操作,图片维护等功能 + * +---------------------------------------------------------------------- + */ +public interface ToolFoodAdminService { + + /** + * 后台分页列表(支持关键词、分类、状态筛选) + */ + List getAdminList(String keyword, String category, String status, + PageParamRequest pageParamRequest); + + /** + * 根据ID获取食物���情 + */ + V2Food getById(Long id); + + /** + * 新增食物 + */ + boolean create(V2Food food); + + /** + * 修改食物 + */ + boolean updateFood(V2Food food); + + /** + * 删除食物 + */ + boolean deleteById(Long id); + + /** + * 修改状态(上架/下架) + */ + boolean changeStatus(Long id, String status); + + /** + * 获取所有分类列表 + */ + List getAllCategories(); +} diff --git a/msh_single_admin/src/api/foodEncyclopedia.js b/msh_single_admin/src/api/foodEncyclopedia.js new file mode 100644 index 0000000..3558527 --- /dev/null +++ b/msh_single_admin/src/api/foodEncyclopedia.js @@ -0,0 +1,93 @@ +// +---------------------------------------------------------------------- +// | 食物百科管理 API +// +---------------------------------------------------------------------- + +import request from '@/utils/request'; + +/** + * 食物列表 + * @param {Object} params - { keyword, category, status, page, limit } + */ +export function ListFood(params) { + return request({ + url: '/admin/tool/food/list', + method: 'GET', + params: { + keyword: params.keyword, + category: params.category, + status: params.status, + page: params.page, + limit: params.limit, + }, + }); +} + +/** + * 食物详情 + * @param {Number} id - 食物ID + */ +export function InfoFood(id) { + return request({ + url: `/admin/tool/food/info/${id}`, + method: 'GET', + }); +} + +/** + * 新增食物 + * @param {Object} data - 食物数据 + */ +export function AddFood(data) { + return request({ + url: '/admin/tool/food/save', + method: 'POST', + data, + }); +} + +/** + * 修改食物 + * @param {Number} id - 食物ID + * @param {Object} data - 食物数据 + */ +export function UpdateFood(id, data) { + return request({ + url: `/admin/tool/food/update/${id}`, + method: 'POST', + data, + }); +} + +/** + * 删除食物 + * @param {Number} id - 食物ID + */ +export function DeleteFood(id) { + return request({ + url: `/admin/tool/food/delete/${id}`, + method: 'GET', + }); +} + +/** + * 修改上下架状态 + * @param {Number} id - 食物ID + * @param {String} status - active/inactive + */ +export function ChangeStatusFood(id, status) { + return request({ + url: `/admin/tool/food/changeStatus/${id}`, + method: 'POST', + params: { status }, + }); +} + +/** + * 获取食物分类列表 + */ +export function GetFoodCategories() { + return request({ + url: '/admin/tool/food/categories', + method: 'GET', + }); +} diff --git a/msh_single_admin/src/router/modules/content.js b/msh_single_admin/src/router/modules/content.js index f0be6b0..d43eb6f 100755 --- a/msh_single_admin/src/router/modules/content.js +++ b/msh_single_admin/src/router/modules/content.js @@ -48,6 +48,25 @@ const contentRouter = { icon: 'clipboard', }, }, + { + path: 'foodManager', + name: 'foodManager', + component: () => import('@/views/content/food/list'), + meta: { + title: '食物百科', + icon: 'clipboard', + }, + }, + { + path: 'foodEdit/:id?', + name: 'foodEdit', + component: () => import('@/views/content/food/edit'), + meta: { + title: '编辑食物', + noCache: true, + activeMenu: '/content/foodManager', + }, + }, ], }; diff --git a/msh_single_admin/src/views/content/food/edit.vue b/msh_single_admin/src/views/content/food/edit.vue new file mode 100644 index 0000000..dbb9f19 --- /dev/null +++ b/msh_single_admin/src/views/content/food/edit.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/msh_single_admin/src/views/content/food/list.vue b/msh_single_admin/src/views/content/food/list.vue new file mode 100644 index 0000000..c263ef5 --- /dev/null +++ b/msh_single_admin/src/views/content/food/list.vue @@ -0,0 +1,237 @@ + + + + +