Product.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. <?php namespace App\Http\Controllers\Admin;
  2. use App\Http\Requests\Admin\Product as Request;
  3. use App\Models\AdminHistory;
  4. use App\Models\Business;
  5. use App\Models\City;
  6. use App\Models\OrdersProduct;
  7. use App\Models\Producer;
  8. use App\Models\Product as Model;
  9. use Illuminate\Support\Carbon;
  10. use App\Models\Product\Spec as ProductSpec;
  11. use App\Models\Product\Type as ProductType;
  12. use App\Models\Product\Attr as ProductAttr;
  13. use App\Models\Product\Skus as ProductSkus;
  14. use App\Models\Product\City as ProductCity;
  15. use App\Models\ProductPhoto;
  16. use Illuminate\Support\Facades\DB;
  17. use App\Models\WeiBan\Tags as WeiBanTags;
  18. use Intervention\Image\Facades\Image;
  19. use Intervention\Image\Gd\Font;
  20. use App\Facades\Servers\WechatMini\Mini;
  21. /**
  22. * 产品管理
  23. *
  24. * @author 刘相欣
  25. *
  26. */
  27. class Product extends Auth{
  28. protected function _initialize(){
  29. parent::_initialize();
  30. $this->assign('breadcrumb1','基础信息');
  31. $this->assign('breadcrumb2','产品管理');
  32. }
  33. /**
  34. * 首页列表
  35. *
  36. * */
  37. public function index(Model $Model,City $City,ProductCity $ProductCity,OrdersProduct $OrdersProduct,AdminHistory $AdminHistory){
  38. // 接受参数
  39. $code = request('product_code','');
  40. $name = request('name','');
  41. $status = request('status');
  42. $startTime = request('start_time','');
  43. $cityIds = request('city_ids',[]);
  44. // 编码转ID
  45. $id = $code ? $Model->codeToId($code) : 0;
  46. // 查询条件
  47. $map = [];
  48. // 是否有城市
  49. $whereIn = $cityIds ? $cityIds : [];
  50. // 编码ID
  51. if( $id ) $map[] = ['product.id','=',$id];
  52. if( $name ) $map[] = ['product.name','like','%'.$name.'%'];
  53. if( $startTime ) $map[] = ['product.insert_time','>=',Carbon::createFromFormat('Y-m-d',$startTime)->startOfDay()->getTimestamp()];
  54. if( $startTime ) $map[] = ['product.insert_time','<=',Carbon::createFromFormat('Y-m-d',$startTime)->endOfDay()->getTimestamp()];
  55. if( !is_null($status) ) $map[] = ['product.status','=',$status];
  56. // 查询数据
  57. $list = $Model->query();
  58. if( $whereIn ) $list = $list->join('product_city','product_city.product_id','=','product.id')->whereIn('product_city.city_id',$whereIn);
  59. $list = $list->groupBy('product.id')
  60. ->where($map)
  61. ->orderBy('product.sort')
  62. ->orderByDesc('product.id')
  63. ->select(['product.*'])
  64. ->paginate(request('limit',config('page_num',10)))->appends(request()->all());
  65. // 统计销量
  66. $salesList = $OrdersProduct->query()->where([['status','<>',4]])->whereIn('product_id',array_column($list->items(),'id'))->groupBy('product_id')->select([DB::raw('sum(`buy_num`) as sales_total'),'product_id'])->pluck('sales_total','product_id')->toArray();
  67. // 循环处理数据
  68. foreach ($list as $key => $value) {
  69. // 判断总库存
  70. if( !$value['stock_total'] ) {
  71. // 以首次添加时的为准
  72. $value['stock_total']= (int) $AdminHistory->query()->where([['table_name','=','product'],['primary_id','=',$value['id']],['notes_type','=',1],['column_name','=','stock']])->orderBy('update_time')->value('after_value');
  73. // 修改库存
  74. $Model->edit($value['id'],['stock_total'=>$value['stock_total']]);
  75. }
  76. // id转编号
  77. $value['product_code'] = $Model->idToCode($value['id']);
  78. // id转编号
  79. $cityName = [];
  80. // 获取城市列表
  81. $cityIds = $ProductCity->query()->where([['product_id','=',$value['id']]])->pluck('city_id');
  82. // 如果有的话
  83. if( $cityIds ) {
  84. // 返回结果
  85. foreach ($cityIds as $cityId) {
  86. // 城市ID
  87. if( $cityId == 1 ) continue;
  88. // 获取城市名
  89. $cityName[] = $City->getOne($cityId,'name');
  90. }
  91. }
  92. // 销量统计
  93. $value['sales_total'] = isset($salesList[$value['id']])?$salesList[$value['id']]:0;
  94. // 城市
  95. $value['city_name'] = implode('、',$cityName);
  96. // 重组
  97. $list[$key] = $value;
  98. }
  99. // 获取列表
  100. $cityList = $City->getCityList();
  101. // 分配数据
  102. $this->assign('empty', '<tr><td colspan="20">~~暂无数据</td></tr>');
  103. $this->assign('cityList',$cityList);
  104. $this->assign('list', $list);
  105. // 加载模板
  106. return $this->fetch();
  107. }
  108. /**
  109. * 获取小程序海报
  110. *
  111. */
  112. public function get_poster(Model $Model){
  113. // 接收参数
  114. $id = request('id',0);
  115. // 查询用户
  116. $oldData = $Model->where(['id'=>$id])->first();
  117. // 错误告知
  118. if( !$oldData ) return $this->error('查无数据');
  119. // 获取分享海报图片
  120. $result = $this->getShareImage('id='.$id,$oldData);
  121. // 错误提示
  122. if( isset($result['error']) ) return $this->error($result['error']);
  123. // 分配数据
  124. $this->assign('image',$result);
  125. $this->assign('oldData',$oldData);
  126. $this->assign('crumbs','海报');
  127. // 加载模板
  128. return $this->fetch();
  129. }
  130. /**
  131. * 获取分享海报图片
  132. * @param int $scene 场景值
  133. *
  134. */
  135. private function getShareImage($scene,$oldData){
  136. // 尝试执行
  137. try {
  138. // 加载图片
  139. $image = Image::make(public_path('uploads/images/poster/').'product.png');
  140. // 产品缩略图
  141. $thumb = Image::make(path_compat($oldData['thumb']))->resize(1000,1000);
  142. // 设置文字样式(字体、大小、颜色等)
  143. $fontPath = public_path().'/fonts/msyh14.ttf';// 指定字体文件路径
  144. // 生成小程序二维码
  145. $qrcode = Mini::getUnlimit($scene,['page'=>'pages/product/index','width'=>280,'is_hyaline'=>true]);
  146. // 错误提示
  147. if( isset($qrcode['error']) ) return $qrcode;
  148. // 加载图片
  149. $qrcode = Image::make($qrcode)->resize(250,250);
  150. // 插入图片
  151. $image->insert($thumb,'top-center',0,130);
  152. // 插入图片
  153. $image->insert($qrcode,'bottom-left',120,120);
  154. // 给图片写入文字
  155. $image->text('¥'.$oldData['price'], 360,1280,function (Font $font) use ($fontPath) {
  156. $font->file($fontPath); // 字体文件地址
  157. $font->size(88); // 字体大小
  158. $font->color('#333333');
  159. $font->align('left');
  160. });
  161. // 给图片写入文字
  162. $image->text($oldData['name'], 120,1380,function (Font $font) use ($fontPath) {
  163. $font->file($fontPath); // 字体文件地址
  164. $font->size(48); // 字体大小
  165. $font->color('#333333');
  166. $font->align('left');
  167. });
  168. // 转码成字符串
  169. $image = $image->encode('jpg', 90)->__toString();
  170. // 转base64
  171. $base64 = 'data:image/jpg;base64,' . base64_encode($image);
  172. // 返回结果
  173. return $base64;
  174. } catch (\Throwable $th) {
  175. // 错误提示
  176. return ['error'=>$th->getMessage()];
  177. }
  178. }
  179. /**
  180. * 添加
  181. *
  182. * */
  183. public function add( Request $request, Model $Model,WeiBanTags $WeiBanTags,ProductPhoto $ProductPhoto,Producer $Producer,Business $Business,ProductType $ProductType,ProductSpec $ProductSpec,ProductAttr $ProductAttr,ProductSkus $ProductSkus,City $City,ProductCity $ProductCity){
  184. if( request()->isMethod('post') ){
  185. // 验证参数
  186. $request->scene('add')->validate();
  187. // 组合数据
  188. $data['name'] = request('name','');
  189. $data['thumb'] = request('thumb','');
  190. $data['poster'] = request('poster','');
  191. $data['spec'] = request('spec','');
  192. $data['price'] = request('price',0);
  193. $data['market_price'] = request('market_price',0);
  194. $data['producer_id'] = request('producer_id',0);
  195. $data['business_id'] = request('business_id',0);
  196. $data['quota'] = request('quota',0);
  197. $data['min_quota'] = request('min_quota',0);
  198. $data['quota_start'] = request('quota_start','');
  199. $data['quota_end'] = request('quota_end','');
  200. $data['puton_time'] = request('puton_time','');
  201. $data['putoff_time'] = request('putoff_time','');
  202. $data['stock'] = request('stock',0);
  203. $data['status'] = 1;
  204. $data['admin_uid'] = admin('uid');
  205. $description = request('description','');
  206. $attr = request('attr',[]);
  207. $skuList = request('sku',[]);
  208. $cityIds = request('city_ids',[]);
  209. $photoList = request('photo_list',[]);
  210. $tagScope = request('tag_scope',[]);
  211. $data['tag_scope'] = implode(',',$tagScope);
  212. $tagExclude = request('tag_exclude',[]);
  213. $data['tag_exclude'] = implode(',',$tagExclude);
  214. // 循环
  215. foreach ($photoList as $key => $value) {
  216. if( !$value ) unset($photoList[$key]);
  217. }
  218. $photoList = array_values($photoList);
  219. // 如果没有选择,则意味着全部
  220. $cityIds = $cityIds ? $cityIds : [1];
  221. $data['quota_start'] = $data['quota_start'] ? strtotime($data['quota_start']) : 0;
  222. $data['quota_end'] = $data['quota_end'] ? strtotime($data['quota_end']) : 0;
  223. $data['puton_time'] = $data['puton_time'] ? strtotime($data['puton_time']) : 0;
  224. $data['putoff_time'] = $data['putoff_time'] ? strtotime($data['putoff_time']) : 0;
  225. // 限购提示
  226. if( !$data['thumb'] ) return json_send(['code'=>'error','msg'=>'请上传产品主图','data'=>['error'=>'请上传产品主图']]);
  227. // 限购提示
  228. if( $attr && !$skuList ) return json_send(['code'=>'error','msg'=>'规格属性存在时,请填写SKU','data'=>['error'=>'规格属性存在时,请填写SKU']]);
  229. // 限购提示
  230. if( $data['quota'] && ( !$data['quota_start'] || !$data['quota_end'] )) return json_send(['code'=>'error','msg'=>'限购必填限购时间','data'=>['error'=>'限购必填限购时间']]);
  231. // 上下架
  232. if( $data['puton_time'] ) {
  233. // 下架时间必填
  234. if( !$data['putoff_time'] ) return json_send(['code'=>'error','msg'=>'请填写自动下架时间','data'=>['error'=>'自动上架请填写下架时间']]);
  235. // 下架时间必填
  236. if( $data['putoff_time'] <= $data['puton_time'] ) return json_send(['code'=>'error','msg'=>'下架时间请晚于上架时间','data'=>['error'=>'下架时间请晚于上架时间']]);
  237. }
  238. // 总库存
  239. if( $skuList ) $data['stock'] = array_sum(array_column($skuList,'stock'));
  240. // 总库存
  241. $data['stock_total'] = $data['stock'];
  242. // 获取规格属性
  243. $specAttr = $this->getSpecAttr($attr,$ProductSpec,true);
  244. // 开启事务
  245. DB::beginTransaction();
  246. try {
  247. // 写入
  248. $id = $Model->add($data);
  249. // 提示新增失败
  250. if( !$id ) {
  251. // 回滚
  252. DB::rollBack();
  253. // 提示
  254. return json_send(['code'=>'error','msg'=>'新增失败']);
  255. }
  256. // 组合数据
  257. foreach ($specAttr as $key => $value) {
  258. // 查询结果
  259. $value['id'] = $ProductAttr->upsertByName($value,$id);
  260. // 提示新增失败
  261. if( !$value['id'] ) {
  262. // 回滚
  263. DB::rollBack();
  264. // 提示
  265. return json_send(['code'=>'error','msg'=>'商品属性新增失败']);
  266. }
  267. // 重组
  268. $specAttr[$key] = $value;
  269. }
  270. // 图册
  271. foreach ($photoList as $key => $value) {
  272. // 整理数据
  273. $value = ['sort'=>$key,'thumb'=>$value,'product_id'=>$id,'insert_time'=>time(),'update_time'=>time()];
  274. // 重新整理
  275. $photoList[$key] = $value;
  276. }
  277. // 存在图册
  278. if( $photoList ) {
  279. // 写入失败
  280. $result = $ProductPhoto->query()->insert($photoList);
  281. // 提示新增失败
  282. if( !$result ) {
  283. // 回滚
  284. DB::rollBack();
  285. // 提示
  286. return json_send(['code'=>'error','msg'=>'商品图册写入失败']);
  287. }
  288. }
  289. // 循环SKU
  290. foreach ($skuList as $attrNames => $value) {
  291. // 属性ID值
  292. $value['attr_ids'] = [];
  293. // 规格属性值组合
  294. $value['attr_names']= $attrNames;
  295. // 切割成数据
  296. $names = explode(',',$attrNames);
  297. // 循环处理
  298. foreach ($names as $name) {
  299. foreach ($specAttr as $vv) {
  300. if( $name == $vv['name'] ) {
  301. // 提示新增失败
  302. if( !$vv['id'] ) {
  303. // 回滚
  304. DB::rollBack();
  305. // 提示
  306. return json_send(['code'=>'error','msg'=>'属性SKU匹配失败']);
  307. }
  308. $value['attr_ids'][] = $vv['id'];
  309. }
  310. }
  311. }
  312. // 转成好存储的数据
  313. $value['attr_ids'] = implode(',',$value['attr_ids']);
  314. // 转成好存储的数据
  315. $value['product_id'] = $id;
  316. // 转成好存储的数据
  317. $value['sku_thumb'] = (string)$value['sku_thumb'];
  318. // 转成好存储的数据
  319. $value['stock_total']= $value['stock'];
  320. // 转成好存储的数据
  321. $value['insert_time']= time();
  322. // 转成好存储的数据
  323. $value['update_time']= time();
  324. // SKU结果
  325. $skuList[$attrNames] = $value;
  326. }
  327. // 循环城市范围
  328. foreach ($cityIds as $key => $value) {
  329. // 重组数据
  330. $cityIds[$key] = ['city_id'=>$value,'product_id'=>$id,'insert_time'=>time(),'update_time'=>time()];
  331. }
  332. // 写入城市范围
  333. $ProductCity->query()->insert($cityIds);
  334. // 返回结果
  335. $result = $ProductSkus->insert($skuList);
  336. // 提示新增失败
  337. if( !$result ) {
  338. // 回滚
  339. DB::rollBack();
  340. // 提示
  341. return json_send(['code'=>'error','msg'=>'SKU插入失败']);
  342. }
  343. // 更新内容
  344. $Model->updateDesc($id,$description);
  345. // 提交
  346. DB::commit();
  347. // 记录行为
  348. $this->addAdminHistory(admin('uid'),$Model->getTable(),$id,1,[],$data);
  349. // 告知结果
  350. return json_send(['code'=>'success','msg'=>'新增成功','action'=>'add']);
  351. } catch (\Throwable $th) {
  352. // 回滚
  353. DB::rollBack();
  354. // 提示
  355. return json_send(['code'=>'error','msg'=>'系统异常,写入失败','data'=>$th->getMessage()]);
  356. }
  357. }
  358. // 获取类型数据
  359. $typeList = $ProductType->getList();
  360. $cityList = $City->getCityList();
  361. $businessList = $Business->getList();
  362. $producerList = $Producer->getList();
  363. // 标签列表
  364. $tagData = $WeiBanTags->query()->groupBy('group')->groupBy('name')->get(['group','name'])->toArray();
  365. // 标签列表
  366. $tagList = [];
  367. // 循环数据
  368. foreach ($tagData as $value) {
  369. $tagList[$value['group']][] = $value['name'];
  370. }
  371. // 分配数据
  372. $this->assign('crumbs','新增');
  373. $this->assign('tagList',$tagList);
  374. $this->assign('typeList',$typeList);
  375. $this->assign('cityList',$cityList);
  376. $this->assign('businessList',$businessList);
  377. $this->assign('producerList',$producerList);
  378. // 加载模板
  379. return $this->fetch();
  380. }
  381. /**
  382. * 编辑
  383. *
  384. * */
  385. public function edit( Request $request, Model $Model,WeiBanTags $WeiBanTags,ProductPhoto $ProductPhoto,Producer $Producer,Business $Business,ProductType $ProductType,ProductSpec $ProductSpec,ProductAttr $ProductAttr,ProductSkus $ProductSkus,City $City,ProductCity $ProductCity){
  386. if(request()->isMethod('post')){
  387. // 验证参数
  388. $request->scene('edit')->validate();
  389. // 组合数据
  390. $id = request('id',0);
  391. $data['name'] = request('name','');
  392. $data['thumb'] = request('thumb','');
  393. $data['poster'] = request('poster','');
  394. $data['spec'] = request('spec','');
  395. $data['price'] = request('price',0);
  396. $data['market_price'] = request('market_price',0);
  397. $data['quota'] = request('quota',0);
  398. $data['min_quota'] = request('min_quota',0);
  399. $data['quota_start'] = request('quota_start','');
  400. $data['quota_end'] = request('quota_end','');
  401. $data['puton_time'] = request('puton_time','');
  402. $data['putoff_time'] = request('putoff_time','');
  403. $data['producer_id'] = request('producer_id',0);
  404. $data['business_id'] = request('business_id',0);
  405. $data['stock'] = request('stock',0);
  406. $data['status'] = 1;
  407. $description = request('description','');
  408. $attr = request('attr',[]);
  409. $skuList = request('sku',[]);
  410. $cityIds = request('city_ids',[]);
  411. $photoList = request('photo_list',[]);
  412. $tagScope = request('tag_scope',[]);
  413. $data['tag_scope'] = implode(',',$tagScope);
  414. $tagExclude = request('tag_exclude',[]);
  415. $data['tag_exclude'] = implode(',',$tagExclude);
  416. // 循环
  417. foreach ($photoList as $key => $value) {
  418. if( !$value ) unset($photoList[$key]);
  419. }
  420. $photoList = array_values($photoList);
  421. // 如果没有选择,则意味着全部
  422. $cityIds = $cityIds ? $cityIds : [1];
  423. $data['quota_start'] = $data['quota_start'] ? strtotime($data['quota_start']) : 0;
  424. $data['quota_end'] = $data['quota_end'] ? strtotime($data['quota_end']) : 0;
  425. $data['puton_time'] = $data['puton_time'] ? strtotime($data['puton_time']) : 0;
  426. $data['putoff_time'] = $data['putoff_time'] ? strtotime($data['putoff_time']) : 0;
  427. // 限购提示
  428. if( !$data['thumb'] ) return json_send(['code'=>'error','msg'=>'请上传产品主图','data'=>['error'=>'请上传产品主图']]);
  429. // 限购提示
  430. if( $attr && !$skuList ) return json_send(['code'=>'error','msg'=>'规格属性存在时,请填写SKU','data'=>['error'=>'规格属性存在时,请填写SKU']]);
  431. // 限购提示
  432. if( $data['quota'] && ( !$data['quota_start'] || !$data['quota_end'] )) return json_send(['code'=>'error','msg'=>'限购必填限购时间','data'=>['error'=>'限购必填限购时间']]);
  433. // 上下架
  434. if( $data['puton_time'] ) {
  435. // 下架时间必填
  436. if( !$data['putoff_time'] ) return json_send(['code'=>'error','msg'=>'请填写自动下架时间','data'=>['error'=>'自动上架请填写下架时间']]);
  437. // 下架时间必填
  438. if( $data['putoff_time'] <= $data['puton_time'] ) return json_send(['code'=>'error','msg'=>'下架时间请晚于上架时间','data'=>['error'=>'下架时间请晚于上架时间']]);
  439. }
  440. // 总库存
  441. if( $skuList ) $data['stock'] = array_sum(array_column($skuList,'stock'));
  442. // 总库存
  443. $data['stock_total'] = $data['stock'];
  444. // 获取规格属性
  445. $specAttr = $this->getSpecAttr($attr,$ProductSpec,true);
  446. // 开启事务
  447. DB::beginTransaction();
  448. try {
  449. // 写入
  450. $result = $Model->edit($id,$data);
  451. // 提示新增失败
  452. if( !$result ) {
  453. // 回滚
  454. DB::rollBack();
  455. // 提示
  456. return json_send(['code'=>'error','msg'=>'修改失败']);
  457. }
  458. // 获取旧图册
  459. $oldPhoto = $ProductPhoto->getListByProductId($id);
  460. // 循环个数
  461. for ($i=0; $i < 4; $i++) {
  462. // 如果存在旧数据与新数据,修改
  463. if( isset($oldPhoto[$i]) && isset($photoList[$i]) ) $ProductPhoto->query()->where([['product_id','=',$id],['sort','=',$i]])->update(['thumb'=>(string)$photoList[$i],'update_time'=>time()]);
  464. // 如果存在旧数据,不存在新数据,删除
  465. if( isset($oldPhoto[$i]) && !isset($photoList[$i]) ) $ProductPhoto->query()->where([['product_id','=',$id],['sort','=',$i]])->delete();
  466. // 如果不存在旧数据,存在新数据,新增
  467. if( !isset($oldPhoto[$i]) && isset($photoList[$i]) ) $ProductPhoto->add(['sort'=>$i,'product_id'=>$id,'thumb'=>(string)$photoList[$i],'insert_time'=>time(),'update_time'=>time()]);
  468. }
  469. // 组合数据
  470. foreach ($specAttr as $key => $value) {
  471. // 查询结果
  472. $value['id'] = $ProductAttr->upsertByName($value,$id);
  473. // 提示新增失败
  474. if( !$value['id'] ) {
  475. // 回滚
  476. DB::rollBack();
  477. // 提示
  478. return json_send(['code'=>'error','msg'=>'商品属性新增失败']);
  479. }
  480. // 重组
  481. $specAttr[$key] = $value;
  482. }
  483. // 如果不在这个属性列表中的数据删除
  484. $ProductAttr->query()->where([['product_id','=',$id]])->whereNotIn('id',array_column($specAttr,'id'))->delete();
  485. // 循环SKU
  486. foreach ($skuList as $attrNames => $value) {
  487. // 属性ID值
  488. $value['attr_ids'] = [];
  489. // 规格属性值组合
  490. $value['attr_names']= $attrNames;
  491. // 切割成数据
  492. $names = explode(',',$attrNames);
  493. // 循环处理
  494. foreach ($names as $name) {
  495. foreach ($specAttr as $vv) {
  496. if( $name == $vv['name'] ) {
  497. // 提示新增失败
  498. if( !$vv['id'] ) {
  499. // 回滚
  500. DB::rollBack();
  501. // 提示
  502. return json_send(['code'=>'error','msg'=>'属性SKU匹配失败']);
  503. }
  504. $value['attr_ids'][] = $vv['id'];
  505. }
  506. }
  507. }
  508. // 转成好存储的数据
  509. $value['attr_ids'] = implode(',',$value['attr_ids']);
  510. // 转成好存储的数据
  511. $value['sku_thumb'] = (string)$value['sku_thumb'];
  512. // 转成好存储的数据
  513. $value['stock_total']= $value['stock'];
  514. // 转成好存储的数据
  515. $value['product_id']= $id;
  516. // 查询
  517. $oldSkuId = $ProductSkus->query()->where([['product_id','=',$id],['attr_ids','=',$value['attr_ids']]])->value('id');
  518. // 判断是否存在ID
  519. $value['id'] = $oldSkuId ? $ProductSkus->edit($oldSkuId,$value) : $ProductSkus->add($value);
  520. // SKU结果
  521. $skuList[$attrNames]= $value;
  522. }
  523. // 如果不在这个属性列表中的数据删除
  524. $ProductSkus->query()->where([['product_id','=',$id]])->whereNotIn('id',array_column($skuList,'id'))->delete();
  525. // 获取之前的城市ID
  526. $oldCityIds = $ProductCity->getListByProductId($id);
  527. // 循环城市范围
  528. foreach ($cityIds as $key => $value) {
  529. // 不存在则新增
  530. if( !in_array($value,$oldCityIds) ) $ProductCity->add(['city_id'=>$value,'product_id'=>$id]);
  531. }
  532. // 不存在的城市删除
  533. $ProductCity->query()->where([['product_id','=',$id]])->whereNotIn('city_id',$cityIds)->delete();
  534. // 更新内容
  535. $Model->updateDesc($id,$description);
  536. // 提交
  537. DB::commit();
  538. // 记录行为
  539. $this->addAdminHistory(admin('uid'),$Model->getTable(),$id,2,[],$data);
  540. // 告知结果
  541. return json_send(['code'=>'success','msg'=>'修改成功','action'=>'edit']);
  542. } catch (\Throwable $th) {
  543. // 回滚
  544. DB::rollBack();
  545. // 提示
  546. return json_send(['code'=>'error','msg'=>'系统异常,写入失败','data'=>$th->getMessage() .'=>'.$th->getLine()]);
  547. }
  548. }
  549. // 接收参数
  550. $id = request('id',0);
  551. // 查询数据
  552. $oldData = $Model->where(['id'=>$id])->first();
  553. // 如果是没有数据
  554. if( !$oldData ) return $this->error('查无数据');
  555. // 产品信息转格式
  556. $oldData = $oldData->toArray();
  557. $oldData['description'] = $Model->getDesc($id);
  558. $oldData['city_ids'] = $ProductCity->getListByProductId($id);
  559. $oldData['tag_scope'] = explode(',',$oldData['tag_scope']);
  560. $oldData['tag_exclude'] = explode(',',$oldData['tag_exclude']);
  561. $photoList = $ProductPhoto->getListByProductId($id);
  562. // 获取产品属性
  563. $attrList = $ProductAttr->getListByProductId($id);
  564. // 规格属性
  565. $specList = [];
  566. // 获取数据
  567. foreach ($attrList as $value) {
  568. $value['active'] = 0;
  569. if( !isset($specList[$value['spec_id']]) ) $specList[$value['spec_id']] = $ProductSpec->getOne($value['spec_id']);
  570. $specList[$value['spec_id']]['attr_list'][] = $value;
  571. }
  572. // 产品类型
  573. $skuList = $ProductSkus->getListByProductId($id);
  574. // 循环处理
  575. foreach ($skuList as $key => $value) {
  576. // 数据解析
  577. $value['attr_ids'] = explode(',',$value['attr_ids']);
  578. // 循环处理
  579. foreach ( $value['attr_ids'] as $k=>$attrId ) {
  580. // 获取ids对应的name
  581. $value['attr_ids'][$k] = $ProductAttr->getValueById($attrId,'name');
  582. }
  583. // 数据合并
  584. $value['attr_ids'] = implode(',',$value['attr_ids']);
  585. // 重组
  586. $skuList[$key] = $value;
  587. }
  588. // 获取类型数据
  589. $typeList = $ProductType->getList();
  590. $cityList = $City->getCityList();
  591. $businessList = $Business->getList();
  592. $producerList = $Producer->getList();
  593. // 标签列表
  594. $tagData = $WeiBanTags->query()->groupBy('group')->groupBy('name')->get(['group','name'])->toArray();
  595. // 标签列表
  596. $tagList = [];
  597. // 循环数据
  598. foreach ($tagData as $value) {
  599. $tagList[$value['group']][] = $value['name'];
  600. }
  601. // 分配数据
  602. $this->assign('tagList',$tagList);
  603. $this->assign('typeList',$typeList);
  604. $this->assign('cityList',$cityList);
  605. $this->assign('businessList',$businessList);
  606. $this->assign('producerList',$producerList);
  607. $this->assign('oldData',$oldData);
  608. $this->assign('photoList',$photoList);
  609. $this->assign('skuList',$skuList);
  610. $this->assign('specList',$specList);
  611. $this->assign('crumbs','修改');
  612. // 加载模板
  613. return $this->fetch();
  614. }
  615. /**
  616. * 编辑
  617. *
  618. * */
  619. public function copy( Request $request, Model $Model,WeiBanTags $WeiBanTags,ProductPhoto $ProductPhoto,Producer $Producer,Business $Business,ProductType $ProductType,ProductSpec $ProductSpec,ProductAttr $ProductAttr,ProductSkus $ProductSkus,City $City,ProductCity $ProductCity){
  620. if( request()->isMethod('post') ){
  621. // 验证参数
  622. $request->scene('add')->validate();
  623. // 组合数据
  624. $data['name'] = request('name','');
  625. $data['thumb'] = request('thumb','');
  626. $data['poster'] = request('poster','');
  627. $data['spec'] = request('spec','');
  628. $data['price'] = request('price',0);
  629. $data['market_price'] = request('market_price',0);
  630. $data['producer_id'] = request('producer_id',0);
  631. $data['business_id'] = request('business_id',0);
  632. $data['quota'] = request('quota',0);
  633. $data['min_quota'] = request('min_quota',0);
  634. $data['quota_start'] = request('quota_start','');
  635. $data['quota_end'] = request('quota_end','');
  636. $data['stock'] = request('stock',0);
  637. $data['status'] = 1;
  638. $data['admin_uid'] = admin('uid');
  639. $description = request('description','');
  640. $attr = request('attr',[]);
  641. $skuList = request('sku',[]);
  642. $cityIds = request('city_ids',[]);
  643. $photoList = request('photo_list',[]);
  644. $tagScope = request('tag_scope',[]);
  645. $data['tag_scope'] = implode(',',$tagScope);
  646. $tagExclude = request('tag_exclude',[]);
  647. $data['tag_exclude'] = implode(',',$tagExclude);
  648. // 循环
  649. foreach ($photoList as $key => $value) {
  650. if( !$value ) unset($photoList[$key]);
  651. }
  652. $photoList = array_values($photoList);
  653. // 如果没有选择,则意味着全部
  654. $cityIds = $cityIds ? $cityIds : [1];
  655. $data['quota_start'] = $data['quota_start'] ? strtotime($data['quota_start']) : 0;
  656. $data['quota_end'] = $data['quota_end'] ? strtotime($data['quota_end']) : 0;
  657. // 限购提示
  658. if( !$data['thumb'] ) return json_send(['code'=>'error','msg'=>'请上传产品主图','data'=>['error'=>'请上传产品主图']]);
  659. // 限购提示
  660. if( $attr && !$skuList ) return json_send(['code'=>'error','msg'=>'规格属性存在时,请填写SKU','data'=>['error'=>'规格属性存在时,请填写SKU']]);
  661. // 限购提示
  662. if( $data['quota'] && ( !$data['quota_start'] || !$data['quota_end'] )) return json_send(['code'=>'error','msg'=>'限购必填限购时间','data'=>['error'=>'限购必填限购时间']]);
  663. // 总库存
  664. if( $skuList ) $data['stock'] = array_sum(array_column($skuList,'stock'));
  665. // 总库存
  666. $data['stock_total'] = $data['stock'];
  667. // 获取规格属性
  668. $specAttr = $this->getSpecAttr($attr,$ProductSpec,true);
  669. // 开启事务
  670. DB::beginTransaction();
  671. try {
  672. // 写入
  673. $id = $Model->add($data);
  674. // 提示新增失败
  675. if( !$id ) {
  676. // 回滚
  677. DB::rollBack();
  678. // 提示
  679. return json_send(['code'=>'error','msg'=>'新增失败']);
  680. }
  681. // 图册
  682. foreach ($photoList as $key => $value) {
  683. // 整理数据
  684. $value = ['sort'=>$key,'thumb'=>$value,'product_id'=>$id,'insert_time'=>time(),'update_time'=>time()];
  685. // 重新整理
  686. $photoList[$key] = $value;
  687. }
  688. // 存在图册
  689. if( $photoList ) {
  690. // 写入失败
  691. $result = $ProductPhoto->query()->insert($photoList);
  692. // 提示新增失败
  693. if( !$result ) {
  694. // 回滚
  695. DB::rollBack();
  696. // 提示
  697. return json_send(['code'=>'error','msg'=>'商品图册写入失败']);
  698. }
  699. }
  700. // 组合数据
  701. foreach ($specAttr as $key => $value) {
  702. // 查询结果
  703. $value['id'] = $ProductAttr->upsertByName($value,$id);
  704. // 提示新增失败
  705. if( !$value['id'] ) {
  706. // 回滚
  707. DB::rollBack();
  708. // 提示
  709. return json_send(['code'=>'error','msg'=>'商品属性新增失败']);
  710. }
  711. // 重组
  712. $specAttr[$key] = $value;
  713. }
  714. // 循环SKU
  715. foreach ($skuList as $attrNames => $value) {
  716. // 属性ID值
  717. $value['attr_ids'] = [];
  718. // 规格属性值组合
  719. $value['attr_names']= $attrNames;
  720. // 切割成数据
  721. $names = explode(',',$attrNames);
  722. // 循环处理
  723. foreach ($names as $name) {
  724. foreach ($specAttr as $vv) {
  725. if( $name == $vv['name'] ) {
  726. // 提示新增失败
  727. if( !$vv['id'] ) {
  728. // 回滚
  729. DB::rollBack();
  730. // 提示
  731. return json_send(['code'=>'error','msg'=>'属性SKU匹配失败']);
  732. }
  733. $value['attr_ids'][] = $vv['id'];
  734. }
  735. }
  736. }
  737. // 转成好存储的数据
  738. $value['attr_ids'] = implode(',',$value['attr_ids']);
  739. // 转成好存储的数据
  740. $value['product_id']= $id;
  741. // 转成好存储的数据
  742. $value['sku_thumb'] = (string) $value['sku_thumb'];
  743. // 转成好存储的数据
  744. $value['stock_total']= $value['stock'];
  745. // 转成好存储的数据
  746. $value['insert_time']= time();
  747. // 转成好存储的数据
  748. $value['update_time']= time();
  749. // SKU结果
  750. $skuList[$attrNames] = $value;
  751. }
  752. // 循环城市范围
  753. foreach ($cityIds as $key => $value) {
  754. // 重组数据
  755. $cityIds[$key] = ['city_id'=>$value,'product_id'=>$id,'insert_time'=>time(),'update_time'=>time()];
  756. }
  757. // 写入城市范围
  758. $ProductCity->query()->insert($cityIds);
  759. // 返回结果
  760. $result = $ProductSkus->insert($skuList);
  761. // 提示新增失败
  762. if( !$result ) {
  763. // 回滚
  764. DB::rollBack();
  765. // 提示
  766. return json_send(['code'=>'error','msg'=>'SKU插入失败']);
  767. }
  768. // 更新内容
  769. $Model->updateDesc($id,$description);
  770. // 提交
  771. DB::commit();
  772. // 记录行为
  773. $this->addAdminHistory(admin('uid'),$Model->getTable(),$id,1,[],$data);
  774. // 告知结果
  775. return json_send(['code'=>'success','msg'=>'新增成功','action'=>'add']);
  776. } catch (\Throwable $th) {
  777. // 回滚
  778. DB::rollBack();
  779. // 提示
  780. return json_send(['code'=>'error','msg'=>'系统异常,写入失败','data'=>$th->getMessage()]);
  781. }
  782. }
  783. // 接收参数
  784. $id = request('id',0);
  785. // 查询数据
  786. $oldData = $Model->where(['id'=>$id])->first();
  787. // 如果是没有数据
  788. if( !$oldData ) return $this->error('查无数据');
  789. // 产品信息转格式
  790. $oldData = $oldData->toArray();
  791. $oldData['description'] = $Model->getDesc($id);
  792. $oldData['city_ids'] = $ProductCity->getListByProductId($id);
  793. $oldData['tag_scope'] = explode(',',$oldData['tag_scope']);
  794. $oldData['tag_exclude'] = explode(',',$oldData['tag_exclude']);
  795. $photoList = $ProductPhoto->getListByProductId($id);
  796. // 获取产品属性
  797. $attrList = $ProductAttr->getListByProductId($id);
  798. // 规格属性
  799. $specList = [];
  800. // 获取数据
  801. foreach ($attrList as $value) {
  802. $value['active'] = 0;
  803. if( !isset($specList[$value['spec_id']]) ) $specList[$value['spec_id']] = $ProductSpec->getOne($value['spec_id']);
  804. $specList[$value['spec_id']]['attr_list'][] = $value;
  805. }
  806. // 产品类型
  807. $skuList = $ProductSkus->getListByProductId($id);
  808. // 循环处理
  809. foreach ($skuList as $key => $value) {
  810. // 数据解析
  811. $value['attr_ids'] = explode(',',$value['attr_ids']);
  812. // 循环处理
  813. foreach ( $value['attr_ids'] as $k=>$attrId ) {
  814. // 获取ids对应的name
  815. $value['attr_ids'][$k] = $ProductAttr->getValueById($attrId,'name');
  816. }
  817. // 数据合并
  818. $value['attr_ids'] = implode(',',$value['attr_ids']);
  819. // 重组
  820. $skuList[$key] = $value;
  821. }
  822. // 标签列表
  823. $tagData = $WeiBanTags->query()->groupBy('group')->groupBy('name')->get(['group','name'])->toArray();
  824. // 标签列表
  825. $tagList = [];
  826. // 循环数据
  827. foreach ($tagData as $value) {
  828. $tagList[$value['group']][] = $value['name'];
  829. }
  830. // 获取类型数据
  831. $typeList = $ProductType->getList();
  832. $cityList = $City->getCityList();
  833. $businessList = $Business->getList();
  834. $producerList = $Producer->getList();
  835. // 分配数据
  836. $this->assign('tagList',$tagList);
  837. $this->assign('typeList',$typeList);
  838. $this->assign('cityList',$cityList);
  839. $this->assign('businessList',$businessList);
  840. $this->assign('producerList',$producerList);
  841. $this->assign('oldData',$oldData);
  842. $this->assign('photoList',$photoList);
  843. $this->assign('skuList',$skuList);
  844. $this->assign('specList',$specList);
  845. $this->assign('crumbs','复制');
  846. // 加载模板
  847. return $this->fetch();
  848. }
  849. /**
  850. * 状态
  851. *
  852. * */
  853. public function set_status( Request $request, Model $Model ){
  854. // 验证参数
  855. $request->scene('set_status')->validate();
  856. // 接收参数
  857. $id = request('id',0);
  858. $status = request('status',0);
  859. // 查询数据
  860. $result = $Model->edit($id,['status'=>$status]);
  861. // 提示新增失败
  862. if( !$result ) return json_send(['code'=>'error','msg'=>'设置失败']);
  863. // 记录行为
  864. $this->addAdminHistory(admin('uid'),$Model->getTable(),$id,2,[],['status'=>$status]);
  865. // 告知结果
  866. return json_send(['code'=>'success','msg'=>'设置成功','path'=>'']);
  867. }
  868. /**
  869. * 获取某个类目下的规格
  870. *
  871. */
  872. public function get_spec_html( ProductSpec $ProductSpec){
  873. // 接收参数
  874. $typeId = request('type_id',0);
  875. // 查询数据
  876. $specList = $ProductSpec->getList();
  877. // 循环处理
  878. foreach ($specList as $key => $value) {
  879. if( $value['type_id'] != $typeId ) unset($specList[$key]);
  880. }
  881. // 分配数据
  882. $this->assign('specList',$specList);
  883. // 加载模板
  884. return $this->fetch();
  885. }
  886. /**
  887. * 获取某个类目下的规格
  888. *
  889. */
  890. public function get_sku_html( ProductSpec $ProductSpec,ProductSkus $ProductSkus){
  891. // 接收参数
  892. $attr = request('attr',[]);
  893. $id = request('product_id',0);
  894. // 产品类型
  895. $oldSkus = $id ? $ProductSkus->getListByProductId($id) : [];
  896. // 获取规格属性
  897. $specAttr = $this->getSpecAttr($attr,$ProductSpec);
  898. // 组合SKU
  899. $brushList = [];
  900. // 循环规格属性
  901. foreach ($specAttr as $specId => $value) {
  902. // 获取SKU组合
  903. $brushList[$specId] = array_column($value['attr_list'],'name');
  904. }
  905. // sku列表
  906. $skuList = $this->brush([],$brushList);
  907. // 循环规格属性
  908. foreach ($skuList as $newKey => $new) {
  909. // 获取新数据
  910. $new = ['attr_names'=>$new,'price'=>0,'stock'=>0,'status'=>0,'sku_thumb'=>''];
  911. // 循环旧的sku
  912. foreach ($oldSkus as $old) {
  913. // 如果有相等的规格
  914. if( $old['attr_names'] == $new['attr_names']) {
  915. $new['sku_thumb']= $old['sku_thumb'];
  916. $new['price'] = $old['price'];
  917. $new['stock'] = $old['stock'];
  918. $new['status'] = $old['status'];
  919. }
  920. }
  921. $skuList[$newKey] = $new;
  922. }
  923. // 分配数据
  924. $this->assign('specAttr',$specAttr);
  925. $this->assign('skuList',$skuList);
  926. // 加载模板
  927. return $this->fetch();
  928. }
  929. /**
  930. * 获取规格属性
  931. * @param ProductSpec $ProductSpec
  932. *
  933. */
  934. private function getSpecAttr($attr,$ProductSpec,$onlyList=false){
  935. // 组合参数
  936. $specAttr = [];
  937. // 循环处理数据
  938. foreach ($attr as $specId => $value) {
  939. $specAttr[$specId]['spec_id'] = $specId;
  940. $specAttr[$specId]['spec_name'] = $ProductSpec->getOne($specId,'name');
  941. $specAttr[$specId]['attr_list'] = [];
  942. foreach ($value['name'] as $key => $name) {
  943. // 如果没有名称的,跳过
  944. if( !$name ) continue;
  945. // 结果
  946. $temp = ['id'=>0,'spec_id'=>$specId,'thumb'=>'','name'=>'','remark'=>''];
  947. // 获取名称
  948. $temp['name'] = str_ireplace(',','',$name);
  949. // 获取对应的备注信息
  950. if( !empty($value['remark'][$key]) ) $temp['remark'] = $value['remark'][$key];
  951. if( !empty($value['thumb'][$key]) ) $temp['thumb'] = $value['thumb'][$key];
  952. // 获取对应的数据
  953. $specAttr[$specId]['attr_list'][]= $temp;
  954. }
  955. // 没有属性的规格,移除
  956. if( !$specAttr[$specId]['attr_list'] ) unset($specAttr[$specId]);
  957. }
  958. // 仅获取属性列表
  959. if( $onlyList ) {
  960. // 属性列表
  961. $attrList = [];
  962. // 判断结果
  963. foreach ($specAttr as $value) {
  964. // 获取数据
  965. array_push($attrList,...$value['attr_list']);
  966. }
  967. // 返回结果
  968. $specAttr = $attrList;
  969. }
  970. // 返回结果
  971. return $specAttr;
  972. }
  973. /**
  974. * 组合SKU
  975. *
  976. */
  977. private function brush($res = [], $arr = []){
  978. // 获取第一个规格的属性
  979. if (empty($res)) $res = (array) array_shift($arr);
  980. // 如果只有一个规格,返回该规格的属性即可
  981. if (empty($arr)) return $res;
  982. // 接下来要参与计算的一组属性
  983. $current = array_shift($arr);
  984. // 计算的列表
  985. $last = [];
  986. // 循环上一次已经算出的集合
  987. foreach ($res as $attr) {
  988. foreach ($current as $col_val) {
  989. $last[] = $attr . ',' . $col_val;
  990. }
  991. }
  992. return $this->brush($last,$arr); # 递归处理, 直到$arr滚到最后一组属性
  993. }
  994. /**
  995. * 排序
  996. *
  997. * */
  998. public function set_sort( Request $request, Model $Model ){
  999. // 验证参数
  1000. $request->scene('set_sort')->validate();
  1001. // 接收参数
  1002. $id = request('id',0);
  1003. $sort = request('sort',0);
  1004. // 查询数据
  1005. $result = $Model->edit($id,['sort'=>$sort]);
  1006. // 提示新增失败
  1007. if( !$result ) return json_send(['code'=>'error','msg'=>'设置失败']);
  1008. // 记录行为
  1009. $this->addAdminHistory(admin('uid'),$Model->getTable(),$id,2,[],['sort'=>$sort]);
  1010. // 告知结果
  1011. return json_send(['code'=>'success','msg'=>'设置成功','path'=>'']);
  1012. }
  1013. }