feat: 导入基本完善-待调试

master
yin5th 2023-10-12 11:27:45 +08:00
parent 1e4e85e022
commit a28b1f034d
8 changed files with 533 additions and 30 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
/.idea
/.vscode
/.vscode
.DS_Store

View File

@ -27,14 +27,17 @@ use app\common\model\goods\GoodsUnit;
use app\common\model\Freight;
use app\common\model\goods\Supplier;
use app\common\model\goods\Goods as GoodsModel;
use app\common\model\shop\Shop;
use app\common\server\JsonServer;
use app\admin\logic\goods\CategoryLogic as MallCategoryLogic;
use app\admin\logic\goods\GoodsLogic;
use app\admin\logic\goods\ColumnLogic;
use app\shop\logic\goods\CategoryLogic as ShopCategoryLogic;
use PhpOffice\PhpSpreadsheet\IOFactory;
use think\exception\ValidateException;
use app\admin\validate\goods\GoodsValidate;
use think\facade\Db;
use think\response\Json;
/**
* 商品管理
@ -165,11 +168,12 @@ class Goods extends AdminBase
if ($this->request->isAjax()) {
return JsonServer::success('获取成功', GoodsLogic::statistics());
}
return false;
}
public function export()
public function export(): Json
{
// ini_set("max_execution_time", 0);
set_time_limit(0);
ini_set('memory_limit', '1024M');
@ -334,4 +338,146 @@ class Goods extends AdminBase
}
echo '文件不存在';exit;
}
// 导入商品
public function import()
{
if($this->request->isAjax()){
set_time_limit(0);
ini_set('memory_limit', '1024M');
$shopId = input('shop_id');
if (empty($shopId)) {
return JsonServer::error('请先选择商家');
}
$file = request()->file('file');
if (!$file) {
return JsonServer::error('文件不存在');
}
if ($file->getSize() > 100 * 1024 * 1024) {
return JsonServer::error('导入文件不要超过100M');
}
$filePath = $file->getPathname();
// 导入Excel文件
$spreadsheet = IOFactory::load($filePath);
$worksheet = $spreadsheet->getActiveSheet();
if ($worksheet === null) {
return JsonServer::error('不是有效的excel文件');
}
// 获取最大行数
$highestRow = $worksheet->getHighestRow();
// 每N条记录处理一次批量导入
$batchSize = 500;
$dataAll = [];
$totalCount = 0;
$batchCode = [];// 同批次商品编码
$needInsertGoods = [];// 同批次要插入的商品
$now = time();
// 读取数据并批量导入
for ($row = 2; $row <= $highestRow; $row++) {
$data = [];
$data['name'] = (string)$worksheet->getCell('A' . $row)->getValue();
if (empty($data['name'])) {
continue;//不存在就跳转到下一条
}
$data['code'] = (string)$worksheet->getCell('B' . $row)->getValue();
if (empty($data['code'])) {
continue;//不存在就跳转到下一条
}
$batchCode[] = $data['code'];
$data['unit'] = (string)$worksheet->getCell('C' . $row)->getValue();
$data['brand'] = (string)$worksheet->getCell('D' . $row)->getValue();
$data['first_cate'] = (string)$worksheet->getCell('E' . $row)->getValue();
$data['second_cate'] = (string)$worksheet->getCell('F' . $row)->getValue();
$data['third_cate'] = (string)$worksheet->getCell('G' . $row)->getValue();
$data['image'] = (string)$worksheet->getCell('H' . $row)->getValue();
$data['images'] = (string)$worksheet->getCell('I' . $row)->getValue();
$data['content'] = (string)$worksheet->getCell('J' . $row)->getValue();
$data['price'] = (string)$worksheet->getCell('K' . $row)->getValue();
$data['stock'] = (string)$worksheet->getCell('L' . $row)->getValue();
$data['custom_param'] = (string)$worksheet->getCell('M' . $row)->getValue();
$dataAll[$data['code']] = $data;
// // 每N条记录进行一次批量插入操作可以调整batchSize的值
if ($totalCount % $batchSize == 0) {
// 已存在的商品编码数组
$exists = GoodsModel::whereIn('code', $batchCode)->column('code');
foreach ($dataAll as $code => $item) {
if (!in_array($code, $exists)) {
$needInsertGoods[$code] = [
'code' => $item['code'],
'name' => $item['name'],
'shop_id' => $shopId,
'first_cate' => $item['first_cate'],
'second_cate' => $item['second_cate'],
'third_cate' => $item['third_cate'],
'brand_name' => $item['brand'],
'unit_name' => $item['unit'],
'images' => $item['images'],
'import_status' => 0,//导入状态 0未完成
'image' => $item['image'],
'content' => $item['content'],
'spec_type' => 1,//1 统一规格
'max_price' => $item['price'],
'min_price' => $item['price'],
'stock' => $item['stock'],
'create_time' => $now,
'update_time' => $now,
'custom_params' => $item['custom_params'],
];
}
}
// 批量插入商品
GoodsModel::insertAll($needInsertGoods);
$dataAll = [];//清空该批次
$batchCode = [];// 清空该批次商品编码
$needInsertGoods = [];// 清空该批次要插入的商品
}
$totalCount++; // 记录已处理的行数
}
// 最后一批次导入
if (!empty($needInsertGoods)) {
GoodsModel::insertAll($needInsertGoods);
}
// 以上 基础商品记录已插入
// SKU相关完善 包含SKU、规格、规格值
// 启动事务
Db::startTrans();
try {
GoodsModel::batchHandleSku();//批量插入规格名
Db::commit();
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
}
GoodsModel::handleImages();//处理商品组图 插入到组图表
GoodsModel::handleBrand();//处理商品品牌 插入到品牌表 未更新商品表中的brand_id
GoodsModel::handleUnit();//处理商品单位 插入到单位表 未更新商品表中的unit_id
GoodsModel::handleCategory();//处理商品分类 插入到分类表 未更新商品表中的分类ID
GoodsModel::handleSelfInfo();//处理商品自身数据的更新 更新分类ID、品牌ID、单位ID
return JsonServer::success('导入成功', $dataAll);
}
$shopList = Shop::where('del', 0)->field('id,name')->select();
return view('', [
'shop_list' => json_encode($shopList)
]);
}
}

View File

@ -0,0 +1,94 @@
{layout name="layout2" /}
<div class="layui-form" lay-filter="layuiadmin-form-user" id="layuiadmin-form-user" style="padding: 20px 30px 0 0;">
<div class="layui-form-item">
<label class="layui-form-label">选择导入商家:</label>
<div class="layui-input-block">
<div id="shopList"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm layui-btn-normal" id="import" data-shop="0">上传文件</button>
</div>
</div>
<!-- <div class="layui-form-item layui-hide">-->
<!-- <input type="button" id="import-submit" data-shop="0" value="确认">-->
<!-- </div>-->
</div>
<script>
layui.config({
version:"{$front_version}",
base: '/static/lib/' //静态资源所在路径
}).extend({
xmSelect: 'xmSelect/xm-select'
}).use(['xmSelect', 'form', 'upload'], function(){
var $ = layui.$,form = layui.form,upload=layui.upload;
var xmSelect = layui.xmSelect;
var shop_list = '{$shop_list|raw}';
var xmIns = xmSelect.render({
el: '#shopList',
language: 'zn',
radio: true,
name: "shop_id",
data: JSON.parse(shop_list),
prop: {
value: 'id'
}
,on: function(data){
//arr: 当前多选已选中的数据
var arr = data.arr;
//change, 此次选择变化的数据,数组
var change = data.change;
//isAdd, 此次操作是新增还是删除
var isAdd = data.isAdd;
if (isAdd && change.length > 0) {
$('#import').attr('data-shop', arr[0].id)
}
console.log(arr[0].id, 'id')
console.log(arr, '已选')
// alert('已有: '+arr.length+' 变化: '+change.length+', 状态: ' + isAdd)
},
})
// // 上传导入文件
var uploadInst = upload.render({
elem: '#import'
,url: '{:url("goods.goods/import")}'
,filed: 'file'
,exts: 'xls|xlsx'
,size: 1024*100 //KB
,before: function(obj){ //obj参数包含的信息跟 choose回调完全一致可参见上文。
this.data = {
shop_id: $('#import').attr('data-shop')
}
layer.load(); //上传loading
}
,done: function(res, index, upload){
console.log(res,'res')
layer.closeAll()
//假设code=0代表上传成功
if(res.code === 1){
//do something 比如将res返回的图片链接保存到表单的隐藏域
} else {
layer.msg(res.msg)
}
//获取当前触发上传的元素,一般用于 elem 绑定 class 的情况,注意:此乃 layui 2.1.0 新增
// var item = this.item;
//文件保存失败
//do something
// uploadInst.reload();
}
,error: function(index, upload){
layer.closeAll()
layer.msg('上传失败')
//当上传失败时,你可以生成一个“重新上传”的按钮,点击该按钮时,执行 upload() 方法即可实现重新上传
}
});
})
</script>

View File

@ -71,7 +71,7 @@
<div class="layui-inline">
<button class="layui-btn layui-btn-sm layui-btn-normal" lay-submit lay-filter="search">查询</button>
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-submit lay-filter="clear-search">重置</button>
<!-- <button class="layui-btn layui-btn-sm layui-btn-normal" lay-filter="import">导入</button>-->
<button class="layui-btn layui-btn-sm layui-btn-normal" id="import-excel">导入</button>
<button class="layui-btn layui-btn-sm layui-btn-normal goods-export">导出</button>
</div>
</div>
@ -138,13 +138,63 @@
<script src="__PUBLIC__/static/lib/qrcode/qrcode.min.js"></script>
<script>
layui.use(['table', 'form', 'element', 'layer'], function(){
layui.use(['table', 'form', 'element', 'layer', 'upload'], function(){
var $ = layui.$
,form = layui.form
,layer = layui.layer
,table = layui.table
,upload = layui.upload
,element = layui.element;
// 上传导入文件
// var shopId = 0;
// var uploadInst = upload.render({
// elem: '#import-excel'
// ,url: '{:url("goods.goods/import")}'
// ,filed: 'file'
// ,data: {shop_id: shopId}
// ,exts: 'xls|xlsx'
// ,size: 1024*100 //KB
// ,before: function(obj){ //obj参数包含的信息跟 choose回调完全一致可参见上文。
// // layer.load(); //上传loading
//
// layer.open({
// type: 2
// ,title: '设置会员标签'
// ,content: '{:url("goods.goods/import")}'
// ,area: ['60%', '60%']
// ,btn: ['确定', '取消']
// ,yes: function(index, layero){
// var iframeWindow = window['layui-layer-iframe'+ index]
// ,submitID = 'import-submit'
// ,submit = layero.find('iframe').contents().find("#import-submit");
// //监听提交
// iframeWindow.layui.form.on('submit(import-submit)', function(data){
// shopId = data.field.shop_id;
// });
// // submit.trigger('click');
// }
// });
// }
// ,done: function(res, index, upload){
// console.log(res,'res')
// //假设code=0代表上传成功
// if(res.code == 0){
// //do something 比如将res返回的图片链接保存到表单的隐藏域
// }
//
// //获取当前触发上传的元素,一般用于 elem 绑定 class 的情况,注意:此乃 layui 2.1.0 新增
// var item = this.item;
//
// //文件保存失败
// //do something
// uploadInst.reload();
// }
// ,error: function(index, upload){
// //当上传失败时,你可以生成一个“重新上传”的按钮,点击该按钮时,执行 upload() 方法即可实现重新上传
// }
// });
//监听搜索
form.on('submit(search)', function(data){
var field = data.field;
@ -205,7 +255,42 @@
console.log('ssss')
});
$(document).on('click', '#import-excel', function () {
layer.open({
type: 2
,title: '商品导入'
,content: '{:url("goods.goods/import")}'
,area: ['60%', '60%']
// ,btn: ['确定', '取消']
,yes: function(index, layero){
var iframeWindow = window['layui-layer-iframe'+ index]
,submitID = 'import-submit'
,submit = layero.find('iframe').contents().find("#import-submit");
//监听提交
// iframeWindow.layui.form.on('submit(import-submit)', function(data){
// $.ajax({
// url:'{:url("goods.goods/import")}',
// data:data.field,
// type:"post",
// success:function(res)
// {
// if(res.code == 1)
// {
// layui.layer.msg(res.msg, {
// offset: '15px'
// , icon: 1
// , time: 1000
// });
// layer.close(index); //关闭弹层
// table.reload('user-lists'); //数据刷新
// }
// }
// });
// });
// submit.trigger('click');
}
});
})
//导出
$(document).on('click', '.goods-export', function () {

View File

@ -152,33 +152,188 @@ class Goods extends Models
}
}
public static function exportFields()
// 批量增加商品SKU相关 包括 规格名、规格值和sku
public static function batchHandleSku(): bool
{
return '';
// 查询import_status=0的商品记录 如果量大的话使用游标查询
$needHandleList = self::where('import_status', 0)->column('id,min_price,stock');
$insertSpec = [];//商品默认规格
$insertItem = [];//商品默认sku
$goodsIds = [];
foreach ($needHandleList as $item) {
$goodsIds[] = $item['id'];
$insertSpec[] = ['goods_id' => $item['id'], 'name' => '默认'];
$insertItem[] = [
'goods_id' => $item['id'],
'spec_value_ids' => '0',//使用0占位
'spec_value_str' => '默认',
'price' => $item['min_price'],
'stock' => $item['stock'],
];
}
if (!empty($insertSpec)) {
GoodsSpec::limit(2000)->insertAll($insertSpec);
}
if (!empty($insertItem)) {
// 查询上述商品的默认规格名
$defaultSpecList = GoodsSpec::whereIn('goods_id', $goodsIds)->where('name', '默认')->column('id', 'goods_id');//只需要拿默认的
foreach ($insertItem as &$i) {
$i['spec_value_ids'] = $defaultSpecList[$i['goods_id']] ?? '0';
}
// 商品规格值
$insertSpecValue = [];
foreach ($defaultSpecList as $goodsId => $specId) {
$insertSpecValue[] = [
'goods_id' => $goodsId,
'spec_id' => $specId,
'value' => '默认',
];
}
GoodsSpecValue::limit(2000)->insertAll($insertSpecValue);
GoodsItem::limit(2000)->insertAll($insertItem);
}
// 上述完成后 就默认商品的导入完成
Goods::whereIn('id', $goodsIds)->save(['import_status' => 1]);
return true;
}
public static function fieldName()
// 处理组图
public static function handleImages()
{
return [
'id' => 'ID',
'type' => '商品类型',
'name' => '商品名称',
'code' => '商品编码',
'shop_id' => '商家',
'shop_cate_id' => '商家商品分类',
'first_cate_id' => '平台商品一级分类',
'second_cate_id' => '平台商品二级分类',
'third_cate_id' => '平台商品三级分类',
'brand_id' => '品牌',
'unit_id' => '商品单位',
'image' => '商品主图',
'content' => '商品详情',
'sales_actual' => '商品实际销量',
'max_price' => '最高价格',
'min_price' => '最低价格',
'market_price' => '市场价',
'stock' => '总库存',
'custom_params' => '自定义参数',
];
// 查询商品表images不为空的记录批量插入到goods_image表再将这些images字段置空
$needHandleList = self::whereNotNull('images')->column('id,images');
$goodsIds = [];
$insertImage = [];
foreach ($needHandleList as $item) {
$goodsIds[] = $item['id'];
$imageArr = explode(',', $item['images']);
foreach ($imageArr as $img) {
$insertImage[] = [
'goods_id' => $item['id'],
'uri' => $img,
];
}
}
GoodsImage::limit(2000)->insertAll($insertImage);
self::whereIn('id', $goodsIds)->save([
'images' => null
]);
}
// 批量插入没有的品牌
public static function handleBrand()
{
// 查询商品表brand_name不为空brand_id=0的记录批量插入到goods_brand表
$needHandleList = self::whereNotNull('brand_name')->where('brand_id', 0)->group('brand_name')->column('brand_name');
$allBrand = GoodsBrand::getAll();
$allBrandName = array_keys($allBrand);
$insert = [];
$now = time();
foreach ($needHandleList as $item) {
if (!in_array($item['brand_name'], $allBrandName)) {
$insert[] = ['name' =>$item['brand_name'], 'create_time'=> $now];
}
}
GoodsBrand::limit(2000)->insertAll($insert);
}
// 批量插入没有的单位
public static function handleUnit()
{
// 查询商品表unit_name不为空unit_id=0的记录批量插入到goods_unit表
$needHandleList = self::whereNotNull('unit_name')->where('unit_id', 0)->group('unit_name')->column('unit_name');
$all = GoodsUnit::getAll();
$allName = array_keys($all);
$insert = [];
$now = time();
foreach ($needHandleList as $item) {
if (!in_array($item['unit_name'], $allName)) {
$insert[] = ['name' =>$item['unit_name'], 'create_time'=> $now];
}
}
GoodsUnit::limit(2000)->insertAll($insert);
}
// 批量插入没有的商品分类
public static function handleCategory()
{
// 查询商品表first_cate不为空first_cate_id=0的记录
$needHandleList1 = self::whereNotNull('first_cate')->where('first_cate_id', 0)->group('first_cate')->column('first_cate');
$needHandleList2 = self::whereNotNull('second_cate')->where('second_cate_id', 0)->group('second_cate')->column('second_cate,first_cate');
$needHandleList3 = self::whereNotNull('third_cate')->where('third_cate_id', 0)->group('third_cate')->column('third_cate,second_cate');
$all = GoodsCategory::getAll();
$allName = array_keys($all);
$insert = [];
$now = time();
foreach ($needHandleList1 as $item) {
if (!in_array($item['first_cate'], $allName)) {
$insert[] = ['name' =>$item['first_cate'], 'pid' => 0, 'level' => 1,'create_time'=> $now, 'parent_name' => null];
}
}
foreach ($needHandleList2 as $item) {
if (!in_array($item['second_cate'], $allName)) {
$insert[] = ['name' =>$item['first_cate'], 'pid' => 0, 'level' => 2,'create_time'=> $now, 'parent_name' => $item['first_cate']];
}
}
foreach ($needHandleList3 as $item) {
if (!in_array($item['third_cate'], $allName)) {
$insert[] = ['name' =>$item['third_cate'], 'pid' => 0, 'level' => 3,'create_time'=> $now, 'parent_name' => $item['second_cate']];
}
}
GoodsCategory::limit(2000)->insertAll($insert);
// 将level > 1但是pid=0且parent_name不为空的记录批量更新
GoodsCategory::alias('main')
->join('goods_category parent', 'main.parent_name = parent.name')
->where('main.level', '>', 1)
->where('main.pid', 0)
->whereNotNull('main.parent_name')
->update(['main.pid' => 'parent.id']);
}
public static function handleSelfInfo()
{
// 更新一级分类ID 一级分类ID=0但分类名不为空的
self::alias('main')
->join('goods_category c', 'main.first_cate = c.name')
->where('main.first_cate_id', 0)
->whereNotNull('main.first_cate')
->update(['main.first_cate_id' => 'c.id']);
// 更新二级分类ID 二级分类ID=0但分类名不为空的
self::alias('main')
->join('goods_category c', 'main.first_cate = c.name')
->where('main.second_cate_id', 0)
->whereNotNull('main.second_cate')
->update(['main.second_cate_id' => 'c.id']);
// 更新三级分类ID 三级分类ID=0但分类名不为空的
self::alias('main')
->join('goods_category c', 'main.first_cate = c.name')
->where('main.third_cate_id', 0)
->whereNotNull('main.third_cate')
->update(['main.third_cate_id' => 'c.id']);
// 更新品牌ID 品牌ID=0但品牌名不为空的
self::alias('main')
->join('goods_brand b', 'main.brand_name = c.name')
->where('main.brand_id', 0)
->whereNotNull('main.brand_name')
->update(['main.brand_id' => 'c.id']);
// 更新单位ID 单位ID=0但单位名不为空的
self::alias('main')
->join('goods_unit b', 'main.unit_name = c.name')
->where('main.unit_id', 0)
->whereNotNull('main.unit_name')
->update(['main.unit_id' => 'c.id']);
}
}

View File

@ -44,4 +44,10 @@ class GoodsBrand extends Models
return empty($lists) ? [] : $lists;
}
//获取所有分类
public static function getAll(): array
{
return self::where('del', 0)->column('id', 'name');
}
}

View File

@ -34,4 +34,14 @@ class GoodsCategory extends Models
{
return $this->hasMany(self::class, 'pid', 'id')->where(['del' => 0]);
}
//获取所有分类
public static function getAll(int $level = 0): array
{
$where = [];
if ($level > 0) {
$where[] = ['level', '=', $level];
}
return self::where($where)->where('del', 0)->column('id', 'name');
}
}

View File

@ -38,4 +38,10 @@ class GoodsUnit extends Models
return empty($lists) ? [] : $lists;
}
//获取所有分类
public static function getAll(): array
{
return self::where('del', 0)->column('id', 'name');
}
}