修复订单系统多项问题:Token认证、购物车、订单创建、发货功能等
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package com.meiruo.cosmetics.config;
|
package com.meiruo.cosmetics.config;
|
||||||
|
|
||||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||||
import cn.dev33.satoken.jwt.SaJwtManager;
|
|
||||||
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
|
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
|
||||||
import cn.dev33.satoken.stp.StpInterface;
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/order")
|
@RequestMapping("/api/order")
|
||||||
@@ -22,13 +23,36 @@ public class OrderController {
|
|||||||
@RequestBody Map<String, Object> params) {
|
@RequestBody Map<String, Object> params) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
Long userId = StpUtil.getLoginIdAsLong();
|
Long userId = StpUtil.getLoginIdAsLong();
|
||||||
List<Long> cartIds = (List<Long>) params.get("cartIds");
|
|
||||||
String receiverName = (String) params.get("receiverName");
|
String receiverName = (String) params.get("receiverName");
|
||||||
String receiverPhone = (String) params.get("receiverPhone");
|
String receiverPhone = (String) params.get("receiverPhone");
|
||||||
String receiverAddress = (String) params.get("receiverAddress");
|
String receiverAddress = (String) params.get("receiverAddress");
|
||||||
String remark = (String) params.get("remark");
|
String remark = (String) params.get("remark");
|
||||||
|
|
||||||
Order order = orderService.create(userId, cartIds, receiverName, receiverPhone, receiverAddress, remark);
|
// 处理 cartIds 类型转换(前端传来的是 Integer,需要转为 Long)
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Object> rawCartIds = (List<Object>) params.get("cartIds");
|
||||||
|
List<Long> cartIds = (rawCartIds == null || rawCartIds.isEmpty())
|
||||||
|
? new ArrayList<>()
|
||||||
|
: rawCartIds.stream()
|
||||||
|
.map(id -> Long.valueOf(id.toString()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 处理直接购买(不传cartIds,传items)
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Map<String, Object>> items = (List<Map<String, Object>>) params.get("items");
|
||||||
|
|
||||||
|
Order order;
|
||||||
|
if (cartIds != null && !cartIds.isEmpty()) {
|
||||||
|
order = orderService.create(userId, cartIds, receiverName, receiverPhone, receiverAddress, remark);
|
||||||
|
} else if (items != null && !items.isEmpty()) {
|
||||||
|
order = orderService.createDirect(userId, items, receiverName, receiverPhone, receiverAddress, remark);
|
||||||
|
} else {
|
||||||
|
result.put("code", 400);
|
||||||
|
result.put("msg", "购物车为空,无法创建订单");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
result.put("code", 200);
|
result.put("code", 200);
|
||||||
result.put("msg", "下单成功");
|
result.put("msg", "下单成功");
|
||||||
result.put("data", order);
|
result.put("data", order);
|
||||||
@@ -72,6 +96,30 @@ public class OrderController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/ship/{id}")
|
||||||
|
public Map<String, Object> shipOrder(@PathVariable Long id, @RequestBody Map<String, String> params) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
String logisticsCompany = params.get("logisticsCompany");
|
||||||
|
String trackingNo = params.get("trackingNo");
|
||||||
|
|
||||||
|
if (logisticsCompany == null || logisticsCompany.trim().isEmpty() ||
|
||||||
|
trackingNo == null || trackingNo.trim().isEmpty()) {
|
||||||
|
result.put("code", 400);
|
||||||
|
result.put("msg", "物流公司和物流单号不能为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = orderService.shipOrder(id, logisticsCompany, trackingNo);
|
||||||
|
if (success) {
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("msg", "发货成功");
|
||||||
|
} else {
|
||||||
|
result.put("code", 500);
|
||||||
|
result.put("msg", "发货失败,订单状态可能不正确");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/revenue/{type}")
|
@GetMapping("/revenue/{type}")
|
||||||
public Map<String, Object> getRevenueStatistics(@PathVariable String type) {
|
public Map<String, Object> getRevenueStatistics(@PathVariable String type) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ public class UserController {
|
|||||||
result.put("msg", "用户名或密码错误");
|
result.put("msg", "用户名或密码错误");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
String token = StpUtil.getTokenInfo().getTokenValue();
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("id", loginUser.getId());
|
data.put("id", loginUser.getId());
|
||||||
data.put("username", loginUser.getUsername());
|
data.put("username", loginUser.getUsername());
|
||||||
data.put("nickname", loginUser.getNickname());
|
data.put("nickname", loginUser.getNickname());
|
||||||
data.put("avatar", loginUser.getAvatar());
|
data.put("avatar", loginUser.getAvatar());
|
||||||
data.put("role", loginUser.getRole());
|
data.put("role", loginUser.getRole());
|
||||||
|
data.put("token", token);
|
||||||
result.put("code", 200);
|
result.put("code", 200);
|
||||||
result.put("msg", "登录成功");
|
result.put("msg", "登录成功");
|
||||||
result.put("data", data);
|
result.put("data", data);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public class Order {
|
|||||||
private String receiverName;
|
private String receiverName;
|
||||||
private String receiverPhone;
|
private String receiverPhone;
|
||||||
private String receiverAddress;
|
private String receiverAddress;
|
||||||
|
private String logisticsCompany;
|
||||||
|
private String trackingNo;
|
||||||
private String remark;
|
private String remark;
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
private LocalDateTime payTime;
|
private LocalDateTime payTime;
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ public class OrderItem {
|
|||||||
private String productImage;
|
private String productImage;
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
private Integer quantity;
|
private Integer quantity;
|
||||||
|
private BigDecimal totalPrice;
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import com.meiruo.cosmetics.entity.Cart;
|
|||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface CartMapper {
|
public interface CartMapper {
|
||||||
|
|
||||||
|
Cart selectById(@Param("id") Long id);
|
||||||
|
|
||||||
Cart selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId);
|
Cart selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId);
|
||||||
|
|
||||||
List<Cart> selectByUserId(@Param("userId") Long userId);
|
List<Map<String, Object>> selectByUserId(@Param("userId") Long userId);
|
||||||
|
|
||||||
int insert(Cart cart);
|
int insert(Cart cart);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public interface OrderMapper {
|
|||||||
|
|
||||||
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
|
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
|
||||||
|
|
||||||
|
int shipOrder(@Param("id") Long id, @Param("logisticsCompany") String logisticsCompany, @Param("trackingNo") String trackingNo);
|
||||||
|
|
||||||
List<Map<String, Object>> selectRevenueStatistics(@Param("type") String type);
|
List<Map<String, Object>> selectRevenueStatistics(@Param("type") String type);
|
||||||
|
|
||||||
List<Map<String, Object>> selectTopProducts(@Param("limit") Integer limit);
|
List<Map<String, Object>> selectTopProducts(@Param("limit") Integer limit);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ public interface OrderService {
|
|||||||
|
|
||||||
Order create(Long userId, List<Long> cartIds, String receiverName, String receiverPhone, String receiverAddress, String remark);
|
Order create(Long userId, List<Long> cartIds, String receiverName, String receiverPhone, String receiverAddress, String remark);
|
||||||
|
|
||||||
|
Order createDirect(Long userId, List<Map<String, Object>> items, String receiverName, String receiverPhone, String receiverAddress, String remark);
|
||||||
|
|
||||||
Order getById(Long id);
|
Order getById(Long id);
|
||||||
|
|
||||||
Order getByOrderNo(String orderNo);
|
Order getByOrderNo(String orderNo);
|
||||||
@@ -18,6 +20,8 @@ public interface OrderService {
|
|||||||
|
|
||||||
void updateStatus(Long id, Integer status);
|
void updateStatus(Long id, Integer status);
|
||||||
|
|
||||||
|
boolean shipOrder(Long id, String logisticsCompany, String trackingNo);
|
||||||
|
|
||||||
Map<String, Object> getRevenueStatistics(String type);
|
Map<String, Object> getRevenueStatistics(String type);
|
||||||
|
|
||||||
List<Map<String, Object>> getTopProducts(Integer limit);
|
List<Map<String, Object>> getTopProducts(Integer limit);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.meiruo.cosmetics.service.impl;
|
package com.meiruo.cosmetics.service.impl;
|
||||||
|
|
||||||
|
import com.meiruo.cosmetics.entity.Cart;
|
||||||
import com.meiruo.cosmetics.entity.Order;
|
import com.meiruo.cosmetics.entity.Order;
|
||||||
import com.meiruo.cosmetics.entity.OrderItem;
|
import com.meiruo.cosmetics.entity.OrderItem;
|
||||||
|
import com.meiruo.cosmetics.entity.Product;
|
||||||
|
import com.meiruo.cosmetics.mapper.CartMapper;
|
||||||
import com.meiruo.cosmetics.mapper.OrderItemMapper;
|
import com.meiruo.cosmetics.mapper.OrderItemMapper;
|
||||||
import com.meiruo.cosmetics.mapper.OrderMapper;
|
import com.meiruo.cosmetics.mapper.OrderMapper;
|
||||||
import com.meiruo.cosmetics.mapper.ProductMapper;
|
import com.meiruo.cosmetics.mapper.ProductMapper;
|
||||||
@@ -11,6 +14,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -25,6 +29,8 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
private OrderItemMapper orderItemMapper;
|
private OrderItemMapper orderItemMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ProductMapper productMapper;
|
private ProductMapper productMapper;
|
||||||
|
@Autowired
|
||||||
|
private CartMapper cartMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -42,9 +48,29 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
for (Long cartId : cartIds) {
|
for (Long cartId : cartIds) {
|
||||||
|
Cart cart = cartMapper.selectById(cartId);
|
||||||
|
if (cart == null || !cart.getUserId().equals(userId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Product product = productMapper.selectById(cart.getProductId());
|
||||||
|
if (product == null || product.getStatus() != 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderItem item = new OrderItem();
|
||||||
|
item.setProductId(product.getId());
|
||||||
|
item.setProductName(product.getName());
|
||||||
|
item.setProductImage(product.getImage());
|
||||||
|
item.setPrice(product.getPrice());
|
||||||
|
item.setQuantity(cart.getQuantity());
|
||||||
|
item.setTotalPrice(product.getPrice().multiply(new BigDecimal(cart.getQuantity())));
|
||||||
|
orderItems.add(item);
|
||||||
|
|
||||||
|
totalAmount = totalAmount.add(item.getTotalPrice());
|
||||||
}
|
}
|
||||||
|
|
||||||
order.setTotalAmount(totalAmount);
|
order.setTotalAmount(totalAmount);
|
||||||
|
order.setCreateTime(LocalDateTime.now());
|
||||||
orderMapper.insert(order);
|
orderMapper.insert(order);
|
||||||
|
|
||||||
for (OrderItem item : orderItems) {
|
for (OrderItem item : orderItems) {
|
||||||
@@ -54,6 +80,11 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
productMapper.incrementSales(item.getProductId(), item.getQuantity());
|
productMapper.incrementSales(item.getProductId(), item.getQuantity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除购物车中的商品
|
||||||
|
for (Long cartId : cartIds) {
|
||||||
|
cartMapper.delete(cartId);
|
||||||
|
}
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +108,67 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
return orderMapper.selectList(keyword, status);
|
return orderMapper.selectList(keyword, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Order createDirect(Long userId, List<Map<String, Object>> items, String receiverName, String receiverPhone, String receiverAddress, String remark) {
|
||||||
|
Order order = new Order();
|
||||||
|
order.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
order.setUserId(userId);
|
||||||
|
order.setReceiverName(receiverName);
|
||||||
|
order.setReceiverPhone(receiverPhone);
|
||||||
|
order.setReceiverAddress(receiverAddress);
|
||||||
|
order.setRemark(remark);
|
||||||
|
order.setStatus(1);
|
||||||
|
|
||||||
|
List<OrderItem> orderItems = new ArrayList<>();
|
||||||
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
for (Map<String, Object> itemData : items) {
|
||||||
|
Long productId = Long.valueOf(itemData.get("productId").toString());
|
||||||
|
Integer quantity = Integer.valueOf(itemData.get("quantity").toString());
|
||||||
|
|
||||||
|
Product product = productMapper.selectById(productId);
|
||||||
|
if (product == null || product.getStatus() != 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderItem item = new OrderItem();
|
||||||
|
item.setProductId(product.getId());
|
||||||
|
item.setProductName(product.getName());
|
||||||
|
item.setProductImage(product.getImage());
|
||||||
|
item.setPrice(product.getPrice());
|
||||||
|
item.setQuantity(quantity);
|
||||||
|
item.setTotalPrice(product.getPrice().multiply(new BigDecimal(quantity)));
|
||||||
|
orderItems.add(item);
|
||||||
|
|
||||||
|
totalAmount = totalAmount.add(item.getTotalPrice());
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setTotalAmount(totalAmount);
|
||||||
|
order.setCreateTime(LocalDateTime.now());
|
||||||
|
orderMapper.insert(order);
|
||||||
|
|
||||||
|
for (OrderItem item : orderItems) {
|
||||||
|
item.setOrderId(order.getId());
|
||||||
|
orderItemMapper.insert(item);
|
||||||
|
productMapper.updateStock(item.getProductId(), item.getQuantity());
|
||||||
|
productMapper.incrementSales(item.getProductId(), item.getQuantity());
|
||||||
|
}
|
||||||
|
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateStatus(Long id, Integer status) {
|
public void updateStatus(Long id, Integer status) {
|
||||||
orderMapper.updateStatus(id, status);
|
orderMapper.updateStatus(id, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shipOrder(Long id, String logisticsCompany, String trackingNo) {
|
||||||
|
int result = orderMapper.shipOrder(id, logisticsCompany, trackingNo);
|
||||||
|
return result > 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getRevenueStatistics(String type) {
|
public Map<String, Object> getRevenueStatistics(String type) {
|
||||||
List<Map<String, Object>> statistics = orderMapper.selectRevenueStatistics(type);
|
List<Map<String, Object>> statistics = orderMapper.selectRevenueStatistics(type);
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
url: jdbc:mysql://localhost:3306/meiruo_cosmetics?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
url: jdbc:mysql://localhost:3307/meiruo_cosmetics?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||||
username: root
|
username: root
|
||||||
password: root
|
password: qq5211314
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB
|
||||||
|
max-request-size: 10MB
|
||||||
|
|
||||||
mybatis:
|
mybatis:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
<result column="product_image" property="productImage"/>
|
<result column="product_image" property="productImage"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectById" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM cart WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectByUserAndProduct" resultMap="BaseResultMap">
|
<select id="selectByUserAndProduct" resultMap="BaseResultMap">
|
||||||
SELECT * FROM cart WHERE user_id = #{userId} AND product_id = #{productId}
|
SELECT * FROM cart WHERE user_id = #{userId} AND product_id = #{productId}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<result column="product_image" property="productImage"/>
|
<result column="product_image" property="productImage"/>
|
||||||
<result column="price" property="price"/>
|
<result column="price" property="price"/>
|
||||||
<result column="quantity" property="quantity"/>
|
<result column="quantity" property="quantity"/>
|
||||||
|
<result column="total_price" property="totalPrice"/>
|
||||||
<result column="create_time" property="createTime"/>
|
<result column="create_time" property="createTime"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
@@ -18,15 +19,15 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO order_item (order_id, product_id, product_name, product_image, price, quantity, create_time)
|
INSERT INTO order_item (order_id, product_id, product_name, product_image, price, quantity, total_price, create_time)
|
||||||
VALUES (#{orderId}, #{productId}, #{productName}, #{productImage}, #{price}, #{quantity}, NOW())
|
VALUES (#{orderId}, #{productId}, #{productName}, #{productImage}, #{price}, #{quantity}, #{totalPrice}, NOW())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<insert id="insertBatch">
|
<insert id="insertBatch">
|
||||||
INSERT INTO order_item (order_id, product_id, product_name, product_image, price, quantity, create_time)
|
INSERT INTO order_item (order_id, product_id, product_name, product_image, price, quantity, total_price, create_time)
|
||||||
VALUES
|
VALUES
|
||||||
<foreach collection="items" item="item" separator=",">
|
<foreach collection="items" item="item" separator=",">
|
||||||
(#{item.orderId}, #{item.productId}, #{item.productName}, #{item.productImage}, #{item.price}, #{item.quantity}, NOW())
|
(#{item.orderId}, #{item.productId}, #{item.productName}, #{item.productImage}, #{item.price}, #{item.quantity}, #{item.totalPrice}, NOW())
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
<result column="receiver_name" property="receiverName"/>
|
<result column="receiver_name" property="receiverName"/>
|
||||||
<result column="receiver_phone" property="receiverPhone"/>
|
<result column="receiver_phone" property="receiverPhone"/>
|
||||||
<result column="receiver_address" property="receiverAddress"/>
|
<result column="receiver_address" property="receiverAddress"/>
|
||||||
|
<result column="logistics_company" property="logisticsCompany"/>
|
||||||
|
<result column="tracking_no" property="trackingNo"/>
|
||||||
<result column="remark" property="remark"/>
|
<result column="remark" property="remark"/>
|
||||||
<result column="create_time" property="createTime"/>
|
<result column="create_time" property="createTime"/>
|
||||||
<result column="pay_time" property="payTime"/>
|
<result column="pay_time" property="payTime"/>
|
||||||
@@ -78,6 +80,15 @@
|
|||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="shipOrder">
|
||||||
|
UPDATE `order`
|
||||||
|
SET status = 3,
|
||||||
|
ship_time = NOW(),
|
||||||
|
logistics_company = #{logisticsCompany},
|
||||||
|
tracking_no = #{trackingNo}
|
||||||
|
WHERE id = #{id} AND status = 2
|
||||||
|
</update>
|
||||||
|
|
||||||
<select id="selectRevenueStatistics" resultType="java.util.Map">
|
<select id="selectRevenueStatistics" resultType="java.util.Map">
|
||||||
SELECT
|
SELECT
|
||||||
<choose>
|
<choose>
|
||||||
|
|||||||
1407
meiruo-frontend/pnpm-lock.yaml
generated
Normal file
1407
meiruo-frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,7 @@ export const orderApi = {
|
|||||||
adminList: (params) => request.get('/order/admin/list', { params }),
|
adminList: (params) => request.get('/order/admin/list', { params }),
|
||||||
getById: (id) => request.get(`/order/${id}`),
|
getById: (id) => request.get(`/order/${id}`),
|
||||||
updateStatus: (id, status) => request.put(`/order/status/${id}`, null, { params: { status } }),
|
updateStatus: (id, status) => request.put(`/order/status/${id}`, null, { params: { status } }),
|
||||||
|
shipOrder: (id, data) => request.post(`/order/ship/${id}`, data),
|
||||||
getRevenue: (type) => request.get(`/order/revenue/${type}`),
|
getRevenue: (type) => request.get(`/order/revenue/${type}`),
|
||||||
getTopProducts: (limit) => request.get(`/order/top/${limit}`)
|
getTopProducts: (limit) => request.get(`/order/top/${limit}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-footer class="footer">
|
<el-footer class="footer">
|
||||||
<p>© 2024 美若彩妆销售平台 版权所有</p>
|
<p>© 2026 美若彩妆销售平台 版权所有</p>
|
||||||
</el-footer>
|
</el-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
async function login(username, password) {
|
async function login(username, password) {
|
||||||
const res = await userApi.login({ username, password })
|
const res = await userApi.login({ username, password })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
token.value = res.data.token || 'token'
|
const tokenValue = res.data?.token
|
||||||
|
if (!tokenValue || tokenValue === 'token') {
|
||||||
|
console.error('登录失败:后端未返回有效的 token', res)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
token.value = tokenValue
|
||||||
userInfo.value = res.data
|
userInfo.value = res.data
|
||||||
localStorage.setItem('token', token.value)
|
localStorage.setItem('token', token.value)
|
||||||
localStorage.setItem('userInfo', JSON.stringify(userInfo.value))
|
localStorage.setItem('userInfo', JSON.stringify(userInfo.value))
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<el-table-column label="轮播图" width="220">
|
<el-table-column label="轮播图" width="220">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="banner-cell">
|
<div class="banner-cell">
|
||||||
<img :src="row.image || '/images/default.png'" :alt="row.title" class="banner-thumb" />
|
<img :src="getImageUrl(row.image)" :alt="row.title" class="banner-thumb" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -140,6 +140,12 @@ const rules = {
|
|||||||
image: [{ required: true, message: '请上传轮播图', trigger: 'blur' }]
|
image: [{ required: true, message: '请上传轮播图', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getImageUrl = (url) => {
|
||||||
|
if (!url) return '/images/default.png'
|
||||||
|
if (url.startsWith('http') || url.startsWith('/upload')) return url
|
||||||
|
return '/upload' + url
|
||||||
|
}
|
||||||
|
|
||||||
const fetchBanners = async () => {
|
const fetchBanners = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await bannerApi.getList()
|
const res = await bannerApi.getList()
|
||||||
|
|||||||
@@ -20,12 +20,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<el-table :data="orderList" stripe style="width: 100%">
|
<el-table :data="orderList" stripe style="width: 100%">
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
<el-table-column prop="order_no" label="订单号" width="200" />
|
<el-table-column prop="orderNo" label="订单号" width="200" />
|
||||||
<el-table-column prop="receiver_name" label="收货人" width="120" />
|
<el-table-column prop="receiverName" label="收货人" width="120" />
|
||||||
<el-table-column prop="receiver_phone" label="联系电话" width="140" />
|
<el-table-column prop="receiverPhone" label="联系电话" width="140" />
|
||||||
<el-table-column prop="total_amount" label="订单金额">
|
<el-table-column prop="totalAmount" label="订单金额">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
¥{{ parseFloat(row.total_amount).toFixed(2) }}
|
¥{{ row.totalAmount ? parseFloat(row.totalAmount).toFixed(2) : '0.00' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="status" label="状态" width="120">
|
<el-table-column prop="status" label="状态" width="120">
|
||||||
@@ -35,15 +35,25 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="create_time" label="下单时间" width="180" />
|
<el-table-column prop="logisticsCompany" label="物流公司" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.logisticsCompany || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="trackingNo" label="物流单号" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.trackingNo || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="下单时间" width="180" />
|
||||||
<el-table-column label="操作" width="200">
|
<el-table-column label="操作" width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" size="small" @click="viewDetail(row)">查看</el-button>
|
<el-button type="primary" size="small" @click="viewDetail(row)">查看</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 1"
|
v-if="row.status === 2"
|
||||||
type="success"
|
type="success"
|
||||||
size="small"
|
size="small"
|
||||||
@click="updateStatus(row.id, 2)"
|
@click="openShipDialog(row)"
|
||||||
>
|
>
|
||||||
发货
|
发货
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -51,16 +61,61 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 发货弹窗 -->
|
||||||
|
<el-dialog v-model="shipDialogVisible" title="订单发货" width="500px">
|
||||||
|
<el-form :model="shipForm" label-width="100px" :rules="shipRules" ref="shipFormRef">
|
||||||
|
<el-form-item label="订单号">
|
||||||
|
<span>{{ currentOrder?.orderNo }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="收货人">
|
||||||
|
<span>{{ currentOrder?.receiverName }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="物流公司" prop="logisticsCompany">
|
||||||
|
<el-select v-model="shipForm.logisticsCompany" placeholder="请选择物流公司" style="width: 100%">
|
||||||
|
<el-option label="顺丰速运" value="顺丰速运" />
|
||||||
|
<el-option label="中通快递" value="中通快递" />
|
||||||
|
<el-option label="圆通速递" value="圆通速递" />
|
||||||
|
<el-option label="韵达快递" value="韵达快递" />
|
||||||
|
<el-option label="申通快递" value="申通快递" />
|
||||||
|
<el-option label="EMS" value="EMS" />
|
||||||
|
<el-option label="京东物流" value="京东物流" />
|
||||||
|
<el-option label="德邦快递" value="德邦快递" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="物流单号" prop="trackingNo">
|
||||||
|
<el-input v-model="shipForm.trackingNo" placeholder="请输入物流单号" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="shipDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitShip" :loading="shipLoading">确认发货</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
import { orderApi } from '../../api'
|
import { orderApi } from '../../api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const orderList = ref([])
|
const orderList = ref([])
|
||||||
const keyword = ref('')
|
const keyword = ref('')
|
||||||
|
const shipDialogVisible = ref(false)
|
||||||
|
const shipLoading = ref(false)
|
||||||
|
const currentOrder = ref(null)
|
||||||
|
const shipFormRef = ref(null)
|
||||||
|
|
||||||
|
const shipForm = reactive({
|
||||||
|
logisticsCompany: '',
|
||||||
|
trackingNo: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const shipRules = {
|
||||||
|
logisticsCompany: [{ required: true, message: '请选择物流公司', trigger: 'change' }],
|
||||||
|
trackingNo: [{ required: true, message: '请输入物流单号', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
1: '待付款',
|
1: '待付款',
|
||||||
@@ -91,14 +146,39 @@ const viewDetail = (order) => {
|
|||||||
console.log('查看订单详情', order)
|
console.log('查看订单详情', order)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateStatus = async (id, status) => {
|
const openShipDialog = (order) => {
|
||||||
try {
|
currentOrder.value = order
|
||||||
await orderApi.updateStatus(id, status)
|
shipForm.logisticsCompany = ''
|
||||||
ElMessage.success('操作成功')
|
shipForm.trackingNo = ''
|
||||||
fetchOrders()
|
shipDialogVisible.value = true
|
||||||
} catch (e) {
|
}
|
||||||
console.error(e)
|
|
||||||
}
|
const submitShip = async () => {
|
||||||
|
if (!shipFormRef.value) return
|
||||||
|
|
||||||
|
await shipFormRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
shipLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await orderApi.shipOrder(currentOrder.value.id, {
|
||||||
|
logisticsCompany: shipForm.logisticsCompany,
|
||||||
|
trackingNo: shipForm.trackingNo
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('发货成功')
|
||||||
|
shipDialogVisible.value = false
|
||||||
|
fetchOrders()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '发货失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
ElMessage.error('发货失败')
|
||||||
|
} finally {
|
||||||
|
shipLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<el-table-column label="商品信息" min-width="280">
|
<el-table-column label="商品信息" min-width="280">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="product-cell">
|
<div class="product-cell">
|
||||||
<img :src="row.image || '/images/default.png'" :alt="row.name" class="product-thumb" />
|
<img :src="getImageUrl(row.image)" :alt="row.name" class="product-thumb" />
|
||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
<span class="product-name">{{ row.name }}</span>
|
<span class="product-name">{{ row.name }}</span>
|
||||||
<span class="product-desc">{{ row.description }}</span>
|
<span class="product-desc">{{ row.description }}</span>
|
||||||
@@ -184,6 +184,12 @@ const getCategoryName = (categoryId) => {
|
|||||||
return cat ? cat.name : '-'
|
return cat ? cat.name : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getImageUrl = (url) => {
|
||||||
|
if (!url) return '/images/default.png'
|
||||||
|
if (url.startsWith('http') || url.startsWith('/upload')) return url
|
||||||
|
return '/upload' + url
|
||||||
|
}
|
||||||
|
|
||||||
const fetchProducts = async () => {
|
const fetchProducts = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await productApi.getList()
|
const res = await productApi.getList()
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
<el-table :data="cartList" stripe style="width: 100%">
|
<el-table :data="cartList" stripe style="width: 100%">
|
||||||
<el-table-column width="180">
|
<el-table-column width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image :src="row.product_image || '/images/default.png'" fit="cover" style="width: 100px; height: 100px" />
|
<el-image :src="row.productImage || '/images/default.png'" fit="cover" style="width: 100px; height: 100px" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="product_name" label="商品名称" />
|
<el-table-column prop="productName" label="商品名称" />
|
||||||
<el-table-column label="单价">
|
<el-table-column label="单价">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
¥{{ parseFloat(row.product_price).toFixed(2) }}
|
¥{{ row.productPrice ? parseFloat(row.productPrice).toFixed(2) : '0.00' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="数量" width="180">
|
<el-table-column label="数量" width="180">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="小计">
|
<el-table-column label="小计">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
¥{{ (parseFloat(row.product_price) * row.quantity).toFixed(2) }}
|
¥{{ row.productPrice ? (parseFloat(row.productPrice) * row.quantity).toFixed(2) : '0.00' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120">
|
<el-table-column label="操作" width="120">
|
||||||
@@ -66,7 +66,7 @@ const cartList = ref([])
|
|||||||
|
|
||||||
const totalPrice = computed(() => {
|
const totalPrice = computed(() => {
|
||||||
return cartList.value.reduce((sum, item) => {
|
return cartList.value.reduce((sum, item) => {
|
||||||
return sum + parseFloat(item.product_price) * item.quantity
|
return sum + (item.productPrice ? parseFloat(item.productPrice) : 0) * item.quantity
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
<h3>订单商品</h3>
|
<h3>订单商品</h3>
|
||||||
<div class="product-list">
|
<div class="product-list">
|
||||||
<div class="product-item" v-for="item in orderItems" :key="item.id">
|
<div class="product-item" v-for="item in orderItems" :key="item.id">
|
||||||
<img :src="item.product_image || '/images/default.png'" alt="" />
|
<img :src="item.productImage || '/images/default.png'" alt="" />
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h4>{{ item.product_name }}</h4>
|
<h4>{{ item.productName }}</h4>
|
||||||
<p>¥{{ parseFloat(item.product_price).toFixed(2) }} x {{ item.quantity }}</p>
|
<p>¥{{ item.productPrice ? parseFloat(item.productPrice).toFixed(2) : '0.00' }} x {{ item.quantity }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtotal">¥{{ (parseFloat(item.product_price) * item.quantity).toFixed(2) }}</div>
|
<div class="subtotal">¥{{ item.productPrice ? (parseFloat(item.productPrice) * item.quantity).toFixed(2) : '0.00' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-total">
|
<div class="order-total">
|
||||||
@@ -82,7 +82,7 @@ const rules = {
|
|||||||
|
|
||||||
const totalAmount = computed(() => {
|
const totalAmount = computed(() => {
|
||||||
return orderItems.value.reduce((sum, item) => {
|
return orderItems.value.reduce((sum, item) => {
|
||||||
return sum + parseFloat(item.product_price) * item.quantity
|
return sum + (item.productPrice ? parseFloat(item.productPrice) : 0) * item.quantity
|
||||||
}, 0)
|
}, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,10 +96,10 @@ const fetchData = async () => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
orderItems.value = [{
|
orderItems.value = [{
|
||||||
id: 0,
|
id: 0,
|
||||||
product_id: res.data.id,
|
productId: res.data.id,
|
||||||
product_name: res.data.name,
|
productName: res.data.name,
|
||||||
product_image: res.data.image,
|
productImage: res.data.image,
|
||||||
product_price: res.data.price,
|
productPrice: res.data.price,
|
||||||
quantity: parseInt(router.currentRoute.value.query.quantity) || 1
|
quantity: parseInt(router.currentRoute.value.query.quantity) || 1
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@@ -122,8 +122,14 @@ const handleSubmit = async () => {
|
|||||||
submitting.value = true
|
submitting.value = true
|
||||||
|
|
||||||
const cartIds = orderItems.value.filter(item => item.id > 0).map(item => item.id)
|
const cartIds = orderItems.value.filter(item => item.id > 0).map(item => item.id)
|
||||||
|
const directItems = orderItems.value.filter(item => item.id === 0).map(item => ({
|
||||||
|
productId: item.productId,
|
||||||
|
quantity: item.quantity
|
||||||
|
}))
|
||||||
|
|
||||||
const res = await orderApi.create({
|
const res = await orderApi.create({
|
||||||
cartIds,
|
cartIds: cartIds.length > 0 ? cartIds : undefined,
|
||||||
|
items: directItems.length > 0 ? directItems : undefined,
|
||||||
...form.value
|
...form.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<el-carousel :interval="5000" height="450px" indicator-position="outside" trigger="click">
|
<el-carousel :interval="5000" height="450px" indicator-position="outside" trigger="click">
|
||||||
<el-carousel-item v-for="banner in banners" :key="banner.id">
|
<el-carousel-item v-for="banner in banners" :key="banner.id">
|
||||||
<div class="banner-item" @click="handleBannerClick(banner)">
|
<div class="banner-item" @click="handleBannerClick(banner)">
|
||||||
<img :src="banner.image || '/images/default.png'" :alt="banner.title" class="banner-img" />
|
<img :src="getImageUrl(banner.image)" :alt="banner.title" class="banner-img" />
|
||||||
<div class="banner-overlay">
|
<div class="banner-overlay">
|
||||||
<h3>{{ banner.title }}</h3>
|
<h3>{{ banner.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="product in productList" :key="product.id">
|
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="product in productList" :key="product.id">
|
||||||
<div class="product-card" @click="goToDetail(product.id)">
|
<div class="product-card" @click="goToDetail(product.id)">
|
||||||
<div class="product-image-wrapper">
|
<div class="product-image-wrapper">
|
||||||
<img :src="product.image || '/images/default.png'" :alt="product.name" class="product-img" />
|
<img :src="getImageUrl(product.image)" :alt="product.name" class="product-img" />
|
||||||
<div class="product-badge" v-if="product.sales > 50">热销</div>
|
<div class="product-badge" v-if="product.sales > 50">热销</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
@@ -108,6 +108,12 @@ const categories = ref([])
|
|||||||
const productList = ref([])
|
const productList = ref([])
|
||||||
const activeCategory = ref(null)
|
const activeCategory = ref(null)
|
||||||
|
|
||||||
|
const getImageUrl = (url) => {
|
||||||
|
if (!url) return '/images/default.png'
|
||||||
|
if (url.startsWith('http') || url.startsWith('/upload')) return url
|
||||||
|
return '/upload' + url
|
||||||
|
}
|
||||||
|
|
||||||
const fetchBanners = async () => {
|
const fetchBanners = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await bannerApi.getList(1)
|
const res = await bannerApi.getList(1)
|
||||||
|
|||||||
@@ -10,26 +10,33 @@
|
|||||||
<el-tabs v-model="activeTab" @tab-change="fetchOrders">
|
<el-tabs v-model="activeTab" @tab-change="fetchOrders">
|
||||||
<el-tab-pane label="全部" name="all" />
|
<el-tab-pane label="全部" name="all" />
|
||||||
<el-tab-pane label="待付款" name="1" />
|
<el-tab-pane label="待付款" name="1" />
|
||||||
<el-tab-pane label="已付款" name="2" />
|
<el-tab-pane label="待发货" name="2" />
|
||||||
<el-tab-pane label="已发货" name="3" />
|
<el-tab-pane label="已发货" name="3" />
|
||||||
<el-tab-pane label="已完成" name="4" />
|
<el-tab-pane label="已完成" name="4" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<div class="order-list" v-if="orderList.length > 0">
|
<div class="order-list" v-if="orderList.length > 0">
|
||||||
<div class="order-item" v-for="order in orderList" :key="order.id">
|
<div class="order-item" v-for="order in orderList" :key="order.id">
|
||||||
<div class="order-header">
|
<div class="order-header">
|
||||||
<span class="order-no">订单号: {{ order.order_no }}</span>
|
<span class="order-no">订单号: {{ order.orderNo }}</span>
|
||||||
<span class="order-status">{{ getStatusText(order.status) }}</span>
|
<span class="order-status">{{ getStatusText(order.status) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-body">
|
<div class="order-body">
|
||||||
<div class="order-info">
|
<div class="order-info">
|
||||||
<p>收货人: {{ order.receiver_name }}</p>
|
<p>收货人: {{ order.receiverName }}</p>
|
||||||
<p>联系电话: {{ order.receiver_phone }}</p>
|
<p>联系电话: {{ order.receiverPhone }}</p>
|
||||||
<p>收货地址: {{ order.receiver_address }}</p>
|
<p>收货地址: {{ order.receiverAddress }}</p>
|
||||||
<p>下单时间: {{ order.create_time }}</p>
|
<p>下单时间: {{ order.createTime }}</p>
|
||||||
|
<p v-if="order.logisticsCompany" class="logistics-info">
|
||||||
|
<el-icon><Van /></el-icon>
|
||||||
|
物流: {{ order.logisticsCompany }} - {{ order.trackingNo }}
|
||||||
|
</p>
|
||||||
|
<p v-if="order.shipTime" class="ship-time">
|
||||||
|
发货时间: {{ order.shipTime }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-total">
|
<div class="order-total">
|
||||||
<span>订单总额:</span>
|
<span>订单总额:</span>
|
||||||
<span class="amount">¥{{ parseFloat(order.total_amount).toFixed(2) }}</span>
|
<span class="amount">¥{{ order.totalAmount ? parseFloat(order.totalAmount).toFixed(2) : '0.00' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-footer">
|
<div class="order-footer">
|
||||||
@@ -42,6 +49,14 @@
|
|||||||
>
|
>
|
||||||
去付款
|
去付款
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="order.status === 3"
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
@click="confirmReceive(order)"
|
||||||
|
>
|
||||||
|
确认收货
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,17 +72,18 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { Van } from '@element-plus/icons-vue'
|
||||||
import Header from '../../components/Header.vue'
|
import Header from '../../components/Header.vue'
|
||||||
import Footer from '../../components/Footer.vue'
|
import Footer from '../../components/Footer.vue'
|
||||||
import { orderApi } from '../../api'
|
import { orderApi } from '../../api'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const activeTab = ref('all')
|
const activeTab = ref('all')
|
||||||
const orderList = ref([])
|
const orderList = ref([])
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
1: '待付款',
|
1: '待付款',
|
||||||
2: '已付款',
|
2: '待发货',
|
||||||
3: '已发货',
|
3: '已发货',
|
||||||
4: '已完成',
|
4: '已完成',
|
||||||
5: '已取消'
|
5: '已取消'
|
||||||
@@ -103,6 +119,23 @@ const payOrder = async (order) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmReceive = async (order) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确认已收到商品?', '确认收货', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
await orderApi.updateStatus(order.id, 4)
|
||||||
|
ElMessage.success('确认收货成功')
|
||||||
|
fetchOrders()
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchOrders()
|
fetchOrders()
|
||||||
})
|
})
|
||||||
@@ -157,6 +190,20 @@ onMounted(() => {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logistics-info {
|
||||||
|
color: #409EFF;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ship-time {
|
||||||
|
color: #67C23A;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-total {
|
.order-total {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<el-main>
|
<el-main>
|
||||||
<div class="detail-container" v-if="product">
|
<div class="detail-container" v-if="product">
|
||||||
<div class="product-gallery">
|
<div class="product-gallery">
|
||||||
<el-image :src="product.image || '/images/default.png'" :preview-src-list="[product.image]" fit="cover" />
|
<el-image :src="getImageUrl(product.image)" :preview-src-list="[getImageUrl(product.image)]" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
<h1>{{ product.name }}</h1>
|
<h1>{{ product.name }}</h1>
|
||||||
@@ -50,6 +50,12 @@ const userStore = useUserStore()
|
|||||||
const product = ref(null)
|
const product = ref(null)
|
||||||
const quantity = ref(1)
|
const quantity = ref(1)
|
||||||
|
|
||||||
|
const getImageUrl = (url) => {
|
||||||
|
if (!url) return '/images/default.png'
|
||||||
|
if (url.startsWith('http') || url.startsWith('/upload')) return url
|
||||||
|
return '/upload' + url
|
||||||
|
}
|
||||||
|
|
||||||
const fetchProduct = async () => {
|
const fetchProduct = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await productApi.getById(route.params.id)
|
const res = await productApi.getById(route.params.id)
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
'/upload': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:8080',
|
target: 'http://localhost:8080',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
|
|||||||
Reference in New Issue
Block a user