初版提交
This commit is contained in:
parent
1f379b9c21
commit
fd043bb9f2
@ -137,6 +137,12 @@
|
||||
<version>6.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- MinIO 客户端 -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>8.5.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
47
na-core/src/main/java/com/nailart/config/MinioConfig.java
Normal file
47
na-core/src/main/java/com/nailart/config/MinioConfig.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.nailart.config;
|
||||
|
||||
|
||||
import io.minio.MinioClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* @Description MinIO配置类,用于创建MinIO客户端实例
|
||||
* @Classname MinioConfig
|
||||
* @Date 2025/7/16 10:13
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Configuration
|
||||
public class MinioConfig {
|
||||
|
||||
@Value("${minio.endpoint}")
|
||||
private String endpoint;
|
||||
|
||||
@Value("${minio.accessKey}")
|
||||
private String accessKey;
|
||||
|
||||
@Value("${minio.secretKey}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${minio.region:}")
|
||||
private String region;
|
||||
|
||||
@Value("${minio.connectTimeout:30}")
|
||||
private long connectTimeout;
|
||||
|
||||
@Value("${minio.writeTimeout:60}")
|
||||
private long writeTimeout;
|
||||
|
||||
@Value("${minio.readTimeout:60}")
|
||||
private long readTimeout;
|
||||
|
||||
@Bean
|
||||
public MinioClient minioClient() {
|
||||
return MinioClient.builder()
|
||||
.endpoint(endpoint)
|
||||
.credentials(accessKey, secretKey)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
package com.nailart.controller;
|
||||
|
||||
|
||||
import com.nailart.service.FileStorageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description 文件服务控制类
|
||||
* @Classname FileStorageController
|
||||
* @Date 2025/7/19 15:47
|
||||
* @Created by 21616
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class FileStorageController {
|
||||
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
@Autowired
|
||||
public FileStorageController(FileStorageService fileStorageService) {
|
||||
this.fileStorageService = fileStorageService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @param file 文件内容
|
||||
* @return 上传成功的文件路径
|
||||
*/
|
||||
@PostMapping("/upload")
|
||||
public ResponseEntity<String> uploadFile(
|
||||
@RequestParam("bucketName") String bucketName,
|
||||
@RequestParam("filePath") String filePath,
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
|
||||
String uploadedFilePath = fileStorageService.uploadFile(bucketName, filePath, file);
|
||||
return ResponseEntity.ok(uploadedFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @return 文件内容流
|
||||
*/
|
||||
@GetMapping("/download")
|
||||
public ResponseEntity<InputStream> downloadFile(
|
||||
@RequestParam("bucketName") String bucketName,
|
||||
@RequestParam("filePath") String filePath) {
|
||||
|
||||
try {
|
||||
InputStream fileStream = fileStorageService.downloadFile(bucketName, filePath);
|
||||
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
// 处理中文文件名编码
|
||||
String encodedFileName = URLEncoder.encode(filePath.substring(filePath.lastIndexOf("/") + 1),
|
||||
StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
headers.setContentDispositionFormData("attachment", encodedFileName);
|
||||
|
||||
return new ResponseEntity<>(fileStream, headers, HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/delete")
|
||||
public ResponseEntity<Void> deleteFile(
|
||||
@RequestParam("bucketName") String bucketName,
|
||||
@RequestParam("filePath") String filePath) {
|
||||
|
||||
fileStorageService.deleteFile(bucketName, filePath);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @return 文件是否存在
|
||||
*/
|
||||
@GetMapping("/exists")
|
||||
public ResponseEntity<Boolean> fileExists(
|
||||
@RequestParam("bucketName") String bucketName,
|
||||
@RequestParam("filePath") String filePath) {
|
||||
|
||||
boolean exists = fileStorageService.fileExists(bucketName, filePath);
|
||||
return ResponseEntity.ok(exists);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的预签名URL
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @param expirySeconds 过期时间(秒)
|
||||
* @return 预签名URL
|
||||
*/
|
||||
@GetMapping("/url")
|
||||
public ResponseEntity<String> getFileUrl(
|
||||
@RequestParam("bucketName") String bucketName,
|
||||
@RequestParam("filePath") String filePath,
|
||||
@RequestParam(value = "expirySeconds", defaultValue = "3600") int expirySeconds) {
|
||||
|
||||
String url = fileStorageService.getFileUrl(bucketName, filePath, expirySeconds);
|
||||
return ResponseEntity.ok(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有存储桶列表
|
||||
* @return 存储桶名称列表
|
||||
*/
|
||||
@GetMapping("/buckets")
|
||||
public ResponseEntity<List<String>> listBuckets() {
|
||||
List<String> buckets = fileStorageService.listBuckets();
|
||||
return ResponseEntity.ok(buckets);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建存储桶
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/buckets")
|
||||
public ResponseEntity<Void> createBucket(@RequestParam("bucketName") String bucketName) {
|
||||
fileStorageService.createBucket(bucketName);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除存储桶
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/buckets")
|
||||
public ResponseEntity<Void> deleteBucket(@RequestParam("bucketName") String bucketName) {
|
||||
fileStorageService.deleteBucket(bucketName);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查存储桶是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 存储桶是否存在
|
||||
*/
|
||||
@GetMapping("/buckets/exists")
|
||||
public ResponseEntity<Boolean> bucketExists(@RequestParam("bucketName") String bucketName) {
|
||||
boolean exists = fileStorageService.bucketExists(bucketName);
|
||||
return ResponseEntity.ok(exists);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.nailart.controller;
|
||||
|
||||
import com.nailart.dataobj.MainInfoDO;
|
||||
import com.nailart.enums.RespCodeEnum;
|
||||
import com.nailart.service.MainInfoService;
|
||||
import com.nailart.service.impl.FileStorageServiceImpl;
|
||||
import com.nailart.vo.RespVO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @Description 主页信息控制类
|
||||
* @Classname MainInfoController
|
||||
* @Date 2025/7/27 23:16
|
||||
* @Created by 21616
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/mainInfo")
|
||||
public class MainInfoController {
|
||||
private final MainInfoService mainInfoService;
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainInfoController.class);
|
||||
|
||||
|
||||
public MainInfoController(MainInfoService mainInfoService) {
|
||||
|
||||
this.mainInfoService = mainInfoService;
|
||||
}
|
||||
|
||||
@RequestMapping("/getMainInfo")
|
||||
public RespVO getMainInfo() {
|
||||
try {
|
||||
MainInfoDO mainInfo = mainInfoService.getMainInfo();
|
||||
return RespVO.success(mainInfo);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取主页信息失败", e);
|
||||
return RespVO.error(RespCodeEnum.DATABASE_ERROR, RespCodeEnum.DATABASE_ERROR.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/updateMainInfo")
|
||||
public RespVO updateMainInfo(@RequestBody MainInfoDO mainInfoDO) {
|
||||
try {
|
||||
mainInfoService.updateMainInfo(mainInfoDO);
|
||||
return RespVO.success();
|
||||
} catch (Exception e) {
|
||||
logger.error("更新主信息失败", e);
|
||||
return RespVO.error(RespCodeEnum.DATABASE_ERROR, RespCodeEnum.DATABASE_ERROR.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -26,8 +26,15 @@ public class PortfolioController {
|
||||
this.portfolioService = portfolioService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取作品集列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页数量
|
||||
* @return RespVO
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public RespVO getPortfolioList(@RequestParam("page") int page, @RequestParam("size") int size) {
|
||||
public RespVO getPortfolioList(@RequestParam("page") Integer page, @RequestParam("size") Integer size) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (page <= 0 || size <= 0) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
@ -47,8 +54,14 @@ public class PortfolioController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加作品集
|
||||
*
|
||||
* @param portfolioDO 作品集信息
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public RespVO addPortfolio(PortfolioDO portfolioDO) {
|
||||
public RespVO addPortfolio(@RequestBody PortfolioDO portfolioDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (portfolioDO == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
@ -69,8 +82,14 @@ public class PortfolioController {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新作品集
|
||||
*
|
||||
* @param portfolioDO 作品集信息
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
public RespVO updatePortfolio(PortfolioDO portfolioDO) {
|
||||
public RespVO updatePortfolio(@RequestBody PortfolioDO portfolioDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (portfolioDO == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
@ -89,6 +108,12 @@ public class PortfolioController {
|
||||
return respVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除作品集
|
||||
*
|
||||
* @param id 作品集id
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/delete")
|
||||
public RespVO deletePortfolio(@RequestParam("id") Integer id) {
|
||||
RespVO respVO = new RespVO();
|
||||
@ -109,8 +134,15 @@ public class PortfolioController {
|
||||
return respVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加作品集
|
||||
*
|
||||
* @param id 作品集id
|
||||
* @param changes 点赞数
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/addLoves")
|
||||
public RespVO addLoves(@RequestParam("id") Integer id,@RequestParam Integer changes) {
|
||||
public RespVO addLoves(@RequestParam("id") Integer id, @RequestParam Integer changes) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (id == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
@ -125,7 +157,31 @@ public class PortfolioController {
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@GetMapping("/getPortfolioByTag")
|
||||
public RespVO getPortfolioByTag(@RequestParam("tagID") Long tagID,
|
||||
@RequestParam("page") Integer page,
|
||||
@RequestParam("size") Integer size) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (tagID == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
IPage<PortfolioDO> portfolioByTag = portfolioService.getPortfolioByTag(tagID, page, size);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(portfolioByTag);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,156 @@
|
||||
package com.nailart.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.nailart.dataobj.SampleCollectionDO;
|
||||
import com.nailart.enums.RespCodeEnum;
|
||||
import com.nailart.service.SampleCollectionService;
|
||||
import com.nailart.vo.RespVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @Classname SampleCollectionController
|
||||
* @Description 样品集控制类
|
||||
* @Date 2025/7/10 18:48
|
||||
* @Created by 21616
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sampleCollection")
|
||||
public class SampleCollectionController {
|
||||
private final SampleCollectionService SampleCollectionService;
|
||||
|
||||
public SampleCollectionController(SampleCollectionService SampleCollectionService) {
|
||||
this.SampleCollectionService = SampleCollectionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取样品集列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页数量
|
||||
* @return RespVO
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public RespVO getSampleCollectionList(@RequestParam("page") Integer page, @RequestParam("size") Integer size) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (page <= 0 || size <= 0) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
IPage<SampleCollectionDO> SampleCollectionDOIPage = SampleCollectionService.listByPage(page, size);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(SampleCollectionDOIPage);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加样品集
|
||||
*
|
||||
* @param SampleCollectionDO 样品集信息
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public RespVO addSampleCollection(@RequestBody SampleCollectionDO SampleCollectionDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (SampleCollectionDO == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
Integer i = SampleCollectionService.addSampleCollection(SampleCollectionDO);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(i);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新样品集
|
||||
*
|
||||
* @param SampleCollectionDO 样品集信息
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
public RespVO updateSampleCollection(@RequestBody SampleCollectionDO SampleCollectionDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (SampleCollectionDO == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
Integer i = SampleCollectionService.updateSampleCollection(SampleCollectionDO);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(i);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除样品集
|
||||
*
|
||||
* @param id 样品集id
|
||||
* @return RespVO
|
||||
*/
|
||||
@PostMapping("/delete")
|
||||
public RespVO deleteSampleCollection(@RequestParam("id") Integer id) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (id == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
Integer i = SampleCollectionService.deleteSampleCollection(id);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(i);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@GetMapping("/getSampleCollectionByTag")
|
||||
public RespVO getSampleCollectionByTag(@RequestParam("tagID") Long tagID,
|
||||
@RequestParam("page") Integer page,
|
||||
@RequestParam("size") Integer size) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (tagID == null) {
|
||||
respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode());
|
||||
respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
try {
|
||||
IPage<SampleCollectionDO> SampleCollectionByTag = SampleCollectionService.getSampleCollectionByTag(tagID, page, size);
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
respVO.setData(SampleCollectionByTag);
|
||||
} catch (Exception e) {
|
||||
respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode());
|
||||
respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package com.nailart.controller;
|
||||
|
||||
import com.nailart.dataobj.TagDO;
|
||||
import com.nailart.service.TagInfoService;
|
||||
import com.nailart.vo.RespVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description 标签信息控制类
|
||||
* @Classname TagInfoController
|
||||
* @Date 2025/7/14 23:44
|
||||
* @Created by 21616
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/tagInfo")
|
||||
public class TagInfoController {
|
||||
private final TagInfoService tagInfoService;
|
||||
|
||||
TagInfoController(TagInfoService tagInfoService) {
|
||||
this.tagInfoService = tagInfoService;
|
||||
}
|
||||
|
||||
@PostMapping("/save")
|
||||
public RespVO saveTagInfo(@RequestBody TagDO tagDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (tagDO == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("标签信息不能为空");
|
||||
return respVO;
|
||||
}
|
||||
if (tagInfoService.saveTagInfo(tagDO) == 0) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("保存标签信息失败");
|
||||
return respVO;
|
||||
}
|
||||
respVO.setCode(200);
|
||||
respVO.setData(tagInfoService.saveTagInfo(tagDO));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@GetMapping("/getById")
|
||||
public RespVO getTagInfoById(@RequestParam Long id) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (id == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("标签id不能为空");
|
||||
return respVO;
|
||||
}
|
||||
TagDO tagInfoById = tagInfoService.getTagInfoById(id);
|
||||
if (tagInfoById == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("查询标签信息失败");
|
||||
return respVO;
|
||||
}
|
||||
respVO.setCode(200);
|
||||
respVO.setData(tagInfoById);
|
||||
return respVO;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/deleteById")
|
||||
public RespVO deleteTagInfoById(@RequestBody Long id) {
|
||||
|
||||
RespVO respVO = new RespVO();
|
||||
if (id == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("标签id不能为空");
|
||||
return respVO;
|
||||
}
|
||||
TagDO tagInfoById = tagInfoService.getTagInfoById(id);
|
||||
if (tagInfoById == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("查询标签信息失败");
|
||||
return respVO;
|
||||
}
|
||||
|
||||
respVO.setCode(200);
|
||||
respVO.setData(tagInfoById);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/updateById")
|
||||
public RespVO updateTagInfoById(@RequestBody TagDO tagDO) {
|
||||
RespVO respVO = new RespVO();
|
||||
if (tagDO == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("标签信息不能为空");
|
||||
return respVO;
|
||||
}
|
||||
TagDO tagInfoById = tagInfoService.getTagInfoById(tagDO.getId());
|
||||
if (tagInfoById == null) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("查询标签信息失败");
|
||||
return respVO;
|
||||
}
|
||||
respVO.setCode(200);
|
||||
respVO.setData(tagInfoById);
|
||||
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@GetMapping("/getAll")
|
||||
public RespVO getAllTagInfo() {
|
||||
RespVO respVO = new RespVO();
|
||||
List<TagDO> allTagInfo = tagInfoService.getAllTagInfo();
|
||||
if (allTagInfo == null || allTagInfo.isEmpty()) {
|
||||
respVO.setCode(500);
|
||||
respVO.setMsg("获取标签信息失败");
|
||||
return respVO;
|
||||
|
||||
}
|
||||
respVO.setCode(200);
|
||||
respVO.setData(allTagInfo);
|
||||
return respVO;
|
||||
}
|
||||
}
|
72
na-core/src/main/java/com/nailart/dataobj/MainInfoDO.java
Normal file
72
na-core/src/main/java/com/nailart/dataobj/MainInfoDO.java
Normal file
@ -0,0 +1,72 @@
|
||||
package com.nailart.dataobj;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Description 主页信息表
|
||||
* @Classname MainInfoDO
|
||||
* @Date 2025/7/23 23:38
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Data
|
||||
@TableName("main_info")
|
||||
public class MainInfoDO {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 主页图
|
||||
*/
|
||||
private String headImg;
|
||||
/**
|
||||
* 店名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 介绍
|
||||
*/
|
||||
private String introduce;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 工作时间
|
||||
*/
|
||||
private String worktime;
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
/**
|
||||
* 广告图,&&分开
|
||||
*/
|
||||
private String publicityImg;
|
||||
/**
|
||||
* 新品图列表,&&分开
|
||||
*/
|
||||
private String newPortfolioImg;
|
||||
/**
|
||||
* 弹窗图
|
||||
*/
|
||||
private String popcard;
|
||||
/**
|
||||
* 指引图
|
||||
*/
|
||||
private String locationImg;
|
||||
/**
|
||||
* 环境图
|
||||
*/
|
||||
private String environmentImg;
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
private String wechat;
|
||||
/**
|
||||
* 电话
|
||||
*/
|
||||
private String phone;
|
||||
}
|
@ -32,13 +32,10 @@ public class PortfolioDO {
|
||||
*/
|
||||
private String imgFilename;
|
||||
/**
|
||||
* 标签
|
||||
* 标签ID
|
||||
*/
|
||||
private String tag;
|
||||
/**
|
||||
* 标签样式
|
||||
*/
|
||||
private String tagStyle;
|
||||
private String tagId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
|
@ -0,0 +1,46 @@
|
||||
package com.nailart.dataobj;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("sample_collection_info")
|
||||
public class SampleCollectionDO {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 图片地址
|
||||
*/
|
||||
private String imgUrl;
|
||||
/**
|
||||
* 图片文件名
|
||||
*/
|
||||
private String imgFilename;
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
private String tagId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private String createTime;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 价格
|
||||
*/
|
||||
private Integer amt;
|
||||
}
|
19
na-core/src/main/java/com/nailart/dataobj/TagDO.java
Normal file
19
na-core/src/main/java/com/nailart/dataobj/TagDO.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.nailart.dataobj;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Description 标签信息实体类
|
||||
* @Classname TagDO
|
||||
* @Date 2025/7/14 23:18
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Data
|
||||
@TableName("tag_info")
|
||||
public class TagDO {
|
||||
private Long id;
|
||||
private String tagName;
|
||||
private String tagColor;
|
||||
private String tagBgColor;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.nailart.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nailart.dataobj.MainInfoDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MainInfoMapper extends BaseMapper<MainInfoDO> {
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.nailart.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nailart.dataobj.PortfolioDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.nailart.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nailart.dataobj.SampleCollectionDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SampleCollectionMapper extends BaseMapper<SampleCollectionDO> {
|
||||
}
|
17
na-core/src/main/java/com/nailart/mapper/TagInfoMapper.java
Normal file
17
na-core/src/main/java/com/nailart/mapper/TagInfoMapper.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.nailart.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nailart.dataobj.TagDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* @Description 标签信息表
|
||||
* @Classname TagInfoMapper
|
||||
* @Date 2025/7/14 23:22
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Mapper
|
||||
public interface TagInfoMapper extends BaseMapper<TagDO> {
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.nailart.service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description 文件存储服务接口,定义文件管理操作
|
||||
* @Classname FileStorageService
|
||||
* @Date 2025/7/19 17:19
|
||||
* @Created by 21616
|
||||
*/
|
||||
public interface FileStorageService {
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @param file 文件内容
|
||||
* @return 文件存储路径
|
||||
*/
|
||||
String uploadFile(String bucketName, String filePath, MultipartFile file);
|
||||
|
||||
/**
|
||||
* 上传文件(从输入流)
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @param inputStream 文件输入流
|
||||
* @param contentType 文件内容类型
|
||||
* @return 文件存储路径
|
||||
*/
|
||||
String uploadFile(String bucketName, String filePath, InputStream inputStream, String contentType);
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @return 文件输入流
|
||||
*/
|
||||
InputStream downloadFile(String bucketName, String filePath);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
*/
|
||||
void deleteFile(String bucketName, String filePath);
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @return 文件是否存在
|
||||
*/
|
||||
boolean fileExists(String bucketName, String filePath);
|
||||
|
||||
/**
|
||||
* 获取文件的预签名URL
|
||||
* @param bucketName 存储桶名称
|
||||
* @param filePath 文件路径
|
||||
* @param expirySeconds 过期时间(秒)
|
||||
* @return 预签名URL
|
||||
*/
|
||||
String getFileUrl(String bucketName, String filePath, int expirySeconds);
|
||||
|
||||
/**
|
||||
* 获取存储桶列表
|
||||
* @return 存储桶名称列表
|
||||
*/
|
||||
List<String> listBuckets();
|
||||
|
||||
/**
|
||||
* 检查存储桶是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 存储桶是否存在
|
||||
*/
|
||||
boolean bucketExists(String bucketName);
|
||||
|
||||
/**
|
||||
* 创建存储桶
|
||||
* @param bucketName 存储桶名称
|
||||
*/
|
||||
void createBucket(String bucketName);
|
||||
|
||||
/**
|
||||
* 删除存储桶(必须为空)
|
||||
* @param bucketName 存储桶名称
|
||||
*/
|
||||
void deleteBucket(String bucketName);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.nailart.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nailart.dataobj.MainInfoDO;
|
||||
|
||||
|
||||
public interface MainInfoService extends IService<MainInfoDO> {
|
||||
public MainInfoDO getMainInfo();
|
||||
public Integer updateMainInfo(MainInfoDO mainInfoDO);
|
||||
}
|
@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nailart.dataobj.PortfolioDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface PortfolioService extends IService<PortfolioDO> {
|
||||
IPage<PortfolioDO> listByPage(int page, int size);
|
||||
@ -14,4 +16,6 @@ public interface PortfolioService extends IService<PortfolioDO> {
|
||||
Integer deletePortfolio(Integer id);
|
||||
|
||||
Integer addLoves(Integer id, Integer changes);
|
||||
|
||||
IPage<PortfolioDO> getPortfolioByTag(Long tagID,int page, int size);
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.nailart.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nailart.dataobj.SampleCollectionDO;
|
||||
|
||||
|
||||
public interface SampleCollectionService extends IService<SampleCollectionDO> {
|
||||
IPage<SampleCollectionDO> listByPage(int page, int size);
|
||||
Integer addSampleCollection(SampleCollectionDO SampleCollectionDO);
|
||||
|
||||
Integer updateSampleCollection(SampleCollectionDO SampleCollectionDO);
|
||||
|
||||
Integer deleteSampleCollection(Integer id);
|
||||
|
||||
IPage<SampleCollectionDO> getSampleCollectionByTag(Long tagID,int page, int size);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.nailart.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nailart.dataobj.TagDO;
|
||||
import org.springframework.data.relational.core.sql.In;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description 标签信息服务类
|
||||
* @Classname TagInfoService
|
||||
* @Date 2025/7/14 23:24
|
||||
* @Created by 21616
|
||||
*/
|
||||
public interface TagInfoService extends IService<TagDO> {
|
||||
/**
|
||||
* 保存标签信息
|
||||
*
|
||||
* @param tagDO 标签信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
public Integer saveTagInfo(TagDO tagDO);
|
||||
|
||||
/**
|
||||
* 根据id查询标签信息
|
||||
*
|
||||
* @param id 标签id
|
||||
* @return 标签信息
|
||||
*/
|
||||
public TagDO getTagInfoById(Long id);
|
||||
|
||||
/**
|
||||
* 根据id删除标签信息
|
||||
*
|
||||
* @param id 标签id
|
||||
* @return 是否成功
|
||||
*/
|
||||
public Integer deleteTagInfoById(Long id);
|
||||
|
||||
/**
|
||||
* 根据id更新标签信息
|
||||
*
|
||||
* @param tagDO 标签信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
public Integer updateTagInfoById(TagDO tagDO);
|
||||
|
||||
/**
|
||||
* 查询所有标签信息
|
||||
*
|
||||
* @return 标签信息列表
|
||||
*/
|
||||
public List<TagDO> getAllTagInfo();
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package com.nailart.service.impl;
|
||||
import com.nailart.service.FileStorageService;
|
||||
import com.nailart.utils.MinioUtils;
|
||||
import io.minio.Result;
|
||||
import io.minio.messages.Bucket;
|
||||
import io.minio.messages.Item;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
/**
|
||||
* @Description MinIO 文件存储服务实现
|
||||
* @Classname FileStorageServiceImpl
|
||||
* @Date 2025/7/19 17:20
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Service
|
||||
public class FileStorageServiceImpl implements FileStorageService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileStorageServiceImpl.class);
|
||||
private final MinioUtils minioUtils;
|
||||
|
||||
@Autowired
|
||||
public FileStorageServiceImpl(MinioUtils minioUtils) {
|
||||
this.minioUtils = minioUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadFile(String bucketName, String filePath, MultipartFile file) {
|
||||
try {
|
||||
ensureBucketExists(bucketName);
|
||||
minioUtils.uploadFile(bucketName, filePath, file);
|
||||
logger.info("文件上传成功: bucket={}, path={}", bucketName, filePath);
|
||||
return filePath;
|
||||
} catch (Exception e) {
|
||||
logger.error("文件上传失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("文件上传失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadFile(String bucketName, String filePath, InputStream inputStream, String contentType) {
|
||||
try {
|
||||
ensureBucketExists(bucketName);
|
||||
minioUtils.uploadFile(bucketName, filePath, inputStream, contentType);
|
||||
logger.info("文件上传成功: bucket={}, path={}", bucketName, filePath);
|
||||
return filePath;
|
||||
} catch (Exception e) {
|
||||
logger.error("文件上传失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("文件上传失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream downloadFile(String bucketName, String filePath) {
|
||||
try {
|
||||
if (!fileExists(bucketName, filePath)) {
|
||||
logger.warn("文件不存在: bucket={}, path={}", bucketName, filePath);
|
||||
throw new RuntimeException("文件不存在");
|
||||
}
|
||||
return minioUtils.getObject(bucketName, filePath);
|
||||
} catch (Exception e) {
|
||||
logger.error("文件下载失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("文件下载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(String bucketName, String filePath) {
|
||||
try {
|
||||
if (!fileExists(bucketName, filePath)) {
|
||||
logger.warn("文件不存在,无需删除: bucket={}, path={}", bucketName, filePath);
|
||||
return;
|
||||
}
|
||||
minioUtils.removeObject(bucketName, filePath);
|
||||
logger.info("文件删除成功: bucket={}, path={}", bucketName, filePath);
|
||||
} catch (Exception e) {
|
||||
logger.error("文件删除失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("文件删除失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileExists(String bucketName, String filePath) {
|
||||
try {
|
||||
if (!bucketExists(bucketName)) {
|
||||
return false;
|
||||
}
|
||||
// 通过列出对象检查文件是否存在
|
||||
Iterable<Result<Item>> results = minioUtils.listObjects(bucketName);
|
||||
return StreamSupport.stream(results.spliterator(), false)
|
||||
.anyMatch(itemResult -> {
|
||||
try {
|
||||
return itemResult.get().objectName().equals(filePath);
|
||||
} catch (Exception e) {
|
||||
logger.error("检查文件存在时出错", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("检查文件存在失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("检查文件存在失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileUrl(String bucketName, String filePath, int expirySeconds) {
|
||||
try {
|
||||
if (!fileExists(bucketName, filePath)) {
|
||||
logger.warn("文件不存在,无法生成URL: bucket={}, path={}", bucketName, filePath);
|
||||
throw new RuntimeException("文件不存在");
|
||||
}
|
||||
return minioUtils.getPresignedUrl(bucketName, filePath, expirySeconds);
|
||||
} catch (Exception e) {
|
||||
logger.error("生成文件URL失败: bucket={}, path={}", bucketName, filePath, e);
|
||||
throw new RuntimeException("生成文件URL失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> listBuckets() {
|
||||
try {
|
||||
List<Bucket> buckets = minioUtils.listBuckets();
|
||||
return buckets.stream().map(Bucket::name).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
logger.error("获取存储桶列表失败", e);
|
||||
throw new RuntimeException("获取存储桶列表失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bucketExists(String bucketName) {
|
||||
try {
|
||||
return minioUtils.bucketExists(bucketName);
|
||||
} catch (Exception e) {
|
||||
logger.error("检查存储桶存在失败: bucket={}", bucketName, e);
|
||||
throw new RuntimeException("检查存储桶存在失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBucket(String bucketName) {
|
||||
try {
|
||||
if (!bucketExists(bucketName)) {
|
||||
minioUtils.makeBucket(bucketName);
|
||||
logger.info("创建存储桶成功: bucket={}", bucketName);
|
||||
} else {
|
||||
logger.info("存储桶已存在: bucket={}", bucketName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建存储桶失败: bucket={}", bucketName, e);
|
||||
throw new RuntimeException("创建存储桶失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBucket(String bucketName) {
|
||||
try {
|
||||
if (bucketExists(bucketName)) {
|
||||
// 检查存储桶是否为空
|
||||
Iterable<Result<Item>> results = minioUtils.listObjects(bucketName);
|
||||
boolean isEmpty = !results.iterator().hasNext();
|
||||
|
||||
if (isEmpty) {
|
||||
minioUtils.removeBucket(bucketName);
|
||||
logger.info("删除存储桶成功: bucket={}", bucketName);
|
||||
} else {
|
||||
logger.warn("无法删除非空存储桶: bucket={}", bucketName);
|
||||
throw new RuntimeException("无法删除非空存储桶");
|
||||
}
|
||||
} else {
|
||||
logger.info("存储桶不存在,无需删除: bucket={}", bucketName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("删除存储桶失败: bucket={}", bucketName, e);
|
||||
throw new RuntimeException("删除存储桶失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保存储桶存在,如果不存在则创建
|
||||
*/
|
||||
private void ensureBucketExists(String bucketName) {
|
||||
try {
|
||||
if (!minioUtils.bucketExists(bucketName)) {
|
||||
minioUtils.makeBucket(bucketName);
|
||||
logger.info("自动创建存储桶: {}", bucketName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("确保存储桶存在失败: bucket={}", bucketName, e);
|
||||
throw new RuntimeException("初始化存储桶失败", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.nailart.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nailart.dataobj.MainInfoDO;
|
||||
import com.nailart.dataobj.PortfolioDO;
|
||||
import com.nailart.enums.RespCodeEnum;
|
||||
import com.nailart.mapper.MainInfoMapper;
|
||||
import com.nailart.mapper.PortfolioMapper;
|
||||
import com.nailart.service.MainInfoService;
|
||||
import com.nailart.service.PortfolioService;
|
||||
import com.nailart.utils.DateUtils;
|
||||
import com.nailart.vo.RespVO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @Author: xiaowang
|
||||
* @Date: 2021/5/10 14:48
|
||||
*/
|
||||
@Service
|
||||
public class MainInfoServiceImpl extends ServiceImpl<MainInfoMapper, MainInfoDO> implements MainInfoService {
|
||||
|
||||
private final MainInfoMapper mainInfoMapper;
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainInfoServiceImpl.class);
|
||||
|
||||
public MainInfoServiceImpl(MainInfoMapper mainInfoMapper) {
|
||||
|
||||
this.mainInfoMapper = mainInfoMapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MainInfoDO getMainInfo() {
|
||||
return mainInfoMapper.selectById(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer updateMainInfo(MainInfoDO mainInfoDO) {
|
||||
return mainInfoMapper.updateById(mainInfoDO);
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
package com.nailart.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nailart.dataobj.PortfolioDO;
|
||||
import com.nailart.dataobj.TagDO;
|
||||
import com.nailart.mapper.PortfolioMapper;
|
||||
import com.nailart.service.PortfolioService;
|
||||
import com.nailart.service.TagInfoService;
|
||||
import com.nailart.utils.DateUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@ -18,7 +22,8 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
|
||||
private final PortfolioMapper portfolioMapper;
|
||||
|
||||
public PortfolioServiceImpl(PortfolioMapper portfolioMapper) {
|
||||
public PortfolioServiceImpl(PortfolioMapper portfolioMapper ) {
|
||||
|
||||
this.portfolioMapper = portfolioMapper;
|
||||
}
|
||||
|
||||
@ -36,6 +41,9 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
|
||||
@Override
|
||||
public Integer addPortfolio(PortfolioDO portfolioDO) {
|
||||
if (portfolioDO.getCreateTime()==null){
|
||||
portfolioDO.setCreateTime(DateUtils.getCurrentDateTime());
|
||||
}
|
||||
return portfolioMapper.insert(portfolioDO);
|
||||
}
|
||||
|
||||
@ -58,4 +66,12 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
portfolioDO.setLoves(portfolioDO.getLoves() + changes);
|
||||
return portfolioMapper.updateById(portfolioDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<PortfolioDO> getPortfolioByTag(Long tagID,int page, int size) {
|
||||
Page<PortfolioDO> portfolioDOPage = new Page<>(page, size);
|
||||
LambdaQueryWrapper<PortfolioDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(PortfolioDO::getTagId, tagID);
|
||||
return page(portfolioDOPage, wrapper);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
package com.nailart.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nailart.dataobj.SampleCollectionDO;
|
||||
import com.nailart.mapper.SampleCollectionMapper;
|
||||
import com.nailart.service.SampleCollectionService;
|
||||
import com.nailart.utils.DateUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
*
|
||||
* @Author: xiaowang
|
||||
* @Date: 2021/5/10 14:48
|
||||
*/
|
||||
@Service
|
||||
public class SampleCollectionServiceImpl extends ServiceImpl<SampleCollectionMapper, SampleCollectionDO> implements SampleCollectionService {
|
||||
|
||||
private final SampleCollectionMapper SampleCollectionMapper;
|
||||
|
||||
public SampleCollectionServiceImpl(SampleCollectionMapper SampleCollectionMapper ) {
|
||||
|
||||
this.SampleCollectionMapper = SampleCollectionMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param page
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<SampleCollectionDO> listByPage(int page, int size) {
|
||||
Page<SampleCollectionDO> objectPage = new Page<>(page, size);
|
||||
return SampleCollectionMapper.selectPage(objectPage, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer addSampleCollection(SampleCollectionDO SampleCollectionDO) {
|
||||
if (SampleCollectionDO.getCreateTime()==null){
|
||||
SampleCollectionDO.setCreateTime(DateUtils.getCurrentDateTime());
|
||||
}
|
||||
return SampleCollectionMapper.insert(SampleCollectionDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer updateSampleCollection(SampleCollectionDO SampleCollectionDO) {
|
||||
return SampleCollectionMapper.updateById(SampleCollectionDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer deleteSampleCollection(Integer id) {
|
||||
return SampleCollectionMapper.deleteById(id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IPage<SampleCollectionDO> getSampleCollectionByTag(Long tagID,int page, int size) {
|
||||
Page<SampleCollectionDO> SampleCollectionDOPage = new Page<>(page, size);
|
||||
LambdaQueryWrapper<SampleCollectionDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.like(SampleCollectionDO::getTagId, tagID);
|
||||
return page(SampleCollectionDOPage, wrapper);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.nailart.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nailart.dataobj.TagDO;
|
||||
import com.nailart.mapper.TagInfoMapper;
|
||||
import com.nailart.service.TagInfoService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description 标签信息服务实现类
|
||||
* @Classname TagInfoServiceImpl
|
||||
* @Date 2025/7/14 23:25
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Service
|
||||
public class TagInfoServiceImpl extends ServiceImpl<TagInfoMapper, TagDO> implements TagInfoService {
|
||||
@Override
|
||||
public Integer saveTagInfo(TagDO tagDO) {
|
||||
if (tagDO == null) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
|
||||
return this.saveTagInfo(tagDO);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagDO getTagInfoById(Long id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return this.getTagInfoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer deleteTagInfoById(Long id) {
|
||||
if (id == null) {
|
||||
return 0;
|
||||
}
|
||||
return this.deleteTagInfoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer updateTagInfoById(TagDO tagDO) {
|
||||
if (tagDO == null) {
|
||||
return 0;
|
||||
}
|
||||
return this.updateTagInfoById(tagDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TagDO> getAllTagInfo() {
|
||||
return this.list();
|
||||
}
|
||||
}
|
303
na-core/src/main/java/com/nailart/utils/MinioUtils.java
Normal file
303
na-core/src/main/java/com/nailart/utils/MinioUtils.java
Normal file
@ -0,0 +1,303 @@
|
||||
package com.nailart.utils;
|
||||
import io.minio.*;
|
||||
import io.minio.errors.*;
|
||||
import io.minio.http.Method;
|
||||
import io.minio.messages.Bucket;
|
||||
import io.minio.messages.Item;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description MinIO工具类,提供文件存储操作的封装
|
||||
* @Classname MinioUtils
|
||||
* @Date 2025/7/16 10:15
|
||||
* @Created by 21616
|
||||
*/
|
||||
@Component
|
||||
public class MinioUtils {
|
||||
|
||||
private final MinioClient minioClient;
|
||||
|
||||
@Autowired
|
||||
public MinioUtils(MinioClient minioClient) {
|
||||
this.minioClient = minioClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查存储桶是否存在
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 存在返回true,否则返回false
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public boolean bucketExists(String bucketName) throws ServerException, InsufficientDataException,
|
||||
ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidResponseException, XmlParserException, InternalException {
|
||||
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建存储桶
|
||||
* @param bucketName 存储桶名称
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void makeBucket(String bucketName) throws ServerException, InsufficientDataException,
|
||||
ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidResponseException, XmlParserException, InternalException {
|
||||
if (!bucketExists(bucketName)) {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有存储桶
|
||||
* @return 存储桶列表
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public List<Bucket> listBuckets() throws ServerException, InsufficientDataException,
|
||||
ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidResponseException, XmlParserException, InternalException {
|
||||
return minioClient.listBuckets();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除存储桶(必须为空桶)
|
||||
* @param bucketName 存储桶名称
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void removeBucket(String bucketName) throws ServerException, InsufficientDataException,
|
||||
ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
|
||||
InvalidResponseException, XmlParserException, InternalException {
|
||||
if (bucketExists(bucketName)) {
|
||||
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传本地文件到MinIO
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称(文件路径)
|
||||
* @param filePath 本地文件路径
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void uploadFile(String bucketName, String objectName, String filePath) throws ServerException,
|
||||
InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
|
||||
minioClient.uploadObject(UploadObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.filename(filePath)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过InputStream上传文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param inputStream 文件输入流
|
||||
* @param contentType 文件内容类型
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void uploadFile(String bucketName, String objectName, InputStream inputStream, String contentType)
|
||||
throws ServerException, InsufficientDataException, ErrorResponseException, IOException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException,
|
||||
InternalException {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(inputStream, inputStream.available(), -1)
|
||||
.contentType(contentType)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Spring MultipartFile上传文件
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param file Spring MultipartFile文件对象
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void uploadFile(String bucketName, String objectName, MultipartFile file)
|
||||
throws ServerException, InsufficientDataException, ErrorResponseException, IOException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException,
|
||||
InternalException {
|
||||
minioClient.putObject(PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(file.getInputStream(), file.getSize(), -1)
|
||||
.contentType(file.getContentType())
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件到本地
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param filePath 本地文件路径
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void downloadFile(String bucketName, String objectName, String filePath) throws ServerException,
|
||||
InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
|
||||
minioClient.downloadObject(DownloadObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.filename(filePath)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件输入流
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @return 文件输入流
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public InputStream getObject(String bucketName, String objectName) throws ServerException,
|
||||
InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
|
||||
return minioClient.getObject(GetObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除对象
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public void removeObject(String bucketName, String objectName) throws ServerException,
|
||||
InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
|
||||
minioClient.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出存储桶中的对象
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 对象迭代器
|
||||
*/
|
||||
public Iterable<Result<Item>> listObjects(String bucketName) {
|
||||
return minioClient.listObjects(ListObjectsArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成预签名URL(用于临时访问私有对象)
|
||||
* @param bucketName 存储桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param expiry 过期时间(秒)
|
||||
* @return 预签名URL
|
||||
* @throws ServerException 服务端异常
|
||||
* @throws InsufficientDataException 数据不足异常
|
||||
* @throws ErrorResponseException 错误响应异常
|
||||
* @throws IOException IO异常
|
||||
* @throws NoSuchAlgorithmException 算法不存在异常
|
||||
* @throws InvalidKeyException 无效密钥异常
|
||||
* @throws InvalidResponseException 无效响应异常
|
||||
* @throws XmlParserException XML解析异常
|
||||
* @throws InternalException 内部异常
|
||||
*/
|
||||
public String getPresignedUrl(String bucketName, String objectName, int expiry) throws ServerException,
|
||||
InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
|
||||
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.expiry(expiry)
|
||||
.build());
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.nailart.vo;
|
||||
|
||||
import com.nailart.enums.RespCodeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -13,4 +14,28 @@ public class RespVO {
|
||||
private Integer code;
|
||||
private String msg;
|
||||
private Object data;
|
||||
|
||||
public static RespVO success() {
|
||||
RespVO respVO = new RespVO();
|
||||
respVO.setCode(RespCodeEnum.SUCCESS.getCode());
|
||||
respVO.setMsg(RespCodeEnum.SUCCESS.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
public static RespVO success(Object data) {
|
||||
RespVO respVO = success();
|
||||
respVO.setData(data);
|
||||
return respVO;
|
||||
}
|
||||
public static RespVO error(RespCodeEnum respCodeEnum) {
|
||||
RespVO respVO = new RespVO();
|
||||
respVO.setCode(respCodeEnum.getCode());
|
||||
respVO.setMsg(respCodeEnum.getMessage());
|
||||
return respVO;
|
||||
}
|
||||
public static RespVO error(RespCodeEnum respCodeEnum, Object data) {
|
||||
RespVO respVO = error(respCodeEnum);
|
||||
respVO.setData(data);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,15 @@ server:
|
||||
servlet:
|
||||
context-path: /api # 应用访问路径前缀
|
||||
|
||||
|
||||
# 数据源配置
|
||||
spring:
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 2GB
|
||||
max-request-size: 5GB
|
||||
file-size-threshold: 1024MB # 超过10MB的文件直接写入磁盘
|
||||
location: /data/temp # 指定临时存储目录(确保有足够空间)
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource # 使用Druid连接池
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL驱动类
|
||||
@ -73,4 +80,14 @@ logging:
|
||||
root: info # 根日志级别
|
||||
com.example.mapper: debug # Mapper接口日志级别(调试用)
|
||||
file:
|
||||
name: logs/application.log # 日志文件存储路径
|
||||
name: logs/application.log # 日志文件存储路径
|
||||
|
||||
minio:
|
||||
endpoint: http://xiaowangnas.com:9000
|
||||
accessKey: 3RDAaP9M8HT4RDWszNSr # 访问密钥ID
|
||||
secretKey: uM7jdTIF3Xk77rwbPq0vaQWXR16zgXHqYwSRiHez # 访问密钥Secret
|
||||
region: # 存储桶所在区域
|
||||
# 超时配置(秒)
|
||||
connectTimeout: 30
|
||||
writeTimeout: 60
|
||||
readTimeout: 60
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
|
||||
|
||||
<!-- 中间内容区:包裹路由视图,控制动画范围 -->
|
||||
<div class="content-container">
|
||||
@ -11,25 +10,22 @@
|
||||
<!-- </transition> -->
|
||||
</div>
|
||||
|
||||
<!-- 固定底部TabBar -->
|
||||
<TabBar class="tab-bar" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TabBar from './components/TabBar.vue';
|
||||
import NavBar from './components/NavBar.vue';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
TabBar,
|
||||
NavBar
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
<style lang="less">
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -39,7 +35,15 @@ export default {
|
||||
overflow: hidden;
|
||||
min-width: 200px;
|
||||
}
|
||||
html {
|
||||
font-size: 20px; /* 基准字体大小 */
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
html {
|
||||
font-size: 14px; /* 在小屏幕上缩小基准字体大小 */
|
||||
}
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -61,7 +65,7 @@ export default {
|
||||
/* 中间内容区:避开上下Bar的位置 */
|
||||
.content-container {
|
||||
position: relative;
|
||||
height: calc(100vh - 110px);
|
||||
// height: calc(100vh - 110px);
|
||||
overflow: hidden;
|
||||
/* 防止内容溢出 */
|
||||
}
|
||||
|
47
na-frontend/src/components/ManTabBar.vue
Normal file
47
na-frontend/src/components/ManTabBar.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="tab-bar">
|
||||
<van-tabbar v-model="active" route :placeholder="true">
|
||||
<van-tabbar-item replace to="/yumi">
|
||||
<span>主页管理</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item replace to="/portfolioMan" icon="search"><span>作品集管理</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template></van-tabbar-item>
|
||||
<van-tabbar-item replace to="/sampleCollectionman" icon="setting-o"><span>样品集管理</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template></van-tabbar-item>
|
||||
<van-tabbar-item replace to="/tagManPage" icon="setting-o"><span>标签管理</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template></van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ManTabBar',
|
||||
components: {},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
icon: {
|
||||
active: 'https://img01.yzcdn.cn/vant/user-active.png',
|
||||
inactive: 'https://img01.yzcdn.cn/vant/user-inactive.png',
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {},
|
||||
methods: {},
|
||||
created() { },
|
||||
mounted() { }
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div class="nav-bar">
|
||||
<van-nav-bar title="屿" left-text="" :placeholder="true" :safe-area-inset-top="true" z-index="50" :fixed="true">
|
||||
<template #right>
|
||||
<van-icon name="search" size="18" />
|
||||
</template>
|
||||
<van-nav-bar title="屿 ● Nail" left-text="" :placeholder="true" :safe-area-inset-top="true" z-index="50" :fixed="true">
|
||||
|
||||
</van-nav-bar>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -7,11 +7,11 @@
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template>
|
||||
</van-tabbar-item>
|
||||
<van-tabbar-item replace to="/portfolio" icon="search"><span>作品集</span>
|
||||
<van-tabbar-item replace to="/portfolio" icon="search"><span>作品集</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template></van-tabbar-item>
|
||||
<van-tabbar-item replace to="/sampleCollection" icon="setting-o"><span>样品集</span>
|
||||
<van-tabbar-item replace to="/sampleCollection" icon="setting-o"><span>样品集</span>
|
||||
<template #icon="props">
|
||||
<img :src="props.active ? icon.active : icon.inactive" />
|
||||
</template></van-tabbar-item>
|
||||
|
@ -3,6 +3,11 @@ import Vuex from "vuex";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import axios from './utils/request'
|
||||
import { FileServicePlugin } from './utils/file';
|
||||
// 注册插件
|
||||
Vue.use(FileServicePlugin , {
|
||||
baseUrl: 'http://wcy111.top:35001' // 可选,API基础URL
|
||||
});
|
||||
import { Tabbar, TabbarItem } from "vant";
|
||||
import { NavBar } from "vant";
|
||||
import { Toast } from "vant";
|
||||
@ -17,7 +22,36 @@ import { Swipe, SwipeItem } from 'vant';
|
||||
import { Loading } from 'vant';
|
||||
import { NoticeBar } from 'vant';
|
||||
import { Sticky } from 'vant';
|
||||
import { Tab, Tabs } from 'vant';
|
||||
import { List } from 'vant';
|
||||
import { Overlay } from 'vant';
|
||||
import { Form } from 'vant';
|
||||
import { Field } from 'vant';
|
||||
import { Uploader } from 'vant';
|
||||
import { Dialog } from 'vant';
|
||||
import { Picker } from 'vant';
|
||||
import { DropdownMenu, DropdownItem } from 'vant';
|
||||
import { Checkbox, CheckboxGroup } from 'vant';
|
||||
import { Cell, CellGroup } from 'vant';
|
||||
import { Divider } from 'vant';
|
||||
|
||||
Vue.use(Divider);
|
||||
Vue.use(Cell);
|
||||
Vue.use(CellGroup);
|
||||
Vue.use(Checkbox);
|
||||
Vue.use(CheckboxGroup);
|
||||
Vue.use(DropdownMenu);
|
||||
Vue.use(DropdownItem);
|
||||
Vue.use(Picker);
|
||||
// 全局注册
|
||||
Vue.use(Dialog);
|
||||
Vue.use(Uploader);
|
||||
Vue.use(Form);
|
||||
Vue.use(Field);
|
||||
Vue.use(Overlay);
|
||||
Vue.use(List);
|
||||
Vue.use(Tab);
|
||||
Vue.use(Tabs);
|
||||
Vue.use(Sticky);
|
||||
Vue.use(NoticeBar);
|
||||
Vue.use(Loading);
|
||||
|
22
na-frontend/src/pages/DetailPage.vue
Normal file
22
na-frontend/src/pages/DetailPage.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="detail-page">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {},
|
||||
methods: {},
|
||||
created() { },
|
||||
mounted() { }
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
@ -1,36 +1,69 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="homepage">
|
||||
<h1>主页</h1>
|
||||
<div class="homepage">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
<div class="images">
|
||||
<h1>轮播图区域</h1>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<h1>信息展示区,包含店名,营业时间,状态,地址,电话图标,微信图标</h1>
|
||||
</div>
|
||||
<div class="new">
|
||||
<h1>新品展示</h1>
|
||||
</div>
|
||||
<div class="navigation">
|
||||
<h1>暂定导航组件</h1>
|
||||
</div>
|
||||
<!-- 固定底部TabBar -->
|
||||
<TabBar class="tab-bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import TabBar from '@/components/TabBar.vue';
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
|
||||
TabBar,
|
||||
NavBar
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
|
||||
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {},
|
||||
methods: {},
|
||||
created() {},
|
||||
mounted() {}
|
||||
created() { },
|
||||
mounted() { }
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.homepage {
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
.images {
|
||||
width: 100%;
|
||||
height: 25vh;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
height: 20vh;
|
||||
}
|
||||
|
||||
.new {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
<van-tabs @click="onClickTab">
|
||||
<van-tab v-for="tab in tagInfo" :title="tab.tagName" :key="tab.id" :name="tab.id">
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
<!-- 使用原生 sticky 定位的公告栏 -->
|
||||
<div class="sticky-notice">
|
||||
<van-notice-bar left-icon="volume-o" :scrollable="true" text="所有标价均为款式价格,如需延长需补差价喔~~~(半贴:¥90,浅贴:¥130)" />
|
||||
@ -7,6 +13,10 @@
|
||||
|
||||
<!-- 内容容器 -->
|
||||
<div class="cards-container">
|
||||
<div class="card_loading" v-if="this.card_loading"
|
||||
style="width: 100%;display: flex;justify-content: center;">
|
||||
<van-loading size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<!-- 卡片列表 -->
|
||||
<div v-for="(card, index) in cards" :key="index" class="card"
|
||||
@ -16,21 +26,23 @@
|
||||
<div class="card-image-container">
|
||||
<van-swipe :autoplay="3000">
|
||||
<van-swipe-item v-for="(image, imgIndex) in card.images" :key="imgIndex">
|
||||
<van-image lazy-load fit="contain" :src="image">
|
||||
<van-image lazy-load fit="fill" :src="image" class="card-image">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20" />
|
||||
</template>
|
||||
</van-image>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<h2 class="card-title">{{ card.title }}</h2>
|
||||
<p class="card-description">{{ card.description }}</p>
|
||||
<div v-if="card.tag" class="card-tag">
|
||||
<van-tag round type="primary">{{ card.tag }}</van-tag>
|
||||
<div class="card-tag">
|
||||
<van-tag v-for="(tag, tagIndex) in card.tagInfo" :key="tagIndex" round type="primary"
|
||||
:text-color="tag.tagColor" :color="tag.tagBgColor" style="margin-left: 5px;">
|
||||
{{ tag.tagName }}
|
||||
</van-tag>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">
|
||||
@ -50,45 +62,77 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="loading" v-show="loading">
|
||||
<van-loading type="spinner" color="#1989fa" />
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="card_loading" v-if="this.card_loading1" style="width: 100%;display: flex;justify-content: center;">
|
||||
<van-loading size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
<!-- 回到顶部按钮 -->
|
||||
<div v-show="showBackToTop" class="back-to-top" @click="backToTop">
|
||||
<van-icon name="arrow-up" size="16" color="#fff" />
|
||||
</div>
|
||||
<!-- 固定底部TabBar -->
|
||||
<TabBar class="tab-bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TabBar from '@/components/TabBar.vue';
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
export default {
|
||||
name: 'PortfolioPage',
|
||||
components: {
|
||||
TabBar,
|
||||
NavBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cards: [],
|
||||
card_loading: false,
|
||||
card_loading1: false,
|
||||
activeCardIndex: -1,
|
||||
page: 1,
|
||||
pageSize: 2,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
loading: false,
|
||||
changes: [],
|
||||
totalPages: 0
|
||||
totalPages: 0,
|
||||
showBackToTop: false, // 控制回到顶部按钮显示/隐藏
|
||||
scrollContainer: null,
|
||||
scrollHandler: null,
|
||||
tagInfo: [{ "id": 0, "tagName": "全部" }],
|
||||
//标签转换成对象
|
||||
idToObjectMap: {},
|
||||
tagID: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async getCarts(pageNum, size) {
|
||||
async getCarts(tagID, pageNum, size) {
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
let res = await this.$axios.get('/portfolio/list', {
|
||||
params: {
|
||||
page: pageNum,
|
||||
size: size
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code !== 200 && res.data.code !== 200) {
|
||||
const errorMsg = res.msg || res.data.msg || '获取数据失败';
|
||||
let res = null;
|
||||
if (tagID == 0) {
|
||||
res = await this.$axios.get('/portfolio/list', {
|
||||
params: {
|
||||
page: pageNum,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res = await this.$axios.get('/portfolio/getPortfolioByTag', {
|
||||
params: {
|
||||
tagID: tagID,
|
||||
page: pageNum,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
}
|
||||
if (res.code !== 200) {
|
||||
const errorMsg = res.msg || '获取数据失败';
|
||||
this.$toast.fail(errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
const records = res.data?.data?.records || [];
|
||||
const records = res.data.records || [];
|
||||
if (records.length === 0) {
|
||||
this.$toast('没有更多数据了');
|
||||
return;
|
||||
@ -98,46 +142,93 @@ export default {
|
||||
const images = item.imgFilename
|
||||
? item.imgFilename.split('&&').map(filename => `${item.imgUrl}/${filename}`)
|
||||
: [];
|
||||
|
||||
const tagStyle = item.tagStyle
|
||||
? { backgroundColor: item.tagStyle.split('&&')[0], color: item.tagStyle.split('&&')[1] }
|
||||
: { backgroundColor: "#eeeeee", color: "#fff" };
|
||||
|
||||
let tagInfo = [];
|
||||
for (let tagID of item.tagId.split("&&")) {
|
||||
tagInfo.push(this.idToObjectMap[tagID])
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
images,
|
||||
duration: '',
|
||||
tag: item.tag || '热门',
|
||||
tagStyle,
|
||||
tagInfo,
|
||||
loves: item.loves || 0,
|
||||
changes: 0,
|
||||
checked: false,
|
||||
amt: item.amt || 0,
|
||||
};
|
||||
});
|
||||
|
||||
this.cards = [...this.cards, ...formattedCards];
|
||||
this.loading = false;
|
||||
this.totalPages = res.data.data.pages;
|
||||
this.card_loading = false;
|
||||
this.card_loading1 = false;
|
||||
this.totalPages = res.data.pages;
|
||||
this.pageNum++;
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取卡片数据失败:', error);
|
||||
this.loading = false;
|
||||
this.card_loading = false;
|
||||
|
||||
this.$toast.fail('网络错误,请稍后重试');
|
||||
}
|
||||
},
|
||||
async getTagInfo() {
|
||||
try {
|
||||
let res = await this.$axios.get('/tagInfo/getAll');
|
||||
if (res.code !== 200) {
|
||||
const errorMsg = res.msg || '获取数据失败';
|
||||
this.$toast.fail(errorMsg);
|
||||
return;
|
||||
}
|
||||
this.tagInfo = [...this.tagInfo, ...res.data];
|
||||
// 创建映射对象
|
||||
|
||||
res.data.forEach(obj => {
|
||||
this.idToObjectMap[obj.id] = obj;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取标签信息失败:', error);
|
||||
this.$toast.fail('网络错误,请稍后重试');
|
||||
}
|
||||
|
||||
},
|
||||
//标签点击事件
|
||||
onClickTab(name, title) {
|
||||
console.log(`标签 ${title} 被点击`);
|
||||
this.card_loading = true;
|
||||
this.cards = [];
|
||||
this.tagID = name;
|
||||
this.pageNum = 1;
|
||||
this.getCarts(this.tagID, this.pageNum, this.pageSize);
|
||||
},
|
||||
/**
|
||||
* 回到顶部
|
||||
* @param index
|
||||
*/
|
||||
handleTouchStart(index) {
|
||||
this.activeCardIndex = index;
|
||||
},
|
||||
/**
|
||||
* 处理触摸结束事件
|
||||
*/
|
||||
handleTouchEnd() {
|
||||
setTimeout(() => {
|
||||
this.activeCardIndex = -1;
|
||||
}, 300);
|
||||
},
|
||||
/**
|
||||
* 卡片点击事件
|
||||
* @param card 点击的卡片
|
||||
*/
|
||||
handleCardClick(card) {
|
||||
console.log('点击了卡片:', card.title);
|
||||
},
|
||||
/**
|
||||
* 点赞处理
|
||||
* @param card 点击的卡片
|
||||
*/
|
||||
handleClickLove(card) {
|
||||
if (card.checked) {
|
||||
card.loves++;
|
||||
@ -153,6 +244,7 @@ export default {
|
||||
changes: card.changes
|
||||
});
|
||||
},
|
||||
|
||||
async savaLoveChanes() {
|
||||
for (const change of this.changes) {
|
||||
try {
|
||||
@ -168,25 +260,44 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
handleScroll() {
|
||||
const container = document.querySelector('.cards-container');
|
||||
if (!container) return;
|
||||
|
||||
const scrollTop = container.scrollTop;
|
||||
const clientHeight = container.clientHeight;
|
||||
const scrollHeight = container.scrollHeight;
|
||||
handleScroll() {
|
||||
if (!this.scrollContainer) return;
|
||||
const scrollTop = this.scrollContainer.scrollTop;
|
||||
// 当滚动距离超过300px时显示回到顶部按钮
|
||||
this.showBackToTop = scrollTop > 300;
|
||||
|
||||
const clientHeight = this.scrollContainer.clientHeight;
|
||||
const scrollHeight = this.scrollContainer.scrollHeight;
|
||||
|
||||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||||
if (!this.loading && this.page < this.totalPages) {
|
||||
this.page++;
|
||||
this.getCarts(this.page, this.pageSize || 2);
|
||||
console.log('加载更多数据,当前页:', this.page);
|
||||
if (this.pageNum == this.totalPages) {
|
||||
this.$toast.fail('已经到底啦~~');
|
||||
return;
|
||||
}
|
||||
if (!this.loading && this.pageNum < this.totalPages) {
|
||||
this.card_loading1 = true;
|
||||
console.log(this.tagID);
|
||||
this.getCarts(this.tagID, this.pageNum + 1, this.pageSize);
|
||||
console.log('加载更多数据,当前页:', this.pageNum);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 回到顶部方法
|
||||
backToTop() {
|
||||
if (!this.scrollContainer) return;
|
||||
|
||||
// 使用平滑滚动回到顶部
|
||||
this.scrollContainer.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getCarts(this.page, this.pageSize || 2);
|
||||
this.getTagInfo();
|
||||
this.tagID = 0;
|
||||
this.getCarts(this.tagID, this.pageNum, this.pageSize || 2);
|
||||
this.scrollContainer = document.querySelector('.cards-container');
|
||||
if (this.scrollContainer) {
|
||||
this.scrollHandler = () => this.handleScroll();
|
||||
@ -204,7 +315,6 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 变量定义
|
||||
@base-bg: #f9fafb;
|
||||
@ -222,34 +332,33 @@ export default {
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
|
||||
// 粘性公告栏
|
||||
.sticky-notice {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: 50px;
|
||||
z-index: 100;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// 卡片容器(滚动区域)
|
||||
.cards-container {
|
||||
padding: 0 5px;
|
||||
overflow: auto;
|
||||
padding-bottom: 20px;
|
||||
|
||||
// 卡片网格布局
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
gap: 5px;
|
||||
|
||||
// 移动端适配
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
// // 移动端适配
|
||||
// @media (max-width: 600px) {
|
||||
// grid-template-columns: 1fr;
|
||||
// }
|
||||
|
||||
// 卡片样式(核心内容区)
|
||||
.card {
|
||||
@ -259,7 +368,7 @@ export default {
|
||||
box-shadow: @shadow-base;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
padding-bottom: 5px;
|
||||
// 悬停与激活状态
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
@ -275,36 +384,38 @@ export default {
|
||||
.card-image-container {
|
||||
position: relative;
|
||||
|
||||
// 卡片标签(图片右上角)
|
||||
.card-tag {
|
||||
.card-image {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
padding: 4px 12px;
|
||||
border-radius: 9999px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
// 卡片标签
|
||||
.card-tag {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片内容区(标题、描述、底部)
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
|
||||
// 卡片标题
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
padding-left: 5px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: @text-primary;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// 卡片描述
|
||||
.card-description {
|
||||
padding-left: 5px;
|
||||
color: @text-secondary;
|
||||
text-wrap: balance;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// 卡片底部(价格与点赞)
|
||||
@ -318,13 +429,12 @@ export default {
|
||||
|
||||
// 价格信息
|
||||
.card-meta {
|
||||
color: #9932CC;
|
||||
color: #FFFAFA;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
background-color: #D8BFD8;
|
||||
font-size: 0.9rem;
|
||||
background-color: #8470FF;
|
||||
border-radius: 8px;
|
||||
padding: 0 5px 0 5px;
|
||||
|
||||
padding: 5px;
|
||||
// 日历图标
|
||||
.fa {
|
||||
margin-right: 1px;
|
||||
@ -416,6 +526,29 @@ export default {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// 回到顶部按钮样式
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
background-color: rgba(243, 148, 227, 0.8); // 使用主色调
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: @accent-dark;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 爱心动画
|
||||
|
@ -1,112 +1,298 @@
|
||||
<template>
|
||||
<div class="sample-collection-page">
|
||||
<div class="card-grid">
|
||||
<!-- 卡片列表 -->
|
||||
<div v-for="(card, index) in cards" :key="index" class="card"
|
||||
:class="{ 'card-active': activeCardIndex === index }" @touchstart="handleTouchStart(index)"
|
||||
@touchend="handleTouchEnd(index)" @click="handleCardClick(card)">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
<van-tabs @click="onClickTab">
|
||||
<van-tab v-for="tab in tagInfo" :title="tab.tagName" :key="tab.id" :name="tab.id">
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
<!-- 使用原生 sticky 定位的公告栏 -->
|
||||
<div class="sticky-notice">
|
||||
<van-notice-bar left-icon="volume-o" :scrollable="true" text="所有标价均为款式价格,如需延长需补差价喔~~~(半贴:¥90,浅贴:¥130)" />
|
||||
</div>
|
||||
|
||||
<div class="card-image-container">
|
||||
<van-swipe :autoplay="3000">
|
||||
<van-swipe-item v-for="(image, imgIndex) in card.images" :key="imgIndex">
|
||||
<van-image :src="image" lazy-load>
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20" />
|
||||
</template>
|
||||
</van-image>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div v-if="card.tag" class="card-tag" :style="card.tagStyle">
|
||||
{{ card.tag }}
|
||||
<!-- 内容容器 -->
|
||||
<div class="cards-container">
|
||||
<div class="card_loading" v-if="this.card_loading"
|
||||
style="width: 100%;display: flex;justify-content: center;">
|
||||
<van-loading size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<!-- 卡片列表 -->
|
||||
<div v-for="(card, index) in cards" :key="index" class="card"
|
||||
:class="{ 'card-active': activeCardIndex === index }" @touchstart="handleTouchStart(index)"
|
||||
@touchend="handleTouchEnd(index)" @click="handleCardClick(card)">
|
||||
|
||||
<div class="card-image-container">
|
||||
<van-swipe :autoplay="3000">
|
||||
<van-swipe-item v-for="(image, imgIndex) in card.images" :key="imgIndex">
|
||||
<van-image lazy-load fit="fill" :src="image" class="card-image">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20" />
|
||||
</template>
|
||||
</van-image>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<h2 class="card-title">{{ card.title }}</h2>
|
||||
<p class="card-description">{{ card.description }}</p>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="employee">
|
||||
<van-tag round type="primary" v-for="employee in card.employee.split('&&')"
|
||||
:key="employee">{{ employee
|
||||
}}</van-tag>
|
||||
|
||||
<div class="card-content">
|
||||
<h2 class="card-title">{{ card.title }}</h2>
|
||||
<p class="card-description">{{ card.description }}</p>
|
||||
<div class="card-tag">
|
||||
<van-tag v-for="(tag, tagIndex) in card.tagInfo" :key="tagIndex" round type="primary"
|
||||
:text-color="tag.tagColor" :color="tag.tagBgColor" style="margin-left: 5px;">
|
||||
{{ tag.tagName }}
|
||||
</van-tag>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">
|
||||
<span>款式价格:¥</span><i class="fa fa-calendar-o mr-1"></i> {{ card.amt }}
|
||||
</span>
|
||||
<!-- <div class="heart" @click.stop>
|
||||
<label class="action-btn heart-label">
|
||||
<input type="checkbox" class="heart-checkbox" v-model="card.checked"
|
||||
@change="handleClickLove(card)">
|
||||
<span class="heart-btn">
|
||||
<span class="heart-icon"></span>
|
||||
</span>
|
||||
</label>
|
||||
<span class="like-count">{{ card.loves }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card_loading" v-if="this.card_loading1" style="width: 100%;display: flex;justify-content: center;">
|
||||
<van-loading size="24px">加载中...</van-loading>
|
||||
</div>
|
||||
<!-- 回到顶部按钮 -->
|
||||
<div v-show="showBackToTop" class="back-to-top" @click="backToTop">
|
||||
<van-icon name="arrow-up" size="16" color="#fff" />
|
||||
</div>
|
||||
<!-- 固定底部TabBar -->
|
||||
<TabBar class="tab-bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TabBar from '@/components/TabBar.vue';
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
export default {
|
||||
name: 'SampleCollectionPage',
|
||||
components: {},
|
||||
props: {},
|
||||
components: {
|
||||
TabBar,
|
||||
NavBar
|
||||
},
|
||||
data() {
|
||||
|
||||
return {
|
||||
cards: [
|
||||
{
|
||||
title: "探索自然之美",
|
||||
description: "深入原始森林,感受大自然的鬼斧神工。这里有清澈的溪流、茂密的树木和各种珍稀野生动物。",
|
||||
images: ["https://picsum.photos/seed/card1/600/800", "https://picsum.photos/seed/card2/600/800"],
|
||||
|
||||
tag: "热门",
|
||||
tagStyle: {
|
||||
backgroundColor: "#F97316",
|
||||
color: "#fff"
|
||||
},
|
||||
status: 0,
|
||||
employee: "小吕&&寒冰射手",
|
||||
neddTime: 2.0
|
||||
}
|
||||
],
|
||||
activeCardIndex: -1
|
||||
cards: [],
|
||||
card_loading: false,
|
||||
card_loading1: false,
|
||||
activeCardIndex: -1,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
loading: false,
|
||||
changes: [],
|
||||
totalPages: 0,
|
||||
showBackToTop: false, // 控制回到顶部按钮显示/隐藏
|
||||
scrollContainer: null,
|
||||
scrollHandler: null,
|
||||
tagInfo: [{ "id": 0, "tagName": "全部" }],
|
||||
//标签转换成对象
|
||||
idToObjectMap: {},
|
||||
tagID: 0
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async getCarts(tagID, pageNum, size) {
|
||||
try {
|
||||
this.loading = true;
|
||||
let res = null;
|
||||
if (tagID == 0) {
|
||||
res = await this.$axios.get('/sampleCollection/list', {
|
||||
params: {
|
||||
page: pageNum,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res = await this.$axios.get('/sampleCollection/getPortfolioByTag', {
|
||||
params: {
|
||||
tagID: tagID,
|
||||
page: pageNum,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
}
|
||||
console.log(res);
|
||||
if (res.code !== 200) {
|
||||
const errorMsg = res.msg || '获取数据失败';
|
||||
this.$toast.fail(errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
const records = res.data.records || [];
|
||||
if (records.length === 0) {
|
||||
this.$toast('没有更多数据了');
|
||||
return;
|
||||
}
|
||||
|
||||
const formattedCards = records.map(item => {
|
||||
const images = item.imgFilename
|
||||
? item.imgFilename.split('&&').map(filename => `${item.imgUrl}/${filename}`)
|
||||
: [];
|
||||
let tagInfo = [];
|
||||
for (let tagID of item.tagId.split("&&")) {
|
||||
tagInfo.push(this.idToObjectMap[tagID])
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
images,
|
||||
duration: '',
|
||||
tagInfo,
|
||||
checked: false,
|
||||
amt: item.amt || 0,
|
||||
};
|
||||
});
|
||||
this.cards = [...this.cards, ...formattedCards];
|
||||
console.log(this.cards);
|
||||
|
||||
this.loading = false;
|
||||
this.card_loading = false;
|
||||
this.card_loading1 = false;
|
||||
this.totalPages = res.data.pages;
|
||||
this.pageNum++;
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取卡片数据失败:', error);
|
||||
this.loading = false;
|
||||
this.card_loading = false;
|
||||
|
||||
this.$toast.fail('网络错误,请稍后重试');
|
||||
}
|
||||
},
|
||||
async getTagInfo() {
|
||||
try {
|
||||
let res = await this.$axios.get('/tagInfo/getAll');
|
||||
if (res.code !== 200) {
|
||||
const errorMsg = res.msg || '获取数据失败';
|
||||
this.$toast.fail(errorMsg);
|
||||
return;
|
||||
}
|
||||
this.tagInfo = [...this.tagInfo, ...res.data];
|
||||
// 创建映射对象
|
||||
|
||||
res.data.forEach(obj => {
|
||||
this.idToObjectMap[obj.id] = obj;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取标签信息失败:', error);
|
||||
this.$toast.fail('网络错误,请稍后重试');
|
||||
}
|
||||
|
||||
},
|
||||
//标签点击事件
|
||||
onClickTab(name, title) {
|
||||
console.log(`标签 ${title} 被点击`);
|
||||
this.card_loading = true;
|
||||
this.cards = [];
|
||||
this.tagID = name;
|
||||
this.pageNum = 1;
|
||||
this.getCarts(this.tagID, this.pageNum, this.pageSize);
|
||||
},
|
||||
/**
|
||||
* 回到顶部
|
||||
* @param index
|
||||
*/
|
||||
handleTouchStart(index) {
|
||||
this.activeCardIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理触摸结束事件
|
||||
*/
|
||||
handleTouchEnd() {
|
||||
setTimeout(() => {
|
||||
this.activeCardIndex = -1;
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 卡片点击事件
|
||||
* @param card 点击的卡片
|
||||
*/
|
||||
handleCardClick(card) {
|
||||
console.log('点击了卡片:', card.title);
|
||||
},
|
||||
|
||||
handleButtonClick(card) {
|
||||
console.log('点击了了解更多:', card.title);
|
||||
/**
|
||||
* 点赞处理
|
||||
* @param card 点击的卡片
|
||||
*/
|
||||
handleClickLove(card) {
|
||||
if (card.checked) {
|
||||
card.loves++;
|
||||
card.changes++;
|
||||
} else {
|
||||
if (card.loves > 0) {
|
||||
card.loves--;
|
||||
card.changes--;
|
||||
}
|
||||
}
|
||||
this.changes.push({
|
||||
id: card.id,
|
||||
changes: card.changes
|
||||
});
|
||||
},
|
||||
|
||||
handleScroll() {
|
||||
if (!this.scrollContainer) return;
|
||||
const scrollTop = this.scrollContainer.scrollTop;
|
||||
// 当滚动距离超过300px时显示回到顶部按钮
|
||||
this.showBackToTop = scrollTop > 300;
|
||||
|
||||
const clientHeight = this.scrollContainer.clientHeight;
|
||||
const scrollHeight = this.scrollContainer.scrollHeight;
|
||||
|
||||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||||
if (this.pageNum == this.totalPages) {
|
||||
this.$toast.fail('已经到底啦~~');
|
||||
return;
|
||||
}
|
||||
if (!this.loading && this.pageNum < this.totalPages) {
|
||||
this.card_loading1 = true;
|
||||
console.log(this.tagID);
|
||||
this.getCarts(this.tagID, this.pageNum + 1, this.pageSize);
|
||||
console.log('加载更多数据,当前页:', this.pageNum);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 回到顶部方法
|
||||
backToTop() {
|
||||
if (!this.scrollContainer) return;
|
||||
|
||||
// 使用平滑滚动回到顶部
|
||||
this.scrollContainer.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 处理按钮触摸反馈
|
||||
const buttons = document.querySelectorAll('.card-button');
|
||||
buttons.forEach(button => {
|
||||
button.addEventListener('touchstart', () => {
|
||||
button.classList.add('button-active');
|
||||
});
|
||||
|
||||
button.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
button.classList.remove('button-active');
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
this.getTagInfo();
|
||||
this.tagID = 0;
|
||||
this.getCarts(this.tagID, this.pageNum, this.pageSize || 2);
|
||||
this.scrollContainer = document.querySelector('.cards-container');
|
||||
if (this.scrollContainer) {
|
||||
this.scrollHandler = () => this.handleScroll();
|
||||
this.scrollContainer.addEventListener('scroll', this.scrollHandler);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.scrollContainer && this.scrollHandler) {
|
||||
this.scrollContainer.removeEventListener('scroll', this.scrollHandler);
|
||||
}
|
||||
},
|
||||
created() { },
|
||||
|
||||
};
|
||||
</script>
|
||||
@ -123,199 +309,252 @@ export default {
|
||||
@shadow-base: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
@shadow-active: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
|
||||
// 基础样式
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: @base-bg;
|
||||
min-height: 100vh;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
// 容器样式
|
||||
// 页面容器(最外层)
|
||||
.sample-collection-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
|
||||
.page-title {
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: bold;
|
||||
color: @text-primary;
|
||||
margin-bottom: 32px;
|
||||
text-align: center;
|
||||
// 粘性公告栏
|
||||
.sticky-notice {
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
z-index: 100;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// 卡片网格布局
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
// 卡片容器(滚动区域)
|
||||
.cards-container {
|
||||
padding: 0 5px;
|
||||
overflow: auto;
|
||||
|
||||
// 移动端适配
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
// 卡片网格布局
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 5px;
|
||||
|
||||
// 卡片样式
|
||||
.card {
|
||||
background-color: @card-bg;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: @shadow-base;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
// // 移动端适配
|
||||
// @media (max-width: 600px) {
|
||||
// grid-template-columns: 1fr;
|
||||
// }
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: @shadow-active;
|
||||
}
|
||||
// 卡片样式(核心内容区)
|
||||
.card {
|
||||
background-color: @card-bg;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: @shadow-base;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
padding-bottom: 5px;
|
||||
|
||||
&.card-active {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: @shadow-active;
|
||||
}
|
||||
// 悬停与激活状态
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: @shadow-active;
|
||||
}
|
||||
|
||||
// 卡片图片容器
|
||||
.card-image-container {
|
||||
position: relative;
|
||||
&.card-active {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: @shadow-active;
|
||||
}
|
||||
|
||||
// 卡片标签
|
||||
.card-tag {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 9999px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
// 卡片图片容器
|
||||
.card-image-container {
|
||||
position: relative;
|
||||
|
||||
// 卡片内容
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
.card-image {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: @text-primary;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: @text-secondary;
|
||||
text-wrap: balance;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// 卡片底部
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.card-meta {
|
||||
color: @accent-color;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
|
||||
.fa {
|
||||
margin-right: 1px;
|
||||
// 卡片标签
|
||||
.card-tag {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
// 爱心按钮区域
|
||||
.heart {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
// 卡片内容区(标题、描述、底部)
|
||||
.card-content {
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
|
||||
.like-count {
|
||||
font-size: 13px;
|
||||
// 卡片标题
|
||||
.card-title {
|
||||
padding-left: 5px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: @text-primary;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// 卡片描述
|
||||
.card-description {
|
||||
padding-left: 5px;
|
||||
color: @text-secondary;
|
||||
text-wrap: balance;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.5;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.heart-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.heart-btn {
|
||||
position: relative;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transition: transform 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.heart-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: color 0.3s;
|
||||
// 卡片底部(价格与点赞)
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
bottom: 0px;
|
||||
|
||||
&::before {
|
||||
content: '❤';
|
||||
font-size: 1.25rem;
|
||||
color: @gray-light;
|
||||
transition: color 0.3s, transform 0.3s;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
transform-origin: center;
|
||||
// 价格信息
|
||||
.card-meta {
|
||||
color: #FFFAFA;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
background-color: #8470FF;
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
|
||||
// 日历图标
|
||||
.fa {
|
||||
margin-right: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选中状态样式
|
||||
.heart-checkbox:checked~.heart-btn .heart-icon::before {
|
||||
color: @danger-color;
|
||||
animation: heartBeat 0.5s cubic-bezier(0.17, 0.89, 0.32, 1.49);
|
||||
// 点赞区域
|
||||
.heart {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
// 点赞数量
|
||||
.like-count {
|
||||
font-size: 13px;
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
// 点赞按钮容器
|
||||
.action-btn {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// 隐藏原生复选框
|
||||
.heart-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
// 点赞按钮样式
|
||||
.heart-btn {
|
||||
position: relative;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transition: transform 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 爱心图标
|
||||
.heart-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: color 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::before {
|
||||
content: '❤';
|
||||
font-size: 1.25rem;
|
||||
color: @gray-light;
|
||||
transition: color 0.3s, transform 0.3s;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中状态(点赞效果)
|
||||
.heart-checkbox:checked~.heart-btn .heart-icon::before {
|
||||
color: @danger-color;
|
||||
animation: heartBeat 0.5s cubic-bezier(0.17, 0.89, 0.32, 1.49);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.employee {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.van-tag {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 9999px;
|
||||
background-color: #7B68EE;
|
||||
color: white;
|
||||
}
|
||||
// 加载动画(备用)
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// 回到顶部按钮样式
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
background-color: rgba(243, 148, 227, 0.8); // 使用主色调
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: @accent-dark;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 爱心动画
|
||||
@keyframes heartBeat {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
358
na-frontend/src/pages/man/HomeManPage.vue
Normal file
358
na-frontend/src/pages/man/HomeManPage.vue
Normal file
@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<div class="home-man-page">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
<div class="images">
|
||||
<van-swipe class="swipe" :autoplay="2000" indicator-color="white">
|
||||
<van-swipe-item v-for="(image, index) in info.headImgs" :key="index">
|
||||
<img fill="contain" v-lazy="image.url" />
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title" @click="showpop">
|
||||
<div>{{ info.name }}</div>
|
||||
</div>
|
||||
<van-divider />
|
||||
<div class="worktime">
|
||||
<div>{{ info.status }} {{ info.worktime }}</div>
|
||||
</div>
|
||||
<van-divider />
|
||||
<div class="location">
|
||||
<div class="left">
|
||||
<span style="color: black; font-size: 15px;">{{ info.location }}</span>
|
||||
<br>
|
||||
<van-icon name="location-o" size="12" />
|
||||
<span>距您驾车或者打车8.0公里 需10分钟</span>
|
||||
<br>
|
||||
<van-icon name="guide-o" size="12" />
|
||||
<span>距离1号线微电园站1号口步行400米 需8分钟</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<van-icon name="wechat" size="25" />
|
||||
<van-icon name="phone" size="25" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-divider>新品展示</van-divider>
|
||||
<div class="new">
|
||||
<van-swipe class="swipe" :autoplay="2000" indicator-color="white">
|
||||
<van-swipe-item v-for="(image, index) in info.newPortfolioImgs" :key="index">
|
||||
<img fill="contain" v-lazy="image.url" />
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
</div>
|
||||
|
||||
|
||||
<van-popup position="bottom" v-model="show" style="height: 80%;" closeable :close-on-click-overlay="false">
|
||||
<van-form @submit="saveChangeInfo">
|
||||
<van-field name="headImgs" label="主页店图">
|
||||
<template #input>
|
||||
<van-uploader v-model="info.headImgs" :after-read="uploadHeadImgs" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field v-model="info.name" name="name" label="店名" placeholder="店名"
|
||||
:rules="[{ required: true, message: '请填写店名' }]" />
|
||||
<van-field v-model="info.worktime" name="worktime" label="营业信息" placeholder="营业信息"
|
||||
:rules="[{ required: true, message: '请填写营业信息' }]" />
|
||||
<van-field readonly clickable label="营业状态" :value="info.status" placeholder="选择营业状态" @click="showPicker = true" />
|
||||
<van-popup v-model="showPicker" round position="bottom">
|
||||
<van-picker show-toolbar :columns="columns" @cancel="showPicker = false" @confirm="onPicker" />
|
||||
</van-popup>
|
||||
<van-field v-model="info.location" name="location" label="地址" placeholder="地址"
|
||||
:rules="[{ required: true, message: '请填写地址' }]" />
|
||||
<van-field v-model="info.wechat" name="wechat" label="微信" placeholder="微信"
|
||||
:rules="[{ required: true, message: '请填写微信' }]" />
|
||||
<van-field v-model="info.phone" name="phone" label="电话" placeholder="电话"
|
||||
:rules="[{ required: true, message: '请填写电话' }]" />
|
||||
|
||||
<van-field name="locationImgs" label="指引图">
|
||||
<template #input>
|
||||
<van-uploader v-model="info.locationImgs" :after-read="uploadLocationImgs" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field name="newPortfolioImgs" label="新品图">
|
||||
<template #input>
|
||||
<van-uploader v-model="info.newPortfolioImgs" :after-read="uploadnewPortfolioImgs" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field name="environmentImgs" label="环境图">
|
||||
<template #input>
|
||||
<van-uploader v-model="info.environmentImgs" :after-read="uploadEnvironmentImgs" />
|
||||
</template>
|
||||
</van-field>
|
||||
<div style="margin: 16px;">
|
||||
<van-button round block type="info" native-type="submit">提交</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
</van-popup>
|
||||
|
||||
|
||||
<!-- 固定底部TabBar -->
|
||||
<ManTabBar class="tab-bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
import ManTabBar from '@/components/ManTabBar.vue';
|
||||
export default {
|
||||
name: 'HomeManPage',
|
||||
components: {
|
||||
ManTabBar,
|
||||
NavBar
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
info: {
|
||||
id: 1,
|
||||
headImgs: [],
|
||||
guide: '距离1号线微电园站1号口步行400米 需8分钟',
|
||||
wechat: '',
|
||||
phone: '',
|
||||
newPortfolioImgs: [],
|
||||
locationImgs: [],
|
||||
environmentImgs: [
|
||||
|
||||
],
|
||||
publicityImg: '',
|
||||
status: ""
|
||||
},
|
||||
storageUrl: 'http://xiaowangnas.com:9000/yumiartnail',
|
||||
saveInfo: {
|
||||
id: 1,
|
||||
headImg: '',
|
||||
name: '',
|
||||
worktime: '',
|
||||
address: '',
|
||||
wechat: '',
|
||||
phone: '',
|
||||
locationImg: '',
|
||||
newPortfolioImg: '',
|
||||
environmentImg: '',
|
||||
publicityImg: '',
|
||||
},
|
||||
showPicker: false,
|
||||
columns: ['营业中', '休息中'],
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {},
|
||||
methods: {
|
||||
async getInfo() {
|
||||
const res = await this.$axios.get('/mainInfo/getMainInfo');
|
||||
console.log(res);
|
||||
if (res.code == 200) {
|
||||
this.id = res.data.id;
|
||||
this.info.headImgs = this.convertToObjectArray(res.data.headImg.split('&&'));
|
||||
this.info.name = res.data.name;
|
||||
this.info.status = res.data.status == 0 ? '休息中' : '营业中';
|
||||
this.info.worktime = res.data.worktime;
|
||||
this.info.location = res.data.address;
|
||||
this.info.wechat = res.data.wechat;
|
||||
this.info.phone = res.data.phone;
|
||||
this.info.locationImgs = this.convertToObjectArray(res.data.locationImg.split('&&'));
|
||||
this.info.newPortfolioImgs = this.convertToObjectArray(res.data.newPortfolioImg.split('&&'));
|
||||
this.info.environmentImgs = this.convertToObjectArray(res.data.environmentImg.split('&&'));
|
||||
}
|
||||
},
|
||||
async saveChangeInfo() {
|
||||
this.show = false;
|
||||
this.saveInfo.id = this.info.id;
|
||||
this.saveInfo.headImg = this.convertUrlsToString(this.info.headImgs);
|
||||
this.saveInfo.name = this.info.name;
|
||||
this.saveInfo.worktime = this.info.worktime;
|
||||
this.saveInfo.address = this.info.location;
|
||||
this.saveInfo.wechat = this.info.wechat;
|
||||
this.saveInfo.phone = this.info.phone;
|
||||
this.saveInfo.locationImg = this.convertUrlsToString(this.info.locationImgs);
|
||||
this.saveInfo.newPortfolioImg = this.convertUrlsToString(this.info.newPortfolioImgs);
|
||||
this.saveInfo.environmentImg = this.convertUrlsToString(this.info.environmentImgs);
|
||||
console.log(this.saveInfo);
|
||||
const res = await this.$axios.post('/mainInfo/updateMainInfo', this.saveInfo);
|
||||
console.log(res);
|
||||
if (res.code == 200) {
|
||||
this.$toast.success('修改成功');
|
||||
} else {
|
||||
this.$toast.fail('修改失败');
|
||||
}
|
||||
},
|
||||
showpop() {
|
||||
this.show = true;
|
||||
},
|
||||
uploadHeadImgs(file) {
|
||||
this.uploadImgs(file, 'headImgs')
|
||||
},
|
||||
uploadLocationImgs(file) {
|
||||
this.uploadImgs(file, 'locationImgs')
|
||||
},
|
||||
uploadnewPortfolioImgs(file) {
|
||||
this.uploadImgs(file, 'newPortfolioImgs')
|
||||
},
|
||||
uploadEnvironmentImgs(file) {
|
||||
this.uploadImgs(file, 'environmentImgs')
|
||||
},
|
||||
//上传文件
|
||||
async uploadImgs(file, type) {
|
||||
try {
|
||||
const filePath = `${type}/${Date.now()}-${file.file.name}`;
|
||||
let result = await this.$fileService.uploadFile({
|
||||
bucketName: 'yumiartnail',
|
||||
filePath: filePath,
|
||||
file: file.file,
|
||||
|
||||
});
|
||||
result = `${this.storageUrl}/${result}`;
|
||||
switch (type) {
|
||||
case 'headImgs':
|
||||
this.info.headImgs.pop();
|
||||
this.info.headImgs.push({ url: result });
|
||||
break;
|
||||
case 'locationImgs':
|
||||
this.info.locationImgs.pop();
|
||||
this.info.locationImgs.push({ url: result });
|
||||
break;
|
||||
case 'newPortfolioImgs':
|
||||
this.info.newPortfolioImgs.pop();
|
||||
this.info.newPortfolioImgs.push({ url: result });
|
||||
break;
|
||||
case 'environmentImgs':
|
||||
this.info.environmentImgs.pop();
|
||||
this.info.environmentImgs.push({ url: result });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// this.uploadResult = `上传失败: ${error.message}`;
|
||||
this.$toast.fail('上传失败');
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
}
|
||||
},
|
||||
convertToObjectArray(urls) {
|
||||
return urls.map(url => {
|
||||
const modifiedUrl = this.storageUrl + url;
|
||||
return { url: modifiedUrl }
|
||||
});
|
||||
},
|
||||
convertUrlsToString(arr) {
|
||||
return arr.map(item => {
|
||||
let modifiedUrl = item.url.replace(this.storageUrl, '');
|
||||
return modifiedUrl;
|
||||
}).join('&&');
|
||||
},
|
||||
onPicker(value) {
|
||||
this.info.status = value=='营业中'?1:0;
|
||||
this.showPicker = false;
|
||||
},
|
||||
},
|
||||
created() { },
|
||||
mounted() {
|
||||
this.getInfo();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// 变量定义
|
||||
@base-bg: #f9fafb;
|
||||
@card-bg: white;
|
||||
@text-primary: #1f2937;
|
||||
@text-secondary: #6b7280;
|
||||
@accent-color: #3b82f6;
|
||||
@accent-dark: #2563eb;
|
||||
@danger-color: #ef4444;
|
||||
@gray-light: #d1d5db;
|
||||
@shadow-base: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
@shadow-active: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
|
||||
.home-man-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
|
||||
.images {
|
||||
width: 100%;
|
||||
height: 25vh;
|
||||
overflow: hidden;
|
||||
min-height: 200px;
|
||||
max-height: 500px;
|
||||
|
||||
.swipe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
|
||||
.location {
|
||||
display: flex;
|
||||
|
||||
.left {
|
||||
width: 70%;
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: #a19e9e;
|
||||
margin-bottom: 10px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
color: #a19e9e;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
width: 90%;
|
||||
height: 25vh;
|
||||
overflow: hidden;
|
||||
min-height: 200px;
|
||||
max-height: 500px;
|
||||
margin: auto;
|
||||
|
||||
.swipe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
1083
na-frontend/src/pages/man/PortfolioManPage.vue
Normal file
1083
na-frontend/src/pages/man/PortfolioManPage.vue
Normal file
File diff suppressed because it is too large
Load Diff
1084
na-frontend/src/pages/man/SampleCollectionManPage.vue
Normal file
1084
na-frontend/src/pages/man/SampleCollectionManPage.vue
Normal file
File diff suppressed because it is too large
Load Diff
31
na-frontend/src/pages/man/TagManPage.vue
Normal file
31
na-frontend/src/pages/man/TagManPage.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="tag-man-page">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<NavBar class="nav-bar" />
|
||||
<!-- 固定底部TabBar -->
|
||||
<ManTabBar class="tab-bar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
import ManTabBar from '@/components/ManTabBar.vue';
|
||||
export default {
|
||||
name: 'TagManPage',
|
||||
components: {
|
||||
ManTabBar,
|
||||
NavBar
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
computed: {},
|
||||
methods: {},
|
||||
created() { },
|
||||
mounted() { }
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
@ -3,6 +3,10 @@ import VueRouter from "vue-router";
|
||||
import HomePage from "@/pages/HomePage.vue";
|
||||
import PortfolioPage from "@/pages/PortfolioPage.vue";
|
||||
import SampleCollectionPage from "@/pages/SampleCollectionPage.vue";
|
||||
import PortfolioMan from "@/pages/man/PortfolioManPage.vue";
|
||||
import SampleCollectionMan from "@/pages/man/SampleCollectionManPage.vue";
|
||||
import HomeManPage from "@/pages/man/HomeManPage.vue";
|
||||
import TagManPage from "@/pages/man/TagManPage.vue";
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
@ -10,20 +14,37 @@ const routes = [
|
||||
path: "/",
|
||||
name: "HomePage",
|
||||
component: HomePage,
|
||||
meta: { direction: "back" },
|
||||
},
|
||||
{
|
||||
path: "/portfolio",
|
||||
name: "PortfolioPage",
|
||||
component: PortfolioPage,
|
||||
meta: { direction: "forward" },
|
||||
},
|
||||
{
|
||||
path: "/sampleCollection",
|
||||
name: "SampleCollectionPage",
|
||||
component: SampleCollectionPage,
|
||||
meta: { direction: "forward" },
|
||||
},
|
||||
{
|
||||
path: "/yumi",
|
||||
name: "Yumi",
|
||||
component: HomeManPage,
|
||||
},
|
||||
{
|
||||
path: "/sampleCollectionman",
|
||||
name: "SampleCollectionMan",
|
||||
component: SampleCollectionMan,
|
||||
},
|
||||
{
|
||||
path: "/portfolioMan",
|
||||
name: "PortfolioMan",
|
||||
component: PortfolioMan,
|
||||
},
|
||||
{
|
||||
path: "/tagManPage",
|
||||
name: "tagManPage",
|
||||
component: TagManPage,
|
||||
}
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
|
227
na-frontend/src/utils/file.js
Normal file
227
na-frontend/src/utils/file.js
Normal file
@ -0,0 +1,227 @@
|
||||
import axios from 'axios';
|
||||
/**
|
||||
* 文件操作工具类,支持上传、下载和删除文件
|
||||
*/
|
||||
|
||||
export class FileService {
|
||||
constructor(baseUrl = '') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到MinIO
|
||||
* @param {Object} options - 上传参数
|
||||
* @param {string} options.bucketName - 存储桶名称
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @param {File} options.file - 文件对象
|
||||
* @param {Function} [options.onProgress] - 进度回调函数
|
||||
* @returns {Promise<string>} - 上传成功后的文件路径
|
||||
*/
|
||||
uploadFile(options) {
|
||||
const {
|
||||
bucketName,
|
||||
filePath,
|
||||
file,
|
||||
} = options;
|
||||
|
||||
if (!bucketName || !filePath || !file) {
|
||||
return Promise.reject(new Error('缺少必要的上传参数'));
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('bucketName', bucketName);
|
||||
formData.append('filePath', filePath);
|
||||
formData.append('file', file);
|
||||
|
||||
return axios({
|
||||
url: `${this.baseUrl}/api/file/upload`,
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Accept': 'application/json' },
|
||||
timeout: 60000
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(`上传失败,状态码:${response.status}`);
|
||||
})
|
||||
.catch(this._handleError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param {Object} options - 下载参数
|
||||
* @param {string} options.bucketName - 存储桶名称
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @param {string} [options.fileName] - 下载时使用的文件名
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
downloadFile(options) {
|
||||
const { bucketName, filePath, fileName } = options;
|
||||
|
||||
if (!bucketName || !filePath) {
|
||||
return Promise.reject(new Error('缺少必要的下载参数'));
|
||||
}
|
||||
|
||||
return axios({
|
||||
url: `${this.baseUrl}/api/file/download`,
|
||||
method: 'get',
|
||||
params: { bucketName, filePath },
|
||||
responseType: 'blob',
|
||||
timeout: 60000
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
this._saveFile(response.data, fileName || filePath.split('/').pop());
|
||||
return;
|
||||
}
|
||||
throw new Error(`下载失败,状态码:${response.status}`);
|
||||
})
|
||||
.catch(this._handleError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param {Object} options - 删除参数
|
||||
* @param {string} options.bucketName - 存储桶名称
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deleteFile(options) {
|
||||
const { bucketName, filePath } = options;
|
||||
|
||||
if (!bucketName || !filePath) {
|
||||
return Promise.reject(new Error('缺少必要的删除参数'));
|
||||
}
|
||||
|
||||
return axios({
|
||||
url: `${this.baseUrl}/api/file/delete`,
|
||||
method: 'delete',
|
||||
params: { bucketName, filePath },
|
||||
timeout: 30000
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
throw new Error(`删除失败,状态码:${response.status}`);
|
||||
})
|
||||
.catch(this._handleError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
* @param {Object} options - 参数
|
||||
* @param {string} options.bucketName - 存储桶名称
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @returns {Promise<boolean>} - 文件是否存在
|
||||
*/
|
||||
fileExists(options) {
|
||||
const { bucketName, filePath } = options;
|
||||
|
||||
if (!bucketName || !filePath) {
|
||||
return Promise.reject(new Error('缺少必要的参数'));
|
||||
}
|
||||
|
||||
return axios({
|
||||
url: `${this.baseUrl}/api/file/exists`,
|
||||
method: 'get',
|
||||
params: { bucketName, filePath },
|
||||
timeout: 30000
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(`检查失败,状态码:${response.status}`);
|
||||
})
|
||||
.catch(this._handleError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件URL
|
||||
* @param {Object} options - 参数
|
||||
* @param {string} options.bucketName - 存储桶名称
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @param {number} [options.expirySeconds=3600] - URL有效期(秒)
|
||||
* @returns {Promise<string>} - 文件URL
|
||||
*/
|
||||
getFileUrl(options) {
|
||||
const { bucketName, filePath, expirySeconds = 3600 } = options;
|
||||
|
||||
if (!bucketName || !filePath) {
|
||||
return Promise.reject(new Error('缺少必要的参数'));
|
||||
}
|
||||
|
||||
return axios({
|
||||
url: `${this.baseUrl}/api/file/url`,
|
||||
method: 'get',
|
||||
params: { bucketName, filePath, expirySeconds },
|
||||
timeout: 30000
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(`获取URL失败,状态码:${response.status}`);
|
||||
})
|
||||
.catch(this._handleError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件到本地
|
||||
* @param {Blob} blob - 文件内容
|
||||
* @param {string} fileName - 文件名
|
||||
*/
|
||||
_saveFile(blob, fileName) {
|
||||
// 处理中文文件名
|
||||
const encodedFileName = encodeURIComponent(fileName);
|
||||
|
||||
if (navigator.msSaveBlob) {
|
||||
// 兼容IE
|
||||
navigator.msSaveBlob(blob, fileName);
|
||||
} else {
|
||||
// 现代浏览器
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.setAttribute('download', encodedFileName);
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理错误
|
||||
* @param {Error} error - 错误对象
|
||||
*/
|
||||
_handleError(error) {
|
||||
let errorMsg = '操作失败';
|
||||
if (error.response) {
|
||||
errorMsg += `,服务器错误:${error.response.status} ${error.response.statusText}`;
|
||||
} else if (error.request) {
|
||||
errorMsg += ',未收到服务器响应';
|
||||
} else {
|
||||
errorMsg += `,${error.message}`;
|
||||
}
|
||||
console.error(errorMsg, error);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Vue插件
|
||||
export const FileServicePlugin = {
|
||||
install(Vue, options = {}) {
|
||||
// 创建全局实例
|
||||
const fileService = new FileService(options.baseUrl);
|
||||
|
||||
// 方式1:添加到Vue原型,全局可用
|
||||
Vue.prototype.$fileService = fileService;
|
||||
|
||||
// 方式2:注册为全局组件
|
||||
Vue.fileService = fileService;
|
||||
}
|
||||
};
|
@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: "http://127.0.0.1:8090/api",
|
||||
baseURL: "http://wcy111.top:35001/api",
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
@ -19,31 +19,11 @@ const getRequestKey = (config) => {
|
||||
const dataStr = data ? JSON.stringify(data) : '';
|
||||
return `${method}:${url}:${paramsStr}:${dataStr}`;
|
||||
};
|
||||
// 请求拦截器:添加防抖逻辑
|
||||
|
||||
// 请求拦截器:仅保留防抖逻辑
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// 1. 检查POST请求体和参数是否都为空
|
||||
if (config.method === 'post') {
|
||||
// 检查请求体是否为空
|
||||
const bodyEmpty =
|
||||
config.data === undefined ||
|
||||
config.data === null ||
|
||||
(typeof config.data === 'object' && Object.keys(config.data).length === 0) ||
|
||||
(typeof config.data === 'string' && config.data.trim().length === 0);
|
||||
|
||||
// 检查URL参数是否为空
|
||||
const paramsEmpty =
|
||||
config.params === undefined ||
|
||||
config.params === null ||
|
||||
(typeof config.params === 'object' && Object.keys(config.params).length === 0);
|
||||
|
||||
// 如果请求体和参数都为空,则打印提示
|
||||
if (bodyEmpty && paramsEmpty) {
|
||||
console.log('提示:POST请求的请求体和参数均为空');
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 防抖逻辑
|
||||
// 防抖逻辑
|
||||
return new Promise((resolve) => {
|
||||
const requestKey = getRequestKey(config);
|
||||
// 清除之前的定时器
|
||||
@ -63,4 +43,29 @@ service.interceptors.request.use(
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器保持不变
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
return response.data;
|
||||
},
|
||||
error => {
|
||||
let errorMessage = '请求失败';
|
||||
|
||||
if (error.response) {
|
||||
errorMessage = `请求失败,状态码: ${error.response.status}`;
|
||||
if (error.response.data && error.response.data.message) {
|
||||
errorMessage += `,原因: ${error.response.data.message}`;
|
||||
}
|
||||
} else if (error.request) {
|
||||
errorMessage = '请求已发送,但没有收到响应';
|
||||
} else {
|
||||
errorMessage = `请求错误: ${error.message}`;
|
||||
}
|
||||
|
||||
console.error(errorMessage);
|
||||
return Promise.reject(errorMessage);
|
||||
}
|
||||
);
|
||||
|
||||
export default service;
|
Loading…
Reference in New Issue
Block a user