Compare commits

..

17 Commits

Author SHA1 Message Date
ppnwsfegt e605e85ada Merge pull request '修复bug' (#77) from Brunch_LPQ into main
6 months ago
riverflow 0546bc0207 修复首页瀑布流左侧图片跳转详情参数错误bug
6 months ago
riverflow 620c336823 修复pc端评论管理界面不能正常显示用户头像的bug
6 months ago
ppnwsfegt 11b9227843 Merge pull request '完成小程序端我的模块跳转我的地址' (#76) from Brunch_LPQ into main
6 months ago
riverflow 8ee5146eff 完成小程序端我的模块跳转我的地址
6 months ago
ppnwsfegt e4c77b3559 Merge pull request '完成登录注册模块的接口与页面 修复首页图标未能正常获取的bug 完成小程序端部分接口对接' (#75) from Brunch_LPQ into main
6 months ago
riverflow 5aad968993 完成登录注册模块的接口与页面
6 months ago
ppnwsfegt 230919cfa5 Merge pull request '首页轮播图接口优化' (#74) from Brunch_LPQ into main
6 months ago
riverflow cfdb4c6122 首页轮播图接口优化
6 months ago
ppnwsfegt 805aa550ce Merge pull request '完成pc端首页数据统计接口和页面的开发' (#73) from Brunch_LPQ into main
6 months ago
riverflow d42739ff31 完成pc端首页数据统计接口和页面的开发
6 months ago
pikvyz67s 21619e215d Merge pull request '确定收货对接、小程序首页轮播图优化' (#72) from Brunch_DBK into main
6 months ago
pikvyz67s 1c4b546f69 Merge pull request '小程序及我的收藏接口对接' (#71) from Brunch_DBK into main
6 months ago
pikvyz67s 2346d0800a Merge pull request '小程序评论列表制作和对接' (#70) from Brunch_DBK into main
6 months ago
pikvyz67s 40f0f6abd6 Merge pull request '评论接口对接' (#69) from Brunch_DBK into main
6 months ago
pikvyz67s a77c8e0933 Merge pull request '小程序评论页面制作' (#68) from Brunch_DBK into main
6 months ago
pikvyz67s 465e56f787 Merge pull request '昵称、头像接口开发对接和数据库获取' (#67) from Brunch_DBK into main
6 months ago

@ -53,6 +53,10 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
</dependencies>
<build>

@ -0,0 +1,48 @@
package com.itmk.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtUtils {
// 使用 Keys.secretKeyFor 生成足够强的密钥512位
private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512);
private static final long EXPIRATION_TIME = 86400000; // 24小时
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY, SignatureAlgorithm.HS512)
.compact();
}
public static Claims getClaimsByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
try {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
System.out.println("validate is token error, token: " + token + ", error: " + e);
return null;
}
}
public static String getSubject(String token) {
Claims claims = getClaimsByToken(token);
return claims != null ? claims.getSubject() : null;
}
}

@ -1,8 +1,10 @@
package com.itmk.web.banner.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itmk.web.goods.entity.SysGoods;
import lombok.Data;
@Data
@ -15,4 +17,6 @@ public class SysBanner {
private String images;
private String status;
private Integer orderNum;
@TableField(exist = false)
private SysGoods sysGoods;
}

@ -5,11 +5,18 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itmk.web.goods_comment.entity.GoodsComment;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface GoodsCommentMapper extends BaseMapper<GoodsComment> {
List<GoodsComment> commentList(@Param("goodsId") Long goodsId);
// 修改getList方法关联查询用户信息
@Select("SELECT gc.*, wu.nick_name as nickName, wu.avatar_url as avatarUrl " +
"FROM goods_comment gc " +
"LEFT JOIN wx_user wu ON gc.openid = wu.openid " +
"ORDER BY gc.create_time DESC")
IPage<GoodsComment> getList(Page<GoodsComment> page);
}

@ -1,4 +1,5 @@
package com.itmk.web.goods_comment.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -10,7 +11,6 @@ import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GoodsCommentServiceImpl extends ServiceImpl<GoodsCommentMapper, GoodsComment> implements GoodsCommentService {
@Override
@ -21,7 +21,7 @@ public class GoodsCommentServiceImpl extends ServiceImpl<GoodsCommentMapper, Goo
@Override
public IPage<GoodsComment> getList(CommentParm parm) {
//构造分页对象
Page<GoodsComment> page = new Page<>(parm.getCurrentPage(),parm.getPageSize());
Page<GoodsComment> page = new Page<>(parm.getCurrentPage(), parm.getPageSize());
return this.baseMapper.getList(page);
}
}

@ -5,56 +5,60 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.itmk.utils.ResultUtils;
import com.itmk.utils.ResultVo;
import com.itmk.web.order.entity.OrderParm;
import com.itmk.web.order.entity.SendParm;
import com.itmk.web.order.entity.UserOrder;
import com.itmk.web.order.entity.WxOrderParm;
import com.itmk.web.order.entity.*;
import com.itmk.web.order.service.UserOrderService;
import com.itmk.web.order_detail.entity.UserOrderDetail;
import com.itmk.web.order_detail.service.UserOrderDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/wxapi/order")
public class UserOrderController {
@Autowired
private UserOrderService userOrderService;
@Autowired
private UserOrderDetailService userOrderDetailService;
//下单
@PostMapping("/splaceOrder")
public ResultVo splaceOrder(@RequestBody OrderParm parm){
public ResultVo splaceOrder(@RequestBody OrderParm parm) {
userOrderService.splaceOrder(parm);
return ResultUtils.success("提交成功!");
}
//查询订单
@GetMapping("/getOrderList")
public ResultVo getOrderList(WxOrderParm parm){
public ResultVo getOrderList(WxOrderParm parm) {
IPage<UserOrder> orderList = userOrderService.getOrderList(parm);
return ResultUtils.success("查询成功!",orderList);
return ResultUtils.success("查询成功!", orderList);
}
//pc端查询订单
//查询pc订单
@GetMapping("/getPcOrderList")
public ResultVo getPcOrderList(WxOrderParm parm){
public ResultVo getPcOrderList(WxOrderParm parm) {
IPage<UserOrder> orderList = userOrderService.getPcOrderList(parm);
return ResultUtils.success("查询成功!",orderList);
return ResultUtils.success("查询成功!", orderList);
}
//发货
@PutMapping("/sendOrder")
public ResultVo sendOrder(@RequestBody SendParm parm){
public ResultVo sendOrder(@RequestBody SendParm parm) {
//判断订单是否被取消
QueryWrapper<UserOrder> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserOrder::getOrderId,parm.getOrderId()).eq(UserOrder::getStatus,"3");
queryWrapper.lambda().eq(UserOrder::getOrderId, parm.getOrderId()).eq(UserOrder::getStatus, "3");
UserOrder one = userOrderService.getOne(queryWrapper);
if(one != null){
if (one != null) {
return ResultUtils.error("订单已被取消,不能发货!");
}
//更新条件
LambdaUpdateWrapper<UserOrder> query = new LambdaUpdateWrapper<>();
query.eq(UserOrder::getOrderId,parm.getOrderId())
.set(UserOrder::getStatus,"1");
if(userOrderService.update(query)){
query.eq(UserOrder::getOrderId, parm.getOrderId())
.set(UserOrder::getStatus, "1");
if (userOrderService.update(query)) {
return ResultUtils.success("更新成功!");
}
return ResultUtils.error("更新失败!");
@ -62,21 +66,81 @@ public class UserOrderController {
//取消订单
@PostMapping("/cancelOrder")
public ResultVo cancelOrder(@RequestBody SendParm parm){
public ResultVo cancelOrder(@RequestBody SendParm parm) {
//如果已发货,不能取消
QueryWrapper<UserOrder> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserOrder::getOrderId,parm.getOrderId()).eq(UserOrder::getStatus,"1");
queryWrapper.lambda().eq(UserOrder::getOrderId, parm.getOrderId()).eq(UserOrder::getStatus, "1");
UserOrder one = userOrderService.getOne(queryWrapper);
if(one != null){
if (one != null) {
return ResultUtils.error("订单已发货,不能取消!");
}
//更新条件
LambdaUpdateWrapper<UserOrder> query = new LambdaUpdateWrapper<>();
query.eq(UserOrder::getOrderId,parm.getOrderId())
.set(UserOrder::getStatus,"3");
if(userOrderService.update(query)){
query.eq(UserOrder::getOrderId, parm.getOrderId())
.set(UserOrder::getStatus, "3");
if (userOrderService.update(query)) {
return ResultUtils.success("取消成功!");
}
return ResultUtils.error("取消失败!");
}
//确定收货
@PostMapping("/confirmOrder")
public ResultVo confirmOrder(@RequestBody SendParm parm) {
//更新条件
LambdaUpdateWrapper<UserOrder> query = new LambdaUpdateWrapper<>();
query.eq(UserOrder::getOrderId, parm.getOrderId())
.set(UserOrder::getStatus, "2");
if (userOrderService.update(query)) {
return ResultUtils.success("收货成功!");
}
return ResultUtils.error("收货失败!");
}
// 在 UserOrderController 中添加删除订单接口
@DeleteMapping("/{orderId}")
public ResultVo deleteOrder(@PathVariable Long orderId) {
// 先删除订单详情
QueryWrapper<UserOrderDetail> detailQuery = new QueryWrapper<>();
detailQuery.lambda().eq(UserOrderDetail::getOrderId, orderId);
userOrderDetailService.remove(detailQuery);
// 再删除订单
if (userOrderService.removeById(orderId)) {
return ResultUtils.success("删除成功!");
}
return ResultUtils.error("删除失败!");
}
//首页统计 0:日 1月 2
@GetMapping("/getTotal")
public ResultVo getTotal(String type) {
try {
List<SunList> list = null;
switch (type) {
case "1":
list = userOrderService.getMonths();
break;
case "2":
list = userOrderService.getYears();
break;
default:
list = userOrderService.getDays();
}
Echarts echarts = new Echarts();
if (list != null && list.size() > 0) {
for (SunList sun : list) {
// 添加空值检查
if (sun.getDays() != null && sun.getPrice() != null) {
echarts.getNames().add(sun.getDays());
echarts.getValues().add(sun.getPrice());
}
}
}
return ResultUtils.success("查询成功", echarts);
} catch (Exception e) {
e.printStackTrace();
return ResultUtils.error("统计查询失败: " + e.getMessage());
}
}
}

@ -0,0 +1,13 @@
package com.itmk.web.order.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Data
public class Echarts {
private List<String> names = new ArrayList<>();
private List<BigDecimal> values = new ArrayList<>();
}

@ -0,0 +1,11 @@
package com.itmk.web.order.entity;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class SunList {
private String days;
private BigDecimal price;
}

@ -1,7 +1,16 @@
package com.itmk.web.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itmk.web.order.entity.SunList;
import com.itmk.web.order.entity.UserOrder;
import java.util.List;
public interface UserOrderMapper extends BaseMapper<UserOrder> {
//按天查询
List<SunList> getDays();
//按月查询
List<SunList> getMonths();
//按年查询
List<SunList> getYears();
}

@ -3,13 +3,20 @@ package com.itmk.web.order.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itmk.web.order.entity.OrderParm;
import com.itmk.web.order.entity.SunList;
import com.itmk.web.order.entity.UserOrder;
import com.itmk.web.order.entity.WxOrderParm;
import java.util.List;
public interface UserOrderService extends IService<UserOrder> {
void splaceOrder(OrderParm parm);
IPage<UserOrder> getOrderList(WxOrderParm parm);
IPage<UserOrder> getPcOrderList(WxOrderParm parm);
//按天查询
List<SunList> getDays();
//按月查询
List<SunList> getMonths();
//按年查询
List<SunList> getYears();
}

@ -5,10 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itmk.web.order.entity.OrderParm;
import com.itmk.web.order.entity.ParmDetail;
import com.itmk.web.order.entity.UserOrder;
import com.itmk.web.order.entity.WxOrderParm;
import com.itmk.web.order.entity.*;
import com.itmk.web.order.mapper.UserOrderMapper;
import com.itmk.web.order.service.UserOrderService;
import com.itmk.web.order_detail.entity.UserOrderDetail;
@ -57,7 +54,8 @@ public class UserOrderServiceImpl extends ServiceImpl<UserOrderMapper, UserOrder
//查询条件
QueryWrapper<UserOrder> query = new QueryWrapper<>();
query.lambda().eq(UserOrder::getOpenid,parm.getOpenid())
.eq(StringUtils.isNotEmpty(parm.getType()),UserOrder::getStatus,parm.getType());
.eq(StringUtils.isNotEmpty(parm.getType()),UserOrder::getStatus,parm.getType())
.orderByDesc(UserOrder::getCreateTime);
//构造分页对象
IPage<UserOrder> page = new Page<>(parm.getCurrentPage(),parm.getPageSize());
//查询订单主表
@ -77,23 +75,40 @@ public class UserOrderServiceImpl extends ServiceImpl<UserOrderMapper, UserOrder
@Override
public IPage<UserOrder> getPcOrderList(WxOrderParm parm) {
//查询订单
//查询条件
QueryWrapper<UserOrder> query = new QueryWrapper<>();
query.lambda().like(StringUtils.isNotEmpty(parm.getUserName()),UserOrder::getUserName,parm.getUserName())
.eq(StringUtils.isNotEmpty(parm.getType()),UserOrder::getStatus,parm.getType())
query.lambda().eq(StringUtils.isNotEmpty(parm.getType()),UserOrder::getStatus,parm.getType())
.like(StringUtils.isNotEmpty(parm.getUserName()),UserOrder::getUserName,parm.getUserName())
.orderByDesc(UserOrder::getCreateTime);
//构造分页对象
IPage<UserOrder> page = new Page<>(parm.getCurrentPage(),parm.getPageSize());
//查询订单主表
IPage<UserOrder> order = this.baseMapper.selectPage(page, query);
//有数据,查询子订单
if(order.getRecords().size()>0){
for (int i = 0;i<order.getRecords().size();i++){
//查询子表
if(order.getRecords().size() > 0){
for(int i=0;i<order.getRecords().size();i++){
QueryWrapper<UserOrderDetail> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(UserOrderDetail::getOrderId,order.getRecords().get(i).getOrderId());
List<UserOrderDetail> list = userOrderDetailService.list(queryWrapper);
//设置订单对应的商品
order.getRecords().get(i).setGoodsList(list);
}
}
return order;
}
@Override
public List<SunList> getDays() {
return this.baseMapper.getDays();
}
@Override
public List<SunList> getMonths() {
return this.baseMapper.getMonths();
}
@Override
public List<SunList> getYears() {
return this.baseMapper.getYears();
}
}

@ -5,22 +5,27 @@ import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itmk.utils.JwtUtils;
import com.itmk.utils.ResultUtils;
import com.itmk.utils.ResultVo;
import com.itmk.web.user.entity.SysUser;
import com.itmk.web.user.entity.UserPageParm;
import com.itmk.web.user.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/user")//所有含有/api/user的请求都交给这个控制器处理
public class SysUserController {
// 注入服务类
// 注入服务类
@Autowired
private SysUserService sysUserService;
// 新增用户
// 新增用户
@PostMapping//使用post请求
public ResultVo addUser(@RequestBody SysUser sysUser) {
// 1. 单独检查用户名
@ -43,41 +48,110 @@ public class SysUserController {
: ResultUtils.error("新增用户失败!");
}
// 编辑用户
// 编辑用户
@PutMapping
public ResultVo editUser(@RequestBody SysUser sysUser){
if(sysUserService.updateById(sysUser)){
public ResultVo editUser(@RequestBody SysUser sysUser) {
if (sysUserService.updateById(sysUser)) {
return ResultUtils.success("编辑用户成功!", sysUser);
}
return ResultUtils.error("编辑用户失败!");
}
// 删除用户
// 删除用户
@DeleteMapping("/{userId}")//delete请求时需要指定参数传递参数用户id
// 接收参数
public ResultVo deleteUser(@PathVariable Long userId){
if(sysUserService.removeById(userId)){
public ResultVo deleteUser(@PathVariable Long userId) {
if (sysUserService.removeById(userId)) {
return ResultUtils.success("删除用户成功!");
}
return ResultUtils.error("删除用户失败!");
}
// 列表查询
// 列表查询
// 列表查询需要分页
@GetMapping("/list")
public ResultVo getList(UserPageParm parm){
public ResultVo getList(UserPageParm parm) {
// 构造分页对象
IPage<SysUser> page = new Page<>(parm.getCurrentPage(),parm.getPageSize());
IPage<SysUser> page = new Page<>(parm.getCurrentPage(), parm.getPageSize());
// 构造查询条件
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
// 当parm.getName()或parm.getPhone()不为空时,设置查询条件,根据姓名和手机号模糊查询
queryWrapper.lambda().like(!StringUtils.isEmpty(parm.getName()),SysUser::getName,parm.getName())
.like(!StringUtils.isEmpty(parm.getPhone()),SysUser::getPhone,parm.getPhone())
queryWrapper.lambda().like(!StringUtils.isEmpty(parm.getName()), SysUser::getName, parm.getName())
.like(!StringUtils.isEmpty(parm.getPhone()), SysUser::getPhone, parm.getPhone())
// 根据姓名升序排序
.orderByAsc(SysUser::getName);
IPage<SysUser> list = sysUserService.page(page,queryWrapper);
IPage<SysUser> list = sysUserService.page(page, queryWrapper);
return ResultUtils.success("查询成功!", list);
}
return ResultUtils.success("查询成功!",list);
// 用户注册接口
@PostMapping("/register")
public ResultVo register(@RequestBody SysUser sysUser) {
// 检查用户名是否已存在
if (sysUserService.lambdaQuery()
.eq(SysUser::getUsername, sysUser.getUsername())
.exists()) {
return ResultUtils.error("用户名已存在!");
}
// 检查手机号是否已存在
if (sysUserService.lambdaQuery()
.eq(SysUser::getPhone, sysUser.getPhone())
.exists()) {
return ResultUtils.error("手机号已被使用!");
}
// 对密码进行MD5加密
String md5Password = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes());
sysUser.setPassword(md5Password);
// 保存用户
if (sysUserService.save(sysUser)) {
return ResultUtils.success("注册成功!");
}
return ResultUtils.error("注册失败!");
}
// 用户登录接口
@PostMapping("/login")
public ResultVo login(@RequestBody SysUser sysUser) {
// 对密码进行MD5加密
String md5Password = DigestUtils.md5DigestAsHex(sysUser.getPassword().getBytes());
// 查询用户
QueryWrapper<SysUser> query = new QueryWrapper<>();
query.lambda().eq(SysUser::getUsername, sysUser.getUsername())
.eq(SysUser::getPassword, md5Password);
SysUser user = sysUserService.getOne(query);
if (user != null) {
// 生成token
String token = JwtUtils.generateToken(user.getUsername());
// 返回用户信息和token
Map<String, Object> result = new HashMap<>();
result.put("user", user);
result.put("token", token);
return ResultUtils.success("登录成功!", result);
}
return ResultUtils.error("用户名或密码错误!");
}
// 根据token获取用户信息
@GetMapping("/getUserInfo")
public ResultVo getUserInfo(@RequestHeader("token") String token) {
String username = JwtUtils.getClaimsByToken(token).getSubject();
SysUser user = sysUserService.lambdaQuery()
.eq(SysUser::getUsername, username)
.one();
if (user != null) {
// 不返回密码
user.setPassword(null);
return ResultUtils.success("查询成功", user);
}
return ResultUtils.error("用户不存在");
}
}

@ -15,14 +15,19 @@ public class WxUserController {
@PostMapping("/saveOrUpdate")
public ResultVo saveOrUpdate(@RequestBody WxUser wxUser){
wxUserService.saveOrUpdate(wxUser);
return ResultUtils.success("更新成功!");
// 使用 MyBatis Plus 自带的 saveOrUpdate 方法
boolean result = wxUserService.saveOrUpdate(wxUser);
if(result) {
return ResultUtils.success("更新成功!");
} else {
return ResultUtils.error("更新失败!");
}
}
//查询头像昵称
@GetMapping("/getUserInfo")
public ResultVo getUserInfo(String openid) {
WxUser user = wxUserService.getById(openid);
return ResultUtils.error("查询成功!",user);
return ResultUtils.success("查询成功!",user);
}
}

@ -1,6 +1,7 @@
package com.itmk.web.wxapi.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.itmk.utils.ResultUtils;
import com.itmk.utils.ResultVo;
import com.itmk.web.goods.entity.SysGoods;
@ -32,6 +33,21 @@ public class HomeController {
QueryWrapper<SysBanner> query = new QueryWrapper<>();
query.lambda().eq(SysBanner::getStatus,"1");
List<SysBanner> list = sysBannerService.list(query);
if(list.size() > 0){
for (int i=0;i<list.size();i++){
if(StringUtils.isNotEmpty(list.get(i).getGoodsId().toString())){
//查询商品
SysGoods goods = sysGoodsService.getById(list.get(i).getGoodsId());
list.get(i).setSysGoods(goods);
//查询价格
QueryWrapper<SysGoodsSpecs> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(SysGoodsSpecs::getGoodsId,list.get(i).getGoodsId())
.orderByAsc(SysGoodsSpecs::getOrderNum);
List<SysGoodsSpecs> specs = sysGoodsSpecsService.list(queryWrapper);
list.get(i).getSysGoods().setSpecs(specs);
}
}
}
return ResultUtils.success("查询成功",list);
}

@ -2,6 +2,27 @@
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itmk.web.user_order.mapper.UserOrderMapper">
<mapper namespace="com.itmk.web.order.mapper.UserOrderMapper">
<select id="getDays" resultType="com.itmk.web.order.entity.SunList">
select DATE_FORMAT(create_time, '%Y-%m-%d') days, sum(price) price
from `user_order`
where `status` = '2'
group by days
order by days asc
</select>
<select id="getMonths" resultType="com.itmk.web.order.entity.SunList">
select DATE_FORMAT(create_time, '%Y-%m') days, sum(price) price
from `user_order`
where `status` = '2'
group by days
order by days asc
</select>
<select id="getYears" resultType="com.itmk.web.order.entity.SunList">
select DATE_FORMAT(create_time, '%Y') days, sum(price) price
from `user_order`
where `status` = '2'
group by days
order by days asc
</select>
</mapper>

@ -0,0 +1,41 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itmk.web.wx_user.mapper.WxUserMapper">
<insert id="saveOrUpdateInfo" parameterType="com.itmk.web.wx_user.entity.WxUser">
insert into wx_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="user.openid != null and user.openid !=''">
openid,
</if>
<if test="user.nickName != null and user.nickName !=''">
nick_name,
</if>
<if test="user.avatarUrl != null and user.avatarUrl !=''">
avatar_url,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="user.openid != null and user.openid !=''">
#{user.openid},
</if>
<if test="user.nickName != null and user.nickName !=''">
#{user.nickName},
</if>
<if test="user.avatarUrl != null and user.avatarUrl !=''">
#{user.avatarUrl},
</if>
</trim>
on duplicate key update
<trim>
<if test="user.nickName != null and user.nickName !=''">
nick_name = #{user.nickName},
</if>
<if test="user.avatarUrl != null and user.avatarUrl !=''">
avatar_url = #{user.avatarUrl},
</if>
</trim>
</insert>
</mapper>

@ -50,6 +50,9 @@
import {
carStore
} from '../../store/car.js'
import {
orderStore
} from '../../store/order.js'
//store
const store = carStore();
const show = ref(true);
@ -140,6 +143,22 @@
})
//
const confirm = (item) => {
//
const selectedGoods = store.carList.filter(item => item.flag);
//
if (selectedGoods.length === 0) {
uni.showToast({
title: '请选择商品',
icon: 'none'
});
return;
}
// orderStore
const ostore = orderStore();
ostore.addOrderList(selectedGoods);
uni.navigateTo({
url: '../confirm/confirm'
});

@ -91,6 +91,19 @@
margin: 5px 10px 5px 0px;
}
.collect-container {
height: 100vh;
background-color: #f8f8f8;
.scroll-view {
height: 100%;
.empty-tip {
padding-top: 200rpx;
}
}
}
.item {
padding: 40rpx 20rpx;
display: flex;

@ -273,7 +273,25 @@
onLoad((options) => {
userLogin()
const goods = JSON.parse(options.goods)
//
let goodsParam = options.goods;
try {
// index.vue
goodsParam = JSON.parse(goodsParam);
} catch (e) {
// category.vue
try {
goodsParam = JSON.parse(decodeURIComponent(goodsParam));
} catch (e2) {
uni.showToast({
title: '参数错误',
icon: 'none'
});
return;
}
}
const goods = goodsParam;
swipperList.value = goods.goodsImage.split(',')
console.log(goods)
goodsUnit.value = goods.goodsUnit

@ -91,12 +91,13 @@
flowList.value = res.data;
}
}
//
// -
const toDetails = (item) => {
console.log(item)
//details.vue
//
const encodedGoods = encodeURIComponent(JSON.stringify(item))
uni.navigateTo({
url: '../detail/detail?goods=' + JSON.stringify(item)
url: '../detail/detail?goods=' + encodedGoods
});
}
onLoad(() => {

@ -18,8 +18,8 @@
<view class="u-m-t-20">
<u-cell-group>
<u-cell-item @click="toOrder" icon="star" title="我的订单"></u-cell-item>
<u-cell-item icon="photo" title="我的收藏"></u-cell-item>
<u-cell-item icon="coupon" title="我的地址"></u-cell-item>
<u-cell-item @click="toCollect" icon="photo" title="我的收藏"></u-cell-item>
<u-cell-item @click="toAddresslist" icon="coupon" title="我的地址"></u-cell-item>
</u-cell-group>
</view>
</view>
@ -102,7 +102,7 @@
}
const onNickName = (e) => {
console.log(e)
uni.createSelectorQuery().in(instance) // in(this)
uni.createSelectorQuery().in(instance)
.select("#nickname-input")
.fields({
properties: ["value"],
@ -233,6 +233,21 @@
url: '../order/order'
});
}
//
const toCollect = () => {
uni.navigateTo({
url: '../collect/collect'
});
}
//
const toAddresslist = () => {
uni.navigateTo({
url: '../addresslist/addresslist'
});
}
onShow(() => {
userLogin()
// getUserInfo()

@ -3,4 +3,18 @@ export type CommentListParm = {
currentPage:number;
pageSize:number;
total:number; //分页的总条数
}
}
// 评论项类型
export type CommentItem = {
commentId: number;
goodsId: number;
orderId: number;
openid: string;
commentText: string;
nickName: string;
avatarUrl: string;
createTime: string;
goodsName: string;
goodsImage: string;
}

@ -2,10 +2,11 @@ import http from "../../http";
import type { CommentListParm } from "./CommentModel";
//列表
export const getListApi = (parm:CommentListParm)=>{
return http.get("/wxapi/comment/pcCommentList",parm)
export const getListApi = (parm: CommentListParm) => {
return http.get("/wxapi/comment/pcCommentList", parm);
}
//删除
export const deleteApi = (commentId:string)=>{
return http.delete(`/wxapi/comment/${commentId}`)
export const deleteApi = (commentId: string) => {
return http.delete(`/wxapi/comment/${commentId}`);
}

@ -8,4 +8,14 @@ export const gePcOrdertListApi = (parm: OrderListParm) => {
// 发货
export const sendOrderApi = (orderId: string) => {
return http.put("/wxapi/order/sendOrder", { orderId: orderId })
}
// 删除订单
export const deleteOrderApi = (orderId: string) => {
return http.delete(`/wxapi/order/${orderId}`);
};
//统计
export const getTotalApi = (type:string)=>{
return http.get("/wxapi/order/getTotal",{type:type})
}

@ -1,5 +1,5 @@
import http from "../../http";
import type { ListUserParm, UserModel } from "./userModel";
import type { ListUserParm, UserModel, RegisterModel } from "./userModel";
// 新增接口
export const addUserApi = (parm:UserModel)=>{
@ -19,4 +19,19 @@ export const editUserApi = (parm:UserModel)=>{
// 删除接口
export const deleteUserApi = (userId:string)=>{
return http.delete(`/api/user/${userId}`)
}
// 用户登录
export const loginApi = (parm: { username: string; password: string }) => {
return http.post('/api/user/login', parm)
}
// 用户注册
export const registerApi = (parm: RegisterModel) => {
return http.post('/api/user/register', parm)
}
// 获取用户信息
export const getUserInfoApi = () => {
return http.get('/api/user/getUserInfo')
}

@ -17,4 +17,15 @@ export type UserModel = {
email: string;
sex: string;
name: string;
}
// 注册表单类型
export type RegisterModel = {
username: string;
password: string;
phone: string;
email: string;
sex: string;
name: string;
confirmPassword?: string;
}

@ -1,65 +1,67 @@
import type { CommentListParm } from "../../api/comment/CommentModel";
import { reactive ,ref,onMounted,nextTick} from "vue";
import {getListApi,deleteApi} from '../../api/comment/index'
import { reactive, ref, onMounted, nextTick } from "vue";
import { getListApi, deleteApi } from '../../api/comment/index'
import useInstance from "@/hooks/useInstance";
import { ElMessage } from "element-plus";
export default function useCommentTable(){
const {global} = useInstance()
export default function useCommentTable() {
const { global } = useInstance()
//表格高度
const tableHeight = ref(0)
//接收表格数据
const tableList = ref([])
//列表查询的参数
const listParm = reactive<CommentListParm>({
currentPage:1,
pageSize:10,
total:0
currentPage: 1,
pageSize: 10,
total: 0
})
//列表
const getList = async()=>{
const getList = async () => {
let res = await getListApi(listParm)
if(res && res.code == 200){
if (res && res.code == 200) {
tableList.value = res.data.records;
listParm.total = res.data.total;
}
}
//删除
const deleteBtn = async(commentId:string)=>{
const deleteBtn = async (commentId: string) => {
const confirm = await global.$myconfirm('确定删除吗?')
if(confirm){
if (confirm) {
let res = await deleteApi(commentId)
if(res && res.code == 200){
if (res && res.code == 200) {
ElMessage.success(res.msg)
getList()
}
}
}
//搜索
const searchBtn = ()=>{
const searchBtn = () => {
getList()
}
//重置
const resetBtn = ()=>{
const resetBtn = () => {
listParm.currentPage = 1;
getList()
}
//页容量改变时触发
const sizeChange = (size:number)=>{
const sizeChange = (size: number) => {
listParm.pageSize = size;
getList()
}
//页数改变触发
const currentChange = (page:number)=>{
const currentChange = (page: number) => {
listParm.currentPage = page;
getList()
}
onMounted(()=>{
onMounted(() => {
getList()
nextTick(()=>{
nextTick(() => {
tableHeight.value = window.innerHeight - 180
})
})
return{
return {
listParm,
getList,
searchBtn,
@ -68,6 +70,6 @@ export default function useCommentTable(){
sizeChange,
currentChange,
tableHeight,
deleteBtn
deleteBtn,
}
}

@ -1,6 +1,6 @@
import type { OrderListParm } from '../../api/order/OrderModel'
import { nextTick, onMounted, reactive, ref } from 'vue'
import { gePcOrdertListApi, sendOrderApi } from '../../api/order'
import { gePcOrdertListApi, sendOrderApi, deleteOrderApi } from '../../api/order' // 导入 deleteOrderApi
import useInstance from '@/hooks/useInstance'
export default function useOrderTable() {
const { global } = useInstance()
@ -34,6 +34,7 @@ export default function useOrderTable() {
const resetBtn = () => {
listParm.currentPage = 1;
listParm.type = ''
listParm.userName = '' // 重置搜索条件
getList()
}
//页容量改变触发
@ -52,7 +53,29 @@ export default function useOrderTable() {
if (confirm) {
let res = await sendOrderApi(orderId)
if (res && res.code == 200) {
global.$message({ type: 'success', message: '发货成功!' })
getList()
} else {
global.$message({ type: 'error', message: res?.msg || '发货失败!' })
}
}
}
// 删除订单
const deleteOrder = async (orderId: string) => {
let confirm = await global.$myconfirm('确定删除该订单吗?此操作不可恢复。')
if (confirm) {
try {
let res = await deleteOrderApi(orderId)
if (res && res.code == 200) {
global.$message({ type: 'success', message: '删除成功!' })
getList()
} else {
global.$message({ type: 'error', message: res?.msg || '删除失败!' })
}
} catch (error) {
console.error('删除订单失败:', error)
global.$message({ type: 'error', message: '删除失败!' })
}
}
}
@ -63,9 +86,11 @@ export default function useOrderTable() {
tableHeight.value = window.innerHeight - 220
})
})
return {
tableList,
sendOrder,
deleteOrder, // 返回删除订单方法
listParm,
getList,
searchBtn,

@ -33,10 +33,10 @@ class Http {
private interceptors() {
// 请求发送之前的拦截器通常用来配置、携带token
this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
// 设置token到请求头部
let token = "";
// 从localStorage获取token
const token = localStorage.getItem('token');
if (token) {
config.headers!['token'] = token;
config.headers!['Authorization'] = `Bearer ${token}`;
}
return config;
}, (error: any) => {
@ -59,58 +59,64 @@ class Http {
console.log('进入错误')
error.data = {}
if (error && error.response) {
switch (error.response.status) {
case 400:
error.data.msg = '错误请求'
ElMessage.error(error.data.msg)
break
case 401:
error.data.msg = '未授权,请重新登录'
ElMessage.error(error.data.msg)
break
case 403:
error.data.msg = '拒绝访问'
ElMessage.error(error.data.msg)
break
case 404:
error.data.msg = '请求错误,未找到该资源'
ElMessage.error(error.data.msg)
break
case 405:
error.data.msg = '请求方法未允许'
ElMessage.error(error.data.msg)
break
case 408:
error.data.msg = '请求超时'
ElMessage.error(error.data.msg)
break
case 500:
error.data.msg = '服务器端出错'
ElMessage.error(error.data.msg)
break
case 501:
error.data.msg = '网络未实现'
ElMessage.error(error.data.msg)
break
case 502:
error.data.msg = '网络错误'
ElMessage.error(error.data.msg)
break
case 503:
error.data.msg = '服务不可用'
ElMessage.error(error.data.msg)
break
case 504:
error.data.msg = '网络超时'
ElMessage.error(error.data.msg)
break
case 505:
error.data.msg = 'http版本不支持该请求'
ElMessage.error(error.data.msg)
break
default:
error.data.msg = `连接错误${error.response.status}`
ElMessage.error(error.data.msg)
if (error.response.status === 401) {
error.data.msg = '未授权,请重新登录'
ElMessage.error(error.data.msg)
// 清除本地存储的token和用户信息
localStorage.removeItem('token')
localStorage.removeItem('user')
// 跳转到登录页
window.location.href = '/login'
} else {
switch (error.response.status) {
case 400:
error.data.msg = '错误请求'
ElMessage.error(error.data.msg)
break
case 403:
error.data.msg = '拒绝访问'
ElMessage.error(error.data.msg)
break
case 404:
error.data.msg = '请求错误,未找到该资源'
ElMessage.error(error.data.msg)
break
case 405:
error.data.msg = '请求方法未允许'
ElMessage.error(error.data.msg)
break
case 408:
error.data.msg = '请求超时'
ElMessage.error(error.data.msg)
break
case 500:
error.data.msg = '服务器端出错'
ElMessage.error(error.data.msg)
break
case 501:
error.data.msg = '网络未实现'
ElMessage.error(error.data.msg)
break
case 502:
error.data.msg = '网络错误'
ElMessage.error(error.data.msg)
break
case 503:
error.data.msg = '服务不可用'
ElMessage.error(error.data.msg)
break
case 504:
error.data.msg = '网络超时'
ElMessage.error(error.data.msg)
break
case 505:
error.data.msg = 'http版本不支持该请求'
ElMessage.error(error.data.msg)
break
default:
error.data.msg = `连接错误${error.response.status}`
ElMessage.error(error.data.msg)
}
}
} else {
error.data.msg = "连接到服务器失败"

@ -1,36 +1,80 @@
<template>
<!-- 添加下拉菜单组件 -->
<el-dropdown>
<span class="el-dropdown-link">
<!-- <img src="../../assets/user.png" alt=""> -->
<!-- 将下拉菜单文字替换为用户头像 -->
<img class="uimage" src="@/assets/user.png" alt="">
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>退出登录</el-dropdown-item>
<!-- 目前只需要退出登录功能注释掉多余action -->
<!-- <el-dropdown-item>Action 2</el-dropdown-item>
<el-dropdown-item>Action 3</el-dropdown-item>
<el-dropdown-item disabled>Action 4</el-dropdown-item>
<el-dropdown-item divided>Action 5</el-dropdown-item> -->
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="user-info">
<span class="username">{{ userName }}</span>
<el-dropdown>
<span class="el-dropdown-link">
<img class="uimage" src="@/assets/user.png" alt="">
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleLogout">退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const userName = ref('')
//
onMounted(() => {
const userStr = localStorage.getItem('user')
if (userStr) {
try {
const user = JSON.parse(userStr)
userName.value = user.name || user.username
} catch (e) {
console.error('解析用户信息失败', e)
}
}
})
// 退
const handleLogout = () => {
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// token
localStorage.removeItem('token')
localStorage.removeItem('user')
// 退
ElMessage.success('退出登录成功')
//
router.push('/login')
}).catch(() => {
//
ElMessage.info('已取消退出')
})
}
</script>
<style scoped lang="scss">
//
.uimage{
height: 45px;
width: 45px;
border-radius: 50%;
cursor: pointer;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.username {
font-size: 14px;
color: #606266;
}
//
.uimage{
height: 45px;
width: 45px;
border-radius: 50%;
cursor: pointer;
}
</style>

@ -35,3 +35,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// 全局注册消息弹出框组件
app.config.globalProperties.$myconfirm = myConfirm
//echarts
import * as echarts from 'echarts'
app.config.globalProperties.$echarts = echarts;

@ -26,7 +26,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'dashboard',
component: () => import('@/views/dashboard/Index.vue'),
meta: {
title: '首页'
title: '首页',
requiresAuth: true // 添加认证要求
},
},
{
@ -34,7 +35,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'banner',
component: () => import('@/views/banner/Index.vue'),
meta: {
title: '广告管理'
title: '广告管理',
requiresAuth: true // 添加认证要求
},
},
{
@ -42,7 +44,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'category',
component: () => import('@/views/category/Index.vue'),
meta: {
title: '菜品分类'
title: '菜品分类',
requiresAuth: true // 添加认证要求
},
},
{
@ -50,7 +53,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'comment',
component: () => import('@/views/comment/Index.vue'),
meta: {
title: '评论管理'
title: '评论管理',
requiresAuth: true // 添加认证要求
},
},
{
@ -58,7 +62,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'goods',
component: () => import('@/views/goods/Index.vue'),
meta: {
title: '菜品管理'
title: '菜品管理',
requiresAuth: true // 添加认证要求
},
},
{
@ -66,7 +71,8 @@ const routes: Array<RouteRecordRaw> = [
name: 'order',
component: () => import('@/views/order/Index.vue'),
meta: {
title: '订单管理'
title: '订单管理',
requiresAuth: true // 添加认证要求
},
},
{
@ -74,10 +80,25 @@ const routes: Array<RouteRecordRaw> = [
name: 'user',
component: () => import('@/views/user/Index.vue'),
meta: {
title: '用户管理'
title: '用户管理',
requiresAuth: true // 添加认证要求
},
},
}
]
},
// 登录和注册作为独立路由不在Layout中
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/Index.vue'),
meta: { title: '登录' }
},
{
path: '/register',
name: 'Register',
component: () => import('@/views/register/Index.vue'),
meta: { title: '注册' }
}
]
@ -86,4 +107,23 @@ const router = createRouter({
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
// 检查路由是否需要认证
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
if (requiresAuth && !token) {
// 如果需要认证但没有token跳转到登录页
next('/login')
} else if ((to.path === '/login' || to.path === '/register') && token) {
// 如果已登录但尝试访问登录/注册页,跳转到首页
next('/dashboard')
} else {
// 否则继续导航
next()
}
})
export default router

@ -1,55 +1,295 @@
<template>
<el-main>
<!-- 表格 -->
<el-table :height="tableHeight" :data="tableList" border stripe>
<el-table-column label="名称" prop="goodsName"></el-table-column>
<el-table-column label="商品图片" prop="goodsImage">
<template #default="scope">
<el-image
:src="scope.row.goodsImage.split(',')[0]"
style="height: 60px; width: 60px; border-radius: 50%"
></el-image>
</template>
</el-table-column>
<el-table-column label="昵称" prop="nickName"></el-table-column>
<el-table-column label="头像" prop="avatarUrl">
<template #default="scope">
<el-image
:src="imgUrl+scope.row.avatarUrl"
style="height: 60px; width: 60px; border-radius: 50%"
></el-image>
</template>
</el-table-column>
<el-table-column label="时间" prop="createTime"></el-table-column>
<el-table-column label="操作" width="220" align="center">
<template #default="scope">
<el-button :icon="Delete" @click="deleteBtn(scope.row.commentId)" type="danger" size="default">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
:current-page.sync="listParm.currentPage"
:page-sizes="[10,20, 40, 80, 100]"
:page-size="listParm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="listParm.total" background>
</el-pagination>
</el-main>
</template>
<el-main>
<!-- 预览遮罩层 -->
<div v-if="isPreviewing" class="preview-mask" @click.self="closePreview">
<div class="preview-close" @click="closePreview">×</div>
<div class="preview-image-container">
<img :src="previewImage" class="preview-image" />
</div>
<div class="preview-title">{{ previewTitle }}</div>
<div class="preview-hint">点击任意位置或按ESC键关闭预览</div>
</div>
<!-- 表格 -->
<el-table :height="tableHeight" :data="tableList" border stripe :class="{'preview-mode': isPreviewing}">
<el-table-column label="名称" prop="goodsName"></el-table-column>
<el-table-column label="商品图片" prop="goodsImage">
<template #default="scope">
<el-image :src="getGoodsImage(scope.row.goodsImage)"
style="height: 60px; width: 60px; border-radius: 8px; cursor: pointer;"
fit="cover"
@error="handleImageError"
@click="openPreview(getGoodsImage(scope.row.goodsImage), scope.row.goodsName)">
<template #error>
<div class="image-error">
<el-icon>
<Picture />
</el-icon>
</div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column label="昵称" prop="nickName"></el-table-column>
<el-table-column label="头像" prop="avatarUrl">
<template #default="scoped">
<el-image :src="getAvatarUrl(scoped.row.avatarUrl)"
style="height: 60px; width: 60px; border-radius: 50%; cursor: pointer;"
fit="cover"
@error="handleImageError"
@click="openPreview(getAvatarUrl(scoped.row.avatarUrl), scoped.row.nickName)">
<template #error>
<div class="image-error">
<el-icon>
<User />
</el-icon>
</div>
</template>
</el-image>
</template>
</el-table-column>
<el-table-column label="评论内容" prop="commentText" width="300">
<template #default="scope">
<el-tooltip :content="scope.row.commentText" placement="top">
<div class="comment-text">{{ scope.row.commentText }}</div>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="时间" prop="createTime" width="160"></el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="scope">
<el-button :icon="Delete"
@click="deleteBtn(scope.row.commentId)"
type="danger"
size="default"
:disabled="isPreviewing">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination @size-change="sizeChange"
@current-change="currentChange"
:current-page.sync="listParm.currentPage"
:page-sizes="[10, 20, 40, 80, 100]"
:page-size="listParm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="listParm.total"
background
:disabled="isPreviewing">
</el-pagination>
</el-main>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Delete, Picture, User } from "@element-plus/icons-vue";
import useCommentTable from "../../compositions/comment/useCommentTable";
// URL
const imgUrl = ref('http://localhost:8089/')
//
const { listParm, deleteBtn, tableList, sizeChange, currentChange, tableHeight } = useCommentTable();
//
const isPreviewing = ref(false);
const previewImage = ref('');
const previewTitle = ref('');
//
const openPreview = (imageUrl: string, title: string) => {
if (!imageUrl) return;
<script setup lang="ts">
import {ref} from 'vue'
import { Delete } from "@element-plus/icons-vue";
import useCommentTable from "../../compositions/comment/useCommentTable";
const imgUrl = ref('http://localhost:8089/')
//
const { listParm, deleteBtn, tableList,sizeChange ,currentChange,tableHeight} = useCommentTable();
</script>
previewImage.value = imageUrl;
previewTitle.value = title || '图片预览';
isPreviewing.value = true;
<style scoped></style>
//
document.body.style.overflow = 'hidden';
};
//
const closePreview = () => {
isPreviewing.value = false;
previewImage.value = '';
previewTitle.value = '';
//
document.body.style.overflow = '';
};
// ESC
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isPreviewing.value) {
closePreview();
}
};
onMounted(() => {
window.addEventListener('keydown', handleKeydown);
});
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown);
});
//
interface DebugInfo {
message: string;
status: string;
}
// 使
const urlDebugInfo = ref<DebugInfo[]>([]);
//
const addDebugInfo = (message: string, status: string) => {
urlDebugInfo.value.push({
message,
status
});
};
// URL - URL
const getAvatarUrl = (url: string) => {
if (!url) {
//
addDebugInfo('头像URL为空,使用默认占位符', 'error');
return '';
}
// URL
if (url.indexOf('http') === 0 || url.indexOf('https') === 0) {
urlDebugInfo.value.push({
message: `头像URL完整: ${url}`,
status: 'success'
});
return url;
}
//
let cleanUrl = url;
if (cleanUrl.indexOf('/') === 0) {
cleanUrl = cleanUrl.substring(1);
}
//
const fullUrl = imgUrl.value + cleanUrl;
urlDebugInfo.value.push({
message: `头像URL不完整使用完整路径: ${fullUrl}`,
status: 'success'
});
return fullUrl;
};
// URL
const getGoodsImage = (images: string) => {
if (!images) return '';
try {
const imageArray = images.split(',');
if (imageArray.length > 0) {
let firstImage = imageArray[0].trim();
if (firstImage.indexOf('http') === 0 || firstImage.indexOf('https') === 0) {
return firstImage;
} else {
return imgUrl.value + firstImage;
}
}
} catch (e) {
console.error('解析商品图片失败:', e);
}
return '';
}
//
const handleImageError = (e: any) => {
console.log('图片加载失败', e);
}
//
onMounted(() => {
//
})
</script>
<style scoped>
.image-error {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: #f5f7fa;
color: #909399;
border-radius: 50%;
}
.comment-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.preview-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.preview-image-container {
max-width: 80%;
max-height: 80%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
}
.preview-close {
position: absolute;
top: 20px;
right: 20px;
color: white;
font-size: 30px;
cursor: pointer;
background: rgba(0, 0, 0, 0.5);
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.preview-title {
color: white;
margin-top: 20px;
font-size: 18px;
text-align: center;
}
.preview-hint {
color: rgba(255, 255, 255, 0.7);
margin-top: 10px;
font-size: 14px;
}
.preview-mode {
opacity: 0.3;
pointer-events: none;
}
</style>

@ -1,33 +1,332 @@
<template>
<div>
首页面
<!-- <el-button type="primary" size="default" @click="onShow('测试')"></el-button> -->
<el-form style="margin: 20px 0px" label-width="80px" size="default">
<el-form-item>
<el-button style="padding: 0px 50px" @click="charts('0')" :type="activeChart === '0' ? 'primary' : ''">
日统计
</el-button>
<el-button @click="charts('1')" style="padding: 0px 50px" :type="activeChart === '1' ? 'primary' : ''">
月统计
</el-button>
<el-button @click="charts('2')" style="padding: 0px 50px" :type="activeChart === '2' ? 'primary' : ''">
年统计
</el-button>
</el-form-item>
</el-form>
<el-alert v-if="errorMessage" :title="errorMessage" type="error" show-icon style="margin-bottom: 20px;"
@close="errorMessage = ''" />
<el-skeleton :rows="5" animated v-if="loading" />
<!-- 确保图表容器始终存在使用v-show -->
<div v-show="!loading" style="min-height: 800px;">
<div ref="myChart" :style="{ width: '100%', height: '400px' }"></div>
<div ref="myChart1" :style="{ width: '100%', height: '350px' }"></div>
</div>
<SysDialog
:title="dialog.title"
:visible="dialog.visible"
:width="dialog.width"
:height="dialog.height"
@onClose="onClose"
@onConfirm="onConfirm"
>
<!-- 使用弹框组件在插槽中传递值 -->
<template #content>
测试弹框组件
</template>
</SysDialog>
</template>
<script setup lang="ts">
import SysDialog from '@/components/SysDialog.vue';
<script lang="ts" setup>
import { onMounted, reactive, ref, nextTick, onUnmounted } from "vue";
import useInstance from "@/hooks/useInstance";
import { getTotalApi } from '../../api/order/index'
// API
interface ApiResponse {
code: number;
data: {
names: string[];
values: number[];
};
message?: string;
}
const { global } = useInstance();
const myChart = ref<HTMLElement | null>(null);
const myChart1 = ref<HTMLElement | null>(null);
const loading = ref(false);
const errorMessage = ref("");
const activeChart = ref("0");
//
let echartInstance: any = null;
let echartInstance1: any = null;
let resizeObserver: ResizeObserver | null = null;
// 使 Promise
// 使 Promise
const wait = (ms: number, callback: () => void): void => {
setTimeout(callback, ms);
};
// DOM - 使 Promise
const ensureElementExists = (callback: () => void): void => {
let attempts = 0;
const checkElement = () => {
attempts++;
if (myChart.value && myChart1.value) {
callback();
} else if (attempts < 20) {
wait(50, checkElement);
} else {
console.warn("图表容器未找到,但继续执行");
callback();
}
};
checkElement();
};
//
const charts = async (type: string) => {
try {
activeChart.value = type;
loading.value = true;
errorMessage.value = "";
//
const res = await getTotalApi(type) as ApiResponse;
if (!res || res.code !== 200) {
throw new Error(res?.message || "获取数据失败");
}
// DOM
await nextTick();
// 使DOM
ensureElementExists(() => {
//
if (!myChart.value || !myChart1.value) {
throw new Error("图表容器未找到");
}
// import { reactive } from 'vue';
import useDialog from '@/hooks/useDialog';
const { dialog,onClose,onConfirm,onShow } = useDialog()
initCharts(res, type);
});
} catch (error: any) {
console.error("图表渲染错误:", error);
errorMessage.value = "加载图表数据失败: " + (error.message || "未知错误");
loading.value = false;
}
};
//
const initCharts = (res: ApiResponse, type: string) => {
try {
//
if (echartInstance) {
echartInstance.dispose();
}
if (echartInstance1) {
echartInstance1.dispose();
}
echartInstance = global.$echarts.init(myChart.value!);
echartInstance1 = global.$echarts.init(myChart1.value!);
let option = {
title: {
text: type === '0' ? "日统计直方图" : type === '1' ? "月统计直方图" : "年统计直方图",
left: 'center'
},
xAxis: {
type: "category",
data: res.data.names || [],
axisLabel: {
show: true,
interval: 0,
rotate: 30 //
},
},
yAxis: {
type: "value",
name: '金额'
},
series: [
{
data: res.data.values || [],
type: "bar",
itemStyle: {
color: function (params: any) {
var colorList = [
"#00A3E0", "#FFA100", "#ffc0cb", "#CCCCCC", "#BBFFAA",
"#749f83", "#ca8622", "#00A3E0", "#FFA100", "#ffc0cb"
];
return colorList[params.dataIndex % colorList.length];
},
},
},
],
tooltip: {
trigger: "axis",
backgroundColor: "rgba(32, 33, 36,.7)",
borderColor: "rgba(32, 33, 36,0.20)",
borderWidth: 1,
textStyle: {
color: "#fff",
fontSize: "12",
},
formatter: function (params: any) {
return `${params[0].name}<br/>金额: ${params[0].value}`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
}
};
let option1 = {
title: {
text: type === '0' ? "日统计折线图" : type === '1' ? "月统计折线图" : "年统计折线图",
left: 'center'
},
xAxis: {
type: "category",
data: res.data.names || [],
axisLabel: {
show: true,
interval: 0,
rotate: 30
},
},
yAxis: {
type: "value",
name: '金额'
},
series: [
{
data: res.data.values || [],
type: "line",
smooth: true,
itemStyle: {
color: '#00A3E0'
},
lineStyle: {
width: 3
}
},
],
tooltip: {
trigger: "axis",
backgroundColor: "rgba(32, 33, 36,.7)",
borderColor: "rgba(32, 33, 36,0.20)",
borderWidth: 1,
textStyle: {
color: "#fff",
fontSize: "12",
},
formatter: function (params: any) {
return `${params[0].name}<br/>金额: ${params[0].value}`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
containLabel: true
}
};
echartInstance.setOption(option);
echartInstance1.setOption(option1);
//
if (resizeObserver) {
resizeObserver.disconnect();
}
resizeObserver = new ResizeObserver(() => {
if (echartInstance) {
echartInstance.resize();
}
if (echartInstance1) {
echartInstance1.resize();
}
});
if (myChart.value) {
resizeObserver.observe(myChart.value);
}
if (myChart1.value) {
resizeObserver.observe(myChart1.value);
}
//
const resizeHandler = function () {
if (echartInstance) {
echartInstance.resize();
}
if (echartInstance1) {
echartInstance1.resize();
}
};
window.addEventListener('resize', resizeHandler);
// resizeHandler便
(window as any).__chartResizeHandler = resizeHandler;
} catch (error: any) {
console.error("图表初始化错误:", error);
errorMessage.value = "图表初始化失败: " + (error.message || "未知错误");
} finally {
loading.value = false;
}
};
onMounted(() => {
// 使setTimeoutDOM
setTimeout(() => {
charts('0');
}, 300); //
});
//
onUnmounted(() => {
if (echartInstance) {
echartInstance.dispose();
}
if (echartInstance1) {
echartInstance1.dispose();
}
//
if ((window as any).__chartResizeHandler) {
window.removeEventListener('resize', (window as any).__chartResizeHandler);
}
});
</script>
<style scoped>
<style lang="scss" scoped>
.chart-container {
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.charts-wrapper {
display: flex;
flex-direction: column;
flex: 1;
gap: 20px;
}
.chart-item {
width: 100%;
flex: 1;
min-height: 300px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.chart-container {
padding: 10px;
}
.charts-wrapper {
gap: 15px;
}
}
</style>

@ -0,0 +1,108 @@
<template>
<div class="login-container">
<el-card class="login-card">
<h2 class="login-title">用户登录</h2>
<el-form :model="loginForm" :rules="loginRules" ref="loginFormRef" class="login-form">
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="请输入用户名" prefix-icon="User"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="请输入密码"
prefix-icon="Lock" show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-btn" @click="handleLogin" :loading="loading">登录</el-button>
</el-form-item>
</el-form>
<div class="login-footer">
<span>还没有账号</span>
<el-link type="primary" @click="goRegister"></el-link>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, type FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import { loginApi } from '../../api/user'
const router = useRouter()
const loading = ref(false)
const loginFormRef = ref<FormInstance>()
const loginForm = reactive({
username: '',
password: ''
})
const loginRules = reactive({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
})
const handleLogin = () => {
loginFormRef.value?.validate(async (valid) => {
if (valid) {
loading.value = true
try {
const res = await loginApi(loginForm)
if (res && res.code === 200) {
ElMessage.success('登录成功')
// token
localStorage.setItem('token', res.data.token)
localStorage.setItem('user', JSON.stringify(res.data.user))
//
router.push('/dashboard')
} else {
ElMessage.error(res.msg || '登录失败')
}
} catch (error) {
ElMessage.error('登录失败,请稍后重试')
} finally {
loading.value = false
}
}
})
}
const goRegister = () => {
router.push('/register')
}
</script>
<style scoped>
.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f7fa;
}
.login-card {
width: 400px;
padding: 20px;
}
.login-title {
text-align: center;
margin-bottom: 30px;
color: #409EFF;
}
.login-form {
margin-top: 20px;
}
.login-btn {
width: 100%;
}
.login-footer {
text-align: center;
margin-top: 20px;
}
</style>

@ -60,11 +60,15 @@
<el-tag v-if="scope.row.status == '2'" size="default" effect="dark"
>已收货</el-tag
>
<el-tag v-if="scope.row.status == '3'" size="default" effect="light"
>已取消</el-tag
>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<el-table-column label="操作" width="250" align="center">
<template #default="scope">
<el-button type="primary" @click="sendOrder(scope.row.orderId)" :icon="Edit" size="default">发货</el-button>
<el-button type="danger" @click="deleteOrder(scope.row.orderId)" :icon="Delete" size="default">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -90,6 +94,7 @@
const {
tableList,
sendOrder,
deleteOrder, //
listParm,
resetBtn,
searchBtn,
@ -99,5 +104,4 @@
} = useOrderTable();
</script>
<style scoped></style>
<style scoped></style>

@ -0,0 +1,171 @@
<template>
<div class="register-container">
<el-card class="register-card">
<h2 class="register-title">用户注册</h2>
<el-form :model="registerForm" :rules="registerRules" ref="registerFormRef" class="register-form">
<el-form-item prop="name" label="姓名">
<el-input v-model="registerForm.name" placeholder="请输入真实姓名"></el-input>
</el-form-item>
<el-form-item prop="sex" label="性别">
<el-radio-group v-model="registerForm.sex">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="phone" label="手机号">
<el-input v-model="registerForm.phone" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-form-item prop="email" label="邮箱">
<el-input v-model="registerForm.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item prop="username" label="用户名">
<el-input v-model="registerForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码">
<el-input v-model="registerForm.password" type="password" placeholder="请输入密码" show-password></el-input>
</el-form-item>
<el-form-item prop="confirmPassword" label="确认密码">
<el-input v-model="registerForm.confirmPassword" type="password" placeholder="请再次输入密码"
show-password></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="register-btn" @click="handleRegister" :loading="loading">注册</el-button>
</el-form-item>
</el-form>
<div class="register-footer">
<span>已有账号</span>
<el-link type="primary" @click="goLogin"></el-link>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, type FormInstance } from 'element-plus'
import { useRouter } from 'vue-router'
import { registerApi } from '../../api/user'
import type { FormRules } from 'element-plus'
import type { RegisterModel } from '../../api/user/userModel'
const router = useRouter()
const loading = ref(false)
const registerFormRef = ref<FormInstance>()
const registerForm = reactive<RegisterModel>({
name: '',
sex: '',
phone: '',
email: '',
username: '',
password: '',
confirmPassword: ''
})
const validatePass2 = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== registerForm.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
const registerRules = reactive<FormRules>({
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
sex: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
phone: [
{ required: true, message: '请输入电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email' as any, message: '请输入正确的邮箱地址', trigger: 'blur' }
],
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ validator: validatePass2, trigger: 'blur' }
]
})
const handleRegister = () => {
registerFormRef.value?.validate(async (valid) => {
if (valid) {
loading.value = true
try {
const res = await registerApi(registerForm)
if (res && res.code === 200) {
ElMessage.success('注册成功')
//
router.push('/login')
} else {
ElMessage.error(res.msg || '注册失败')
}
} catch (error) {
ElMessage.error('注册失败,请稍后重试')
} finally {
loading.value = false
}
}
})
}
const goLogin = () => {
router.push('/login')
}
</script>
<style scoped>
.register-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f7fa;
padding: 20px 0;
}
.register-card {
width: 500px;
padding: 20px;
}
.register-title {
text-align: center;
margin-bottom: 30px;
color: #409EFF;
}
.register-form {
margin-top: 20px;
}
.register-btn {
width: 100%;
}
.register-footer {
text-align: center;
margin-top: 20px;
}
</style>

@ -2,7 +2,6 @@
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
//
"baseUrl": ".",
"paths": {
"@/*": [
@ -12,8 +11,8 @@
"src/api/*"
]
},
//
"lib": [
"ES2015",
"ESNext",
"DOM",
"DOM.Iterable"
@ -21,23 +20,22 @@
"types": [
"vite/client"
],
//
"module": "ESNext",
"moduleResolution": "bundler",
//
"target": "ESNext",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"jsx": "preserve",
"downlevelIteration": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"vite-env.d.ts",
"vite.config.ts",
"src/api/**/*.ts"
]
}

@ -10,7 +10,7 @@ export default defineConfig(({ mode }) => {
return {
define: {
// 替换为全局变量
__APP_BASE_URL__: JSON.stringify("http://localhost:8089")
__APP_BASE_URL__: JSON.stringify("http://localhost:8089"),
},
plugins: [vue()],
server: {
@ -50,6 +50,4 @@ export default defineConfig(({ mode }) => {
chunkSizeWarningLimit: 1500
}
}
})
})
Loading…
Cancel
Save