moonsflyer 3 kuukautta sitten
vanhempi
commit
a1724e3b11

+ 27 - 0
app/adminapi/controller/goods/GoodsController.php

@@ -198,4 +198,31 @@ class GoodsController extends BaseAdminController
         (new GoodsLogic)->changeCategory($params);
         return $this->success('修改成功',[],1,1);
     }
+
+    /**
+     * @notes 商品规格价格导出
+     * @return \think\response\Json
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function exportSpecPrice()
+    {
+        return $this->dataLists(new \app\adminapi\lists\goods\GoodsSpecPriceLists());
+    }
+
+    /**
+     * @notes 商品规格价格导入
+     * @return \think\response\Json
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function importSpecPrice()
+    {
+        $params = (new \app\adminapi\validate\goods\GoodsSpecPriceValidate())->post()->goCheck('import');
+        $result = (new GoodsLogic)->importSpecPrice($params);
+        if ($result === true) {
+            return $this->success('导入成功', [], 1, 1);
+        }
+        return $this->fail($result);
+    }
 }

+ 130 - 0
app/adminapi/lists/goods/GoodsSpecPriceLists.php

@@ -0,0 +1,130 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeshop100%开源免费商用商城系统
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | 商业版本务必购买商业授权,以免引起法律纠纷
+// | 禁止对系统程序代码以任何目的,任何形式的再发布
+// | gitee下载:https://gitee.com/likeshop_gitee
+// | github下载:https://github.com/likeshop-github
+// | 访问官网:https://www.likeshop.cn
+// | 访问社区:https://home.likeshop.cn
+// | 访问手册:http://doc.likeshop.cn
+// | 微信公众号:likeshop技术社区
+// | likeshop团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeshopTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\lists\goods;
+
+use app\adminapi\lists\BaseAdminDataLists;
+use app\common\lists\ListsExcelInterface;
+use app\common\lists\ListsSearchInterface;
+use app\common\model\Goods;
+use app\common\model\GoodsItem;
+
+/**
+ * 商品规格价格列表
+ * Class GoodsSpecPriceLists
+ * @package app\adminapi\lists\goods
+ */
+class GoodsSpecPriceLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExcelInterface
+{
+    /**
+     * @notes 搜索条件
+     * @return array
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function setSearch(): array
+    {
+        return [
+            '=' => ['g.id', 'g.category_id', 'g.status'],
+            'like' => ['g.name', 'g.code'],
+        ];
+    }
+
+    /**
+     * @notes 获取商品规格价格列表
+     * @return array
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function lists(): array
+    {
+        $lists = GoodsItem::alias('gi')
+            ->leftJoin('goods g', 'g.id = gi.goods_id')
+            ->where($this->searchWhere)
+            ->where('g.delete_time', null)
+            ->field([
+                'g.id as goods_id',
+                'g.code as goods_code', 
+                'g.name as goods_name',
+                'gi.id as item_id',
+                'gi.spec_value_str',
+                'gi.sell_price',
+                'gi.lineation_price',
+                'gi.cost_price',
+                'gi.stock',
+                'gi.weight',
+                'gi.volume'
+            ])
+            ->limit($this->limitOffset, $this->limitLength)
+            ->order('g.id desc, gi.id asc')
+            ->select()
+            ->toArray();
+
+        return $lists;
+    }
+
+    /**
+     * @notes 获取数量
+     * @return int
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function count(): int
+    {
+        return GoodsItem::alias('gi')
+            ->leftJoin('goods g', 'g.id = gi.goods_id')
+            ->where($this->searchWhere)
+            ->where('g.delete_time', null)
+            ->count();
+    }
+
+    /**
+     * @notes 设置导出字段
+     * @return array
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function setExcelFields(): array
+    {
+        return [
+            'goods_id' => '商品ID',
+            'goods_code' => '商品编码',
+            'goods_name' => '商品名称',
+            'item_id' => '规格ID',
+            'spec_value_str' => '规格名称',
+            'sell_price' => '销售价格',
+            'lineation_price' => '划线价格',
+            'cost_price' => '成本价格',
+            'stock' => '库存数量',
+            'weight' => '重量(kg)',
+            'volume' => '体积(m³)'
+        ];
+    }
+
+    /**
+     * @notes 设置文件名
+     * @return string
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function setFileName(): string
+    {
+        return '商品规格价格';
+    }
+}

+ 175 - 0
app/adminapi/logic/goods/GoodsLogic.php

@@ -797,4 +797,179 @@ class GoodsLogic
 
         return true;
     }
+
+    /**
+     * @notes 商品规格价格导入
+     * @param array $params
+     * @return bool|string
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function importSpecPrice(array $params)
+    {
+        if (empty($params['file'])) {
+            return '请上传导入文件';
+        }
+
+        Db::startTrans();
+        try {
+            // 处理上传的Excel文件
+            $file = $params['file'];
+            $data = $this->parseExcelFile($file);
+            
+            if (empty($data)) {
+                return '导入文件为空或格式错误';
+            }
+
+            $successCount = 0;
+            $errorMessages = [];
+
+            foreach ($data as $row => $item) {
+                try {
+                    // 验证必要字段
+                    if (empty($item['item_id']) || empty($item['goods_id'])) {
+                        $errorMessages[] = "第{$row}行:商品ID或规格ID不能为空";
+                        continue;
+                    }
+
+                    // 检查商品是否存在
+                    $goods = Goods::find($item['goods_id']);
+                    if (!$goods) {
+                        $errorMessages[] = "第{$row}行:商品ID {$item['goods_id']} 不存在";
+                        continue;
+                    }
+
+                    // 检查规格是否存在
+                    $goodsItem = GoodsItem::where('id', $item['item_id'])
+                        ->where('goods_id', $item['goods_id'])
+                        ->find();
+                    
+                    if (!$goodsItem) {
+                        $errorMessages[] = "第{$row}行:规格ID {$item['item_id']} 不存在或不属于该商品";
+                        continue;
+                    }
+
+                    // 更新规格价格信息
+                    $updateData = [];
+                    if (isset($item['sell_price']) && is_numeric($item['sell_price'])) {
+                        $updateData['sell_price'] = $item['sell_price'];
+                    }
+                    if (isset($item['lineation_price']) && is_numeric($item['lineation_price'])) {
+                        $updateData['lineation_price'] = $item['lineation_price'];
+                    }
+                    if (isset($item['cost_price']) && is_numeric($item['cost_price'])) {
+                        $updateData['cost_price'] = $item['cost_price'];
+                    }
+                    if (isset($item['stock']) && is_numeric($item['stock'])) {
+                        $updateData['stock'] = $item['stock'];
+                    }
+                    if (isset($item['weight']) && is_numeric($item['weight'])) {
+                        $updateData['weight'] = $item['weight'];
+                    }
+                    if (isset($item['volume']) && is_numeric($item['volume'])) {
+                        $updateData['volume'] = $item['volume'];
+                    }
+
+                    if (!empty($updateData)) {
+                        GoodsItem::where('id', $item['item_id'])->update($updateData);
+                        $successCount++;
+                    }
+
+                } catch (\Exception $e) {
+                    $errorMessages[] = "第{$row}行:" . $e->getMessage();
+                }
+            }
+
+            Db::commit();
+
+            if (!empty($errorMessages)) {
+                return "导入完成,成功{$successCount}条,失败" . count($errorMessages) . "条。错误信息:" . implode(';', array_slice($errorMessages, 0, 5));
+            }
+
+            return true;
+
+        } catch (\Exception $e) {
+            Db::rollback();
+            return '导入失败:' . $e->getMessage();
+        }
+    }
+
+    /**
+     * @notes 解析Excel文件
+     * @param string $file
+     * @return array
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    private function parseExcelFile($file)
+    {
+        try {
+            // 使用PhpSpreadsheet处理Excel文件
+            if (is_array($file)) {
+                return $file;
+            }
+            
+            // 检查文件是否存在
+            if (!file_exists($file)) {
+                throw new \Exception('文件不存在');
+            }
+            
+            // 加载Excel文件
+            $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file);
+            $worksheet = $spreadsheet->getActiveSheet();
+            $data = $worksheet->toArray();
+            
+            if (empty($data)) {
+                throw new \Exception('Excel文件为空');
+            }
+            
+            // 获取表头
+            $headers = array_shift($data);
+            $result = [];
+            
+            // 将表头映射为字段名
+            $fieldMap = [
+                '商品ID' => 'goods_id',
+                '商品编码' => 'goods_code',
+                '商品名称' => 'goods_name',
+                '规格ID' => 'item_id',
+                '规格名称' => 'spec_value_str',
+                '销售价格' => 'sell_price',
+                '划线价格' => 'lineation_price',
+                '成本价格' => 'cost_price',
+                '库存数量' => 'stock',
+                '重量(kg)' => 'weight',
+                '体积(m³)' => 'volume'
+            ];
+            
+            // 创建字段索引映射
+            $fieldIndexes = [];
+            foreach ($headers as $index => $header) {
+                if (isset($fieldMap[$header])) {
+                    $fieldIndexes[$fieldMap[$header]] = $index;
+                }
+            }
+            
+            // 处理数据行
+            foreach ($data as $rowIndex => $row) {
+                if (empty(array_filter($row))) {
+                    continue; // 跳过空行
+                }
+                
+                $item = [];
+                foreach ($fieldIndexes as $field => $index) {
+                    $item[$field] = isset($row[$index]) ? trim($row[$index]) : '';
+                }
+                
+                if (!empty($item)) {
+                    $result[$rowIndex + 2] = $item; // +2 因为表头占一行,数组从0开始
+                }
+            }
+            
+            return $result;
+            
+        } catch (\Exception $e) {
+            throw new \Exception('Excel文件解析失败:' . $e->getMessage());
+        }
+    }
 }

+ 88 - 0
app/adminapi/validate/goods/GoodsSpecPriceValidate.php

@@ -0,0 +1,88 @@
+<?php
+// +----------------------------------------------------------------------
+// | likeshop100%开源免费商用商城系统
+// +----------------------------------------------------------------------
+// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
+// | 开源版本可自由商用,可去除界面版权logo
+// | 商业版本务必购买商业授权,以免引起法律纠纷
+// | 禁止对系统程序代码以任何目的,任何形式的再发布
+// | gitee下载:https://gitee.com/likeshop_gitee
+// | github下载:https://github.com/likeshop-github
+// | 访问官网:https://www.likeshop.cn
+// | 访问社区:https://home.likeshop.cn
+// | 访问手册:http://doc.likeshop.cn
+// | 微信公众号:likeshop技术社区
+// | likeshop团队 版权所有 拥有最终解释权
+// +----------------------------------------------------------------------
+// | author: likeshopTeam
+// +----------------------------------------------------------------------
+
+namespace app\adminapi\validate\goods;
+
+use app\common\validate\BaseValidate;
+
+/**
+ * 商品规格价格验证器
+ * Class GoodsSpecPriceValidate
+ * @package app\adminapi\validate\goods
+ */
+class GoodsSpecPriceValidate extends BaseValidate
+{
+    protected $rule = [
+        'file' => 'require|file|fileExt:xlsx,xls',
+        'goods_id' => 'require|integer|gt:0',
+        'item_id' => 'require|integer|gt:0',
+        'sell_price' => 'float|egt:0',
+        'lineation_price' => 'float|egt:0',
+        'cost_price' => 'float|egt:0',
+        'stock' => 'integer|egt:0',
+        'weight' => 'float|egt:0',
+        'volume' => 'float|egt:0',
+    ];
+
+    protected $message = [
+        'file.require' => '请上传导入文件',
+        'file.file' => '上传的文件格式不正确',
+        'file.fileExt' => '只支持xlsx和xls格式的Excel文件',
+        'goods_id.require' => '商品ID不能为空',
+        'goods_id.integer' => '商品ID必须为整数',
+        'goods_id.gt' => '商品ID必须大于0',
+        'item_id.require' => '规格ID不能为空',
+        'item_id.integer' => '规格ID必须为整数',
+        'item_id.gt' => '规格ID必须大于0',
+        'sell_price.float' => '销售价格必须为数字',
+        'sell_price.egt' => '销售价格不能小于0',
+        'lineation_price.float' => '划线价格必须为数字',
+        'lineation_price.egt' => '划线价格不能小于0',
+        'cost_price.float' => '成本价格必须为数字',
+        'cost_price.egt' => '成本价格不能小于0',
+        'stock.integer' => '库存数量必须为整数',
+        'stock.egt' => '库存数量不能小于0',
+        'weight.float' => '重量必须为数字',
+        'weight.egt' => '重量不能小于0',
+        'volume.float' => '体积必须为数字',
+        'volume.egt' => '体积不能小于0',
+    ];
+
+    /**
+     * @notes 导入场景
+     * @return GoodsSpecPriceValidate
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function sceneImport()
+    {
+        return $this->only(['file']);
+    }
+
+    /**
+     * @notes 单条数据验证场景
+     * @return GoodsSpecPriceValidate
+     * @author 
+     * @date 2024/01/01 00:00
+     */
+    public function sceneItem()
+    {
+        return $this->only(['goods_id', 'item_id', 'sell_price', 'lineation_price', 'cost_price', 'stock', 'weight', 'volume']);
+    }
+}

+ 76 - 0
test_goods_spec_price.php

@@ -0,0 +1,76 @@
+<?php
+/**
+ * 商品规格价格导入导出功能测试
+ * 
+ * 使用说明:
+ * 1. 导出功能测试:访问 /adminapi/goods/exportSpecPrice 接口
+ * 2. 导入功能测试:准备Excel文件,通过POST请求访问 /adminapi/goods/importSpecPrice 接口
+ * 
+ * Excel文件格式要求:
+ * - 第一行为表头:商品ID, 商品编码, 商品名称, 规格ID, 规格名称, 销售价格, 划线价格, 成本价格, 库存数量, 重量(kg), 体积(m³)
+ * - 数据行:对应的商品规格价格数据
+ * 
+ * 示例数据:
+ * 商品ID | 商品编码 | 商品名称 | 规格ID | 规格名称 | 销售价格 | 划线价格 | 成本价格 | 库存数量 | 重量(kg) | 体积(m³)
+ * 1      | G001    | 测试商品 | 1      | 默认规格 | 99.00   | 120.00  | 80.00   | 100     | 0.5     | 0.01
+ * 
+ * 接口说明:
+ * 
+ * 1. 导出接口
+ * URL: GET /adminapi/goods/exportSpecPrice
+ * 参数: 支持搜索条件,如 goods.id, goods.name 等
+ * 返回: Excel文件下载链接
+ * 
+ * 2. 导入接口  
+ * URL: POST /adminapi/goods/importSpecPrice
+ * 参数: file (上传的Excel文件)
+ * 返回: 导入结果信息
+ * 
+ * 错误处理:
+ * - 文件格式验证:只支持xlsx和xls格式
+ * - 数据验证:商品ID、规格ID必须存在,价格、库存等数值必须合法
+ * - 批量处理:支持批量导入,会返回成功和失败的统计信息
+ * 
+ * 注意事项:
+ * 1. 确保商品和规格在数据库中已存在
+ * 2. 价格和库存等数值字段必须为非负数
+ * 3. Excel文件表头必须与系统定义的字段对应
+ * 4. 导入过程中会进行事务处理,确保数据一致性
+ */
+
+// 测试数据示例
+$testData = [
+    [
+        'goods_id' => 1,
+        'goods_code' => 'G001',
+        'goods_name' => '测试商品1',
+        'item_id' => 1,
+        'spec_value_str' => '默认规格',
+        'sell_price' => 99.00,
+        'lineation_price' => 120.00,
+        'cost_price' => 80.00,
+        'stock' => 100,
+        'weight' => 0.5,
+        'volume' => 0.01
+    ],
+    [
+        'goods_id' => 1,
+        'goods_code' => 'G001', 
+        'goods_name' => '测试商品1',
+        'item_id' => 2,
+        'spec_value_str' => '红色/L',
+        'sell_price' => 109.00,
+        'lineation_price' => 130.00,
+        'cost_price' => 85.00,
+        'stock' => 50,
+        'weight' => 0.6,
+        'volume' => 0.012
+    ]
+];
+
+echo "商品规格价格导入导出功能已实现完成!\n";
+echo "请通过API接口进行测试:\n";
+echo "导出:GET /adminapi/goods/exportSpecPrice\n";
+echo "导入:POST /adminapi/goods/importSpecPrice\n";
+echo "\n详细使用说明请参考文件注释。\n";
+?>