基于springboot的超市收银系统

这是一个收银系统,虽然会有点粗糙,但是功能完整,可供大家学习,源码和运行截图放在下面了。

github仓库地址:iwait3/Supermarket-cashier-system-based-on-SpringBoot: 一个简单的小项目,适合初学者学习

运行效果:

(这边的测试数据跟上图不一样,当时没截)

(这边写进去的测试数据不一样,这是添加,添加和编辑页面是一样的,编辑成功也会弹出这个页面)

以上就是所有运行界面,接下来带你手把手在电脑上面运行这个项目。

我的编译器是idea,数据库管理工具是Navicat,就以此为例。

在初始界面点击新建查询,在该页面下运行以下代码:

260302.spl

复制代码
CREATE DATABASE IF NOT EXISTS supermarket;
USE supermarket;
-- 创建管理员表
CREATE TABLE IF NOT EXISTS admin (
  id BIGINT NOT NULL AUTO_INCREMENT,
  username VARCHAR(255) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 创建商品表
CREATE TABLE IF NOT EXISTS product (
  id BIGINT NOT NULL AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL,
  price DOUBLE NOT NULL,
  stock INT NOT NULL,
  description TEXT NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 创建购物车表
CREATE TABLE IF NOT EXISTS cart (
  id BIGINT NOT NULL AUTO_INCREMENT,
  product_id BIGINT NOT NULL,
  quantity INT NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 创建订单表
CREATE TABLE IF NOT EXISTS orders (
  id BIGINT NOT NULL AUTO_INCREMENT,
  order_number VARCHAR(255) NOT NULL UNIQUE,
  order_date DATETIME NOT NULL,
  total_price DOUBLE NOT NULL,
  status VARCHAR(50) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 创建订单项表
CREATE TABLE IF NOT EXISTS order_item (
  id BIGINT NOT NULL AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  product_id BIGINT NOT NULL,
  quantity INT NOT NULL,
  price DOUBLE NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE,
  FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 插入默认管理员数据
INSERT INTO admin (username, password) VALUES ('admin', 'admin123') ON DUPLICATE KEY UPDATE password = 'admin123';

-- 插入默认商品数据
INSERT INTO product (name, price, stock, description) VALUES
('苹果', 5.99, 100, '新鲜苹果,口感甜美'),
('香蕉', 3.99, 150, '新鲜香蕉,软糯香甜'),
('橙子', 4.99, 80, '新鲜橙子,酸甜可口'),
('草莓', 12.99, 50, '新鲜草莓,香甜多汁')
ON DUPLICATE KEY UPDATE price = VALUES(price), stock = VALUES(stock), description = VALUES(description);

代码运行后,数据库中就新建了一个名为supermarker的库,接下来打开idea,创建maven项目,点击图中红色圈起来的加号,添加数据源,点击MySQL(第一次用的,这个选项会出现在下方,滑动滚轮后能在下面找到)

照下图所示填写配置,注意输入正确密码,如果密码不记得的,emmmm(重装MySQL,重新设置个密码,然后把以上步骤再做一遍),填写完毕后点击测试连接的蓝色字体,

出现以下界面,说明连接成功,注意navicat软件要在后台运行(打开软件,supermaket字样左边的图标要是绿色,不能是灰色),否则会出现连接错误

项目结构:

接下来就是项目代码了,先打开pom.xml文件(创建项目时自动生成,无需创建)

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <groupId>com.supermarket</groupId>
    <artifactId>supermarket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>supermarket</name>
    <description>Spring Boot Supermarket Management System</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <!-- Web依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- JPA依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Thymeleaf模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

复制进去后点击右上角用红色矩形圈起来的地方,进行maven更改,根据下图可知出现了警告,这个问题可以忽略(警告不足为惧),不影响运行,但是有一点需要注意,初次运行maven项目,会出现卡顿,然后报错无法运行,此时可以先重启idea,再测试一遍,如果问题没解决的话,这边建议直接重启电脑(别问,问就是出现过这种情况),如果还是运行不了,那就是环境有问题了,建议去csdn或博客园找帖子,不建议问ai来解决(因为有可能越改越错)

创建application.properties文件,并将以下代码复制进去,再把密码填上

src/main/resources/application.properties

复制代码
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/supermarket?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=//这边填写你自己的密码

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

spring.thymeleaf.cache=false

现在,我们开始编写实体类(需先创建软件包,按照项目结构创建即可)

src/main/java/com/lab/entity/Admin.java(这个文件我还没改,有些方法是没用的,但是不影响运行,我就没更改)

复制代码
package com.lab.entity;

import jakarta.persistence.*;


@Entity
@Table(name="admin")
public class Admin {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable=false,unique=true)
    private String username;
    @Column(nullable=false)
    private String password;
    public Long getId(){
        return id;
    }
    public void setId(Long id){
        this.id=id;
    }
    public String getUsername(){
        return username;
    }
    public void setUsername(String username){
        this.username=username;
    }
    public String getPassword(){
        return password;
    }
    public void setPassword(String password){
        this.password=password;
    }
}

src/main/java/com/lab/entity/Cart.java

复制代码
package com.lab.entity;

import jakarta.persistence.*;

//临时购物车,用于创建订单
@Entity
@Table(name="cart")
public class Cart {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name="product_id",nullable=false)
    private Product product;

    @Column(nullable = false)
    private int quantity;
    public Long getId(){
        return id;
    }
    public void setId(Long id){
        this.id=id;
    }

    public Product getProduct(){
        return product;
    }
    public void setProduct(Product product){
        this.product=product;
    }
    public int getQuantity(){
        return quantity;
    }
    public void setQuantity(int quantity){
        this.quantity=quantity;
    }
}

src/main/java/com/lab/entity/Order.java

复制代码
package com.lab.entity;

import jakarta.persistence.*;

import java.util.Date;
import java.util.List;

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String orderNumber;

    @Column(nullable = false)
    private Date orderDate;

    @Column(nullable = false)
    private double totalPrice;

    @Column(nullable = false)
    private String status; // 订单状态

    // 关联订单项
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<OrderItem> orderItems;

    // getter和setter方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getOrderNumber() {
        return orderNumber;
    }

    public void setOrderNumber(String orderNumber) {
        this.orderNumber = orderNumber;
    }

    public Date getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }

    public double getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(double totalPrice) {
        this.totalPrice = totalPrice;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public List<OrderItem> getOrderItems() {
        return orderItems;
    }

    public void setOrderItems(List<OrderItem> orderItems) {
        this.orderItems = orderItems;
    }
}

src/main/java/com/lab/entity/OrderItem.java

复制代码
package com.lab.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "order_item")
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "order_id", nullable = false)
    private Order order;

    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;

    @Column(nullable = false)
    private int quantity;

    @Column(nullable = false)
    private double price;

    // getter和setter方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

src/main/java/com/lab/entity/Product.java

复制代码
package com.lab.entity;

import jakarta.persistence.*;
import java.util.List;

@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private double price;

    @Column(nullable = false)
    private int stock;

    @Column(nullable = false)
    private String description;

    // 关联购物车
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Cart> carts;

    // 关联订单项
    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;

    // getter和setter方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Cart> getCarts() {
        return carts;
    }

    public void setCarts(List<Cart> carts) {
        this.carts = carts;
    }

    public List<OrderItem> getOrderItems() {
        return orderItems;
    }

    public void setOrderItems(List<OrderItem> orderItems) {
        this.orderItems = orderItems;
    }
}

实体类到这就全部写完了,接下来编写接口,由于继承了JpaRepository方法,无需编写繁琐的数据访问层代码,能够大幅提高开发效率,比较方便

src/main/java/com/lab/repository/AdminRepository.java

复制代码
package com.lab.repository;

import com.lab.entity.Admin;
import org.springframework.data.jpa.repository.JpaRepository;


public interface AdminRepository extends JpaRepository<Admin, Long> {
    Admin findByUsername(String username);
}

src/main/java/com/lab/repository/CartRepository.java

复制代码
package com.lab.repository;

import com.lab.entity.Cart;
import com.lab.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;


public interface CartRepository extends JpaRepository<Cart, Long> {
    // 根据商品查找购物车
    Cart findByProduct(Product product);
}

src/main/java/com/lab/repository/OrderItemRepository.java

复制代码
package com.lab.repository;

import com.lab.entity.OrderItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
}

src/main/java/com/lab/repository/OrderRepository.java

复制代码
package com.lab.repository;

import com.lab.entity.OrderItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
}

src/main/java/com/lab/repository/ProductRepository.java

复制代码
package com.lab.repository;

import com.lab.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

接下来是服务类

src/main/java/com/lab/service/AdminService.java

复制代码
package com.lab.service;

import com.lab.entity.Admin;
import com.lab.repository.AdminRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AdminService {
    @Autowired
    private AdminRepository adminRepository;

    public Admin save(Admin admin) {
        return adminRepository.save(admin);
    }

    public Admin findById(Long id) {
        return adminRepository.findById(id).orElse(null);
    }

    public List<Admin> findAll() {
        return adminRepository.findAll();
    }


    public Admin findByUsername(String username) {
        return adminRepository.findByUsername(username);
    }


    public void delete(Long id) {
        adminRepository.deleteById(id);
    }
}

src/main/java/com/lab/service/CartService.java

复制代码
package com.lab.service;

import com.lab.entity.Admin;
import com.lab.repository.AdminRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AdminService {
    @Autowired
    private AdminRepository adminRepository;

    public Admin save(Admin admin) {
        return adminRepository.save(admin);
    }

    public Admin findById(Long id) {
        return adminRepository.findById(id).orElse(null);
    }

    public List<Admin> findAll() {
        return adminRepository.findAll();
    }


    public Admin findByUsername(String username) {
        return adminRepository.findByUsername(username);
    }


    public void delete(Long id) {
        adminRepository.deleteById(id);
    }
}

src/main/java/com/lab/service/OrderService.java

复制代码
package com.lab.service;

import com.lab.entity.Admin;
import com.lab.repository.AdminRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AdminService {
    @Autowired
    private AdminRepository adminRepository;

    public Admin save(Admin admin) {
        return adminRepository.save(admin);
    }

    public Admin findById(Long id) {
        return adminRepository.findById(id).orElse(null);
    }

    public List<Admin> findAll() {
        return adminRepository.findAll();
    }


    public Admin findByUsername(String username) {
        return adminRepository.findByUsername(username);
    }


    public void delete(Long id) {
        adminRepository.deleteById(id);
    }
}

src/main/java/com/lab/service/ProductService.java

复制代码
package com.lab.service;

import com.lab.entity.Product;
import com.lab.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;


    public Product save(Product product) {
        return productRepository.save(product);
    }

    public Product findById(Long id) {
        return productRepository.findById(id).orElse(null);
    }


    public List<Product> findAll() {
        return productRepository.findAll();
    }


    public void delete(Long id) {
        productRepository.deleteById(id);
    }
}

src/main/java/com/lab/service/StatisticsService.java

复制代码
package com.lab.service;

import com.lab.entity.Order;
import com.lab.entity.OrderItem;
import com.lab.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class StatisticsService {
    @Autowired
    private OrderService orderService;

    @Autowired
    private ProductService productService;
    public Map<String, Integer> getSalesStatistics() {
        List<Order> orders = orderService.findAll();
        Map<Long, Integer> productSales = new HashMap<>();

        for (Order order : orders) {
            if (order.getOrderItems() != null) {
                for (OrderItem item : order.getOrderItems()) {
                    Long productId = item.getProduct().getId();
                    int quantity = item.getQuantity();
                    productSales.put(productId, productSales.getOrDefault(productId, 0) + quantity);
                }
            }
        }

        Map<String, Integer> salesMap = new HashMap<>();
        for (Map.Entry<Long, Integer> entry : productSales.entrySet()) {
            Product product = productService.findById(entry.getKey());
            if (product != null) {
                salesMap.put(product.getName(), entry.getValue());
            }
        }

        // 如果没有数据,添加默认数据
        if (salesMap.isEmpty()) {
            salesMap.put("暂无数据", 0);
        }

        return salesMap;
    }

    /**
     * 获取盈利统计数据
     */
    public Map<String, Double> getProfitStatistics() {
        List<Order> orders = orderService.findAll();
        Map<Long, Double> productProfit = new HashMap<>();

        // 统计每个商品的盈利
        for (Order order : orders) {
            if (order.getOrderItems() != null) {
                for (OrderItem item : order.getOrderItems()) {
                    Long productId = item.getProduct().getId();
                    double profit = item.getPrice() * item.getQuantity();
                    productProfit.put(productId, productProfit.getOrDefault(productId, 0.0) + profit);
                }
            }
        }

        // 转换为商品名称和盈利的映射
        Map<String, Double> profitMap = new HashMap<>();
        for (Map.Entry<Long, Double> entry : productProfit.entrySet()) {
            Product product = productService.findById(entry.getKey());
            if (product != null) {
                profitMap.put(product.getName(), entry.getValue());
            }
        }

        // 如果没有数据,添加默认数据
        if (profitMap.isEmpty()) {
            profitMap.put("暂无数据", 0.0);
        }

        return profitMap;
    }

    /**
     * 获取总销售额
     */
    public double getTotalSales() {
        List<Order> orders = orderService.findAll();
        return orders.stream()
                .mapToDouble(Order::getTotalPrice)
                .sum();
    }


    public int getOrderCount() {
        return orderService.findAll().size();
    }
}

服务类写完了,就轮到控制类了

src/main/java/com/lab/controller/AdminController.java

复制代码
package com.lab.controller;

import com.lab.entity.Admin;
import com.lab.service.AdminService;

import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/admin")
public class AdminController {
    @Autowired
    private AdminService adminService;

    /**
     * 管理员登录
     * 验证用户名和密码,登录成功后跳转到收银页面
     */
    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password, HttpSession session, Model model) {
        Admin admin = adminService.findByUsername(username);
        if (admin != null && admin.getPassword().equals(password)) {
            session.setAttribute("admin", admin);
            return "redirect:/admin/cashier";
        } else {
            model.addAttribute("error", "用户名或密码错误");
            return "login";
        }
    }

    /**
     * 管理员登出
     * 清除session并返回登录页面
     */
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "redirect:/login";
    }
}

src/main/java/com/lab/controller/CashierController.java

复制代码
package com.lab.controller;

import com.lab.entity.Cart;
import com.lab.entity.Order;
import com.lab.entity.OrderItem;
import com.lab.entity.Product;
import com.lab.repository.OrderItemRepository;
import com.lab.service.CartService;
import com.lab.service.OrderService;
import com.lab.service.ProductService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.ui.Model;

import java.util.List;
import java.util.UUID;
import java.util.Date;

@Controller
@RequestMapping("/admin/cashier")
public class CashierController {
    @Autowired
    private ProductService productService;

    @Autowired
    private CartService cartService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderItemRepository orderItemRepository;

    /**
     * 收银页面
     */
    @GetMapping("")
    public String cashier(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        // 查找所有商品
        List<Product> products = productService.findAll();
        model.addAttribute("products", products);

        // 查找购物车
        List<Cart> carts = cartService.findAll();
        model.addAttribute("carts", carts);

        // 计算总金额
        double total = 0;
        for (Cart cart : carts) {
            total += cart.getProduct().getPrice() * cart.getQuantity();
        }
        model.addAttribute("total", total);

        return "cashier";
    }

    /**
     * 添加商品到购物车
     */
    @PostMapping("/add-to-cart")
    public String addToCart(@RequestParam Long productId, @RequestParam int quantity, HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        Product product = productService.findById(productId);
        if (product == null) {
            return "redirect:/admin/cashier";
        }

        // 检查购物车中是否已有该商品
        Cart cart = cartService.findByProduct(product);
        if (cart != null) {
            // 如果已有,增加数量
            cart.setQuantity(cart.getQuantity() + quantity);
        } else {
            // 如果没有,创建新购物车项
            cart = new Cart();
            cart.setProduct(product);
            cart.setQuantity(quantity);
        }

        cartService.save(cart);
        return "redirect:/admin/cashier";
    }

    /**
     * 删除购物车项
     */
    @GetMapping("/remove-from-cart/{id}")
    public String removeFromCart(@PathVariable Long id, HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        cartService.delete(id);
        return "redirect:/admin/cashier";
    }

    /**
     * 更新购物车项数量
     */
    @PostMapping("/update-cart")
    public String updateCart(@RequestParam Long id, @RequestParam int quantity, HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        // 查找购物车项
        List<Cart> carts = cartService.findAll();
        Cart cart = null;
        for (Cart c : carts) {
            if (c.getId().equals(id)) {
                cart = c;
                break;
            }
        }

        if (cart != null) {
            cart.setQuantity(quantity);
            cartService.save(cart);
        }
        return "redirect:/admin/cashier";
    }

    /**
     * 清空购物车
     */
    @GetMapping("/clear-cart")
    public String clearCart(HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        cartService.deleteAll();
        return "redirect:/admin/cashier";
    }

    /**
     * 生成订单
     */
    @PostMapping("/create-order")
    public String createOrder(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        // 获取购物车
        List<Cart> carts = cartService.findAll();
        if (carts.isEmpty()) {
            return "redirect:/admin/cashier";
        }

        // 创建订单
        Order order = new Order();
        order.setOrderNumber(UUID.randomUUID().toString());
        order.setOrderDate(new Date());
        order.setStatus("已支付"); // 收银系统直接标记为已支付

        // 计算总金额
        double totalPrice = 0;
        List<OrderItem> orderItems = new java.util.ArrayList<>();

        for (Cart cart : carts) {
            Product product = cart.getProduct();
            if (product.getStock() < cart.getQuantity()) {
                // 库存不足
                return "redirect:/admin/cashier?error=库存不足";
            }

            // 创建订单项
            OrderItem orderItem = new OrderItem();
            orderItem.setOrder(order);
            orderItem.setProduct(product);
            orderItem.setQuantity(cart.getQuantity());
            orderItem.setPrice(product.getPrice());
            orderItems.add(orderItem);

            // 计算总金额
            totalPrice += product.getPrice() * cart.getQuantity();

            // 减少库存
            product.setStock(product.getStock() - cart.getQuantity());
            productService.save(product);
        }

        order.setTotalPrice(totalPrice);
        order.setOrderItems(orderItems);

        // 先保存订单
        Order savedOrder = orderService.save(order);

        // 保存每个订单项
        for (OrderItem orderItem : orderItems) {
            orderItem.setOrder(savedOrder);
            orderItemRepository.save(orderItem);
        }

        // 清空购物车
        cartService.deleteAll();

        // 跳转到订单成功页面,传递整个order对象
        model.addAttribute("order", savedOrder);
        return "order_success";
    }
}

src/main/java/com/lab/controller/HomeController.java

复制代码
package com.lab.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;


@Controller
public class HomeController {
    /**
     * 首页(重定向到登录)
     */
    @GetMapping("/")
    public String home() {
        return "redirect:/login";
    }

    /**
     * 登录页面
     */
    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

src/main/java/com/lab/controller/OrderManagementController.java

复制代码
package com.lab.controller;

import com.lab.entity.Order;
import com.lab.service.OrderService;
import com.lab.service.StatisticsService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;


@Controller
@RequestMapping("/admin/orders")
public class OrderManagementController {
    @Autowired
    private OrderService orderService;

    @Autowired
    private StatisticsService statisticsService;

    /**
     * 订单列表
     */
    @GetMapping("")
    public String orderList(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        List<Order> orders = orderService.findAll();
        model.addAttribute("orders", orders);
        return "admin/orders";
    }

    /**
     * 订单详情
     */
    @GetMapping("/detail/{id}")
    public String orderDetail(@PathVariable Long id, HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        Order order = orderService.findById(id);
        if (order == null) {
            return "redirect:/admin/orders";
        }

        model.addAttribute("order", order);
        return "admin/order_detail";
    }

    /**
     * 删除订单
     */
    @GetMapping("/delete/{id}")
    public String deleteOrder(@PathVariable Long id, HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        orderService.delete(id);
        return "redirect:/admin/orders";
    }

    /**
     * 更新订单状态
     */
    @PostMapping("/update")
    public String updateOrderStatus(@RequestParam Long id, @RequestParam String status, HttpSession session) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        Order order = orderService.findById(id);
        if (order != null) {
            order.setStatus(status);
            orderService.save(order);
        }
        return "redirect:/admin/orders/detail/" + id;
    }

    /**
     * 统计页面
     */
    @GetMapping("/statistics")
    public String statistics(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        // 获取销量统计数据
        Map<String, Integer> salesData = statisticsService.getSalesStatistics();
        model.addAttribute("salesData", salesData);

        // 获取盈利统计数据
        Map<String, Double> profitData = statisticsService.getProfitStatistics();
        model.addAttribute("profitData", profitData);

        // 获取总销售额和订单数
        double totalSales = statisticsService.getTotalSales();
        int orderCount = statisticsService.getOrderCount();
        model.addAttribute("totalSales", totalSales);
        model.addAttribute("orderCount", orderCount);

        return "admin/statistics";
    }
}

src/main/java/com/lab/controller/ProductController.java

复制代码
package com.lab.controller;

import com.lab.entity.Product;
import com.lab.service.ProductService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.ui.Model;

import java.util.List;

@Controller
@RequestMapping("/admin/product")
public class ProductController {
    @Autowired
    private ProductService productService;

    /**
     * 商品管理页面
     */
    @GetMapping("/list")
    public String productList(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        List<Product> products = productService.findAll();
        model.addAttribute("products", products);
        return "admin/products";
    }

    /**
     * 添加商品页面
     */
    @GetMapping("/add")
    public String addProductPage(HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        model.addAttribute("product", new Product());
        return "admin/product_add";
    }

    /**
     * 添加商品(表单提交)
     */
    @PostMapping("/add")
    public String addProduct(@ModelAttribute Product product, HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        productService.save(product);
        model.addAttribute("product", product);
        model.addAttribute("action", "添加");
        return "product_success";
    }

    /**
     * 编辑商品页面
     */
    @GetMapping("/edit/{id}")
    public String editProductPage(@PathVariable Long id, HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        Product product = productService.findById(id);
        if (product == null) {
            return "redirect:/admin/product/list";
        }

        model.addAttribute("product", product);
        return "admin/product_edit";
    }

    /**
     * 编辑商品(表单提交)
     */
    @PostMapping("/edit")
    public String editProduct(@ModelAttribute Product product, HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        productService.save(product);
        model.addAttribute("product", product);
        model.addAttribute("action", "编辑");
        return "product_success";
    }

    /**
     * 删除商品
     */
    @GetMapping("/delete/{id}")
    public String deleteProduct(@PathVariable Long id, HttpSession session, Model model) {
        if (session.getAttribute("admin") == null) {
            return "redirect:/login";
        }

        Product product = productService.findById(id);
        if (product != null) {
            model.addAttribute("product", product);
            productService.delete(id);
            model.addAttribute("action", "删除");
            return "product_success";
        }
        return "redirect:/admin/product/list";
    }
}

接下来就是应用启动入口,运行整个项目时就是运行这个文件,它可以协调各个组件的运行

src/main/java/com/lab/application.java

复制代码
package com.lab;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class application {
    public static void main(String[] args){
        SpringApplication.run(application.class,args);
    }
}

以上就是后端所有代码,现在开始写前端

src/main/resources/templates/admin/order_detail.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>订单详情</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        body::before {
            content: "";
            position: fixed;
            top: -50%;
            right: -20%;
            width: 600px;
            height: 600px;
            background: radial-gradient(
                    circle,
                    rgba(30, 136, 229, 0.08) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        body::after {
            content: "";
            position: fixed;
            bottom: -30%;
            left: -10%;
            width: 500px;
            height: 500px;
            background: radial-gradient(
                    circle,
                    rgba(21, 101, 192, 0.06) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 40px 40px;
            opacity: 0.03;
            z-index: 0;
            pointer-events: none;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 100;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 900px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: "📄";
            font-size: 32px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            overflow: hidden;
            margin-bottom: 24px;
            position: relative;
        }

        .card::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #1e88e5, #1565c0, #1e88e5);
        }

        .card-header {
            padding: 24px;
            border-bottom: 2px solid #f0f0f0;
        }

        .card-header h3 {
            font-size: 22px;
            font-weight: 700;
            color: #1565c0;
        }

        .card-body {
            padding: 24px;
        }

        .order-info {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 20px;
        }

        .order-info-item {
            padding: 16px;
            background: #f5f5f5;
            border-radius: 12px;
        }

        .order-info-label {
            font-size: 14px;
            color: #666;
            margin-bottom: 8px;
        }

        .order-info-value {
            font-size: 18px;
            font-weight: 700;
            color: #333;
        }

        .order-info-value.price {
            color: #e53935;
        }

        .order-info-value.price::before {
            content: "¥";
        }

        .order-info-value.status {
            display: inline-block;
            padding: 6px 16px;
            border-radius: 20px;
            background: #e8f5e9;
            color: #2e7d32;
        }

        .order-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 16px;
            border-bottom: 1px solid #eee;
            transition: background 0.2s ease;
        }

        .order-item:hover {
            background: #f9f9f9;
        }

        .order-item:last-child {
            border-bottom: none;
        }

        .order-item-name {
            font-size: 18px;
            font-weight: 700;
            color: #333;
        }

        .order-item-quantity {
            font-size: 16px;
            color: #666;
            margin: 0 24px;
        }

        .order-item-price {
            font-size: 18px;
            font-weight: 700;
            color: #e53935;
        }

        .order-item-price::before {
            content: "¥";
        }

        .order-total {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 24px;
            background: #f5f5f5;
            border-radius: 12px;
            margin-top: 24px;
        }

        .order-total-label {
            font-size: 20px;
            font-weight: 700;
            color: #333;
        }

        .order-total-amount {
            font-size: 32px;
            font-weight: 800;
            color: #e53935;
        }

        .order-total-amount::before {
            content: "¥";
            font-size: 24px;
        }

        .order-actions {
            text-align: center;
            margin-top: 24px;
        }

        .btn-back {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 14px 32px;
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            text-decoration: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .btn-back:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(30, 136, 229, 0.4);
        }

        .btn-back::before {
            content: "←";
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders" class="active">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">订单详情</h2>

    <div class="card">
        <div class="card-header">
            <h3>订单信息</h3>
        </div>
        <div class="card-body">
            <div class="order-info">
                <div class="order-info-item">
                    <div class="order-info-label">订单号</div>
                    <div
                            class="order-info-value"
                            th:text="${order.orderNumber}"
                    ></div>
                </div>
                <div class="order-info-item">
                    <div class="order-info-label">下单时间</div>
                    <div
                            class="order-info-value"
                            th:text="${#dates.format(order.orderDate, 'yyyy-MM-dd HH:mm:ss')}"
                    ></div>
                </div>
                <div class="order-info-item">
                    <div class="order-info-label">订单状态</div>
                    <div
                            class="order-info-value status"
                            th:text="${order.status}"
                    ></div>
                </div>
                <div class="order-info-item">
                    <div class="order-info-label">商品数量</div>
                    <div
                            class="order-info-value"
                            th:text="${#lists.size(order.orderItems)} + ' 件'"
                    ></div>
                </div>
            </div>
        </div>
    </div>

    <div class="card">
        <div class="card-header">
            <h3>商品明细</h3>
        </div>
        <div class="card-body">
            <div th:each="item : ${order.orderItems}" class="order-item">
                <span class="order-item-name" th:text="${item.product.name}"></span>
                <span class="order-item-quantity">× [[${item.quantity}]]</span>
                <span class="order-item-price" th:text="${item.price}"></span>
            </div>
            <div class="order-total">
                <span class="order-total-label">订单总计</span>
                <span
                        class="order-total-amount"
                        th:text="${order.totalPrice}"
                ></span>
            </div>
        </div>
    </div>

    <div class="order-actions">
        <a href="/admin/orders" class="btn-back">返回列表</a>
    </div>
</div>
</body>
</html>

src/main/resources/templates/admin/orders.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>订单管理</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        body::before {
            content: "";
            position: fixed;
            top: -50%;
            right: -20%;
            width: 600px;
            height: 600px;
            background: radial-gradient(
                    circle,
                    rgba(30, 136, 229, 0.08) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        body::after {
            content: "";
            position: fixed;
            bottom: -30%;
            left: -10%;
            width: 500px;
            height: 500px;
            background: radial-gradient(
                    circle,
                    rgba(21, 101, 192, 0.06) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 40px 40px;
            opacity: 0.03;
            z-index: 0;
            pointer-events: none;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 100;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 1400px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: "📋";
            font-size: 32px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            overflow: hidden;
            position: relative;
        }

        .card::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #1e88e5, #1565c0, #1e88e5);
        }

        .card-header {
            padding: 24px;
            border-bottom: 2px solid #f0f0f0;
        }

        .card-header h3 {
            font-size: 22px;
            font-weight: 700;
            color: #1565c0;
        }

        .card-body {
            padding: 24px;
        }

        table {
            width: 100%;
            border-collapse: collapse;
        }

        th {
            background: #f5f5f5;
            padding: 16px;
            text-align: left;
            font-size: 16px;
            font-weight: 700;
            color: #333;
            border-bottom: 2px solid #e0e0e0;
        }

        td {
            padding: 20px 16px;
            border-bottom: 1px solid #eee;
            font-size: 16px;
            color: #333;
        }

        tr:hover {
            background: #f9f9f9;
        }

        .order-number {
            font-family: monospace;
            font-size: 14px;
            color: #666;
        }

        .order-date {
            font-size: 15px;
            color: #666;
        }

        .order-price {
            font-size: 18px;
            font-weight: 700;
            color: #e53935;
        }

        .order-price::before {
            content: "¥";
        }

        .order-status {
            display: inline-block;
            padding: 6px 16px;
            border-radius: 20px;
            font-size: 14px;
            font-weight: 600;
        }

        .status-paid {
            background: #e8f5e9;
            color: #2e7d32;
        }

        .action-buttons {
            display: flex;
            gap: 10px;
        }

        .action-buttons a {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 10px 16px;
            border-radius: 8px;
            text-decoration: none;
            font-size: 14px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .btn-detail {
            background: #e3f2fd;
            color: #1565c0;
        }

        .btn-detail:hover {
            background: #1565c0;
            color: white;
        }

        .btn-detail::before {
            content: "👁️";
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders" class="active">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">订单管理</h2>

    <div class="card">
        <div class="card-header">
            <h3>订单列表</h3>
        </div>
        <div class="card-body">
            <table>
                <thead>
                <tr>
                    <th>ID</th>
                    <th>订单号</th>
                    <th>下单时间</th>
                    <th>总金额</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="order : ${orders}">
                    <td th:text="${order.id}"></td>
                    <td class="order-number" th:text="${order.orderNumber}"></td>
                    <td
                            class="order-date"
                            th:text="${#dates.format(order.orderDate, 'yyyy-MM-dd HH:mm:ss')}"
                    ></td>
                    <td class="order-price" th:text="${order.totalPrice}"></td>
                    <td>
                  <span
                          class="order-status status-paid"
                          th:text="${order.status}"
                  ></span>
                    </td>
                    <td class="action-buttons">
                        <a
                                th:href="@{/admin/orders/detail/{id}(id=${order.id})}"
                                class="btn-detail"
                        >查看详情</a
                        >
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

src/main/resources/templates/admin/product_add.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>添加商品</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 700px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: "➕";
            font-size: 32px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        .card-header {
            padding: 24px;
            border-bottom: 2px solid #e3f2fd;
            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
        }

        .card-header h3 {
            font-size: 22px;
            font-weight: 700;
            color: #1565c0;
        }

        .card-body {
            padding: 32px;
        }

        .form-group {
            margin-bottom: 24px;
        }

        .form-group label {
            display: block;
            font-size: 16px;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }

        .form-group input,
        .form-group textarea {
            width: 100%;
            padding: 14px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            font-size: 16px;
            transition: all 0.3s ease;
        }

        .form-group input:focus,
        .form-group textarea:focus {
            outline: none;
            border-color: #1e88e5;
            box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1);
        }

        .form-group textarea {
            min-height: 120px;
            resize: vertical;
        }

        .form-actions {
            display: flex;
            gap: 16px;
            margin-top: 32px;
        }

        .btn-submit {
            flex: 1;
            padding: 14px 24px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            color: white;
            border: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .btn-submit:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(67, 160, 71, 0.4);
        }

        .btn-cancel {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 14px 24px;
            background: #f5f5f5;
            color: #666;
            text-decoration: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .btn-cancel:hover {
            background: #e0e0e0;
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list" class="active">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">添加商品</h2>

    <div class="card">
        <div class="card-header">
            <h3>商品信息</h3>
        </div>
        <div class="card-body">
            <form th:action="@{/admin/product/add}" method="post">
                <div class="form-group">
                    <label for="name">商品名称</label>
                    <input
                            type="text"
                            id="name"
                            name="name"
                            placeholder="请输入商品名称"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="price">商品价格</label>
                    <input
                            type="number"
                            id="price"
                            name="price"
                            step="0.01"
                            min="0"
                            placeholder="请输入商品价格"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="stock">库存数量</label>
                    <input
                            type="number"
                            id="stock"
                            name="stock"
                            min="0"
                            placeholder="请输入库存数量"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="description">商品描述</label>
                    <textarea
                            id="description"
                            name="description"
                            placeholder="请输入商品描述"
                    ></textarea>
                </div>
                <div class="form-actions">
                    <a href="/admin/product/list" class="btn-cancel">取消</a>
                    <button type="submit" class="btn-submit">添加商品</button>
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>

src/main/resources/templates/admin/product_edit.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>编辑商品</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 700px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: "✏️";
            font-size: 32px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        .card-header {
            padding: 24px;
            border-bottom: 2px solid #e3f2fd;
            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
        }

        .card-header h3 {
            font-size: 22px;
            font-weight: 700;
            color: #1565c0;
        }

        .card-body {
            padding: 32px;
        }

        .form-group {
            margin-bottom: 24px;
        }

        .form-group label {
            display: block;
            font-size: 16px;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }

        .form-group input,
        .form-group textarea {
            width: 100%;
            padding: 14px 16px;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            font-size: 16px;
            transition: all 0.3s ease;
        }

        .form-group input:focus,
        .form-group textarea:focus {
            outline: none;
            border-color: #1e88e5;
            box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1);
        }

        .form-group textarea {
            min-height: 120px;
            resize: vertical;
        }

        .form-actions {
            display: flex;
            gap: 16px;
            margin-top: 32px;
        }

        .btn-submit {
            flex: 1;
            padding: 14px 24px;
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            border: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .btn-submit:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(30, 136, 229, 0.4);
        }

        .btn-cancel {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 14px 24px;
            background: #f5f5f5;
            color: #666;
            text-decoration: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .btn-cancel:hover {
            background: #e0e0e0;
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list" class="active">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">编辑商品</h2>

    <div class="card">
        <div class="card-header">
            <h3>商品信息</h3>
        </div>
        <div class="card-body">
            <form th:action="@{/admin/product/edit}" method="post">
                <input type="hidden" name="id" th:value="${product.id}" />
                <div class="form-group">
                    <label for="name">商品名称</label>
                    <input
                            type="text"
                            id="name"
                            name="name"
                            th:value="${product.name}"
                            placeholder="请输入商品名称"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="price">商品价格</label>
                    <input
                            type="number"
                            id="price"
                            name="price"
                            step="0.01"
                            min="0"
                            th:value="${product.price}"
                            placeholder="请输入商品价格"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="stock">库存数量</label>
                    <input
                            type="number"
                            id="stock"
                            name="stock"
                            min="0"
                            th:value="${product.stock}"
                            placeholder="请输入库存数量"
                            required
                    />
                </div>
                <div class="form-group">
                    <label for="description">商品描述</label>
                    <textarea
                            id="description"
                            name="description"
                            placeholder="请输入商品描述"
                            th:text="${product.description}"
                    ></textarea>
                </div>
                <div class="form-actions">
                    <a href="/admin/product/list" class="btn-cancel">取消</a>
                    <button type="submit" class="btn-submit">保存修改</button>
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>

src/main/resources/templates/admin/products.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>商品管理</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 1400px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 24px;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
        }

        .page-title::before {
            content: "📦";
            font-size: 32px;
        }

        .btn-add {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 14px 28px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            color: white;
            text-decoration: none;
            border-radius: 12px;
            font-size: 17px;
            font-weight: 600;
            transition: all 0.3s ease;
            box-shadow: 0 4px 15px rgba(67, 160, 71, 0.3);
        }

        .btn-add:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(67, 160, 71, 0.4);
        }

        .btn-add::before {
            content: "➕";
        }

        .products-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
            gap: 24px;
        }

        .product-card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            overflow: hidden;
            transition: all 0.3s ease;
            position: relative;
        }

        .product-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
        }

        .product-card::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #1e88e5, #1565c0);
        }

        .product-header {
            padding: 20px 24px 16px;
            border-bottom: 1px solid #f0f0f0;
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
        }

        .product-id {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-width: 36px;
            height: 36px;
            padding: 0 10px;
            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
            color: #1565c0;
            font-size: 14px;
            font-weight: 700;
            border-radius: 8px;
        }

        .product-name {
            font-size: 22px;
            font-weight: 700;
            color: #333;
            flex: 1;
            margin-left: 16px;
            line-height: 1.3;
        }

        .product-body {
            padding: 20px 24px;
        }

        .product-info {
            display: flex;
            gap: 24px;
            margin-bottom: 16px;
        }

        .info-item {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .info-label {
            font-size: 13px;
            color: #999;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .info-value {
            font-size: 20px;
            font-weight: 700;
            color: #333;
        }

        .info-value.price {
            color: #e53935;
        }

        .info-value.price::before {
            content: "¥";
            font-size: 16px;
        }

        .info-value.stock {
            color: #43a047;
        }

        .info-value.stock::after {
            content: " 件";
            font-size: 14px;
            font-weight: 400;
            color: #666;
        }

        .product-description {
            background: #f8f9fa;
            border-radius: 10px;
            padding: 16px;
            margin-top: 16px;
        }

        .description-label {
            display: flex;
            align-items: center;
            gap: 6px;
            font-size: 13px;
            color: #666;
            font-weight: 600;
            margin-bottom: 8px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .description-label::before {
            content: "📝";
        }

        .description-text {
            font-size: 15px;
            color: #555;
            line-height: 1.6;
            word-break: break-word;
        }

        .description-empty {
            font-size: 14px;
            color: #999;
            font-style: italic;
        }

        .product-actions {
            display: flex;
            gap: 12px;
            padding: 16px 24px;
            background: #fafafa;
            border-top: 1px solid #f0f0f0;
        }

        .btn-edit,
        .btn-delete {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            padding: 12px 16px;
            border-radius: 10px;
            text-decoration: none;
            font-size: 15px;
            font-weight: 600;
            transition: all 0.3s ease;
            cursor: pointer;
        }

        .btn-edit {
            background: #e3f2fd;
            color: #1565c0;
        }

        .btn-edit:hover {
            background: #1565c0;
            color: white;
            transform: translateY(-1px);
        }

        .btn-edit::before {
            content: "✏️";
        }

        .btn-delete {
            background: #ffebee;
            color: #c62828;
        }

        .btn-delete:hover {
            background: #c62828;
            color: white;
            transform: translateY(-1px);
        }

        .btn-delete::before {
            content: "🗑️";
        }

        .empty-state {
            grid-column: 1 / -1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 80px 40px;
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
        }

        .empty-icon {
            font-size: 80px;
            margin-bottom: 20px;
            opacity: 0.5;
        }

        .empty-text {
            font-size: 20px;
            color: #999;
            font-weight: 500;
        }

        @media (max-width: 768px) {
            .main-content {
                padding: 16px 20px;
            }

            .page-header {
                flex-direction: column;
                gap: 16px;
                align-items: stretch;
            }

            .page-title {
                font-size: 24px;
            }

            .btn-add {
                justify-content: center;
            }

            .products-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list" class="active">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <div class="page-header">
        <h2 class="page-title">商品管理</h2>
        <a href="/admin/product/add" class="btn-add">添加商品</a>
    </div>

    <div class="products-grid">
        <th:block th:if="${products.empty}">
            <div class="empty-state">
                <div class="empty-icon">📦</div>
                <p class="empty-text">暂无商品,点击上方按钮添加</p>
            </div>
        </th:block>

        <div class="product-card" th:each="product : ${products}">
            <div class="product-header">
                <span class="product-id" th:text="${product.id}"></span>
                <h3 class="product-name" th:text="${product.name}"></h3>
            </div>

            <div class="product-body">
                <div class="product-info">
                    <div class="info-item">
                        <span class="info-label">价格</span>
                        <span
                                class="info-value price"
                                th:text="${product.price}"
                        ></span>
                    </div>
                    <div class="info-item">
                        <span class="info-label">库存</span>
                        <span
                                class="info-value stock"
                                th:text="${product.stock}"
                        ></span>
                    </div>
                </div>

                <div class="product-description">
                    <div class="description-label">商品描述</div>
                    <p
                            class="description-text"
                            th:if="${product.description != null and !product.description.isEmpty()}"
                            th:text="${product.description}"
                    ></p>
                    <p
                            class="description-empty"
                            th:if="${product.description == null or product.description.isEmpty()}"
                    >
                        暂无描述
                    </p>
                </div>
            </div>

            <div class="product-actions">
                <a
                        th:href="@{/admin/product/edit/{id}(id=${product.id})}"
                        class="btn-edit"
                >
                    编辑
                </a>
                <a
                        th:href="@{/admin/product/delete/{id}(id=${product.id})}"
                        class="btn-delete"
                >
                    删除
                </a>
            </div>
        </div>
    </div>
</div>
</body>
</html>

src/main/resources/templates/admin/statistics.html

将代码拉进去时会警告,这个图表的显示需要chart.js库,右键点击警告,会有一个新建库的选项,点击后idea会自动下载库,随后就能正常运行,除此之外其他警告不足为惧

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>统计分析</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            padding: 24px 40px;
            max-width: 1400px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: "📊";
            font-size: 32px;
        }

        .charts-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 24px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        .card-header {
            padding: 24px;
            border-bottom: 2px solid #e3f2fd;
            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
        }

        .card-header h3 {
            font-size: 22px;
            font-weight: 700;
            color: #1565c0;
        }

        .card-body {
            padding: 24px;
        }

        .chart-container {
            position: relative;
            height: 350px;
        }

        .no-data {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 350px;
            color: #999;
        }

        .no-data-icon {
            font-size: 64px;
            margin-bottom: 16px;
        }

        .no-data-text {
            font-size: 18px;
        }

        @media (max-width: 900px) {
            .charts-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics" class="active">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">统计分析</h2>

    <div class="charts-grid">
        <div class="card">
            <div class="card-header">
                <h3>销量统计(扇形图)</h3>
            </div>
            <div class="card-body">
                <div class="chart-container">
                    <canvas id="salesChart"></canvas>
                </div>
            </div>
        </div>

        <div class="card">
            <div class="card-header">
                <h3>盈利统计(柱状图)</h3>
            </div>
            <div class="card-body">
                <div class="chart-container">
                    <canvas id="profitChart"></canvas>
                </div>
            </div>
        </div>
    </div>
</div>

<script th:inline="javascript">
    /*<![CDATA[*/
    var salesLabels = /*[[${salesData.keySet().toArray()}]]*/ [];
    var salesValues = /*[[${salesData.values().toArray()}]]*/ [];
    var profitLabels = /*[[${profitData.keySet().toArray()}]]*/ [];
    var profitValues = /*[[${profitData.values().toArray()}]]*/ [];
    /*]]>*/

    console.log("销量标签:", salesLabels);
    console.log("销量数据:", salesValues);
    console.log("盈利标签:", profitLabels);
    console.log("盈利数据:", profitValues);

    if (typeof Chart === "undefined") {
        console.error("Chart.js未正确加载");
        alert("图表库未正确加载,请检查网络连接");
    } else {
        console.log("Chart.js已加载");

        var salesCtx = document.getElementById("salesChart");
        if (salesCtx && salesLabels.length > 0) {
            new Chart(salesCtx, {
                type: "pie",
                data: {
                    labels: salesLabels,
                    datasets: [
                        {
                            data: salesValues,
                            backgroundColor: [
                                "rgba(255, 99, 132, 0.8)",
                                "rgba(54, 162, 235, 0.8)",
                                "rgba(255, 206, 86, 0.8)",
                                "rgba(75, 192, 192, 0.8)",
                                "rgba(153, 102, 255, 0.8)",
                                "rgba(255, 159, 64, 0.8)",
                            ],
                            borderColor: [
                                "rgba(255, 99, 132, 1)",
                                "rgba(54, 162, 235, 1)",
                                "rgba(255, 206, 86, 1)",
                                "rgba(75, 192, 192, 1)",
                                "rgba(153, 102, 255, 1)",
                                "rgba(255, 159, 64, 1)",
                            ],
                            borderWidth: 1,
                        },
                    ],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: {
                            position: "bottom",
                        },
                    },
                },
            });
            console.log("销量图表创建成功");
        } else {
            console.error("找不到销量图表容器或数据为空");
        }

        var profitCtx = document.getElementById("profitChart");
        if (profitCtx && profitLabels.length > 0) {
            new Chart(profitCtx, {
                type: "bar",
                data: {
                    labels: profitLabels,
                    datasets: [
                        {
                            label: "盈利",
                            data: profitValues,
                            backgroundColor: "rgba(54, 162, 235, 0.8)",
                            borderColor: "rgba(54, 162, 235, 1)",
                            borderWidth: 1,
                        },
                    ],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        y: {
                            beginAtZero: true,
                        },
                    },
                },
            });
            console.log("盈利图表创建成功");
        } else {
            console.error("找不到盈利图表容器或数据为空");
        }
    }
</script>
</body>
</html>

src/main/resources/templates/cashier.html

复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>收银台</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: '🏪';
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a.active {
            background: rgba(255, 255, 255, 0.25);
        }

        .nav a[href*="cashier"]::before { content: '💰'; }
        .nav a[href*="orders"]::before { content: '📋'; }
        .nav a[href*="statistics"]::before { content: '📊'; }
        .nav a[href*="product"]::before { content: '📦'; }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: '🚪';
        }

        .main-content {
            padding: 24px 40px;
            max-width: 1600px;
            margin: 0 auto;
            position: relative;
            z-index: 1;
        }

        .page-title {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 28px;
            font-weight: 700;
            color: #1565c0;
            margin-bottom: 24px;
        }

        .page-title::before {
            content: '💰';
            font-size: 32px;
        }

        .content-grid {
            display: grid;
            grid-template-columns: 1fr 420px;
            gap: 24px;
        }

        .card {
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            overflow: hidden;
            position: relative;
        }

        .card::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #1e88e5, #1565c0);
        }

        .card-header {
            padding: 20px 24px;
            border-bottom: 2px solid #f0f0f0;
            background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
        }

        .card-header h3 {
            font-size: 20px;
            font-weight: 700;
            color: #1565c0;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .card-header h3::before {
            content: '📦';
        }

        .card-body {
            padding: 20px;
        }

        .product-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
            gap: 20px;
        }

        .product-card {
            background: #fafafa;
            border-radius: 14px;
            overflow: hidden;
            transition: all 0.3s ease;
            border: 2px solid transparent;
            position: relative;
        }

        .product-card:hover {
            background: #fff;
            border-color: #1e88e5;
            box-shadow: 0 8px 24px rgba(30, 136, 229, 0.15);
            transform: translateY(-4px);
        }

        .product-card::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 3px;
            background: linear-gradient(90deg, #1e88e5, #1565c0);
        }

        .product-header {
            padding: 16px 16px 12px;
            border-bottom: 1px solid #eee;
        }

        .product-name {
            font-size: 20px;
            font-weight: 700;
            color: #333;
            margin-bottom: 8px;
            line-height: 1.3;
        }

        .product-info {
            display: flex;
            gap: 20px;
        }

        .info-item {
            display: flex;
            flex-direction: column;
            gap: 2px;
        }

        .info-label {
            font-size: 12px;
            color: #999;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .info-value {
            font-size: 18px;
            font-weight: 700;
        }

        .info-value.price {
            color: #e53935;
        }

        .info-value.price::before {
            content: '¥';
            font-size: 14px;
        }

        .info-value.stock {
            color: #43a047;
        }

        .info-value.stock::after {
            content: ' 件';
            font-size: 12px;
            font-weight: 400;
            color: #666;
        }

        .product-description {
            padding: 12px 16px;
            background: #f0f0f0;
            margin: 0;
        }

        .description-label {
            display: flex;
            align-items: center;
            gap: 4px;
            font-size: 12px;
            color: #666;
            font-weight: 600;
            margin-bottom: 6px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .description-label::before {
            content: '📝';
        }

        .description-text {
            font-size: 14px;
            color: #555;
            line-height: 1.5;
            word-break: break-word;
        }

        .description-empty {
            font-size: 13px;
            color: #999;
            font-style: italic;
        }

        .product-actions {
            padding: 12px 16px;
            background: #fff;
            border-top: 1px solid #eee;
        }

        .product-actions form {
            display: flex;
            gap: 10px;
            align-items: center;
        }

        .quantity-label {
            font-size: 14px;
            color: #666;
            font-weight: 500;
        }

        .product-actions input[type="number"] {
            width: 70px;
            padding: 10px 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            text-align: center;
            font-size: 15px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .product-actions input[type="number"]:focus {
            outline: none;
            border-color: #1e88e5;
            box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.1);
        }

        .btn-add {
            flex: 1;
            padding: 10px 16px;
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .btn-add:hover {
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(30, 136, 229, 0.3);
        }

        .cart-card {
            position: sticky;
            top: 24px;
            align-self: start;
        }

        .cart-card .card-header h3::before {
            content: '🛒';
        }

        .empty-cart {
            padding: 60px 20px;
            text-align: center;
            color: #999;
        }

        .empty-cart-icon {
            font-size: 64px;
            margin-bottom: 16px;
            opacity: 0.5;
        }

        .empty-cart p {
            font-size: 16px;
        }

        .cart-list {
            max-height: 400px;
            overflow-y: auto;
        }

        .cart-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 16px;
            border-bottom: 1px solid #f0f0f0;
            transition: background 0.2s ease;
        }

        .cart-item:hover {
            background: #fafafa;
        }

        .cart-item:last-child {
            border-bottom: none;
        }

        .cart-item-info {
            flex: 1;
        }

        .cart-item-info h4 {
            font-size: 16px;
            font-weight: 700;
            color: #333;
            margin-bottom: 4px;
        }

        .cart-item-price {
            font-size: 16px;
            font-weight: 700;
            color: #e53935;
        }

        .cart-item-price::before {
            content: '¥';
        }

        .cart-item-controls {
            margin-top: 8px;
        }

        .cart-item-controls form {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        .cart-item-controls input[type="number"] {
            width: 50px;
            padding: 6px;
            border: 2px solid #e0e0e0;
            border-radius: 6px;
            text-align: center;
            font-size: 14px;
        }

        .cart-item-controls button {
            padding: 6px 12px;
            background: #e3f2fd;
            color: #1565c0;
            border: none;
            border-radius: 6px;
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .cart-item-controls button:hover {
            background: #1565c0;
            color: white;
        }

        .cart-item-delete {
            font-size: 24px;
            text-decoration: none;
            transition: transform 0.2s ease;
            padding: 8px;
        }

        .cart-item-delete:hover {
            transform: scale(1.2);
        }

        .cart-summary {
            padding: 20px;
            background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
            border-top: 2px solid #f0f0f0;
        }

        .cart-total {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
        }

        .cart-total span:first-child {
            font-size: 18px;
            color: #666;
            font-weight: 500;
        }

        .cart-total .amount {
            font-size: 32px;
            font-weight: 800;
            color: #e53935;
        }

        .cart-total .amount::before {
            content: '¥';
            font-size: 22px;
        }

        .cart-actions {
            display: flex;
            gap: 12px;
        }

        .btn-clear {
            padding: 14px 24px;
            background: #f5f5f5;
            color: #666;
            text-decoration: none;
            border-radius: 10px;
            font-size: 16px;
            font-weight: 600;
            text-align: center;
            transition: all 0.3s ease;
        }

        .btn-clear:hover {
            background: #e0e0e0;
        }

        .btn-checkout {
            width: 100%;
            padding: 16px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            color: white;
            border: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 700;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .btn-checkout:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(67, 160, 71, 0.4);
        }

        @media (max-width: 1200px) {
            .content-grid {
                grid-template-columns: 1fr;
            }

            .cart-card {
                position: static;
            }

            .product-grid {
                grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
            }
        }

        @media (max-width: 768px) {
            .main-content {
                padding: 16px 20px;
            }

            .product-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier" class="active">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <h2 class="page-title">收银台</h2>

    <div class="content-grid">
        <div class="card">
            <div class="card-header">
                <h3>商品列表</h3>
            </div>
            <div class="card-body">
                <div class="product-grid">
                    <th:block th:each="product : ${products}">
                        <div class="product-card">
                            <div class="product-header">
                                <h4 class="product-name" th:text="${product.name}"></h4>
                                <div class="product-info">
                                    <div class="info-item">
                                        <span class="info-label">价格</span>
                                        <span class="info-value price" th:text="${product.price}"></span>
                                    </div>
                                    <div class="info-item">
                                        <span class="info-label">库存</span>
                                        <span class="info-value stock" th:text="${product.stock}"></span>
                                    </div>
                                </div>
                            </div>

                            <div class="product-description">
                                <div class="description-label">商品描述</div>
                                <p class="description-text"
                                   th:if="${product.description != null and !product.description.isEmpty()}"
                                   th:text="${product.description}"></p>
                                <p class="description-empty"
                                   th:if="${product.description == null or product.description.isEmpty()}">
                                    暂无描述
                                </p>
                            </div>

                            <div class="product-actions">
                                <form th:action="@{/admin/cashier/add-to-cart}" method="post">
                                    <input type="hidden" name="productId" th:value="${product.id}">
                                    <span class="quantity-label">数量:</span>
                                    <input type="number" name="quantity" value="1" min="1" th:max="${product.stock}">
                                    <button type="submit" class="btn-add">添加到购物车</button>
                                </form>
                            </div>
                        </div>
                    </th:block>
                </div>
            </div>
        </div>

        <div class="card cart-card">
            <div class="card-header">
                <h3>当前订单</h3>
            </div>
            <div class="card-body" style="padding: 0;">
                <th:block th:if="${carts.empty}">
                    <div class="empty-cart">
                        <div class="empty-cart-icon">🛒</div>
                        <p>购物车为空</p>
                    </div>
                </th:block>
                <th:block th:unless="${carts.empty}">
                    <div class="cart-list">
                        <div th:each="cart : ${carts}" class="cart-item">
                            <div class="cart-item-info">
                                <h4 th:text="${cart.product.name}"></h4>
                                <p class="cart-item-price">[[${cart.product.price}]]</p>
                                <div class="cart-item-controls">
                                    <form th:action="@{/admin/cashier/update-cart}" method="post">
                                        <input type="hidden" name="id" th:value="${cart.id}">
                                        <input type="number" name="quantity" th:value="${cart.quantity}" min="1" th:max="${cart.product.stock}">
                                        <button type="submit">更新</button>
                                    </form>
                                </div>
                            </div>
                            <a th:href="@{/admin/cashier/remove-from-cart/{id}(id=${cart.id})}" class="cart-item-delete">🗑️</a>
                        </div>
                    </div>
                    <div class="cart-summary">
                        <div class="cart-total">
                            <span>总计</span>
                            <span class="amount" th:text="${total}"></span>
                        </div>
                        <div class="cart-actions">
                            <a href="/admin/cashier/clear-cart" class="btn-clear">清空</a>
                            <form th:action="@{/admin/cashier/create-order}" method="post" style="flex: 1;">
                                <button type="submit" class="btn-checkout">立即结算</button>
                            </form>
                        </div>
                    </div>
                </th:block>
            </div>
        </div>
    </div>
</div>
</body>
</html>

src/main/resources/templates/login.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>用户登录</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            overflow-x: hidden;
        }

        body::before {
            content: "";
            position: fixed;
            top: -50%;
            right: -20%;
            width: 600px;
            height: 600px;
            background: radial-gradient(
                    circle,
                    rgba(30, 136, 229, 0.08) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        body::after {
            content: "";
            position: fixed;
            bottom: -30%;
            left: -10%;
            width: 500px;
            height: 500px;
            background: radial-gradient(
                    circle,
                    rgba(21, 101, 192, 0.06) 0%,
                    transparent 70%
            );
            border-radius: 50%;
            z-index: 0;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 40px 40px;
            opacity: 0.03;
            z-index: 0;
            pointer-events: none;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 20px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            position: relative;
            z-index: 1;
        }

        .header h1 {
            font-size: 28px;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .header h1::before {
            content: "🏪";
            font-size: 32px;
        }

        .main-content {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 40px 20px;
            position: relative;
            z-index: 1;
        }

        .login-card {
            background: white;
            border-radius: 20px;
            padding: 48px;
            width: 100%;
            max-width: 440px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
            position: relative;
            overflow: hidden;
        }

        .login-card::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #1e88e5, #1565c0, #1e88e5);
        }

        .login-header {
            text-align: center;
            margin-bottom: 32px;
        }

        .login-header h2 {
            font-size: 32px;
            color: #333;
            margin-bottom: 8px;
            font-weight: 600;
        }

        .login-header p {
            color: #666;
            font-size: 16px;
        }

        .form-group {
            margin-bottom: 24px;
        }

        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-size: 16px;
            font-weight: 500;
            color: #333;
        }

        .form-group input {
            width: 100%;
            padding: 16px;
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            font-size: 18px;
            transition: all 0.3s ease;
            background: #fafafa;
        }

        .form-group input:focus {
            outline: none;
            border-color: #1e88e5;
            background: #fff;
            box-shadow: 0 0 0 4px rgba(30, 136, 229, 0.1);
        }

        .login-btn {
            width: 100%;
            padding: 18px;
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            border: none;
            border-radius: 12px;
            font-size: 20px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-top: 8px;
        }

        .login-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(30, 136, 229, 0.4);
        }

        .error {
            background: #ffebee;
            color: #c62828;
            padding: 14px;
            border-radius: 10px;
            margin-top: 20px;
            font-size: 15px;
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

        .error::before {
            content: "⚠️";
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <h1>收银系统</h1>
</div>

<div class="main-content">
    <div class="login-card">
        <div class="login-header">
            <h2>管理员登录</h2>
            <p>欢迎使用智能收银系统</p>
        </div>
        <form th:action="@{/admin/login}" method="post">
            <div class="form-group">
                <label for="username">用户名</label>
                <input
                        type="text"
                        id="username"
                        name="username"
                        placeholder="请输入用户名"
                        required
                />
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input
                        type="password"
                        id="password"
                        name="password"
                        placeholder="请输入密码"
                        required
                />
            </div>
            <button type="submit" class="login-btn">登录</button>
            <div class="error" th:if="${error != null}" th:text="${error}"></div>
        </form>
    </div>
</div>
</body>
</html>

src/main/resources/templates/order_success.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>订单创建成功</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            display: flex;
            flex-direction: column;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 40px;
            position: relative;
            z-index: 1;
        }

        .success-card {
            background: white;
            border-radius: 20px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            padding: 48px;
            text-align: center;
            max-width: 500px;
            width: 100%;
        }

        .success-icon {
            width: 100px;
            height: 100px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 auto 24px;
            font-size: 48px;
            color: white;
        }

        .success-title {
            font-size: 32px;
            font-weight: 800;
            color: #2e7d32;
            margin-bottom: 16px;
        }

        .success-message {
            font-size: 18px;
            color: #666;
            margin-bottom: 32px;
            line-height: 1.6;
        }

        .order-info {
            background: #f5f5f5;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 32px;
        }

        .order-info-row {
            display: flex;
            justify-content: space-between;
            padding: 12px 0;
            border-bottom: 1px solid #e0e0e0;
        }

        .order-info-row:last-child {
            border-bottom: none;
        }

        .order-info-label {
            font-size: 16px;
            color: #666;
        }

        .order-info-value {
            font-size: 18px;
            font-weight: 700;
            color: #333;
        }

        .order-info-value.price {
            color: #e53935;
        }

        .order-info-value.price::before {
            content: "¥";
        }

        .btn-back {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 16px 40px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            color: white;
            text-decoration: none;
            border-radius: 12px;
            font-size: 20px;
            font-weight: 700;
            transition: all 0.3s ease;
        }

        .btn-back:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(67, 160, 71, 0.4);
        }

        .btn-back::before {
            content: "←";
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <div class="success-card">
        <div class="success-icon">✓</div>
        <h1 class="success-title">订单创建成功</h1>
        <p class="success-message">
            订单已成功创建并保存到数据库,感谢使用收银系统!
        </p>

        <div class="order-info">
            <div class="order-info-row">
                <span class="order-info-label">订单号</span>
                <span
                        class="order-info-value"
                        th:text="${order.orderNumber}"
                ></span>
            </div>
            <div class="order-info-row">
                <span class="order-info-label">下单时间</span>
                <span
                        class="order-info-value"
                        th:text="${#dates.format(order.orderDate, 'yyyy-MM-dd HH:mm:ss')}"
                ></span>
            </div>
            <div class="order-info-row">
                <span class="order-info-label">订单金额</span>
                <span
                        class="order-info-value price"
                        th:text="${order.totalPrice}"
                ></span>
            </div>
            <div class="order-info-row">
                <span class="order-info-label">订单状态</span>
                <span class="order-info-value" th:text="${order.status}"></span>
            </div>
        </div>

        <a href="/admin/cashier" class="btn-back">返回收银台</a>
    </div>
</div>
</body>
</html>

src/main/resources/templates/product_success.html

复制代码
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>操作成功</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family:
                    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                    "Helvetica Neue", Arial, sans-serif;
            min-height: 100vh;
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
            position: relative;
            display: flex;
            flex-direction: column;
            overflow-x: hidden;
        }

        .decorative-dots {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-image: radial-gradient(circle, #1e88e5 1px, transparent 1px);
            background-size: 30px 30px;
            opacity: 0.08;
            pointer-events: none;
            z-index: 0;
        }

        .header {
            background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%);
            color: white;
            padding: 16px 40px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: space-between;
            position: relative;
            z-index: 10;
        }

        .header-left {
            display: flex;
            align-items: center;
            gap: 32px;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 26px;
            font-weight: 700;
        }

        .logo::before {
            content: "🏪";
            font-size: 32px;
        }

        .nav {
            display: flex;
            gap: 8px;
        }

        .nav a {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .nav a:hover {
            background: rgba(255, 255, 255, 0.15);
        }

        .nav a[href*="cashier"]::before {
            content: "💰";
        }
        .nav a[href*="orders"]::before {
            content: "📋";
        }
        .nav a[href*="statistics"]::before {
            content: "📊";
        }
        .nav a[href*="product"]::before {
            content: "📦";
        }

        .logout-btn {
            display: flex;
            align-items: center;
            gap: 8px;
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 8px;
            font-size: 17px;
            font-weight: 500;
            transition: all 0.3s ease;
            background: rgba(255, 255, 255, 0.1);
        }

        .logout-btn:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .logout-btn::before {
            content: "🚪";
        }

        .main-content {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 40px;
            position: relative;
            z-index: 1;
        }

        .success-card {
            background: white;
            border-radius: 20px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
            padding: 48px;
            text-align: center;
            max-width: 500px;
            width: 100%;
        }

        .success-icon {
            width: 100px;
            height: 100px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0 auto 24px;
            font-size: 48px;
            color: white;
        }

        .success-title {
            font-size: 32px;
            font-weight: 800;
            color: #2e7d32;
            margin-bottom: 16px;
        }

        .success-message {
            font-size: 18px;
            color: #666;
            margin-bottom: 32px;
            line-height: 1.6;
        }

        .product-info {
            background: #f5f5f5;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 32px;
        }

        .product-info-row {
            display: flex;
            justify-content: space-between;
            padding: 12px 0;
            border-bottom: 1px solid #e0e0e0;
        }

        .product-info-row:last-child {
            border-bottom: none;
        }

        .product-info-label {
            font-size: 16px;
            color: #666;
        }

        .product-info-value {
            font-size: 18px;
            font-weight: 700;
            color: #333;
        }

        .product-info-value.price {
            color: #e53935;
        }

        .product-info-value.price::before {
            content: "¥";
        }

        .btn-back {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 16px 40px;
            background: linear-gradient(135deg, #43a047 0%, #2e7d32 100%);
            color: white;
            text-decoration: none;
            border-radius: 12px;
            font-size: 20px;
            font-weight: 700;
            transition: all 0.3s ease;
        }

        .btn-back:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(67, 160, 71, 0.4);
        }

        .btn-back::before {
            content: "←";
        }
    </style>
</head>
<body>
<div class="decorative-dots"></div>
<div class="header">
    <div class="header-left">
        <div class="logo">收银系统</div>
        <nav class="nav">
            <a href="/admin/cashier">收银</a>
            <a href="/admin/orders">订单管理</a>
            <a href="/admin/orders/statistics">统计分析</a>
            <a href="/admin/product/list">商品管理</a>
        </nav>
    </div>
    <a href="/admin/logout" class="logout-btn">退出</a>
</div>

<div class="main-content">
    <div class="success-card">
        <div class="success-icon">✓</div>
        <h1 class="success-title" th:text="${action + '成功'}"></h1>
        <p class="success-message">
            商品信息已成功<span th:text="${action}"></span>并保存到数据库!
        </p>

        <div class="product-info">
            <div class="product-info-row">
                <span class="product-info-label">商品ID</span>
                <span class="product-info-value" th:text="${product.id}"></span>
            </div>
            <div class="product-info-row">
                <span class="product-info-label">商品名称</span>
                <span class="product-info-value" th:text="${product.name}"></span>
            </div>
            <div class="product-info-row">
                <span class="product-info-label">商品价格</span>
                <span class="product-info-value price" th:text="${product.price}"></span>
            </div>
            <div class="product-info-row">
                <span class="product-info-label">库存数量</span>
                <span class="product-info-value" th:text="${product.stock}"></span>
            </div>
            <div class="product-info-row" th:if="${product.description != null and !product.description.isEmpty()}">
                <span class="product-info-label">商品描述</span>
                <span class="product-info-value" th:text="${product.description}"></span>
            </div>
        </div>

        <a href="/admin/product/list" class="btn-back">返回商品列表</a>
    </div>
</div>
</body>
</html>

以上就是项目的所有代码,大家可以基于这个项目进行更改,做出一个新的项目

相关推荐
Zww08914 分钟前
idea配置注释模板
java·ide·intellij-idea
Renhao-Wan7 分钟前
Docker 核心原理详解:镜像、容器、Namespace、Cgroups 与 UnionFS
java·后端·docker·容器
Rsun0455113 分钟前
ScheduledExecutorService类作用
java
小钊(求职中)21 分钟前
算法知识、常用方法总结
java·算法·排序算法·力扣
萧逸才24 分钟前
【learn-claude-code】S07TaskSystem - 任务系统:大目标拆成小任务,持久化到磁盘
java·人工智能·ai
Rsun0455138 分钟前
MessageUtils.message(“user.jcaptcha.expire“)
java
zaim11 小时前
计算机的错误计算(二百二十六)
java·python·c#·c·错数·mpmath
小江的记录本1 小时前
【RabbitMQ】RabbitMQ核心知识体系全解(5大核心模块:Exchange类型、消息确认机制、死信队列、延迟队列、镜像队列)
java·前端·分布式·后端·spring·rabbitmq·mvc
!停1 小时前
C++入门—内存管理
java·jvm·c++
海参崴-1 小时前
C语言与C++语言发展历史详解
java·c语言·c++