基于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>

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

相关推荐
青衫客362 小时前
浅谈 Apache POI:XSSFWorkbook 的原理与实践(Java 操作 Excel 实践指南)
java·apache·excel
SunnyDays10112 小时前
使用 Java 高效删除 Excel 空白行与空白列
java·删除 excel 空白行·删除 excel 空白列
笨手笨脚の2 小时前
Java 性能优化
java·jvm·数据库·性能优化·分布式锁·分布式事务·并发容器
l软件定制开发工作室2 小时前
Spring开发系列教程(32)——Spring Boot开发
java·spring boot·后端·spring
DolphinScheduler社区2 小时前
Apache DolphinScheduler 3.4.1 发布,新增任务分发超时检测
java·数据库·开源·apache·海豚调度·大数据工作流调度
黑眼圈子2 小时前
Java正则表达式基础知识
java·开发语言·正则表达式
iPadiPhone2 小时前
性能优化的“快车道”:Spring @Async 注解深度原理与大厂实战
java·后端·spring·面试·性能优化
彭于晏Yan2 小时前
JsonProperty注解的access属性
java·spring boot
Mr.朱鹏2 小时前
分布式-redis集群架构
java·redis·分布式·后端·spring·缓存·架构