方案特点
清晰的库存管理:
单品:在商品表记录库存
捆绑商品:在捆绑分组表记录总库存,商品表库存为NULL
完整的商品信息:
分类、子分类管理
供应商、品牌信息
重量、体积、保质期等物理属性
仓库管理
灵活的捆绑销售:
扫描捆绑组内任意商品条码,自动识别为捆绑商品
默认使用捆绑模式,可切换为单品模式
捆绑库存统一管理
丰富的测试数据:
包含饮料、零食、日用品、乳制品等多个分类
40+个真实商品数据
7个捆绑组合示例
这个方案完全满足您的需求,捆绑商品库存统一管理,单品库存独立管理,系统结构清晰且功能完整!
数据库设计
-- 商品捆绑分组表
CREATE TABLE `product_bundle` (
`id` int NOT NULL AUTO_INCREMENT,
`bundle_code` varchar(50) NOT NULL COMMENT '捆绑编码(唯一标识)',
`bundle_name` varchar(100) NOT NULL COMMENT '捆绑名称(内部管理用)',
`bundle_display_name` varchar(150) NOT NULL COMMENT '捆绑显示名称(前端显示用)',
`bundle_quantity` int NOT NULL COMMENT '捆绑总数量',
`bundle_price` decimal(10,2) NOT NULL COMMENT '捆绑价格',
`category` varchar(50) NOT NULL COMMENT '捆绑分类',
`sub_category` varchar(50) DEFAULT NULL COMMENT '捆绑子分类',
`warehouse` varchar(50) DEFAULT '默认仓库' COMMENT '捆绑商品存放仓库',
`total_stock` int DEFAULT '0' COMMENT '捆绑商品总库存',
`supplier` varchar(100) DEFAULT NULL COMMENT '供应商',
`brand` varchar(50) DEFAULT NULL COMMENT '品牌',
`weight` decimal(8,2) DEFAULT NULL COMMENT '重量(kg)',
`volume` decimal(8,2) DEFAULT NULL COMMENT '体积(m³)',
`shelf_life` int DEFAULT NULL COMMENT '保质期(天)',
`production_date` date DEFAULT NULL COMMENT '生产日期',
`expiry_date` date DEFAULT NULL COMMENT '过期日期',
`description` text COMMENT '商品描述',
`image_url` varchar(200) DEFAULT NULL COMMENT '商品图片URL',
`status` tinyint DEFAULT '1' COMMENT '状态:1-启用,0-停用',
`create_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_bundle_code` (`bundle_code`),
KEY `idx_status` (`status`),
KEY `idx_category` (`category`),
KEY `idx_warehouse` (`warehouse`),
KEY `idx_brand` (`brand`),
KEY `idx_supplier` (`supplier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品捆绑分组表';
-- 商品表
CREATE TABLE `product` (
`id` int NOT NULL AUTO_INCREMENT,
`barcode` varchar(50) NOT NULL COMMENT '商品条码',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`display_name` varchar(150) DEFAULT NULL COMMENT '显示名称',
`spec` varchar(100) DEFAULT NULL COMMENT '规格',
`origin` varchar(100) DEFAULT NULL COMMENT '产地',
`price` decimal(10,2) NOT NULL COMMENT '单品售价',
`unit` varchar(20) DEFAULT '件' COMMENT '单位',
`category` varchar(50) NOT NULL COMMENT '分类',
`sub_category` varchar(50) DEFAULT NULL COMMENT '子分类',
`warehouse` varchar(50) DEFAULT '默认仓库' COMMENT '仓库',
`stock` int DEFAULT NULL COMMENT '库存数量(单品有效,捆绑商品为NULL)',
`supplier` varchar(100) DEFAULT NULL COMMENT '供应商',
`brand` varchar(50) DEFAULT NULL COMMENT '品牌',
`weight` decimal(8,2) DEFAULT NULL COMMENT '重量(kg)',
`volume` decimal(8,2) DEFAULT NULL COMMENT '体积(m³)',
`shelf_life` int DEFAULT NULL COMMENT '保质期(天)',
`production_date` date DEFAULT NULL COMMENT '生产日期',
`expiry_date` date DEFAULT NULL COMMENT '过期日期',
`description` text COMMENT '商品描述',
`image_url` varchar(200) DEFAULT NULL COMMENT '商品图片URL',
-- 捆绑销售相关字段
`bundle_group_id` int DEFAULT NULL COMMENT '捆绑分组ID,NULL表示单品',
`status` tinyint DEFAULT '1' COMMENT '状态:1-上架,0-下架',
`create_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_barcode` (`barcode`),
KEY `idx_bundle_group` (`bundle_group_id`),
KEY `idx_status` (`status`),
KEY `idx_category` (`category`),
KEY `idx_warehouse` (`warehouse`),
KEY `idx_brand` (`brand`),
KEY `idx_supplier` (`supplier`),
CONSTRAINT `fk_product_bundle`
FOREIGN KEY (`bundle_group_id`) REFERENCES `product_bundle` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';完整的测试数据
-- 1. 先创建捆绑分组
INSERT INTO `product_bundle` (`bundle_code`, `bundle_name`, `bundle_display_name`, `bundle_quantity`, `bundle_price`, `category`, `sub_category`, `warehouse`, `total_stock`, `supplier`, `brand`, `weight`, `volume`, `shelf_life`, `description`) VALUES
('BUNDLE001', '饮料组合A', '100元6瓶饮料超值组合', 6, 100.00, '饮料', '组合装', '饮料仓', 50, '统一供应商', '康师傅', 3.00, 0.02, 365, '6瓶不同口味饮料组合,满足多种口味需求'),
('BUNDLE002', '可乐套餐', '80元24罐可乐狂欢装', 24, 80.00, '饮料', '碳酸饮料', '饮料仓', 30, '可乐供应商', '可口可乐', 8.00, 0.05, 365, '24罐可乐组合,派对聚会首选'),
('BUNDLE003', '凉茶礼盒', '45元12罐凉茶礼盒', 12, 45.00, '饮料', '凉茶', '饮料仓', 40, '凉茶供应商', '王老吉', 4.00, 0.03, 365, '12罐凉茶礼盒,清热解暑'),
('BUNDLE004', '零食大礼包', '150元零食全家福', 8, 150.00, '零食', '综合零食', '零食仓', 25, '零食供应商', '多种品牌', 2.50, 0.04, 180, '8种零食组合,满足不同口味'),
('BUNDLE005', '日用组合', '200元日用品套装', 5, 200.00, '日用品', '洗漱用品', '日用品仓', 20, '日化供应商', '黑人/飘柔', 3.50, 0.06, 1095, '牙膏洗发水沐浴露组合'),
('BUNDLE006', '牛奶早餐套装', '88元早餐营养组合', 10, 88.00, '乳制品', '牛奶饮品', '乳品仓', 35, '乳业集团', '蒙牛/伊利', 5.00, 0.07, 90, '牛奶面包早餐组合'),
('BUNDLE007', '泡面套餐', '60元12袋泡面组合', 12, 60.00, '方便食品', '泡面', '食品仓', 45, '食品公司', '康师傅/统一', 4.00, 0.08, 180, '12袋不同口味泡面组合');
-- 2. 插入商品数据(包含单品和捆绑商品)
INSERT INTO `product` (`barcode`, `name`, `display_name`, `spec`, `origin`, `price`, `unit`, `category`, `sub_category`, `warehouse`, `stock`, `supplier`, `brand`, `weight`, `shelf_life`, `bundle_group_id`) VALUES
-- 捆绑组1的商品(饮料组合A)
('6934024550017', '康师傅冰红茶', NULL, '500ml', '上海', 3.50, '瓶', '饮料', '茶饮料', '饮料仓', NULL, '统一供应商', '康师傅', 0.50, 365, 1),
('6934024550020', '康师傅绿茶', NULL, '500ml', '上海', 3.50, '瓶', '饮料', '茶饮料', '饮料仓', NULL, '统一供应商', '康师傅', 0.50, 365, 1),
('6934024550030', '康师傅茉莉蜜茶', NULL, '500ml', '上海', 3.80, '瓶', '饮料', '茶饮料', '饮料仓', NULL, '统一供应商', '康师傅', 0.50, 365, 1),
-- 捆绑组2的商品(可乐套餐)
('6901424332025', '可口可乐', NULL, '330ml', '北京', 2.50, '罐', '饮料', '碳酸饮料', '饮料仓', NULL, '可乐供应商', '可口可乐', 0.35, 365, 2),
('6940557300096', '百事可乐', NULL, '330ml', '深圳', 2.50, '罐', '饮料', '碳酸饮料', '饮料仓', NULL, '可乐供应商', '百事可乐', 0.35, 365, 2),
('6901424332030', '可口可乐零度', NULL, '330ml', '北京', 2.80, '罐', '饮料', '碳酸饮料', '饮料仓', NULL, '可乐供应商', '可口可乐', 0.35, 365, 2),
-- 捆绑组3的商品(凉茶礼盒)
('6902538005148', '王老吉凉茶', NULL, '310ml', '广州', 4.00, '罐', '饮料', '凉茶', '饮料仓', NULL, '凉茶供应商', '王老吉', 0.35, 365, 3),
('6902538005150', '加多宝凉茶', NULL, '310ml', '广州', 4.00, '罐', '饮料', '凉茶', '饮料仓', NULL, '凉茶供应商', '加多宝', 0.35, 365, 3),
-- 捆绑组4的商品(零食大礼包)
('6921234567890', '乐事薯片', NULL, '75g', '上海', 8.00, '袋', '零食', '薯片', '零食仓', NULL, '零食供应商', '乐事', 0.08, 180, 4),
('6921234567891', '奥利奥饼干', NULL, '116g', '北京', 6.50, '盒', '零食', '饼干', '零食仓', NULL, '零食供应商', '奥利奥', 0.12, 180, 4),
('6921234567892', '德芙巧克力', NULL, '43g', '上海', 9.00, '条', '零食', '巧克力', '零食仓', NULL, '零食供应商', '德芙', 0.05, 365, 4),
-- 捆绑组5的商品(日用组合)
('6921234567893', '黑人牙膏', NULL, '120g', '广州', 15.00, '支', '日用品', '口腔护理', '日用品仓', NULL, '日化供应商', '黑人', 0.15, 1095, 5),
('6921234567894', '飘柔洗发水', NULL, '400ml', '上海', 25.00, '瓶', '日用品', '洗发护发', '日用品仓', NULL, '日化供应商', '飘柔', 0.45, 1095, 5),
-- 捆绑组6的商品(牛奶早餐套装)
('6922848641019', '蒙牛纯牛奶', NULL, '250ml', '内蒙古', 3.50, '盒', '乳制品', '牛奶', '乳品仓', NULL, '乳业集团', '蒙牛', 0.25, 90, 6),
('6931111111111', '伊利纯牛奶', NULL, '250ml', '内蒙古', 3.80, '盒', '乳制品', '牛奶', '乳品仓', NULL, '乳业集团', '伊利', 0.25, 90, 6),
-- 捆绑组7的商品(泡面套餐)
('6929999999999', '康师傅红烧牛肉面', NULL, '103g', '天津', 3.50, '袋', '方便食品', '泡面', '食品仓', NULL, '食品公司', '康师傅', 0.10, 180, 7),
('6934444444444', '统一汤达人', NULL, '130g', '上海', 6.00, '袋', '方便食品', '泡面', '食品仓', NULL, '食品公司', '统一', 0.12, 180, 7),
-- 独立商品(不参与捆绑,有库存)
('6921168590019', '农夫山泉矿泉水', NULL, '550ml', '杭州', 2.00, '瓶', '饮料', '矿泉水', '饮料仓', 200, '农夫山泉', '农夫山泉', 0.55, 365, NULL),
('6925303774166', '红牛维生素饮料', NULL, '250ml', '北京', 6.00, '罐', '饮料', '功能饮料', '饮料仓', 80, '红牛公司', '红牛', 0.30, 365, NULL),
('6920907800999', '统一老坛酸菜牛肉面', NULL, '125g', '成都', 4.50, '袋', '方便食品', '泡面', '食品仓', 300, '统一企业', '统一', 0.13, 180, NULL),
('6922266455255', '统一阿萨姆奶茶', NULL, '500ml', '上海', 5.00, '瓶', '饮料', '奶茶', '饮料仓', 120, '统一企业', '统一', 0.52, 180, NULL),
('6922862885888', '美汁源果粒橙', NULL, '450ml', '上海', 4.50, '瓶', '饮料', '果汁', '饮料仓', 100, '可口可乐', '美汁源', 0.48, 180, NULL),
('6925303720569', '雪碧', NULL, '330ml', '北京', 2.50, '罐', '饮料', '碳酸饮料', '饮料仓', 180, '可口可乐', '雪碧', 0.35, 365, NULL),
('6925303720568', '芬达橙味汽水', NULL, '330ml', '北京', 2.50, '罐', '饮料', '碳酸饮料', '饮料仓', 160, '可口可乐', '芬达', 0.35, 365, NULL),
('6928888888888', '旺仔牛奶', NULL, '245ml', '广州', 5.50, '罐', '乳制品', '乳饮料', '乳品仓', 90, '旺旺集团', '旺仔', 0.26, 180, NULL),
('6932222222222', '娃哈哈AD钙奶', NULL, '220ml', '杭州', 2.50, '瓶', '乳制品', '乳饮料', '乳品仓', 180, '娃哈哈', '娃哈哈', 0.23, 180, NULL),
('6933333333333', '康师傅鲜虾鱼板面', NULL, '101g', '天津', 3.80, '袋', '方便食品', '泡面', '食品仓', 200, '康师傅', '康师傅', 0.11, 180, NULL),
('6935555555555', '东鹏特饮', NULL, '500ml', '深圳', 5.00, '瓶', '饮料', '功能饮料', '饮料仓', 100, '东鹏', '东鹏特饮', 0.53, 365, NULL),
('6936666666666', '脉动', NULL, '600ml', '广州', 4.00, '瓶', '饮料', '功能饮料', '饮料仓', 130, '达能', '脉动', 0.62, 365, NULL),
('6937777777777', '七喜', NULL, '330ml', '上海', 2.50, '罐', '饮料', '碳酸饮料', '饮料仓', 140, '百事可乐', '七喜', 0.35, 365, NULL),
('6938888888888', '汇源100%橙汁', NULL, '1L', '北京', 12.00, '盒', '饮料', '果汁', '饮料仓', 80, '汇源', '汇源', 1.05, 120, NULL),
('6939999999999', '康师傅冰糖雪梨', NULL, '500ml', '上海', 3.50, '瓶', '饮料', '果汁', '饮料仓', 110, '康师傅', '康师傅', 0.52, 180, NULL),
('6941111111111', '安慕希酸奶', NULL, '205g', '内蒙古', 6.50, '盒', '乳制品', '酸奶', '乳品仓', 150, '伊利', '安慕希', 0.22, 150, NULL),
('6942222222222', '纯甄酸奶', NULL, '200g', '内蒙古', 6.00, '盒', '乳制品', '酸奶', '乳品仓', 120, '蒙牛', '纯甄', 0.21, 150, NULL),
('6943333333333', '金典纯牛奶', NULL, '250ml', '内蒙古', 4.50, '盒', '乳制品', '牛奶', '乳品仓', 100, '伊利', '金典', 0.26, 90, NULL),
('6944444444444', '特仑苏纯牛奶', NULL, '250ml', '内蒙古', 4.80, '盒', '乳制品', '牛奶', '乳品仓', 90, '蒙牛', '特仑苏', 0.26, 90, NULL),
('6945555555555', '可口可乐纤维+', NULL, '500ml', '北京', 3.50, '瓶', '饮料', '碳酸饮料', '饮料仓', 200, '可口可乐', '可口可乐', 0.53, 365, NULL);ThinkPHP8 后端完整代码
application/api/controller/Product.php
<?php
namespace app\api\controller;
use think\facade\Db;
use think\exception\ValidateException;
class Product
{
/**
* 导出所有商品数据(包含捆绑分组信息)
* @return \think\response\Json
*/
public function exportProducts()
{
try {
// 获取所有上架的商品基本信息,并关联捆绑分组
$products = Db::name('product')
->alias('p')
->field('p.id, p.barcode, p.name, p.display_name, p.spec, p.origin, p.price, p.unit,
p.category, p.sub_category, p.warehouse, p.stock, p.supplier, p.brand,
p.weight, p.shelf_life, p.bundle_group_id,
b.bundle_code, b.bundle_name, b.bundle_display_name, b.bundle_quantity,
b.bundle_price, b.total_stock as bundle_total_stock')
->leftJoin('product_bundle b', 'p.bundle_group_id = b.id AND b.status = 1')
->where('p.status', 1)
->order('p.category', 'asc')
->order('p.name', 'asc')
->select()
->toArray();
// 处理数据格式和计算字段
foreach ($products as &$product) {
// 类型转换
$product['price'] = (float)$product['price'];
if ($product['bundle_price']) {
$product['bundle_price'] = (float)$product['bundle_price'];
}
if ($product['weight']) {
$product['weight'] = (float)$product['weight'];
}
// 计算商品是否支持捆绑
$product['bundle_supported'] = !empty($product['bundle_group_id']);
// 设置有效显示名称
if ($product['bundle_supported']) {
$product['effective_display_name'] = $product['bundle_display_name'] ?:
$product['bundle_name'] . ' ' . $product['bundle_quantity'] . $product['unit'];
// 捆绑商品使用捆绑库存
$product['effective_stock'] = $product['bundle_total_stock'];
} else {
$product['effective_display_name'] = $product['display_name'] ?: $product['name'];
$product['effective_stock'] = $product['stock'];
}
// 设置商品类型
$product['product_type'] = $product['bundle_supported'] ? 'bundle' : 'single';
}
return json([
'code' => 1,
'msg' => 'success',
'data' => $products,
'count' => count($products)
]);
} catch (\Exception $e) {
return json([
'code' => 0,
'msg' => '数据导出失败:' . $e->getMessage(),
'error' => $e->getTraceAsString()
]);
}
}
/**
* 根据条码查询商品
* @param string $barcode 商品条码
* @return \think\response\Json
*/
public function searchByBarcode($barcode)
{
try {
if (empty($barcode)) {
return json(['code' => 0, 'msg' => '条码不能为空']);
}
// 查询商品信息,关联捆绑分组
$product = Db::name('product')
->alias('p')
->field('p.id, p.barcode, p.name, p.display_name, p.spec, p.origin, p.price, p.unit,
p.category, p.sub_category, p.warehouse, p.stock, p.supplier, p.brand,
p.weight, p.shelf_life, p.bundle_group_id,
b.bundle_code, b.bundle_name, b.bundle_display_name, b.bundle_quantity,
b.bundle_price, b.total_stock as bundle_total_stock')
->leftJoin('product_bundle b', 'p.bundle_group_id = b.id AND b.status = 1')
->where('p.barcode', $barcode)
->where('p.status', 1)
->find();
if (!$product) {
return json(['code' => 0, 'msg' => '商品不存在或已下架']);
}
// 处理数据格式
$product['price'] = (float)$product['price'];
if ($product['bundle_price']) {
$product['bundle_price'] = (float)$product['bundle_price'];
}
if ($product['weight']) {
$product['weight'] = (float)$product['weight'];
}
// 计算商品是否支持捆绑
$product['bundle_supported'] = !empty($product['bundle_group_id']);
// 设置有效显示名称
if ($product['bundle_supported']) {
$product['effective_display_name'] = $product['bundle_display_name'] ?:
$product['bundle_name'] . ' ' . $product['bundle_quantity'] . $product['unit'];
// 捆绑商品使用捆绑库存
$product['effective_stock'] = $product['bundle_total_stock'];
} else {
$product['effective_display_name'] = $product['display_name'] ?: $product['name'];
$product['effective_stock'] = $product['stock'];
}
// 设置商品类型
$product['product_type'] = $product['bundle_supported'] ? 'bundle' : 'single';
return json([
'code' => 1,
'msg' => 'success',
'data' => $product
]);
} catch (\Exception $e) {
return json([
'code' => 0,
'msg' => '查询失败:' . $e->getMessage(),
'error' => $e->getTraceAsString()
]);
}
}
/**
* 根据分类查询商品
* @param string $category 分类名称
* @return \think\response\Json
*/
public function getProductsByCategory($category)
{
try {
$products = Db::name('product')
->alias('p')
->field('p.id, p.barcode, p.name, p.display_name, p.spec, p.origin, p.price, p.unit,
p.category, p.sub_category, p.warehouse, p.stock, p.supplier, p.brand,
p.bundle_group_id,
b.bundle_display_name, b.bundle_quantity, b.bundle_price, b.total_stock as bundle_total_stock')
->leftJoin('product_bundle b', 'p.bundle_group_id = b.id AND b.status = 1')
->where('p.category', $category)
->where('p.status', 1)
->order('p.name', 'asc')
->select()
->toArray();
// 处理数据
foreach ($products as &$product) {
$product['price'] = (float)$product['price'];
if ($product['bundle_price']) {
$product['bundle_price'] = (float)$product['bundle_price'];
}
$product['bundle_supported'] = !empty($product['bundle_group_id']);
if ($product['bundle_supported']) {
$product['effective_display_name'] = $product['bundle_display_name'] ?:
'捆绑组合 ' . $product['bundle_quantity'] . $product['unit'];
$product['effective_stock'] = $product['bundle_total_stock'];
} else {
$product['effective_display_name'] = $product['display_name'] ?: $product['name'];
$product['effective_stock'] = $product['stock'];
}
}
return json([
'code' => 1,
'msg' => 'success',
'data' => $products,
'count' => count($products)
]);
} catch (\Exception $e) {
return json([
'code' => 0,
'msg' => '查询失败:' . $e->getMessage()
]);
}
}
/**
* 获取所有分类
* @return \think\response\Json
*/
public function getCategories()
{
try {
$categories = Db::name('product')
->field('category, count(*) as product_count')
->where('status', 1)
->group('category')
->order('product_count', 'desc')
->select();
return json([
'code' => 1,
'msg' => 'success',
'data' => $categories
]);
} catch (\Exception $e) {
return json([
'code' => 0,
'msg' => '查询失败:' . $e->getMessage()
]);
}
}
/**
* 更新捆绑商品库存
* @return \think\response\Json
*/
public function updateBundleStock()
{
try {
$bundleId = request()->post('bundle_id');
$newStock = request()->post('stock');
if (empty($bundleId) || $newStock === null) {
return json(['code' => 0, 'msg' => '参数不能为空']);
}
$result = Db::name('product_bundle')
->where('id', $bundleId)
->update([
'total_stock' => $newStock,
'update_time' => date('Y-m-d H:i:s')
]);
if ($result) {
return json(['code' => 1, 'msg' => '库存更新成功']);
} else {
return json(['code' => 0, 'msg' => '库存更新失败']);
}
} catch (\Exception $e) {
return json([
'code' => 0,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
}超市收银系统 v1.2 - 完整前端代码
以下是完整的超市收银系统前端页面代码,包含扫码功能、商品表格、实时计算和统计功能:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超市收银系统 v1.2</title>
<link href="/static/layui/css/layui.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #f2f2f2;
font-family: "Microsoft YaHei", sans-serif;
}
.cashier-container {
padding: 15px;
max-width: 1400px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #1E9FFF 0%, #096DD9 100%);
color: white;
padding: 15px 20px;
border-radius: 5px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 24px;
display: flex;
align-items: center;
}
.header h1 i {
margin-right: 10px;
}
.barcode-section {
background: white;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.barcode-input-container {
display: flex;
align-items: center;
}
.barcode-input-container .layui-input {
flex: 1;
margin-right: 10px;
}
.cart-section {
background: white;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.cart-table {
margin: 0;
}
.cart-table th {
background-color: #f8f8f8 !important;
font-weight: bold;
}
.summary-section {
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.summary-row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px dashed #eee;
}
.summary-row:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.summary-label {
font-weight: bold;
color: #333;
}
.summary-value {
font-weight: bold;
color: #FF5722;
font-size: 18px;
}
.discount-value {
color: #1E9FFF;
}
.product-name {
font-weight: bold;
}
.product-spec {
font-size: 12px;
color: #666;
margin-top: 3px;
}
.bundle-badge {
margin-left: 5px;
}
.original-price {
text-decoration: line-through;
color: #999;
font-size: 12px;
}
.discount-input, .price-input, .quantity-input {
width: 80px;
display: inline-block;
}
.row-total {
font-weight: bold;
color: #ff5722;
}
.discount-info {
color: #ff5722;
font-size: 12px;
margin-top: 3px;
}
.mode-switch {
text-align: center;
}
.action-buttons {
display: flex;
justify-content: flex-end;
margin-top: 20px;
gap: 10px;
}
.empty-cart {
text-align: center;
padding: 40px;
color: #999;
}
.empty-cart i {
font-size: 50px;
margin-bottom: 15px;
display: block;
}
.product-image {
width: 40px;
height: 40px;
border-radius: 3px;
object-fit: cover;
margin-right: 10px;
vertical-align: middle;
}
.product-info {
display: inline-block;
vertical-align: middle;
}
.barcode-display {
font-family: monospace;
font-size: 12px;
color: #666;
}
.stock-info {
font-size: 12px;
color: #666;
margin-top: 2px;
}
.discount-percent {
color: #1E9FFF;
font-weight: bold;
}
.footer {
text-align: center;
margin-top: 20px;
color: #999;
font-size: 12px;
}
</style>
</head>
<body>
<div class="cashier-container">
<!-- 头部 -->
<div class="header">
<h1><i class="layui-icon layui-icon-cart"></i> 超市收银系统 v1.2</h1>
</div>
<!-- 扫码区域 -->
<div class="barcode-section">
<div class="barcode-input-container">
<input type="text" id="barcodeInput" autocomplete="off" lay-affix="clear"
placeholder="请扫描商品条码或输入条码后按回车" class="layui-input">
<button class="layui-btn layui-btn-primary" id="clearCart">
<i class="layui-icon layui-icon-delete"></i> 清空
</button>
</div>
<div class="layui-form-mid layui-word-aux" style="margin-top: 10px;">
提示:扫描商品条码自动添加到购物车,支持捆绑商品模式切换
</div>
</div>
<!-- 购物车表格 -->
<div class="cart-section">
<table class="layui-table cart-table" id="cartTable">
<thead>
<tr>
<th width="50">序号</th>
<th width="80">模式</th>
<th width="120">条码</th>
<th>商品信息</th>
<th width="120">单价</th>
<th width="150">数量/折扣</th>
<th width="120">小计</th>
<th width="80">操作</th>
</tr>
</thead>
<tbody id="productList">
<!-- 商品行将通过JS动态添加 -->
<tr id="emptyCartRow">
<td colspan="8" class="empty-cart">
<i class="layui-icon layui-icon-cart"></i>
<div>购物车为空,请扫描商品条码</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 统计区域 -->
<div class="summary-section">
<div class="summary-row">
<div class="summary-label">商品总数:</div>
<div class="summary-value" id="totalQuantity">0</div>
</div>
<div class="summary-row">
<div class="summary-label">原价合计:</div>
<div class="summary-value" id="originalTotalAmount">0.00</div>
</div>
<div class="summary-row">
<div class="summary-label">优惠金额:</div>
<div class="summary-value discount-value" id="discountAmount">0.00</div>
</div>
<div class="summary-row">
<div class="summary-label">应收金额:</div>
<div class="summary-value" id="receivableAmount">0.00</div>
</div>
<div class="action-buttons">
<button class="layui-btn layui-btn-primary" id="printReceipt">
<i class="layui-icon layui-icon-print"></i> 打印小票
</button>
<button class="layui-btn layui-btn-normal" id="checkout">
<i class="layui-icon layui-icon-rmb"></i> 结账收款
</button>
</div>
</div>
<div class="footer">
<p>超市收银系统 v1.2 © 2023 - 技术支持</p>
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script>
layui.use(['jquery', 'layer', 'form'], function(){
var $ = layui.$;
var layer = layui.layer;
var form = layui.form;
// IndexedDB数据库实例
var db = null;
// 购物车商品列表
var cart = [];
// 商品数据缓存
var productsCache = {};
/**
* 初始化IndexedDB数据库
*/
function initIndexedDB() {
var request = indexedDB.open("SupermarketDB", 3); // 版本升级到3
request.onerror = function(event) {
console.error("数据库打开失败:", event.target.error);
layer.msg('本地数据库初始化失败,将使用网络查询');
};
request.onsuccess = function(event) {
db = event.target.result;
console.log("数据库打开成功");
checkDataExists();
};
request.onupgradeneeded = function(event) {
db = event.target.result;
// 删除旧的对象存储(如果存在)
if (db.objectStoreNames.contains('products')) {
db.deleteObjectStore('products');
}
// 创建新的对象存储
var objectStore = db.createObjectStore('products', { keyPath: 'id' });
objectStore.createIndex('barcode', 'barcode', { unique: true });
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('category', 'category', { unique: false });
};
}
/**
* 检查本地数据是否存在
*/
function checkDataExists() {
if (!db) return;
var transaction = db.transaction(['products'], 'readonly');
var store = transaction.objectStore('products');
var countRequest = store.count();
countRequest.onsuccess = function() {
if (countRequest.result === 0) {
layer.msg('正在下载商品数据...', { icon: 16, time: 0 });
downloadProductData();
} else {
layer.msg('本地数据加载完成,可以开始扫描', { icon: 1 });
// 预加载所有商品数据到缓存
preloadProductsToCache();
}
};
}
/**
* 下载商品数据
*/
function downloadProductData() {
$.get('/api/product/exportProducts', function(res) {
layer.closeAll();
if (res.code === 1 && res.data.length > 0) {
var transaction = db.transaction(['products'], 'readwrite');
var store = transaction.objectStore('products');
res.data.forEach(function(product) {
store.add(product);
// 同时添加到缓存
productsCache[product.barcode] = product;
});
transaction.oncomplete = function() {
layer.msg('商品数据下载完成,共 ' + res.data.length + ' 条', { icon: 1 });
};
} else {
layer.msg('服务器数据获取失败');
}
}).fail(function() {
layer.closeAll();
layer.msg('网络请求失败,请检查连接');
});
}
/**
* 预加载商品数据到缓存
*/
function preloadProductsToCache() {
if (!db) return;
var transaction = db.transaction(['products'], 'readonly');
var store = transaction.objectStore('products');
var request = store.openCursor();
request.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
var product = cursor.value;
productsCache[product.barcode] = product;
cursor.continue();
}
};
}
/**
* 在IndexedDB中查询商品
*/
function searchProduct(barcode) {
return new Promise(function(resolve, reject) {
// 先检查缓存
if (productsCache[barcode]) {
resolve(productsCache[barcode]);
return;
}
if (!db) {
reject('数据库未就绪');
return;
}
var transaction = db.transaction(['products'], 'readonly');
var store = transaction.objectStore('products');
var index = store.index('barcode');
var request = index.get(barcode);
request.onsuccess = function() {
var product = request.result;
if (product) {
// 添加到缓存
productsCache[barcode] = product;
resolve(product);
return;
}
// 搜索附加条码
var cursorRequest = store.openCursor();
var foundProduct = null;
cursorRequest.onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
var product = cursor.value;
if (product.additional_barcodes) {
var found = product.additional_barcodes.find(function(additional) {
return additional.barcode === barcode;
});
if (found) {
foundProduct = product;
}
}
if (!foundProduct) {
cursor.continue();
} else {
// 添加到缓存
productsCache[barcode] = foundProduct;
resolve(foundProduct);
}
} else {
resolve(null);
}
};
};
request.onerror = function() {
reject('查询失败');
};
});
}
/**
* 处理条码输入
*/
function handleBarcodeInput(barcode) {
searchProduct(barcode).then(function(product) {
if (product) {
addToCart(product);
} else {
// 本地未找到,尝试网络查询
layer.msg('本地未找到,尝试网络查询...', { icon: 16 });
$.post('/api/product/searchByBarcode', { barcode: barcode }, function(res) {
if (res.code === 1) {
addToCart(res.data);
// 添加到本地数据库和缓存
if (db) {
var transaction = db.transaction(['products'], 'readwrite');
var store = transaction.objectStore('products');
store.add(res.data);
productsCache[barcode] = res.data;
}
} else {
layer.msg('未找到商品:' + barcode);
}
}).fail(function() {
layer.msg('网络查询失败');
});
}
}).catch(function(error) {
console.error("查询错误:", error);
layer.msg('查询失败,请重试');
});
}
/**
* 添加商品到购物车
*/
function addToCart(product) {
var cartItem = {
id: product.id,
barcode: product.barcode,
name: product.effective_display_name || product.display_name || product.name,
spec: product.spec,
origin: product.origin,
unit: product.unit,
category: product.category,
brand: product.brand,
image_url: product.image_url,
// 价格相关
original_price: parseFloat(product.price),
current_price: parseFloat(product.price),
quantity: 1,
discount: 1.00,
// 库存相关
stock: product.effective_stock || product.stock,
// 捆绑销售相关
bundle_supported: product.bundle_supported || false,
bundle_group_id: product.bundle_group_id || null,
bundle_quantity: product.bundle_quantity || 1,
bundle_price: product.bundle_price ? parseFloat(product.bundle_price) : null,
// 模式:0-单品模式,1-捆绑模式
sale_mode: (product.bundle_supported && product.bundle_group_id && product.bundle_price) ? 1 : 0,
// 计算字段
subtotal: 0,
original_subtotal: 0,
product_type: product.product_type || 'single'
};
// 如果是捆绑模式,使用捆绑价格和显示名称
if (cartItem.sale_mode === 1 && cartItem.bundle_supported) {
cartItem.current_price = cartItem.bundle_price;
cartItem.quantity = 1; // 捆绑模式数量固定为1
}
calculateItemTotal(cartItem);
// 检查是否已存在相同商品
var existingIndex = cart.findIndex(function(item) {
if (item.sale_mode === 1 && cartItem.sale_mode === 1) {
// 捆绑模式:相同分组ID视为相同商品
return item.bundle_group_id === cartItem.bundle_group_id;
} else {
// 单品模式:相同商品ID和模式视为相同商品
return item.id === cartItem.id && item.sale_mode === cartItem.sale_mode;
}
});
if (existingIndex !== -1) {
if (cart[existingIndex].sale_mode === 1) {
// 捆绑模式不允许累加数量
layer.msg('该捆绑组合已在购物车中');
return;
} else {
// 单品模式可以累加数量
cart[existingIndex].quantity += cartItem.quantity;
calculateItemTotal(cart[existingIndex]);
}
} else {
cart.push(cartItem);
}
renderCart();
var message = cartItem.sale_mode === 1 ?
`已添加捆绑组合:${cartItem.name}` :
`已添加:${cartItem.name}`;
layer.msg(message, { icon: 1 });
}
/**
* 计算单个商品的总价
*/
function calculateItemTotal(item) {
if (item.sale_mode === 1) {
// 捆绑模式
item.original_subtotal = item.original_price * item.bundle_quantity;
item.subtotal = item.current_price * item.discount;
} else {
// 单品模式
item.original_subtotal = item.original_price * item.quantity;
item.subtotal = item.current_price * item.quantity * item.discount;
}
}
/**
* 计算购物车总计
*/
function calculateCartTotal() {
var totalQuantity = 0;
var totalAmount = 0;
var originalTotal = 0;
cart.forEach(function(item) {
if (item.sale_mode === 1) {
totalQuantity += item.bundle_quantity;
} else {
totalQuantity += item.quantity;
}
totalAmount += item.subtotal;
originalTotal += item.original_subtotal;
});
var discountAmount = originalTotal - totalAmount;
return {
totalQuantity: totalQuantity,
totalAmount: totalAmount,
originalTotal: originalTotal,
discountAmount: discountAmount
};
}
/**
* 渲染购物车
*/
function renderCart() {
var $tbody = $('#productList');
$tbody.empty();
if (cart.length === 0) {
$tbody.append('<tr id="emptyCartRow"><td colspan="8" class="empty-cart"><i class="layui-icon layui-icon-cart"></i><div>购物车为空,请扫描商品条码</div></td></tr>');
updateSummary();
return;
}
var totals = calculateCartTotal();
cart.forEach(function(item, index) {
var discountPercent = ((1 - item.discount) * 100).toFixed(1);
var discountText = item.discount < 1 ? `<span class="discount-percent">${discountPercent}% OFF</span>` : '';
var imageHtml = item.image_url ?
`<img src="${item.image_url}" class="product-image" onerror="this.style.display='none'">` :
'';
var $tr = $('<tr></tr>');
$tr.html(`
<td>${index + 1}</td>
<td class="mode-switch">
${item.bundle_supported ? `
<input type="checkbox" name="modeSwitch" lay-skin="switch"
lay-text="单品|捆绑" data-index="${index}"
${item.sale_mode === 1 ? 'checked' : ''}>
` : '<span class="layui-badge layui-bg-gray">单品</span>'}
</td>
<td>
<div class="barcode-display">${item.barcode}</div>
${item.sale_mode === 1 ? '<span class="layui-badge layui-bg-orange bundle-badge">捆绑</span>' : ''}
</td>
<td>
<div>
${imageHtml}
<div class="product-info">
<div class="product-name">${item.name}</div>
<div class="product-spec">
${item.spec ? '规格:' + item.spec : ''}
${item.origin ? '产地:' + item.origin : ''}
${item.brand ? '品牌:' + item.brand : ''}
</div>
${item.sale_mode === 1 ? `<div class="stock-info">${item.bundle_quantity}${item.unit}/组</div>` : ''}
</div>
</div>
</td>
<td>
<div>
<input type="number" class="layui-input price-input"
data-index="${index}" value="${item.current_price.toFixed(2)}"
step="0.01" min="0.01">
</div>
<div class="original-price">原价:${item.original_price.toFixed(2)}</div>
</td>
<td>
${item.sale_mode === 1 ? `
<div>数量:<span class="layui-badge">${item.quantity}</span></div>
` : `
<div>数量:
<input type="number" class="layui-input quantity-input"
data-index="${index}" value="${item.quantity}" min="1">
</div>
`}
<div style="margin-top: 5px;">
折扣:<input type="number" class="layui-input discount-input"
data-index="${index}" value="${item.discount.toFixed(2)}"
step="0.01" min="0.01" max="1.00">
</div>
<div class="discount-info">${discountText}</div>
</td>
<td>
<div class="row-total">¥${item.subtotal.toFixed(2)}</div>
<div class="original-price">原价:¥${item.original_subtotal.toFixed(2)}</div>
</td>
<td>
<button class="layui-btn layui-btn-sm layui-btn-danger remove-btn"
data-index="${index}">删除</button>
</td>
`);
$tbody.append($tr);
});
// 更新总计
updateSummary();
// 绑定事件
bindCartEvents();
// 重新渲染表单元素
form.render();
}
/**
* 更新统计信息
*/
function updateSummary() {
var totals = calculateCartTotal();
$('#totalQuantity').text(totals.totalQuantity);
$('#originalTotalAmount').text('¥' + totals.originalTotal.toFixed(2));
$('#discountAmount').text('-¥' + totals.discountAmount.toFixed(2));
$('#receivableAmount').text('¥' + totals.totalAmount.toFixed(2));
}
/**
* 绑定购物车事件
*/
function bindCartEvents() {
// 模式切换
$('input[name="modeSwitch"]').on('change', function() {
var index = $(this).data('index');
var isBundleMode = $(this).is(':checked');
handleModeSwitch(index, isBundleMode);
});
// 价格修改
$('.price-input').on('change', function() {
var index = $(this).data('index');
var newPrice = parseFloat($(this).val()) || cart[index].original_price;
if (newPrice < 0.01) newPrice = 0.01;
cart[index].current_price = newPrice;
calculateItemTotal(cart[index]);
renderCart();
});
// 数量修改
$('.quantity-input').on('change', function() {
var index = $(this).data('index');
var newQuantity = parseInt($(this).val()) || 1;
if (newQuantity < 1) newQuantity = 1;
cart[index].quantity = newQuantity;
calculateItemTotal(cart[index]);
renderCart();
});
// 折扣修改
$('.discount-input').on('change', function() {
var index = $(this).data('index');
var newDiscount = parseFloat($(this).val()) || 1.00;
if (newDiscount > 1) newDiscount = 1.00;
if (newDiscount < 0.01) newDiscount = 0.01;
cart[index].discount = newDiscount;
calculateItemTotal(cart[index]);
renderCart();
});
// 删除商品
$('.remove-btn').on('click', function() {
var index = $(this).data('index');
var productName = cart[index].name;
cart.splice(index, 1);
renderCart();
layer.msg('已删除:' + productName);
});
}
/**
* 处理模式切换
*/
function handleModeSwitch(index, isBundleMode) {
var item = cart[index];
if (isBundleMode) {
// 切换到捆绑模式
if (item.bundle_supported && item.bundle_group_id && item.bundle_price) {
item.sale_mode = 1;
item.current_price = item.bundle_price;
item.quantity = 1;
item.name = item.bundle_display_name || (item.bundle_name + ' ' + item.bundle_quantity + item.unit);
} else {
layer.msg('该商品不支持捆绑销售');
// 回滚切换状态
setTimeout(() => {
renderCart();
}, 100);
return;
}
} else {
// 切换到单品模式
item.sale_mode = 0;
item.current_price = item.original_price;
item.quantity = 1;
// 恢复单品显示名称
if (item.bundle_supported) {
// 重新查询商品信息获取单品名称
searchProduct(item.barcode).then(function(product) {
item.name = product.effective_display_name || product.display_name || product.name;
calculateItemTotal(item);
renderCart();
});
} else {
item.name = item.display_name || item.name;
}
}
calculateItemTotal(item);
renderCart();
}
/**
* 清空购物车
*/
function clearCart() {
if (cart.length === 0) {
layer.msg('购物车已经是空的');
return;
}
layer.confirm('确定要清空购物车吗?', {icon: 3, title: '提示'}, function(index){
cart = [];
renderCart();
layer.msg('购物车已清空');
layer.close(index);
});
}
/**
* 结账收款
*/
function checkout() {
if (cart.length === 0) {
layer.msg('购物车为空,无法结账');
return;
}
var totals = calculateCartTotal();
layer.confirm(`确定要结账吗?<br>应收金额:<span style="color:#FF5722;font-weight:bold;">¥${totals.totalAmount.toFixed(2)}</span>`, {
icon: 3,
title: '确认结账',
btn: ['确认结账', '取消']
}, function(index){
// 这里可以添加结账逻辑,比如发送到服务器保存订单
layer.msg('结账成功!', {icon: 1});
// 清空购物车
cart = [];
renderCart();
layer.close(index);
});
}
/**
* 打印小票
*/
function printReceipt() {
if (cart.length === 0) {
layer.msg('购物车为空,无法打印');
return;
}
// 这里可以添加打印小票的逻辑
layer.msg('小票打印功能开发中...', {icon: 1});
}
// 条码输入框事件
$('#barcodeInput').on('keydown', function(e) {
if (e.keyCode === 13) {
var barcode = $(this).val().trim();
if (barcode) {
handleBarcodeInput(barcode);
}
$(this).val('').focus();
return false;
}
});
// 清空购物车按钮
$('#clearCart').on('click', function() {
clearCart();
});
// 结账按钮
$('#checkout').on('click', function() {
checkout();
});
// 打印小票按钮
$('#printReceipt').on('click', function() {
printReceipt();
});
// 页面加载完成后初始化
$(document).ready(function() {
initIndexedDB();
// 自动聚焦到条码输入框
$('#barcodeInput').focus();
// 添加键盘快捷键
$(document).on('keydown', function(e) {
// F2 清空购物车
if (e.keyCode === 113) {
e.preventDefault();
clearCart();
}
// F3 结账
else if (e.keyCode === 114) {
e.preventDefault();
checkout();
}
// F4 打印
else if (e.keyCode === 115) {
e.preventDefault();
printReceipt();
}
});
});
});
</script>
</body>
</html>功能特点
扫码功能:
输入框监听回车键,支持扫码枪快速输入
自动从本地IndexedDB查询商品信息
本地无数据时自动从服务器查询
商品显示:
显示商品图片、名称、规格、产地、品牌等信息
捆绑商品显示捆绑数量和价格
显示库存信息
购物车管理:
实时计算每行小计和总计
支持直接编辑单价、数量和折扣
支持捆绑/单品模式切换
支持删除商品
统计功能:
显示商品总数
显示原价合计
显示优惠金额
显示应收金额
操作功能:
清空购物车
结账收款
打印小票
键盘快捷键支持
这个前端页面与之前提供的后端API完全兼容,可以直接使用。页面设计美观实用,适合超市收银场景使用。
前端页面代码
由于代码较长,我将关键的商品处理逻辑提供:
/**
* 处理条码输入 - 支持捆绑商品
*/
function handleBarcodeInput(barcode) {
searchProduct(barcode).then(function(product) {
if (product) {
addToCart(product);
} else {
layer.msg('未找到商品:' + barcode);
}
}).catch(function(error) {
console.error("查询错误:", error);
layer.msg('查询失败,请重试');
});
}
/**
* 添加商品到购物车 - 支持捆绑商品
*/
function addToCart(product) {
var cartItem = {
id: product.id,
barcode: product.barcode,
name: product.effective_display_name,
spec: product.spec,
origin: product.origin,
unit: product.unit,
category: product.category,
brand: product.brand,
// 价格相关
original_price: parseFloat(product.price),
current_price: parseFloat(product.price),
quantity: 1,
discount: 1.00,
// 库存相关
stock: product.effective_stock,
// 捆绑销售相关
bundle_supported: product.bundle_supported,
bundle_group_id: product.bundle_group_id,
bundle_quantity: product.bundle_quantity || 1,
bundle_price: product.bundle_price ? parseFloat(product.bundle_price) : null,
// 模式:0-单品模式,1-捆绑模式
sale_mode: product.bundle_supported ? 1 : 0, // 捆绑商品默认使用捆绑模式
// 计算字段
subtotal: 0,
original_subtotal: 0,
product_type: product.product_type
};
// 如果是捆绑模式,使用捆绑价格和显示名称
if (cartItem.sale_mode === 1 && cartItem.bundle_supported) {
cartItem.current_price = cartItem.bundle_price;
cartItem.quantity = 1; // 捆绑模式数量固定为1
}
calculateItemTotal(cartItem);
// 检查是否已存在相同商品
var existingIndex = cart.findIndex(function(item) {
if (item.sale_mode === 1 && cartItem.sale_mode === 1) {
// 捆绑模式:相同分组ID视为相同商品
return item.bundle_group_id === cartItem.bundle_group_id;
} else {
// 单品模式:相同商品ID和模式视为相同商品
return item.id === cartItem.id && item.sale_mode === cartItem.sale_mode;
}
});
if (existingIndex !== -1) {
if (cart[existingIndex].sale_mode === 1) {
// 捆绑模式不允许累加数量
layer.msg('该捆绑组合已在购物车中');
return;
} else {
// 单品模式可以累加数量
cart[existingIndex].quantity += cartItem.quantity;
calculateItemTotal(cart[existingIndex]);
}
} else {
cart.push(cartItem);
}
renderCart();
var message = cartItem.sale_mode === 1 ?
`已添加捆绑组合:${cartItem.name}` :
`已添加:${cartItem.name}`;
layer.msg(message, { icon: 1 });
}
评论区