这是一个收银系统,虽然会有点粗糙,但是功能完整,可供大家学习,源码和运行截图放在下面了。
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>
以上就是项目的所有代码,大家可以基于这个项目进行更改,做出一个新的项目