chore: 添加静态资源文件和版本配置文件

添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材
添加系统版本配置文件(.version)和常量定义文件(.constant)
更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件
添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
2026-03-06 20:14:23 +08:00
commit c9a98f342c
4485 changed files with 490986 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\branch;
use app\dao\product\sku\StoreProductAttrValueDao;
use app\services\BaseServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\product\product\StoreProductStockRecordServices;
use crmeb\exceptions\AdminException;
use crmeb\traits\ServicesTrait;
/**
* Class StoreBranchProductAttrValueServices
* @package app\services\product\branch
* @mixin StoreProductAttrValueDao
*/
class StoreBranchProductAttrValueServices extends BaseServices
{
use ServicesTrait;
/**
* StoreBranchProductAttrValueServices constructor.
* @param StoreProductAttrValueDao $dao
*/
public function __construct(StoreProductAttrValueDao $dao)
{
$this->dao = $dao;
}
/**
* @param string $unique
* @param int $storeId
* @return int|mixed
*/
public function uniqueByStock(string $unique, int $storeId)
{
if (!$unique) return 0;
return $this->dao->uniqueByStock($unique, $storeId);
}
/**
* 更新
* @param int $id
* @param array $data
* @param int $store_id
*/
public function updataAll(int $id, array $data, int $store_id)
{
/** @var StoreBranchProductServices $productServices */
$productServices = app()->make(StoreBranchProductServices::class);
$where = [];
$where['product_id'] = $id;
$where['store_id'] = $store_id;
$where['type'] = 0;
$this->transaction(function () use ($id, $store_id, $where, $data, $productServices) {
$attrArr = [];
$stock = 0;
$this->dao->delete($where);
foreach ($data['attrs'] as $key => $item) {
$attrArr[$key]['product_id'] = $id;
$attrArr[$key]['store_id'] = $store_id;
$attrArr[$key]['unique'] = $item['unique'] ?? '';
$attrArr[$key]['stock'] = intval($item['stock']) ?? 0;
$attrArr[$key]['bar_code'] = $item['bar_code'] ?? 0;
$attrArr[$key]['type'] = 0;
$stock += (int)($item['stock'] ?? 0);
}
$res1 = $this->dao->saveAll($attrArr);
$productServices->saveStoreProduct($id, $store_id, $stock, $data);
$unique = array_column($data['attrs'], 'unique');
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$storeProductAttrValueServices->updateSumStock($unique);
//记录入出库
/** @var StoreProductStockRecordServices $storeProductStockRecordServces */
$storeProductStockRecordServces = app()->make(StoreProductStockRecordServices::class);
$storeProductStockRecordServces->saveRecord($id, $attrArr, 0, $store_id);
if (!$res1) {
throw new AdminException('添加失败!');
}
});
}
/**
* 获取门店商品规格信息
* @param int $id
* @param int $type
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStoreProductAttr(int $id, int $type = 0)
{
return $this->dao->getProductAttrValue(['product_id' => $id, 'type' => $type]);
}
}

View File

@@ -0,0 +1,217 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\branch;
use app\dao\product\product\StoreProductDao;
use app\services\BaseServices;
use app\services\order\StoreCartServices;
use app\services\product\product\StoreDescriptionServices;
use app\services\product\product\StoreProductRelationServices;
use app\services\product\product\StoreProductReplyCommentServices;
use app\services\product\product\StoreProductReplyServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrResultServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\sku\StoreProductAttrValueServices;
use crmeb\exceptions\AdminException;
use crmeb\traits\ServicesTrait;
use think\exception\ValidateException;
/**
* Class StoreBranchProductServices
* @package app\services\product\branch
* @mixin StoreProductDao
*/
class StoreBranchProductServices extends BaseServices
{
use ServicesTrait;
/**
* StoreBranchProductServices constructor.
* @param StoreProductDao $dao
*/
public function __construct(StoreProductDao $dao)
{
$this->dao = $dao;
}
/**
* 保存或者修改门店数据
* @param int $id
* @param int $storeId
* @param int $stock
* @param array $data
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveStoreProduct(int $id, int $storeId, int $stock, array $data = [])
{
/** @var StoreProductServices $service */
$service = app()->make(StoreProductServices::class);
$productData = $service->get($id, ['store_name', 'image', 'sort', 'store_info', 'keyword', 'bar_code', 'cate_id', 'is_show']);
if (!$productData) {
throw new ValidateException('商品不穿在');
}
$productData = $productData->toArray();
$productInfo = $this->dao->get(['product_id' => $id, 'store_id' => $storeId]);
if ($productInfo) {
$productInfo->label_id = isset($data['label_id']) ? implode(',', $data['label_id']) : '';
$productInfo->is_show = $data['is_show'] ?? 1;
$productInfo->stock = $stock;
$productInfo->image = $productData['image'];
$productInfo->sort = $productData['sort'];
$productInfo->store_name = $productData['store_name'];
$productInfo->store_info = $productData['store_info'];
$productInfo->keyword = $productData['keyword'];
$productInfo->bar_code = $productData['bar_code'];
$productInfo->cate_id = $productData['cate_id'];
$productInfo->save();
} else {
$product = [];
$product['product_id'] = $id;
$product['label_id'] = isset($data['label_id']) ? implode(',', $data['label_id']) : '';
$product['is_show'] = $data['is_show'] ?? 1;
$product['store_id'] = $storeId;
$product['stock'] = $stock;
$product['image'] = $productData['image'];
$product['sort'] = $productData['sort'];
$product['store_name'] = $productData['store_name'];
$product['store_info'] = $productData['store_info'];
$product['keyword'] = $productData['keyword'];
$product['bar_code'] = $productData['bar_code'];
$product['cate_id'] = $productData['cate_id'];
$product['add_time'] = time();
$this->dao->save($product);
}
return true;
}
/**
* 获取商品库存
* @param int $productId
* @param string $uniqueId
* @return int|mixed
*/
public function getProductStock(int $productId, int $storeId, string $uniqueId = '')
{
/** @var StoreProductAttrValueServices $StoreProductAttrValue */
$StoreProductAttrValue = app()->make(StoreProductAttrValueServices::class);
return $uniqueId == '' ?
$this->dao->value(['product_id' => $productId], 'stock') ?: 0
: $StoreProductAttrValue->uniqueByStock($uniqueId);
}
/**
* 上下架
* @param int $store_id
* @param int $id
* @param int $is_show
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setShow(int $store_id, int $id, int $is_show)
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('操作失败!');
}
//平台统一商品
if ($info['pid']) {
$productInfo = $this->dao->get($info['pid']);
if ($is_show && !$productInfo['is_show']) {
throw new AdminException('平台该商品暂未上架!');
}
}
/** @var StoreCartServices $cartService */
$cartService = app()->make(StoreCartServices::class);
$cartService->batchUpdate([$id], ['status' => $is_show], 'product_id');
$update = ['is_show' => $is_show];
if ($is_show) {//手动上架 清空定时下架状态
if ($info['is_verify'] != 1) {//验证商品是否审核通过
throw new AdminException('该商品暂未审核通过');
}
if($info['price'] <= 0) {
throw new AdminException('未设置售价,请联系平台处理');
}
$update['auto_off_time'] = 0;
}
$res = $this->update($info['id'], $update);
/** @var StoreProductRelationServices $storeProductRelationServices */
$storeProductRelationServices = app()->make(StoreProductRelationServices::class);
$storeProductRelationServices->setShow([$id], (int)$is_show);
if (!$res) throw new AdminException('操作失败!');
}
/**
* 删除门店、供应商同步删除商品
* @param array $where
* @param int $type
* @param int $relation_id
* @return bool
*/
public function deleteProducts(array $where = [], int $type = 0, int $relation_id = 0)
{
$where['type'] = $type;
$where['relation_id'] = $relation_id;
$productIds = $this->dao->getColumn($where, 'id');
if ($productIds) {
/** @var StoreProductAttrServices $productAttrServices */
$productAttrServices = app()->make(StoreProductAttrServices::class);
/** @var StoreProductAttrResultServices $productAttrResultServices */
$productAttrResultServices = app()->make(StoreProductAttrResultServices::class);
/** @var StoreProductAttrValueServices $productAttrValueServices */
$productAttrValueServices = app()->make(StoreProductAttrValueServices::class);
/** @var StoreDescriptionServices $productDescriptionServices */
$productDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductRelationServices $productRelationServices */
$productRelationServices = app()->make(StoreProductRelationServices::class);
/** @var StoreProductReplyServices $productReplyServices */
$productReplyServices = app()->make(StoreProductReplyServices::class);
/** @var StoreProductReplyCommentServices $productReplyCommentServices */
$productReplyCommentServices = app()->make(StoreProductReplyCommentServices::class);
$idsArr = array_chunk($productIds, 100);
foreach ($idsArr as $ids) {
$productAttrServices->delete(['product_id' => $ids, 'type' => 0]);
$productAttrResultServices->delete(['product_id' => $ids, 'type' => 0]);
$productAttrValueServices->delete(['product_id' => $ids, 'type' => 0]);
$productDescriptionServices->delete(['product_id' => $ids, 'type' => 0]);
$productRelationServices->delete(['product_id' => $ids]);
$this->dao->delete(['id' => $ids]);
$replyIds = $productReplyServices->getColumn([['product_id', 'IN', $ids]], 'id');
$replyIdsArr = array_chunk($replyIds, 100);
foreach ($replyIdsArr as $rids) {
$productReplyCommentServices->delete(['reply_id' => $rids]);
$productReplyServices->delete(['id' => $rids]);
}
event('product.delete', [$ids]);
}
$this->dao->cacheTag()->clear();
$productAttrServices->cacheTag()->clear();
}
return true;
}
}

View File

@@ -0,0 +1,278 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\brand;
use app\dao\product\brand\StoreBrandDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
/**
* 商品品牌
* Class StoreBrandServices
* @package app\services\product\brand
* @mixin StoreBrandDao
*/
class StoreBrandServices extends BaseServices
{
/**
* @var StoreBrandDao
*/
#[Inject]
protected StoreBrandDao $dao;
/**
* 获取缓存
* @param int $id
* @return array|false|mixed|string|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/3
*/
public function getCacheBrandInfo(int $id)
{
$storeBrandInfo = $this->dao->cacheRemember($id, function () use ($id) {
$storeBrandInfo = $this->dao->get(['id' => $id, 'is_show' => 1, 'is_del' => 0]);
if ($storeBrandInfo) {
$storeBrandInfo = $storeBrandInfo->toArray();
}
return $storeBrandInfo ?: [];
});
return $storeBrandInfo;
}
/**
* 获取品牌列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getTreeList($where)
{
$list = $this->dao->getList($where, ['product']);
if (!empty($list) && $where['brand_name'] !== '') {
$fid = [];
foreach ($list as $item) {
$fid = array_merge($fid, explode(',', $item['fid']));
}
$pids = array_unique(array_filter($fid));
$parentList = $this->dao->getList(['id' => $pids], ['product']);
$list = array_merge($list, $parentList);
foreach ($list as $key => $item) {
$arr = $list[$key];
unset($list[$key]);
if (!in_array($arr, $list)) {
$list[] = $arr;
}
}
}
foreach ($list as &$item) {
$item['brand_num'] = $item['product'][0]['brand_num'] ?? 0;
$item['fid'] = $item['fid'] ? array_map('intval', explode(',', $item['fid'])) : [];
$item['type'] = count($item['fid']) < 2 ? 1 : 0;
//添加子品牌fid
if ($item['type'] == 1) {
$item['fid_son'] = $item['fid'];
$item['fid_son'][] = $item['id'];
}
unset($item['product']);
}
$list = get_tree_children($list);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取品牌列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList($where)
{
$list = $this->dao->getList($where, ['product', 'children']);
$count = $this->dao->count($where);
if ($list) {
foreach ($list as &$item) {
$item['brand_num'] = $item['product'][0]['brand_num'] ?? 0;
$item['fid'] = $item['fid'] ? array_map('intval', explode(',', $item['fid'])) : [];
$item['type'] = count($item['fid']) < 2 ? 1 : 0;
//添加子品牌fid
if ($item['type'] == 1) {
$item['fid_son'] = $item['fid'];
$item['fid_son'][] = $item['id'];
}
if (isset($item['children']) && $item['children']) {
$item['children'] = [];
$item['loading'] = false;
$item['_loading'] = false;
} else {
unset($item['children']);
}
unset($item['product']);
}
}
return compact('list', 'count');
}
/**
* 获取品牌cascader
* @param string $show
* @param int $type
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function cascaderList($type = 1)
{
$where = [];
if ($type == 1) {
$top = true;
} else {
$top = false;
}
$menus = [];
$where['is_del'] = 0;
$where['is_show'] = 1;
$list = get_tree_children($this->dao->getList($where, [], ['id as value', 'brand_name as label', 'pid']), 'children', 'value');
if ($top) {
$menus = [['value' => 0, 'label' => '顶级品牌']];
foreach ($list as &$item) {
if (isset($item['children']) && $item['children']) {
foreach ($item['children'] as &$val) {
if (isset($val['children']) && $val['children']) {
unset($val['children']);
}
}
}
}
}
$menus = array_merge($menus, $list);
return $menus;
}
/**
* 设置品牌状态
* @param $id
* @param $is_show
*/
public function setShow(int $id, int $is_show)
{
$res = $this->dao->update($id, ['is_show' => $is_show]);
// $res = $res && $this->dao->update($id, ['is_show' => $is_show], 'pid');
if (!$res) {
throw new AdminException('设置失败');
}
//设置缓存
if (!$is_show) {
$this->cacheDelById($id);
return;
}
$branInfo = $this->dao->cacheInfoById($id);
if ($branInfo) {
$branInfo['is_show'] = 1;
} else {
$branInfo = $this->dao->get($id);
if (!$branInfo) {
return;
}
$branInfo = $branInfo->toArray();
}
$this->dao->cacheUpdate($branInfo);
}
/**
* 保存新增数据
* @param $data
*/
public function createData($data)
{
$data['pid'] = end($data['fid']);
if ($this->dao->getOne(['brand_name' => $data['brand_name'], 'pid' => $data['pid']])) {
throw new AdminException('该品牌已经存在');
}
$data['fid'] = implode(',', $data['fid']);
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
//更新缓存
$data['id'] = $res->id;
$this->cacheUpdate($data);
}
/**
* 保存修改数据
* @param $id
* @param $data
*/
public function editData($id, $data)
{
$cate = $this->dao->getOne(['id' => $id]);
if (!$cate) {
throw new AdminException('该品牌不存在');
}
$data['pid'] = end($data['fid']) ?? 0;
$data['fid'] = implode(',', $data['fid']);
$cate = $this->dao->getOne(['pid' => $data['pid'], 'brand_name' => $data['brand_name']]);
if ($cate && $cate['id'] != $id) {
throw new AdminException('该品牌已经存在');
}
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
//更新缓存
$data['id'] = $id;
$this->cacheUpdate($data);
}
/**
* 删除数据
* @param int $id
*/
public function del(int $id)
{
if ($this->dao->count(['pid' => $id])) {
throw new AdminException('请先删除子品牌!');
}
$res = $this->dao->delete($id);
if (!$res) throw new AdminException('删除失败');
//更新缓存
$this->cacheDelById($id);
}
/**
* 获取品牌id
* @param $product_brand
* @return array
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/5/28
*/
public function getBrandId($product_brand)
{
$brand_id = $this->dao->value(['brand_name' => $product_brand], 'id');
if (!$brand_id) {
$brand_id = $this->dao->save(['brand_name' => $product_brand, 'add_time' => time()])['id'];
}
return [$brand_id];
}
}

View File

@@ -0,0 +1,589 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\category;
use app\dao\product\category\StoreProductCategoryDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use crmeb\utils\Arr;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* Class StoreCategoryService
* @package app\services\product\product
* @mixin StoreProductCategoryDao
*/
class StoreProductCategoryServices extends BaseServices
{
/**
* 查询字段定义
* @var array|string[]
*/
public array $cateField = [0 => 'cid', 1 => 'sid', 2 => 'tid'];
/**
* @var StoreProductCategoryDao
*/
#[Inject]
protected StoreProductCategoryDao $dao;
/**
* 获取分类列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getTreeList($where)
{
$list = $this->dao->getTierList($where);
if (!empty($list) && ($where['cate_name'] !== '' || $where['pid'] !== '')) {
$pids = Arr::getUniqueKey($list, 'pid');
$parentList = $this->dao->getTierList(['id' => $pids]);
$list = array_merge($list, $parentList);
foreach ($list as $key => $item) {
$arr = $list[$key];
unset($list[$key]);
if (!in_array($arr, $list)) {
$list[] = $arr;
}
}
}
$list = get_tree_children($list);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取分类列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList($where, string $field = '*', array $with = ['children'])
{
$list = $this->dao->getList($where, $field, 0, 0, $with);
$count = $this->dao->count($where);
if ($list) {
foreach ($list as $key => &$item) {
if (isset($item['children']) && $item['children']) {
$item['children'] = [];
$item['loading'] = false;
$item['_loading'] = false;
} else {
unset($item['children']);
}
if (isset($item['AdminChildren']) && $item['AdminChildren']) {
$item['children'] = [];
$item['loading'] = false;
$item['_loading'] = false;
unset($item['AdminChildren']);
}
}
}
return compact('list', 'count');
}
/**
* 商品分类搜索下拉
* @param string $show
* @param string $type
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getTierList($show = '', $type = 0)
{
$where = [];
if ($show !== '') $where['is_show'] = $show;
if (!$type) $where['pid'] = 0;
return sort_list_tier($this->dao->getTierList($where));
}
/**
* 获取分类cascader
* @param int $type
* @param int $relation_id
* @param int $level
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function cascaderList(int $type = 0, int $relation_id = 0, int $level = -1)
{
$where = ['is_show' => 1, 'type' => $type, 'relation_id' => $relation_id, 'level' => $level];
$data = get_tree_children($this->dao->getTierList($where, ['id as value', 'cate_name as label', 'pid']), 'children', 'value');
// foreach ($data as &$item) {
// if (!isset($item['children'])) {
// $item['disabled'] = true;
// }
// }
return $data;
}
/**
* 设置分类状态
* @param $id
* @param $is_show
*/
public function setShow(int $id, int $is_show)
{
$res = $this->dao->update($id, ['is_show' => $is_show]);
$res = $res && $this->dao->update($id, ['is_show' => $is_show], 'pid');
if (!$res) {
throw new AdminException('设置失败');
} else {
$this->cacheTag()->clear();
$this->cacheTag()->set('category_version', uniqid());
$this->getCategory();
}
}
/**
* 创建新增表单
* @param int $type
* @return mixed
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm(int $type = 0)
{
return create_form('添加分类', $this->form([], $type), Url::buildUrl('/product/category'), 'POST');
}
/**
* 创建编辑表单
* @param int $id
* @param int $type
* @return mixed
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function editForm(int $id, int $type = 0)
{
$info = $this->dao->get($id);
return create_form('编辑分类', $this->form($info, $type), $this->url('/product/category/' . $id), 'PUT');
}
/**
* 生成表单参数
* @param array $info
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function form($info = [], int $type = 0)
{
$menus = $this->cascaderList(0, 0, 1);
array_unshift($menus, ['label' => '顶级分类', 'value' => 0]);
$value = [0];
if (isset($info['pid']) && $info['pid']) {
$value = [$info['pid']];
$pid = $this->dao->value(['id' => $info['pid']], 'pid');
if ($pid) {
array_unshift($value, $pid);
}
}
$f[] = Form::cascader('pid', '父级:', $value)->data($menus)->changeOnSelect(true)->filterable(1);
$url = $type ? 'store/widget.images/index' : 'admin/widget.images/index';
$f[] = Form::input('cate_name', '分类名称:', $info['cate_name'] ?? '')->maxlength(30)->required();
$f[] = Form::frameImage('pic', '移动端图(180*180):', Url::buildUrl($url, array('fodder' => 'pic')), $info['pic'] ?? '')->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true]);
$f[] = Form::frameImage('big_pic', 'PC大图(468*340):', Url::buildUrl($url, array('fodder' => 'big_pic')), $info['big_pic'] ?? '')->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true]);
$f[] = Form::number('sort', '排序:', (int)($info['sort'] ?? 0))->min(0)->min(0);
$f[] = Form::radio('is_show', '状态:', $info['is_show'] ?? 1)->options([['label' => '显示', 'value' => 1], ['label' => '隐藏', 'value' => 0]]);
return $f;
}
/**
* 获取一级分类组合数据
* @return array[]
*/
public function menus()
{
$list = $this->dao->getMenus(['pid' => 0]);
$menus = [['value' => 0, 'label' => '顶级菜单']];
foreach ($list as $menu) {
$menus[] = ['value' => $menu['id'], 'label' => $menu['cate_name']];
}
return $menus;
}
/**
* 获取分类的层级、path
* @param int $pid
* @param $pInfo
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLevelPath(int $pid, $pInfo = [])
{
if (!$pid) {
return [0, ''];
}
if (!$pInfo) {
$pInfo = $this->dao->get($pid, ['id', 'level', 'pid', 'path']);
}
if (!$pInfo) {
return [0, ''];
}
$path = explode(',', $pInfo['path']);
$path[] = $pid;
return [$pInfo['level'] + 1, trim(implode(',', $path), ',')];
}
/**
* 保存新增数据
* @param $data
*/
public function createData($data)
{
$pid = $data['pid'] ?? 0;
if ($this->dao->getOne(['pid' => $pid, 'cate_name' => $data['cate_name']])) {
throw new AdminException('该分类已经存在');
}
[$level, $path] = $this->getLevelPath($pid);
if ($level > 3) {
throw new AdminException('不可添加更低阶分类');
}
$data['level'] = $level;
$data['path'] = $path;
$data['add_time'] = time();
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加失败');
$this->cacheTag()->clear();
$this->cacheTag()->set('category_version', uniqid());
$this->getCategory();
return $res;
}
/**
* 保存修改数据
* @param $id
* @param $data
*/
public function editData($id, $data)
{
$pid = $data['pid'] ?? 0;
$cate = $this->dao->getOne(['id' => $id]);
if (!$cate) {
throw new AdminException('该分类不存在');
}
if ($data['pid'] == $id) {
throw new AdminException('上级分类不能是自己');
}
if ($data['pid']) {
$pcate = $this->dao->getOne(['id' => $pid]);
if (!$pcate) {
throw new AdminException('上级分类不存在');
}
if ($pcate['pid'] == $id) {
throw new AdminException('不能互为上级');
}
if ($this->dao->count(['pid' => $id]) && $pcate['pid']) {//超过三层层级
throw new AdminException('最大层级为三级');
}
$cate = $this->dao->getOne(['pid' => $data['pid'], 'cate_name' => $data['cate_name']]);
if ($cate && $cate['id'] != $id) {
throw new AdminException('该分类已经存在');
}
}
[$level, $path] = $this->getLevelPath($pid);
if ($level > 2) {
throw new AdminException('不可添加更低阶分类');
}
$data['level'] = $level;
$data['path'] = $path;
$res = $this->dao->update($id, $data);
if (!$res) throw new AdminException('修改失败');
$this->cacheTag()->clear();
$this->cacheTag()->set('category_version', uniqid());
$this->getCategory();
}
/**
* 删除数据
* @param int $id
*/
public function del(int $id)
{
if ($this->dao->count(['pid' => $id])) {
throw new AdminException('请先删除子分类!');
}
$res = $this->dao->delete($id);
if (!$res) throw new AdminException('删除失败');
$this->cacheTag()->clear();
$this->cacheTag()->set('category_version', uniqid());
$this->getCategory();
}
/**
* 获取分类版本
* @return mixed
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/11
*/
public function getCategoryVersion()
{
return $this->dao->cacheTag()->remember('category_version', function () {
return uniqid();
});
}
/**
* 获取指定id下的分类,一=以数组形式返回
* @param string $cateIds
* @return array
*/
public function getCateArray(string $cateIds)
{
return $this->dao->getCateArray($cateIds);
}
/**
* 前台分类列表
* @return bool|mixed|null
*/
public function getCategory(array $where = [])
{
[$page, $limit] = $this->getPageValue();
if ($limit) {
return $this->dao->cacheTag()->remember(md5(json_encode($where + ['limit' => $limit])), function () use ($where, $limit) {
return $this->dao->getALlByIndex($where, 'id,cate_name,pid,pic', $limit);
});
} else {
$name = 'CATEGORY_All_';
$category = $this->dao->cacheTag()->remember($name, function () {
$category = $this->dao->getTierList(['is_show' => 1], ['id', 'pid', 'cate_name', 'pic', 'big_pic']);
return get_tree_children($category);
});
foreach ($category as &$item) {
if (!isset($item['children'])) $item['children'] = [];
}
return $category;
}
}
/**
* 前台分类列表
* @return bool|mixed|null
*/
public function getLevelCategory(int $id)
{
$info = $this->dao->get($id, ['id', 'pid']);
$result = [];
if ($info) {
$where['pid'] = $info['pid'] ?? 0;
$result = $this->dao->cacheTag()->remember(md5('level_category_' . $info['pid']), function () use ($where) {
return $this->dao->getALlByIndex($where);
});
}
return $result;
}
/**
* 获取
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getRankCategory()
{
return $this->dao->getList(['is_show' => 1, 'level' => 0], 'id,pid,cate_name,pic,big_pic', 0, 0, []);
}
/**
* 获取分类列表
* @param $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getOutList()
{
$list = $this->dao->getTierList();
if (!empty($list)) {
$pids = Arr::getUniqueKey($list, 'pid');
$parentList = $this->dao->getTierList(['id' => $pids]);
$list = array_merge($list, $parentList);
foreach ($list as $key => $item) {
$arr = $list[$key];
unset($list[$key]);
if (!in_array($arr, $list)) {
$list[] = $arr;
}
}
}
$list = get_tree_children($list);
return $list;
}
/**
* 获取一级分类
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getOneCategory(array $where = [])
{
return $this->dao->getTierList($where + ['pid' => 0, 'is_show' => 1]);
}
/**
* 分类列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCategoryList(array $where)
{
return $this->dao->getALlByIndex($where, 'id, cate_name, pid, pic, big_pic, sort, is_show, add_time');
}
/**
* 分类详情
* @param int $id
* @return array|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id, ['id', 'cate_name', 'pid', 'pic', 'big_pic', 'sort', 'is_show']);
if ($info) {
$info = $info->toArray();
}
return $info;
}
/**
* 根据IDS获取分类名称
* @param array $cateList
* @param array $cate_ids
* @return array
*/
public function getCateName(array $cate_ids, array $cateList = [])
{
$cate_name = [];
if ($cate_ids) {
if (!$cateList) {
$cateName = $this->dao->getCateParentAndChildName(implode(',', array_unique($cate_ids)));
} else {
$cateName = array_filter($cateList, function ($val) use ($cate_ids) {
if (in_array($val['id'], $cate_ids)) {
return $val;
}
});
}
foreach ($cateName as $k => $v) {
$str = '';
if (isset($v['one']) && $v['one']) {
$str = $v['one'] . '/';
}
if (isset($v['two']) && $v['two']) {
$str .= $v['two'];
}
if (isset($v['three']) && $v['three']) {
$str .= $v['three'];
}
if ($str) $cate_name[] = $str;
}
}
return $cate_name;
}
/**
* 处理获取分类搜索条件
* @param int $id
* @param bool $isWhere
* @return int|int[]|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCateLevel(int $id, bool $isWhere = true)
{
$category = $this->dao->get($id, ['id', 'level']);
if (!$category) {
$level = 0;
} else {
$level = $category['level'];
}
if ($isWhere) {
$levelArr = [0 => 'cid', '1' => 'sid', 2 => 'tid'];
return [$levelArr[$level] ?? 'cid' => $id];
} else {
return $level;
}
}
/**
* 获取分类ID
* @param string $cate_name_one
* @param string $cate_name_two
* @param string $cate_name_three
* @return array
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/5/28
*/
public function getCateId($cate_name_one = '', $cate_name_two = '', $cate_name_three = '')
{
if ($cate_name_one != '') {
$cate_name_one = explode(',', $cate_name_one)[0];
$cate_id_one = $this->dao->value(['cate_name' => $cate_name_one], 'id');
if (!$cate_id_one) {
$cate_id_one = $this->dao->save(['cate_name' => $cate_name_one, 'add_time' => time()])['id'];
}
if ($cate_name_two != '') {
$cate_name_two = explode(',', $cate_name_two)[0];
$cate_id_two = $this->dao->value(['cate_name' => $cate_name_two, 'pid' => $cate_id_one], 'id');
if (!$cate_id_two) {
$cate_id_two = $this->dao->save(['cate_name' => $cate_name_two, 'pid' => $cate_id_one, 'add_time' => time()])['id'];
}
if ($cate_name_three != '') {
$cate_name_three = explode(',', $cate_name_three)[0];
$cate_id_three = $this->dao->value(['cate_name' => $cate_name_three, 'pid' => $cate_id_two], 'id');
if (!$cate_id_three) {
$cate_id_three = $this->dao->save(['cate_name' => $cate_name_three, 'pid' => $cate_id_two, 'add_time' => time()])['id'];
}
return [$cate_id_one, $cate_id_two, $cate_id_three];
}
return [$cate_id_one, $cate_id_two];
}
return [$cate_id_one];
}
return [];
}
}

View File

@@ -0,0 +1,152 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\ensure;
use app\dao\product\ensure\StoreProductEnsureDao;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use crmeb\services\FormBuilder as Form;
use FormBuilder\Factory\Iview;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 商品保障服务
* Class StoreProductEnsureServices
* @package app\services\product\ensure
* @mixin StoreProductEnsureDao
*/
class StoreProductEnsureServices extends BaseServices
{
/**
* @var StoreProductEnsureDao
*/
#[Inject]
protected StoreProductEnsureDao $dao;
/**
* 获取所有保障服务
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllEnsure(int $type = 0, int $relation_id = 0)
{
// $where['relation_id'] = $relation_id;
// $where['type'] = $type;
$where['status'] = 1;
return $this->dao->getList($where, 'id,name');
}
/**
* 获取缓存内的数据
* @param array $ids
* @param array|null $field
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/14
*/
public function getEnsurCache(array $ids, array $field = null)
{
if (app()->config->get('cache.is_data')) {
$list = $this->dao->cacheInfoByIds($ids);
} else {
$list = null;
}
if (!$list) {
$list = $this->dao->getList(['ids' => $ids, 'status' => 1]);
foreach ($list as $item) {
$this->dao->cacheUpdate($item);
}
}
if ($field && $list) {
$newList = [];
foreach ($list as $item) {
$data = [];
foreach ($field as $k) {
$data[$k] = $item[$k] ?? null;
}
$newList[] = $data;
}
$list = $newList;
}
return $list;
}
/**
* 获取保障服务列表(带标签)
* @param array $where
* @return array
*/
public function getEnsureList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
if ($list) {
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
foreach ($list as &$item) {
$item['product_count'] = $storeProductServices->getUseEnsureCount((int)$item['id']);
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 创建新增表单
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm()
{
return create_form('添加保障服务', $this->form(), Url::buildUrl('/product/ensure'), 'POST');
}
/**
* 创建编辑表单
* @param $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function editForm(int $id)
{
$info = $this->dao->get($id);
return create_form('编辑保障服务', $this->form($info), $this->url('/product/ensure/' . $id), 'PUT');
}
/**
* 生成表单参数
* @param array $info
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function form($info = [])
{
$f[] = Form::input('name', '保障服务条款:', $info['name'] ?? '')->maxlength(100)->required();
$f[] = Form::textarea('desc', '内容描述:', $info['desc'] ?? '')->required();
$f[] = Form::frameImage('image', '图标(100*100):', Url::buildUrl('admin/widget.images/index', array('fodder' => 'image')), $info['image'] ?? '')->icon('ios-add')->width('960px')->appendValidate(Iview::validateStr()->message('请选择图标(建议尺寸:100px*100px)')->required())->height('505px')->modal(['footer-hide' => true]);
$f[] = Form::number('sort', '排序:', (int)($info['sort'] ?? 0))->min(0)->min(0);
return $f;
}
}

View File

@@ -0,0 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\label;
use app\dao\product\label\StoreProductLabelAuxiliaryDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 标签辅助表
* Class StoreProductLabelAuxiliaryServices
* @package app\services\product\label
* @mixin StoreProductLabelAuxiliaryDao
*/
class StoreProductLabelAuxiliaryServices extends BaseServices
{
/**
* @var StoreProductLabelAuxiliaryDao
*/
#[Inject]
protected StoreProductLabelAuxiliaryDao $dao;
/**
* 关联标签
* @param int $productId
* @param array $labelIds
* @return bool
*/
public function saveLabelRelation(int $productId, array $labelIds)
{
$data = [];
foreach ($labelIds as $labelId) {
$data[] = [
'product_id' => $productId,
'label_id' => $labelId
];
}
$this->dao->delete(['product_id' => $productId]);
if ($labelIds) {
$this->dao->saveAll($data);
}
return true;
}
}

View File

@@ -0,0 +1,110 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\label;
use app\dao\other\CategoryDao;
use app\services\BaseServices;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 商品标签分类
* Class StoreProductLabelCateServices
* @package app\services\product\brand
* @mixin CategoryDao
*/
class StoreProductLabelCateServices extends BaseServices
{
/**
* 在分类库中2
*/
const GROUP = 2;
/**
* @var CategoryDao
*/
#[Inject]
protected CategoryDao $dao;
/**
* 获取标签组列表(带标签)
* @param array $where
* @return array
*/
public function getProductLabelCateList(array $where)
{
[$page, $limit] = $this->getPageValue();
$count = $this->dao->count($where);
$list = $this->dao->getCateList($where, $page, $limit, ['*'], ['productLabel']);
if ($list) {
foreach ($list as &$item) {
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
}
return compact('list', 'count');
}
/**
* 获取所有的商品标签分类
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllProductLabelCate(int $type = 0, int $relation_id = 0)
{
$where = [
'type' => $type,
'relation_id' => $relation_id,
'group' => self::GROUP
];
return $this->dao->getAll($where);
}
/**
* 创建新增表单
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm()
{
return create_form('添加标签组', $this->form(), Url::buildUrl('/product/label_cate'), 'POST');
}
/**
* 创建编辑表单
* @param $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function editForm(int $id)
{
$info = $this->dao->get($id);
return create_form('编辑标签组', $this->form($info), $this->url('/product/label_cate/' . $id), 'PUT');
}
/**
* 生成表单参数
* @param array $info
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function form($info = [])
{
$f[] = Form::input('name', '标签组名称:', $info['name'] ?? '')->maxlength(30)->required();
$f[] = Form::number('sort', '排序:', (int)($info['sort'] ?? 0))->min(0)->min(0);
return $f;
}
}

View File

@@ -0,0 +1,172 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\label;
use app\dao\product\label\StoreProductLabelDao;
use app\services\BaseServices;
use crmeb\services\FormBuilder;
use think\annotation\Inject;
use think\facade\Route as Url;
/**
* 商品标签
* Class StoreProductLabelServices
* @package app\services\product\label
* @mixin StoreProductLabelDao
*/
class StoreProductLabelServices extends BaseServices
{
/**
* @var StoreProductLabelDao
*/
#[Inject]
protected StoreProductLabelDao $dao;
/**
* 获取列表
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取所有商品标签
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllLabel(int $type = 0, int $relation_id = 0)
{
$where['relation_id'] = $relation_id;
$where['type'] = $type;
return $this->dao->getList($where, 'id,label_name');
}
/**
* 获取商品标签 树形结构
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductLabelTreeList(int $type = 0, int $relation_id = 0)
{
/** @var StoreProductLabelCateServices $productLabelCateServices */
$productLabelCateServices = app()->make(StoreProductLabelCateServices::class);
$cate = $productLabelCateServices->getAllProductLabelCate($type, $relation_id);
$data = [];
$label = [];
$where = [];
if ($cate) {
foreach ($cate as $value) {
$data[] = [
'id' => $value['id'] ?? 0,
'value' => $value['id'] ?? 0,
'label_cate' => 0,
'label_name' => $value['name'] ?? '',
'label' => $value['name'] ?? '',
'relation_id' => $value['relation_id'] ?? 0,
'type' => $value['type'] ?? 0,
];
}
$label = $this->dao->getList($where);
if ($label) {
foreach ($label as &$item) {
$item['label'] = $item['label_name'];
$item['value'] = $item['id'];
}
}
}
return $this->get_tree_children($data, $label);
}
/**
* @param array $ids
* @return array|mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/1
*/
public function getLabelCache(array $ids, array $field = null, bool $isShow = true)
{
if (app()->config->get('cache.is_data')) {
$list = $this->dao->cacheInfoByIds($ids);
} else {
$list = null;
}
if (!$list) {
$list = $this->dao->getList(['ids' => $ids, 'status' => 1]);
foreach ($list as $item) {
$this->dao->cacheUpdate($item);
}
}
if ($field && $list) {
$newList = [];
array_multisort(array_column($list, 'sort'), SORT_DESC, array_column($list, 'id'), SORT_ASC, $list);
foreach ($list as $item) {
$data = [];
//标签不在移动端展示
if ($isShow && isset($item['is_show']) && !$item['is_show']) {
continue;
}
foreach ($field as $k) {
$data[$k] = $item[$k] ?? null;
}
$newList[] = $data;
}
$list = $newList;
}
return $list;
}
/**
* 获取商品标签表单
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getLabelForm()
{
/** @var StoreProductLabelCateServices $service */
$service = app()->make(StoreProductLabelCateServices::class);
$options = $service->getAllProductLabelCate();
$data = [];
foreach ($options as $option) {
$data[] = ['label' => $option['name'], 'value' => $option['id']];
}
$rule = [
FormBuilder::select('label_cate', '标签分组')->options($data),
FormBuilder::input('label_name', '标签名称')->maxlength(20),
];
return create_form('添加商品标签', $rule, Url::buildUrl('/product/label/0'), 'POST');
}
}

View File

@@ -0,0 +1,776 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\product\product;
use app\services\BaseServices;
use app\services\product\shipping\ShippingTemplatesServices;
use app\services\product\sku\StoreProductAttrServices;
use app\services\product\category\StoreProductCategoryServices;
use app\services\serve\ServeServices;
use app\services\system\attachment\SystemAttachmentCategoryServices;
use app\services\system\attachment\SystemAttachmentServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CopyProductService;
use crmeb\services\UploadService;
use think\exception\ValidateException;
/**
*
* Class CopyTaobaoServices
* @package app\services\product\product
*/
class CopyTaobaoServices extends BaseServices
{
/**
* @var bool
*/
protected bool $errorInfo = true;
/**
* @var string
*/
protected string $AttachmentCategoryName = '远程下载';
/**
* @var string[]
*/
protected array $host = ['taobao', 'tmall', 'jd', 'pinduoduo', 'suning', 'yangkeduo', '1688'];
/**
* @param $type
* @param $id
* @param $shopid
* @param $url
* @return array
*/
public function copyProduct($type, $id, $shopid, $url)
{
switch ((int)sys_config('system_product_copy_type')) {
case 1://平台
/** @var ServeServices $services */
$services = app()->make(ServeServices::class);
$resultData = $services->copy()->goods($url);
if (isset($resultData['description_image']) && is_string($resultData['description_image'])) {
$resultData['description_image'] = json_decode($resultData['description_image'], true);
}
if (isset($resultData['slider_image']) && is_string($resultData['slider_image'])) {
$resultData['slider_image'] = json_decode($resultData['slider_image'], true);
}
$result['status'] = 200;
$result['data'] = $resultData;
break;
case 2://99API
$apikey = sys_config('copy_product_apikey');
if (!$apikey) throw new AdminException('请先配置接口密钥');
if ((!$type || !$id) && $url) {
$url_arr = parse_url($url);
if (isset($url_arr['host'])) {
foreach ($this->host as $name) {
if (strpos($url_arr['host'], $name) !== false) {
$type = $name;
}
}
}
$type = ($type == 'pinduoduo' || $type == 'yangkeduo') ? 'pdd' : $type;
switch ($type) {
case 'taobao':
case 'tmall':
$params = [];
if (isset($url_arr['query']) && $url_arr['query']) {
$queryParts = explode('&', $url_arr['query']);
foreach ($queryParts as $param) {
$item = explode('=', $param);
if (isset($item[0]) && $item[1]) $params[$item[0]] = $item[1];
}
}
$id = $params['id'] ?? '';
break;
case 'jd':
$params = [];
if (isset($url_arr['path']) && $url_arr['path']) {
$path = str_replace('.html', '', $url_arr['path']);
$params = explode('/', $path);
}
$id = $params[1] ?? '';
break;
case 'pdd':
$params = [];
if (isset($url_arr['query']) && $url_arr['query']) {
$queryParts = explode('&', $url_arr['query']);
foreach ($queryParts as $param) {
$item = explode('=', $param);
if (isset($item[0]) && $item[1]) $params[$item[0]] = $item[1];
}
}
$id = $params['goods_id'] ?? $params['goodsId'] ?? '';
break;
case 'suning':
$params = [];
if (isset($url_arr['path']) && $url_arr['path']) {
$path = str_replace('.html', '', $url_arr['path']);
$params = explode('/', $path);
}
$id = $params[2] ?? '';
$shopid = $params[1] ?? '';
break;
case '1688':
$params = [];
if (isset($url_arr['query']) && $url_arr['query']) {
$path = str_replace('.html', '', $url_arr['path']);
$params = explode('/', $path);
}
$id = $params[2] ?? '';
$shopid = $params[1] ?? '';
break;
}
}
$result = CopyProductService::getInfo($type, ['itemid' => $id, 'shopid' => $shopid], $apikey);
break;
}
if (isset($result['status']) && $result['status']) {
/** @var ShippingTemplatesServices $shippingTemplates */
$shippingTemplates = app()->make(ShippingTemplatesServices::class);
/** @var StoreProductCategoryServices $storeCategoryService */
$storeCategoryService = app()->make(StoreProductCategoryServices::class);
$data = [];
$productInfo = $result['data'];
//参数
$sku_props = $this->parameter($productInfo['sku_props'] ?? [], $url);
$productInfo['specs'] = $sku_props;
//查询附件分类
/** @var SystemAttachmentCategoryServices $systemAttachmentCategoryService */
$systemAttachmentCategoryService = app()->make(SystemAttachmentCategoryServices::class);
$AttachmentCategory = $systemAttachmentCategoryService->getOne(['name' => $this->AttachmentCategoryName]);
//不存在则创建
if (!$AttachmentCategory) $AttachmentCategory = $systemAttachmentCategoryService->save(['pid' => '0', 'name' => $this->AttachmentCategoryName, 'enname' => '']);
//生成附件目录
try {
if (make_path('attach', 3, true) === '')
throw new AdminException('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
} catch (\Exception $e) {
throw new AdminException($e->getMessage() . '或无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
}
$images = [
['w' => 305, 'h' => 305, 'line' => $productInfo['image'], 'valuename' => 'image']
];
//执行下载
$res = $this->uploadImage($images, false, 0, $AttachmentCategory['id']);
if (!is_array($res)) throw new AdminException($this->errorInfo ? $this->errorInfo : '保存图片失败');
if (isset($res['image'])) $productInfo['image'] = $res['image'];
$productInfo['image'] = str_replace('\\', '/', $productInfo['image']);
if (count($productInfo['slider_image'])) {
$productInfo['slider_image'] = array_map(function ($item) {
$item = str_replace('\\', '/', $item);
return $item;
}, $productInfo['slider_image']);
}
$productInfo['slider_image'] = array_slice($productInfo['slider_image'], 0, 10);
$data['tempList'] = $shippingTemplates->getTemp();
$menus = [];
foreach ($storeCategoryService->getTierList(1) as $menu) {
$menus[] = ['value' => $menu['id'], 'label' => $menu['html'] . $menu['cate_name'], 'disabled' => $menu['pid'] == 0 ? 0 : 1];//,'disabled'=>$menu['pid']== 0];
}
$data['cateList'] = $menus;
$productInfo['attrs'] = $result['data']['info']['value'];
foreach ($productInfo['attrs'] as $attrs_k => $attrs_v) {
$productInfo['attrs'][$attrs_k]['attr_arr'] = array_values($attrs_v['detail']);
$productInfo['attrs'][$attrs_k]['is_show'] = 1;
$productInfo['attrs'][$attrs_k]['is_default_select'] = 0;
}
$productInfo['type'] = 0;
$productInfo['product_clear'] = ['is_general_product'];
$productInfo['relation_id'] = 0;
$productInfo['activity'] = ['默认', '秒杀', '砍价', '拼团'];
$productInfo['bar_code'] = '';
$productInfo['product_type'] = 0;
$productInfo['browse'] = 0;
$productInfo['cate_id'] = [];
$productInfo['code_path'] = '';
$productInfo['command_word'] = '';
$productInfo['coupons'] = [];
$productInfo['is_bargain'] = '';
$productInfo['is_benefit'] = 0;
$productInfo['is_best'] = 0;
$productInfo['is_del'] = 0;
$productInfo['is_good'] = 0;
$productInfo['is_hot'] = 0;
$productInfo['is_new'] = 0;
$productInfo['is_postage'] = 0;
$productInfo['is_seckill'] = 0;
$productInfo['is_show'] = 0;
$productInfo['is_sub'] = [];
$productInfo['is_vip'] = 0;
$productInfo['label_id'] = [];
$productInfo['mer_id'] = 0;
$productInfo['mer_use'] = 0;
$productInfo['recommend_image'] = '';
$productInfo['sales'] = '';
$productInfo['sort'] = 0;
$productInfo['spec_type'] = 1;
$productInfo['spu'] = '';
$productInfo['id'] = 0;
$productInfo['recommend'] = [];
$productInfo['recommend_list'] = [];
$productInfo['delivery_type'] = ['1'];
$productInfo['soure_link'] = $url;
$productInfo['code'] = '';
$productInfo['freight'] = 1;
$productInfo['postage'] = 0;
$productInfo['temp_id'] = 0;
$productInfo['is_support_refund'] = 1;
$productInfo['is_presale_product'] = 0;
$productInfo['presale_time'] = [];
$productInfo['presale_day'] = 0;
$productInfo['is_vip_product'] = 0;
$productInfo['auto_on_time'] = 0;
$productInfo['auto_off_time'] = 0;
$productInfo['custom_form'] = [];
$productInfo['system_form_id'] = 0;
$productInfo['store_label_id'] = [];
$productInfo['brand_id'] = [];
$productInfo['ensure_id'] = [];
$productInfo['specs_id'] = [];
$productInfo['is_limit'] = 0;
$productInfo['limit_type'] = 1;
$productInfo['limit_num'] = 0;
//兼容新版本
foreach ($productInfo['items'] as &$items) {
$details = [];
foreach ($items['detail'] as $detail) {
$details[] = [
'value' => $detail,
'pic' => '',
];
}
$items['detail'] = $details;
$items['add_pic'] = 0;
}
$data['productInfo'] = $productInfo;
return $data;
} else {
throw new AdminException($result['msg']);
}
}
/**
* 解析第三方平台商品参数
* @param array $data 原始参数数据
* @param string $url 商品链接
* @return array
*/
public function parameter($data, $url)
{
if (!$data || !$url) {
return [];
}
$url_arr = parse_url($url);
if (isset($url_arr['host'])) {
foreach ($this->host as $name) {
if (strpos($url_arr['host'], $name) !== false) {
$type = $name;
}
}
}
$props = [];
$type = ($type == 'pinduoduo' || $type == 'yangkeduo') ? 'pdd' : $type;
switch ($type) {
case 'taobao':
case 'tmall':
foreach ($data as $item) {
foreach ($item as $val) {
foreach ($val as $k => $v) {
foreach ($v as $name => $value) {
if ($value) {
$props[] = [
'name' => $name,
'value' => $value
];
}
}
}
}
}
break;
case 'jd':
foreach ($data as $item) {
$props[] = [
'name' => $item,
'value' => ''
];
}
break;
case '1688':
foreach ($data as $item) {
$_props = explode(':', $item);
if (count($_props) == 2) {
$props[] = [
'name' => $_props[0],
'value' => $_props[1]
];
}
}
break;
default:
}
return $props;
}
/**
* 下载商品详情图片
* @param int $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function uploadDescriptionImage(int $id)
{
//查询附件分类
/** @var SystemAttachmentCategoryServices $systemAttachmentCategoryService */
$systemAttachmentCategoryService = app()->make(SystemAttachmentCategoryServices::class);
/** @var StoreDescriptionServices $storeDescriptionServices */
$storeDescriptionServices = app()->make(StoreDescriptionServices::class);
/** @var StoreProductServices $StoreProductServices */
$StoreProductServices = app()->make(StoreProductServices::class);
$AttachmentCategory = $systemAttachmentCategoryService->getOne(['name' => $this->AttachmentCategoryName]);
//不存在则创建
if (!$AttachmentCategory) $AttachmentCategory = $systemAttachmentCategoryService->save(['pid' => '0', 'name' => $this->AttachmentCategoryName, 'enname' => '']);
//生成附件目录
try {
if (make_path('attach', 3, true) === '')
throw new AdminException('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
} catch (\Exception $e) {
throw new AdminException($e->getMessage() . '或无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
}
$description = $storeDescriptionServices->getDescription(['product_id ' => $id, 'type' => 0]);
if (!$description) throw new AdminException('商品参数错误!');
//替换并下载详情里面的图片默认下载全部图片
$description = preg_replace('#<style>.*?</style>#is', '', $description);
$description = $this->uploadImage([], $description, 1, $AttachmentCategory['id']);
$storeDescriptionServices->saveDescription((int)$id, $description);
}
/**
* 下载商品轮播图片
* @param int $id
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function uploadSliderImage(int $id)
{
//查询附件分类
/** @var SystemAttachmentCategoryServices $systemAttachmentCategoryService */
$systemAttachmentCategoryService = app()->make(SystemAttachmentCategoryServices::class);
/** @var StoreProductServices $StoreProductServices */
$StoreProductServices = app()->make(StoreProductServices::class);
$AttachmentCategory = $systemAttachmentCategoryService->getOne(['name' => $this->AttachmentCategoryName]);
//不存在则创建
if (!$AttachmentCategory) $AttachmentCategory = $systemAttachmentCategoryService->save(['pid' => '0', 'name' => $this->AttachmentCategoryName, 'enname' => '']);
//生成附件目录
try {
if (make_path('attach', 3, true) === '')
throw new AdminException('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
} catch (\Exception $e) {
throw new AdminException($e->getMessage() . '或无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
}
$slider_image = $StoreProductServices->value(['id' => $id], 'slider_image');
$slider_image = $slider_image ? json_decode($slider_image, true) : [];
$images = [];
//放入轮播图
foreach ($slider_image as $item) {
$value = ['w' => 640, 'h' => 640, 'line' => $item, 'valuename' => 'slider_image', 'isTwoArray' => true];
$images[] = $value;
}
$res = $this->uploadImage($images, false, 0, $AttachmentCategory['id']);
if (!is_array($res)) throw new AdminException($this->errorInfo ? $this->errorInfo : '保存图片失败');
if (isset($res['slider_image'])) $slider_images = $res['slider_image'];
if (count($slider_images)) {
$slider_images = array_map(function ($item) {
$item = str_replace('\\', '/', $item);
return $item;
}, $slider_images);
}
$slider_images = $slider_images ? json_encode($slider_images) : '';
$StoreProductServices->update($id, ['slider_image' => $slider_images]);
}
/**
* 保存数据
* @param array $data
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function save(array $data)
{
$detail = $data['attrs'];
$attrs = $data['items'];
$data['spec_type'] = $data['attrs'] ? 1 : 0;
if (count($detail)) {
$data['price'] = min(array_column($detail, 'price'));
$data['ot_price'] = min(array_column($detail, 'ot_price'));
$data['cost'] = min(array_column($detail, 'cost'));
$data['stock'] = array_sum(array_column($detail, 'stock'));
}
unset($data['attrs'], $data['items'], $data['info']);
if (!$data['cate_id']) throw new AdminException('请选择分类!');
if (!$data['store_name']) throw new AdminException('请填写商品名称');
if (!$data['unit_name']) throw new AdminException('请填写商品单位');
if (!$data['temp_id']) throw new AdminException('请选择运费模板');
//关联补充信息
$relationData = [];
$relationData['cate_id'] = $data['cate_id'] ?? [];
$relationData['brand_id'] = $data['brand_id'] ?? [];
$relationData['store_label_id'] = $data['store_label_id'] ?? [];
$relationData['label_id'] = $data['label_id'] ?? [];
$relationData['ensure_id'] = $data['ensure_id'] ?? [];
$relationData['specs_id'] = $data['specs_id'] ?? [];
$relationData['coupon_ids'] = $data['coupon_ids'] ?? [];
//查询附件分类
/** @var SystemAttachmentCategoryServices $systemAttachmentCategoryService */
$systemAttachmentCategoryService = app()->make(SystemAttachmentCategoryServices::class);
$AttachmentCategory = $systemAttachmentCategoryService->getOne(['name' => $this->AttachmentCategoryName]);
//不存在则创建
if (!$AttachmentCategory) $AttachmentCategory = $systemAttachmentCategoryService->save(['pid' => '0', 'name' => $this->AttachmentCategoryName, 'enname' => '']);
//生成附件目录
try {
if (make_path('attach', 3, true) === '')
throw new AdminException('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
} catch (\Exception $e) {
throw new AdminException($e->getMessage() . '或无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
}
ini_set("max_execution_time", '600');
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
//开始图片下载处理
$this->transaction(function () use ($attrs, $detail, $data, $AttachmentCategory, $storeProductServices, $storeProductAttrServices, $relationData) {
//放入主图
$images = [
['w' => 305, 'h' => 305, 'line' => $data['image'], 'valuename' => 'image']
];
//放入轮播图
foreach ($data['slider_image'] as $item) {
$value = ['w' => 640, 'h' => 640, 'line' => $item, 'valuename' => 'slider_image', 'isTwoArray' => true];
$images[] = $value;
}
//执行下载
$res = $this->uploadImage($images, false, 0, $AttachmentCategory['id']);
if (!is_array($res)) throw new AdminException($this->errorInfo ? $this->errorInfo : '保存图片失败');
if (isset($res['image'])) $data['image'] = $res['image'];
if (isset($res['slider_image'])) $data['slider_image'] = $res['slider_image'];
$data['image'] = str_replace('\\', '/', $data['image']);
if (count($data['slider_image'])) {
$data['slider_image'] = array_map(function ($item) {
$item = str_replace('\\', '/', $item);
return $item;
}, $data['slider_image']);
}
$data['slider_image'] = count($data['slider_image']) ? json_encode($data['slider_image']) : '';
//替换并下载详情里面的图片默认下载全部图片
$data['description'] = preg_replace('#<style>.*?</style>#is', '', $data['description']);
$data['description'] = $this->uploadImage($data['description_images'], $data['description'], 1, $AttachmentCategory['id']);
unset($data['description_images']);
$description = $data['description'];
unset($data['description']);
$data['add_time'] = time();
$data['cate_id'] = implode(',', $data['cate_id']);
$productInfo = $storeProductServices->getOne(['soure_link' => $data['soure_link']]);
if ($productInfo) {
$is_new = 0;
$id = $productInfo['id'];
$productInfo->slider_image = $data['slider_image'];
$productInfo->image = $data['image'];
$productInfo->store_name = $data['store_name'];
$res = $storeProductServices->update($productInfo->id, $data);
$skuList = [];
} else {
$is_new = 1;
$data['code_path'] = '';
$data['spu'] = $this->createSpu();
if ($data['spec_type'] == 0) {
$attrs = [
[
'value' => '规格',
'detailValue' => '',
'attrHidden' => '',
'detail' => ['默认']
]
];
$detail[0]['value1'] = '规格';
$detail[0]['detail'] = ['规格' => '默认'];
}
$res = $storeProductServices->save($data);
$id = $res['id'] ?? 0;
$skuList = $storeProductAttrServices->validateProductAttr($attrs, $detail, (int)$res['id']);
$attrRes = $storeProductAttrServices->saveProductAttr($skuList, (int)$res['id'], 0);
}
if (!$res) throw new AdminException('保存失败');
event('product.create', [$id, $data, $skuList, $is_new, [], $description, 1, $relationData]);
});
}
/**
* 上传图片处理
* @param array $images
* @param string $html
* @param int $uploadType
* @param int $AttachmentCategoryId
* @return array|bool|string|string[]|null
*/
public function uploadImage(array $images = [], $html = '', $uploadType = 0, $AttachmentCategoryId = 0)
{
/** @var SystemAttachmentServices $systemAttachmentService */
$systemAttachmentService = app()->make(SystemAttachmentServices::class);
$uploadImage = [];
$siteUrl = sys_config('site_url');
switch ($uploadType) {
case 0:
foreach ($images as $item) {
//下载图片文件
if ($item['w'] && $item['h'])
$uploadValue = $this->downloadImage($item['line'], '', 0, 30, $item['w'], $item['h']);
else
$uploadValue = $this->downloadImage($item['line']);
//下载成功更新数据库
if (is_array($uploadValue)) {
// 拼接图片地址
if ($uploadValue['image_type'] == 1) $imagePath = $siteUrl . $uploadValue['path'];
else $imagePath = $uploadValue['path'];
//写入数据库
if (!$uploadValue['is_exists'] && $AttachmentCategoryId) {
$systemAttachmentService->save([
'name' => $uploadValue['name'],
'real_name' => $uploadValue['name'],
'att_dir' => $imagePath,
'satt_dir' => $imagePath,
'att_size' => $uploadValue['size'],
'att_type' => $uploadValue['mime'],
'image_type' => $uploadValue['image_type'],
'module_type' => 1,
'time' => time(),
'pid' => $AttachmentCategoryId
]);
}
//组装数组
if (isset($item['isTwoArray']) && $item['isTwoArray'])
$uploadImage[$item['valuename']][] = $imagePath;
else
$uploadImage[$item['valuename']] = $imagePath;
}
}
break;
case 1:
preg_match_all('#<img.*?src="([^"]*)"[^>]*>#i', $html, $match);
if (isset($match[1])) {
foreach ($match[1] as $item) {
if (is_int(strpos($item, 'http')))
$arcurl = $item;
else
$arcurl = 'http://' . ltrim($item, '\//');
$uploadValue = $this->downloadImage($arcurl);
//下载成功更新数据库
if (is_array($uploadValue)) {
// 拼接图片地址
if ($uploadValue['image_type'] == 1) $imagePath = $siteUrl . $uploadValue['path'];
else $imagePath = $uploadValue['path'];
//写入数据库
if (!$uploadValue['is_exists'] && $AttachmentCategoryId) {
$systemAttachmentService->save([
'name' => $uploadValue['name'],
'real_name' => $uploadValue['name'],
'att_dir' => $imagePath,
'satt_dir' => $imagePath,
'att_size' => $uploadValue['size'],
'att_type' => $uploadValue['mime'],
'image_type' => $uploadValue['image_type'],
'module_type' => 1,
'time' => time(),
'pid' => $AttachmentCategoryId
]);
}
//替换图片
$html = str_replace($item, $imagePath, $html);
} else {
//替换掉没有下载下来的图片
$html = preg_replace('#<img.*?src="' . $item . '"*>#i', '', $html);
}
}
}
return $html;
break;
default:
throw new AdminException('上传方式错误');
break;
}
return $uploadImage;
}
/**
* @param string $url
* @param string $name
* @param int $type
* @param int $timeout
* @param int $w
* @param int $h
* @return string
*/
public function downloadImage($url = '', $name = '', $type = 0, $timeout = 30, $w = 0, $h = 0)
{
if (!strlen(trim($url))) return '';
if (!strlen(trim($name))) {
// 获取要下载的文件名称
$downloadImageInfo = $this->getImageExtname($url);
$name = $downloadImageInfo['file_name'];
$ext = $downloadImageInfo['ext_name'];
if (!strlen(trim($name))) return '';
} else {
$ext = $this->getImageExtname($name)['ext_name'];
}
if (!in_array($ext, ['png', 'jpg', 'jpeg', 'gif'])) {
throw new ValidateException('格式错误,文件后缀不允许');
}
// 获取远程文件所采用的方法
if ($type) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
if (stripos($url, "https://") !== FALSE) curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_HTTPHEADER, array('user-agent:' . $_SERVER['HTTP_USER_AGENT']));
if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);// 是否采集301、302之后的页面
$content = curl_exec($ch);
curl_close($ch);
} else {
try {
ob_start();
if (substr($url, 0, 2) == '//') {
$url = "https:" . $url;
}
readfile($url);
$content = ob_get_contents();
ob_end_clean();
} catch (\Exception $e) {
throw new AdminException($e->getMessage());
}
}
$size = strlen(trim($content));
if (!$content || $size <= 2) throw new AdminException('图片流获取失败');
$date_dir = date('Y') . '/' . date('m') . '/' . date('d');
$upload_type = sys_config('upload_type', 1);
$upload = UploadService::init($upload_type);
if ($upload->to('attach/' . $date_dir)->validate()->stream($content, $name) === false) {
throw new AdminException($upload->getError());
}
$imageInfo = $upload->getUploadInfo();
$date['path'] = $imageInfo['dir'];
$date['name'] = $imageInfo['name'];
$date['size'] = $imageInfo['size'];
$date['mime'] = $imageInfo['type'];
$date['image_type'] = $upload_type;
$date['is_exists'] = false;
return $date;
}
/**
* 获取即将要下载的图片扩展名
* @param string $url
* @param string $ex
* @return array|string[]
*/
public function getImageExtname($url = '', $ex = 'jpg')
{
$_empty = ['file_name' => '', 'ext_name' => $ex];
if (!$url) return $_empty;
if (strpos($url, '?')) {
$_tarr = explode('?', $url);
$url = trim($_tarr[0]);
}
$arr = explode('.', $url);
if (!is_array($arr) || count($arr) <= 1) return $_empty;
$ext_name = trim($arr[count($arr) - 1]);
$ext_name = !$ext_name ? $ex : $ext_name;
return ['file_name' => md5($url) . '.' . $ext_name, 'ext_name' => $ext_name];
}
/**
* SPU
* @return string
*/
public function createSpu()
{
mt_srand();
return substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8) . str_pad((string)mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
}
/**
* 下载远程图片并上传
* @param $image
* @return false|mixed|string
* @throws \Exception
*/
public function downloadCopyImage($image)
{
//查询附件分类
/** @var SystemAttachmentCategoryServices $systemAttachmentCategoryService */
$attachmentCategoryService = app()->make(SystemAttachmentCategoryServices::class);
$AttachmentCategory = $attachmentCategoryService->getOne(['name' => '远程下载']);
//不存在则创建
if (!$AttachmentCategory) {
$AttachmentCategory = $systemAttachmentCategoryService->save(['pid' => '0', 'name' => '远程下载', 'enname' => '']);
}
//生成附件目录
if (make_path('attach', 3, true) === '') {
throw new AdminException('无法创建文件夹,请检查您的上传目录权限:' . app()->getRootPath() . 'public' . DS . 'uploads' . DS . 'attach' . DS);
}
//上传图片
/** @var SystemAttachmentServices $systemAttachmentService */
$systemAttachmentService = app()->make(SystemAttachmentServices::class);
$siteUrl = sys_config('site_url');
$uploadValue = $this->downloadImage($image);
if (is_array($uploadValue)) {
//TODO 拼接图片地址
if ($uploadValue['image_type'] == 1) {
$imagePath = $siteUrl . $uploadValue['path'];
} else {
$imagePath = $uploadValue['path'];
}
//写入数据库
if (!$uploadValue['is_exists'] && $AttachmentCategory['id']) {
$systemAttachmentService->save([
'name' => $uploadValue['name'],
'real_name' => $uploadValue['name'],
'att_dir' => $imagePath,
'satt_dir' => $imagePath,
'att_size' => $uploadValue['size'],
'att_type' => $uploadValue['mime'],
'image_type' => $uploadValue['image_type'],
'module_type' => 1,
'time' => time(),
'pid' => $AttachmentCategory['id']
]);
}
return $imagePath;
}
return false;
}
}

View File

@@ -0,0 +1,66 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreDescriptionDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
/**
* Class StoreDescriptionService
* @package app\services\product\product
* @mixin StoreDescriptionDao
*/
class StoreDescriptionServices extends BaseServices
{
/**
* @var StoreDescriptionDao
*/
#[Inject]
protected StoreDescriptionDao $dao;
/**
* 获取商品详情
* @param array $where
* @return string
*/
public function getDescription(array $where)
{
$info = $this->dao->getDescription($where);
if ($info) return htmlspecialchars_decode($info->description);
return '';
}
/**
* 保存商品详情
* @param int $id
* @param string $description
* @param int $type
* @return bool
*/
public function saveDescription(int $id, string $description, int $type = 0)
{
$description = htmlspecialchars($description);
$data = ['product_id' => $id, 'type' => $type];
$info = $this->dao->count($data);
if ($info) {
$res = $this->dao->update($data, ['description' => $description]);
} else {
$data['description'] = $description;
$res = $this->dao->save($data);
}
if (!$res) throw new AdminException('商品详情保存失败!');
}
}

View File

@@ -0,0 +1,281 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
//declare (strict_types=1);
namespace app\services\product\product;
use app\jobs\product\ProductBatchJob;
use app\jobs\product\ProductCouponJob;
use app\jobs\product\ProductRelationJob;
use app\services\BaseServices;
use app\dao\product\product\StoreProductDao;
use app\services\product\category\StoreProductCategoryServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 商品批量操作
* Class StoreProductBatchProcessServices
* @package app\services\product\product
* @mixin StoreProductDao
*/
class StoreProductBatchProcessServices extends BaseServices
{
/**
* 批量处理参数
* @var array[]
*/
protected array $data = [
'cate_id' => [],//分类ids
'store_label_id' => [],//商品标签
'brand_id' => [],//商品品牌
'delivery_type' => [],//物流方式1:快递,2:门店自提,3:门店配送
'freight' => 1,//运费设置1:包邮,2:固定运费,3:运费模版
'postage' => 0,//邮费金额
'temp_id' => 0,//运费模版
'give_integral' => 0,//赠送积分
'coupon_ids' => [],//赠送优惠券ids
'label_id' => [],//关联用户标签ids
'recommend' => [],//活动推荐:is_hot、is_benefit、is_new、is_best、is_good
// 'custom_form' => [],//自定义留言
'system_form_id' => 0,//系统表单
];
/**
* @var StoreProductDao
*/
#[Inject]
protected StoreProductDao $dao;
/**
* 根据搜索条件查询ids
* @param $where
* @return array
*/
public function getIdsByWhere($where)
{
$ids = [];
$cateIds = [];
if (isset($where['cate_id']) && $where['cate_id']) {
/** @var StoreProductCategoryServices $storeCategory */
$storeCategory = app()->make(StoreProductCategoryServices::class);
$cateIds = $storeCategory->getColumn(['pid', 'in', $where['cate_id']], 'id');
}
if ($cateIds) {
$cateIds[] = $where['cate_id'];
$where['cate_id'] = $cateIds;
}
/** @var StoreProductServices $productService */
$productService = app()->make(StoreProductServices::class);
$dataInfo = $productService->getProductListByWhere($where, 'id');
if ($dataInfo) {
$ids = array_unique(array_column($dataInfo, 'id'));
}
return $ids;
}
/**
* 商品批量操作
* @param int $type
* @param array $ids
* @param array $input_data
* @param bool $is_all
* @param array $where
* @return bool
*/
public function batchProcess(int $type, array $ids, array $input_data, bool $is_all = false, array $where = [])
{
//全选
if ($is_all == 1) {
$ids = $this->getIdsByWhere($where);
}
$data = [];
$isBatch = true;
switch ($type) {
case 1://分类
$isBatch = false;
$cate_id = $input_data['cate_id'] ?? 0;
if (!$cate_id) throw new ValidateException('请选择分类');
$data['cate_id'] = $cate_id;
break;
case 2://商品标签 不选默认清空标签
$store_label_id = $input_data['store_label_id'] ?? [];
$data['store_label_id'] = $store_label_id;
break;
case 3://物流设置
$data['delivery_type'] = $input_data['delivery_type'] ?? [];
if (!$data['delivery_type']) throw new AdminException('请选择商品配送方式');
break;
case 4://购买即送积分、优惠券
$isBatch = false;
$data['give_integral'] = $input_data['give_integral'] ?? 0;
$data['coupon_ids'] = $input_data['coupon_ids'] ?? [];
if (!$data['give_integral'] && !$data['coupon_ids']) {
throw new AdminException('请输入赠送积分或选择赠送优惠券');
}
break;
case 5://关联用户标签 不选默认清空标签
$label_id = $input_data['label_id'] ?? [];
$data['label_id'] = $label_id;
break;
case 6://活动推荐
$recommend = $input_data['recommend'] ?? [];
$data['is_hot'] = $data['is_benefit'] = $data['is_new'] = $data['is_good'] = $data['is_best'] = 0;
if ($recommend) {
$arr = ['is_hot', 'is_benefit', 'is_new', 'is_good', 'is_best'];
foreach ($recommend as $item) {
if (in_array($item, $arr)) {
$data[$item] = 1;
}
}
}
break;
case 7://自定义留言 可以为空 清空之前的设置
$data['system_form_id'] = $input_data['system_form_id'] ?? 0;
break;
case 8://运费设置
$data['freight'] = $input_data['freight'] ?? 0;
$data['postage'] = $input_data['postage'] ?? 0;
$data['temp_id'] = $input_data['temp_id'] ?? 0;
if ($data['freight'] == 2 && !$data['postage']) {
throw new AdminException('请设置运费金额');
}
if ($data['freight'] == 3 && !$data['temp_id']) {
throw new AdminException('请选择运费模版');
}
if ($data['freight'] == 1) {
$data['temp_id'] = 0;
$data['postage'] = 0;
} elseif ($data['freight'] == 2) {
$data['temp_id'] = 0;
} elseif ($data['freight'] == 3) {
$data['postage'] = 0;
}
break;
case 9://商品品牌 不选默认清空标签
$data['brand_com'] = $input_data['brand_id'] ? implode(',', $input_data['brand_id']) : '';
$data['brand_id'] = $input_data['brand_id'] ? end($input_data['brand_id']) : 0;
break;
case 10://商品佣金设置
$data['brokerage'] = $input_data['brokerage'] ?? 0;
$data['brokerage_two'] = $input_data['brokerage_two'] ?? 0;
break;
default:
throw new AdminException('暂不支持该类型批操作');
}
//加入批量队列
ProductBatchJob::dispatchDo('productBatch', [$type, $ids, $data, $isBatch]);
return true;
}
/**
* 执行批量修改eb_store_product
* @param array $ids
* @param array $data
* @return bool
*/
public function runBatch(array $ids, array $data, int $type = 2)
{
if (!$ids || !$data) {
return false;
}
if (isset($data['delivery_type']) && $data['delivery_type']) {
$data['delivery_type'] = is_array($data['delivery_type']) ? implode(',', $data['delivery_type']) : $data['delivery_type'];
}
switch ($type) {
case 2://商品标签
$this->dao->batchUpdate($ids, ['store_label_id' => implode(',', $data['store_label_id'])], 'id');
foreach ($ids as $id) {
ProductRelationJob::dispatch([$id, $data['store_label_id'], 3]);
}
break;
case 5://用户标签
$this->dao->batchUpdate($ids, ['label_id' => implode(',', $data['label_id'])], 'id');
foreach ($ids as $id) {
ProductRelationJob::dispatch([$id, $data['label_id'], 4]);
}
break;
case 6://活动推荐 用商品标签关联
$this->dao->batchUpdate($ids, $data, 'id');
$arr = ['is_hot' => 1, 'is_benefit' => 2, 'is_best' => 3, 'is_new' => 4, 'is_good' => 5];
$label_ids = [];
foreach ($data as $key => $value) {
$label_id = $arr[$key] ?? 0;
if (!$label_id || !$value) continue;
$label_ids[] = $label_id;
}
foreach ($ids as $id) {
ProductRelationJob::dispatch([$id, $label_ids, 3]);
}
break;
case 9:// 商品品牌
$this->dao->batchUpdate($ids, ['brand_id' => $data['brand_id'], 'brand_com' => $data['brand_com']], 'id');
foreach ($ids as $id) {
ProductRelationJob::dispatch([$id, $data['brand_id'], 2]);
}
break;
case 10://商品佣金
foreach ($ids as $id) {
ProductBrokerageJob::dispatch([$id, $data['brokerage'], $data['brokerage_two']]);
}
break;
default://
$this->dao->batchUpdate($ids, $data, 'id');
break;
}
return true;
}
/**
* 保存商品分类
* @param int $id
* @param array $data
* @return bool
*/
public function setPrdouctCate(int $id, array $data)
{
if (!$id || !$data) {
return true;
}
$cate_id = $data['cate_id'] ?? 0;
if ($cate_id) {
$this->dao->update($id, ['cate_id' => implode(',', $cate_id)]);
//商品分类关联
ProductRelationJob::dispatch([$id, $cate_id, 1]);
}
return true;
}
/**
* 保存商品赠送积分、优惠券
* @param int $id
* @param array $data
* @return bool
*/
public function setGiveIntegralCoupon(int $id, array $data)
{
if (!$id || !$data) {
return true;
}
$give_integral = $data['give_integral'] ?? 0;
if ($give_integral) {
$this->dao->update($id, ['give_integral' => $give_integral]);
}
$coupon_ids = $data['coupon_ids'] ?? [];
if ($coupon_ids) {
//商品关联优惠券
ProductCouponJob::dispatchDo('setProductCoupon', [$id, $coupon_ids]);
}
return true;
}
}

View File

@@ -0,0 +1,70 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductCateDao;
use app\services\BaseServices;
use app\services\product\category\StoreProductCategoryServices;
use think\annotation\Inject;
/**
* Class StoreProductCateService
* @package app\services\product\product
* @mixin StoreProductCateDao
*/
class StoreProductCateServices extends BaseServices
{
/**
* @var StoreProductCateDao
*/
#[Inject]
protected StoreProductCateDao $dao;
/**
* 保存商品分类关联
* @param int $id
* @param array $cate_id
* @param int $is_show
* @return bool
*/
public function saveCate(int $id, array $cate_id, int $is_show = 0)
{
$cateData = [];
if ($cate_id) {
$time = time();
/** @var StoreProductCategoryServices $storeCategoryServices */
$storeCategoryServices = app()->make(StoreProductCategoryServices::class);
$cateGory = $storeCategoryServices->getColumn([['id', 'IN', $cate_id]], 'id,pid', 'id');
foreach ($cate_id as $cid) {
if ($cid && isset($cateGory[$cid]['pid'])) {
$cateData[] = ['product_id' => $id, 'cate_id' => $cid, 'cate_pid' => $cateGory[$cid]['pid'], 'status' => $is_show, 'add_time' => $time];
}
}
}
$this->change($id, $cateData);
return true;
}
/**
* 商品添加修改商品分类关联
* @param $id
* @param $cateData
*/
public function change($id, $cateData)
{
$this->dao->delete(['product_id' => $id]);
if ($cateData) $this->dao->saveAll($cateData);
}
}

View File

@@ -0,0 +1,141 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\product\product;
use app\services\BaseServices;
use app\dao\product\product\StoreProductCouponDao;
use app\services\activity\coupon\StoreCouponIssueServices;
use app\services\order\StoreOrderCartInfoServices;
use app\services\order\StoreOrderServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
*
* Class StoreProductCouponServices
* @package app\services\coupon
* @mixin StoreProductCouponDao
*/
class StoreProductCouponServices extends BaseServices
{
/**
* @var StoreProductCouponDao
*/
#[Inject]
protected StoreProductCouponDao $dao;
/**
* 商品关联优惠券
* @param int $id
* @param array $coupon_ids
* @return bool
*/
public function setCoupon(int $id, array $coupon_ids)
{
$this->dao->delete(['product_id' => $id]);
if ($coupon_ids) {
$data = $data_all = [];
$data['product_id'] = $id;
$data['add_time'] = time();
foreach ($coupon_ids as $cid) {
if (!empty($cid) && (int)$cid) {
$data['issue_coupon_id'] = $cid;
$data_all[] = $data;
}
}
$res = true;
if ($data_all) {
$res = $this->dao->saveAll($data_all);
}
if (!$res) throw new AdminException('关联优惠券失败!');
}
return true;
}
/**
* 获取下单赠送优惠券
* @param int $uid
* @param string $orderId
* @param $order
* @return array|mixed
*/
public function getOrderProductCoupon(int $uid, string $orderId, $order = [])
{
if (!$uid || !$orderId) {
return [];
}
if (!$order) {
/** @var StoreOrderServices $storeOrder */
$storeOrder = app()->make(StoreOrderServices::class);
$order = $storeOrder->getOne(['order_id' => $orderId]);
}
if (!$order || $order['uid'] != $uid) {
throw new ValidateException('订单不存在');
}
$key = 'order_product_coupon_' . $uid . '_' . $order['id'];
return CacheService::redisHandler()->get($key, []);
}
/**
* 下单赠送优惠劵
* @param int $uid
* @param int $orderId
* @return bool
*/
public function giveOrderProductCoupon(int $uid, int $orderId)
{
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$user = $userServices->getUserInfo($uid);
if (!$user) {
throw new ValidateException('用户不存在');
}
/** @var StoreOrderServices $storeOrder */
$storeOrder = app()->make(StoreOrderServices::class);
$order = $storeOrder->getOne(['id' => $orderId]);
if (!$order || $order['uid'] != $uid) {
throw new ValidateException('订单不存在');
}
/** @var StoreOrderCartInfoServices $storeOrderCartInfo */
$storeOrderCartInfo = app()->make(StoreOrderCartInfoServices::class);
$productIds = $storeOrderCartInfo->getColumn(['oid' => $order['id']], 'product_id');
$list = [];
if ($productIds) {
$couponList = $this->dao->getProductCoupon($productIds);
if ($couponList) {
/** @var StoreCouponIssueServices $storeCoupon */
$storeCoupon = app()->make(StoreCouponIssueServices::class);
$list = $storeCoupon->orderPayGiveCoupon($uid, array_column((array)$couponList, 'issue_coupon_id'));
if ($list) {
$ids = array_column($list, 'cid');
$coupons = $storeCoupon->getColumn(['id' => $ids], 'id,type,coupon_type,product_id,category_id,brand_id', 'id');
foreach ($list as &$item) {
$item['applicable_type'] = $coupons[$item['cid']]['type'] ?? 1;
$item['coupon_type'] = $coupons[$item['cid']]['coupon_type'] ?? 1;
$item['product_id'] = $coupons[$item['cid']]['product_id'] ?? 1;
$item['category_id'] = $coupons[$item['cid']]['category_id'] ?? 1;
$item['brand_id'] = $coupons[$item['cid']]['brand_id'] ?? 1;
$item['add_time'] = date('Y-m-d', $item['add_time']);
$item['end_time'] = date('Y-m-d', $item['end_time']);
}
}
}
}
$key = 'order_product_coupon_' . $uid . '_' . $orderId;
CacheService::redisHandler()->set($key, $list, 7200);
return true;
}
}

View File

@@ -0,0 +1,183 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductLogDao;
use app\services\order\StoreOrderCartInfoServices;
use app\services\BaseServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 商品访问记录日志
* Class StoreProductLogServices
* @package app\services\product\product
* @mixin StoreProductLogDao
*/
class StoreProductLogServices extends BaseServices
{
/**
* @var StoreProductLogDao
*/
#[Inject]
protected StoreProductLogDao $dao;
/**
* 创建各种访问日志
* @param string $type
* @param array $data
* @return bool
*/
public function createLog(string $type, array $data)
{
if (!in_array($type, ['order', 'pay', 'refund']) && (!isset($data['product_id']) || !$data['product_id'])) {
throw new ValidateException('缺少商品ID');
}
$log_data = $log_data_all = [];
$log_data['type'] = $type;
$log_data['uid'] = $data['uid'] ?? 0;
$log_data['add_time'] = time();
switch ($type) {
case 'visit'://访问
case 'cart'://加入购物车
case 'collect'://收藏
$key = $type . '_num';
$log_data[$key] = isset($data[$key]) && $data[$key] ? $data[$key] : 1;
$log_data['product_id'] = 0;
if (isset($data['product_id']) && $data['product_id']) {
if (is_array($data['product_id'])) {
foreach ($data['product_id'] as $key => $product_id) {
$log_data['product_id'] = $product_id;
$log_data_all[] = $log_data;
}
} else {
$log_data['product_id'] = $data['product_id'];
}
}
break;
case 'order'://下单
if (!isset($data['order_id']) || !$data['order_id']) {
throw new ValidateException('缺少订单ID');
}
/** @var StoreOrderCartInfoServices $cartInfoServices */
$cartInfoServices = app()->make(StoreOrderCartInfoServices::class);
$cartInfo = $cartInfoServices->getOrderCartInfo((int)$data['order_id']);
foreach ($cartInfo as $value) {
$product = $value['cart_info'];
$log_data['product_id'] = $product['product_id'] ?? 0;
$log_data['order_num'] = $product['cart_num'] ?? 1;
$log_data_all[] = $log_data;
}
break;
case 'pay'://支付
if (!isset($data['order_id']) || !$data['order_id']) {
throw new ValidateException('缺少订单ID');
}
/** @var StoreOrderCartInfoServices $cartInfoServices */
$cartInfoServices = app()->make(StoreOrderCartInfoServices::class);
$cartInfo = $cartInfoServices->getOrderCartInfo((int)$data['order_id']);
foreach ($cartInfo as $value) {
$product = $value['cart_info'];
$log_data['product_id'] = $product['product_id'] ?? 0;
$log_data['pay_num'] = $product['cart_num'] ?? 0;
$log_data['cost_price'] = $product['costPrice'] ?? 0;
$log_data['pay_price'] = $product['truePrice'] ?? 0;
$log_data['pay_uid'] = $data['uid'] ?? 0;
$log_data_all[] = $log_data;
}
break;
case 'refund'://退款
if (!isset($data['order_id']) || !$data['order_id']) {
throw new ValidateException('缺少订单ID');
}
/** @var StoreOrderCartInfoServices $cartInfoServices */
$cartInfoServices = app()->make(StoreOrderCartInfoServices::class);
$cartInfo = $cartInfoServices->getOrderCartInfo((int)$data['order_id']);
foreach ($cartInfo as $value) {
$product = $value['cart_info'];
$log_data['product_id'] = $product['product_id'] ?? 0;
$log_data['order_num'] = $product['cart_num'] ?? 1;
$log_data_all[] = $log_data;
}
break;
default:
throw new ValidateException('暂不支持该类型记录');
break;
}
if ($log_data_all) {
$res = $this->dao->saveAll($log_data_all);
} else {
$res = $this->dao->save($log_data);
}
if (!$res) {
throw new ValidateException('添加商品记录失败');
}
return true;
}
/**
* 查找购买商品排行
* @param $where
* @return mixed
*/
public function getRanking(array $where)
{
$list = $this->dao->getRanking($where);
foreach ($list as $key => &$item) {
if (!$item['store_name'] || !$item['image']) {
unset($list[$key]);
}
if ($item['profit'] == null) $item['profit'] = 0;
if ($item['changes'] == null) $item['changes'] = 0;
if ($item['repeats'] == null) {
$item['repeats'] = 0;
} else {
$item['repeats'] = bcdiv($this->dao->getRepeats($where, $item['product_id']), $item['repeats'], 2);
}
}
return array_merge($list);
}
/**
* 浏览商品列表
* @param array $where
* @param string $group
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where, string $group = '', string $field = '*')
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $field, $page, $limit, $group);
if ($group) {
$count = $this->dao->getDistinctCount($where, $group, true);
} else {
$count = $this->dao->count($where);
}
return compact('list', 'count');
}
/**
* 获取商品日志关联的商品列表
* @param array $where 查询条件
* @return array
*/
public function getProduct(array $where)
{
[$page, $limit] = $this->getPageValue();
return $this->dao->getProduct($where, $page, $limit);
}
}

View File

@@ -0,0 +1,103 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductDao;
use app\services\BaseServices;
use app\services\user\UserServices;
use think\annotation\Inject;
/**
* Class StoreProductRankServices
* @package app\services\product\product
* @mixin StoreProductDao
*/
class StoreProductRankServices extends BaseServices
{
/**
* 排名最大名次
* @var int
*/
protected int $rankMax = 20;
/**
* @var StoreProductDao
*/
#[Inject]
protected StoreProductDao $dao;
/**
* 获取商品在排行榜中排名
* @param int $uid
* @param int $productId
* @param int $type
* @return int
*/
public function getProductRank(int $uid, int $productId, int $type = 1)
{
$where['is_vip_product'] = 0;
$where['is_verify'] = 1;
$where['pid'] = 0;
$where['is_show'] = 1;
$where['is_del'] = 0;
if ($uid) {
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$is_vip = $userServices->value(['uid' => $uid], 'is_money_level');
$where['is_vip_product'] = $is_vip ? -1 : 0;
}
$field = ['id', 'IFNULL(sales,0) + IFNULL(ficti,0) as sales', 'star'];
$order = match($type) {
1 => 'sales desc, sort desc, id desc',//销量
2 => 'star desc, sort desc, id desc',//评分
3 => 'collect desc, sort desc, id desc',//收藏
default => 'sales desc, sort desc, id desc',
};
$list = $this->dao->getRecommendProduct($where, $field, $this->rankMax, 0, 0, [], $order);
$rank = 0;
if ($list) {
$key = array_search($productId, array_column($list, 'id'));
if ($key !== false) {
$rank = (int)$key + 1;
}
}
return $rank;
}
/**
* 获取商品排行数据
* @param int $uid
* @param int $type
* @param array $where
* @param int $limit
* @return array|null
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @throws \throwable
*/
public function getProductRankList(int $uid, int $type = 1, array $where = [], int $limit = 0)
{
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$order = match($type) {
1 => 'sales desc, sort desc, id desc',//销量
2 => 'star desc, sort desc, id desc',//评分
3 => 'collect desc, sort desc, id desc',//收藏
default => 'sales desc, sort desc, id desc',
};
return $productServices->getRecommendProduct($uid, $where, $limit, 'mid', $order);
}
}

View File

@@ -0,0 +1,193 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductRelationDao;
use app\services\BaseServices;
use app\services\product\category\StoreProductCategoryServices;
use think\annotation\Inject;
/**
* 商品关联关系
* Class StoreProductRelationServices
* @package app\services\product\product
* @mixin StoreProductRelationDao
*/
class StoreProductRelationServices extends BaseServices
{
/**
* 1:分类2:品牌3:商品标签4:用户标签5:保障服务6:商品参数
* @var string[]
*/
protected array $type = [
1 => '商品分类',
2 => '商品品牌',
3 => '商品标签',
4 => '用户标签',
5 => '保障服务',
6 => '商品参数'
];
/**
* 缓存key
* @var string[]
*/
protected array $typeKey = [
1 => 'cate',
2 => 'brand',
3 => 'product_label',
4 => 'user_label',
5 => 'ensure',
6 => 'specs'
];
/**
* @var StoreProductRelationDao
*/
#[Inject]
protected StoreProductRelationDao $dao;
/**
* 保存商品关联关系
* @param int $id
* @param array $relation_id
* @param int $type
* @param int $is_show
* @return bool
*/
public function saveRelation(int $id, array $relation_id, int $type = 1, int $is_show = 1)
{
$cateData = [];
if ($relation_id) {
$time = time();
if ($type == 1) {//分类
/** @var StoreProductCategoryServices $storeCategoryServices */
$storeCategoryServices = app()->make(StoreProductCategoryServices::class);
$cateGory = $storeCategoryServices->getColumn([['id', 'IN', $relation_id]], 'id,pid', 'id');
foreach ($relation_id as $cid) {
if ($cid && isset($cateGory[$cid]['pid'])) {
$cateData[] = ['type' => $type, 'product_id' => $id, 'relation_id' => $cid, 'relation_pid' => $cateGory[$cid]['pid'], 'status' => $is_show, 'add_time' => $time];
}
}
} else {
foreach ($relation_id as $cid) {
$cateData[] = ['type' => $type, 'product_id' => $id, 'relation_id' => $cid, 'status' => 1, 'add_time' => $time];
}
}
}
$this->change($id, $cateData, $type);
return true;
}
/**
* 商品添加商品关联
* @param int $id
* @param array $cateData
* @param int $type
* @return bool
*/
public function change(int $id, array $cateData, int $type = 1)
{
$this->dao->delete(['product_id' => $id, 'type' => $type]);
if ($cateData) $this->dao->saveAll($cateData);
$this->setProductRelationCache($id, $cateData, $type);
return true;
}
/**
* 批量设置关联状态
* @param array $ids
* @param int $is_show
* @param int $type
* @return bool
*/
public function setShow(array $ids, int $is_show = 1, int $type = 1)
{
$this->dao->setShow($ids, $is_show, $type);
return true;
}
/**
* 设置商品关联缓存
* @param int $product_id
* @param array $data
* @param int $type
* @return array
*/
public function setProductRelationCache(int $product_id, array $data, int $type = 1)
{
$key = 'cache_product_relation_' . ($this->typeKey[$type] ?? '') . '_' . $product_id;
if ($type == 1) {
$cacheData = $data;
} else {
$cacheData = array_column($data, 'relation_id');
}
$this->dao->cacheHander()->delete($key);
$this->dao->cacheTag()->set($key, $cacheData);
return $cacheData;
}
/**
* 更新缓存
* @param int $product_id
* @param int $type
* @param string $key
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updateProductRelationCache(int $product_id, int $type = 1)
{
$data = $this->dao->getList(['product_id' => $product_id, 'type' => $type]);
$cacheData = [];
if ($data) {
$cacheData = $this->setProductRelationCache($product_id, $data, $type);
}
return $cacheData;
}
/**
* 获取商品关联缓存
* @param int $product_id
* @param array $type
* @param bool $isCache
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductRelationCache(int $product_id, array $type = [], bool $isCache = false)
{
if (!$type) {
$typeArr = [1, 2, 3, 4, 5, 6];
} else {
$typeArr = $type;
}
$data = [];
$typeKey = $this->typeKey;
$key = 'cache_product_relation_';
foreach ($typeArr as $value) {
$key .= ($typeKey[$value] ?? '') . '_' . $product_id;
$relation = $this->dao->cacheHander()->get($key);
if (!$relation || $isCache) {
$relation = $this->updateProductRelationCache($product_id, (int)$value, $key);
}
$data[$value] = $relation;
}
return $data;
}
}

View File

@@ -0,0 +1,194 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductReplyCommentDao;
use app\services\BaseServices;
use app\services\user\UserRelationServices;
use app\services\user\UserServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreProductReplyCommentServices
* @package app\services\product\product
* @mixin StoreProductReplyCommentDao
*/
class StoreProductReplyCommentServices extends BaseServices
{
/**
* @var StoreProductReplyCommentDao
*/
#[Inject]
protected StoreProductReplyCommentDao $dao;
/**
* 获取评论回复列表
* @param int $replyId
* @param string $time
* @param int $uid
* @param bool $notUid
* @return array
*/
public function getReplCommenList(int $replyId, string $time = '', int $uid = 0, bool $notUid = true)
{
[$page, $limit] = $this->getPageValue();
$where = ['reply_id' => $replyId, 'pid' => 0, 'notUid' => 0, 'create_time' => $time];
if (false === $notUid) {
unset($where['notUid']);
}
$list = $this->dao->getDataList($where, ['*'], $page, $limit, ['praise' => 'desc', 'create_time' => 'desc'], [
'user' => function ($query) {
$query->field('uid,avatar,nickname,is_money_level');
},
'children' => function ($query) use ($uid) {
$query->with([
'user' => function ($query) {
$query->field('uid,avatar,nickname,is_money_level');
},
'productRelation' => function ($query) use ($uid) {
$query->where('uid', $uid)->where('type', UserRelationServices::TYPE_LIKE)
->where('category', UserRelationServices::CATEGORY_COMMENT)
->field(['uid', 'relation_id']);
}
]);
},
'productRelation' => function ($query) use ($uid) {
$query->where('uid', $uid)->where('type', UserRelationServices::TYPE_LIKE)
->where('category', UserRelationServices::CATEGORY_COMMENT)
->field(['uid', 'relation_id']);
}
]);
$count = $this->dao->count($where);
$siteLogoSquare = sys_config('site_logo_square');
$siteName = sys_config('site_name');
//用户是否是付费会员和等级会员
$uids = array_column($list, 'uid');
$uids = array_filter(array_unique($uids));
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$vipArray = $userServices->checkIsVip($uids);
foreach ($list as &$item) {
if (!isset($item['user']) && $item['uid'] === 0) {
$item['user'] = ['nickname' => $siteName, 'avatar' => $siteLogoSquare];
}
if (isset($item['children']) && !isset($item['children']['user']) && $item['children']['uid'] === 0) {
$item['children']['user'] = ['nickname' => $siteName, 'avatar' => $siteLogoSquare];
}
if (isset($item['user']['nickname'])) {
$item['user']['nickname'] = anonymity($item['user']['nickname']);
}
$item['user']['level_name'] = $vipArray[$item['uid']]['level_name'] ?? '';
$item['user']['vip_status'] = $vipArray[$item['uid']]['vip_status'] ?? '';
if ($uid) {
$item['is_praise'] = !empty($item['productRelation']);
if (isset($item['children'])) {
$item['children']['is_praise'] = !empty($item['children']['productRelation']);
}
} else {
$item['is_praise'] = false;
if (isset($item['children'])) {
$item['children']['is_praise'] = false;
}
}
}
return compact('list', 'count');
}
/**
* 保存回复
* @param int $uid
* @param int $replyId
* @param string $content
* @return \crmeb\basic\BaseModel|\think\Model
*/
public function saveComment(int $uid, int $replyId, string $content)
{
return $this->dao->save(['uid' => $uid, 'reply_id' => $replyId, 'content' => $content, 'create_time' => time()]);
}
/**
* 点赞回复
* @param int $id
* @param int $uid
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function commentPraise(int $id, int $uid)
{
$commentInfo = $this->dao->get($id, ['id', 'praise']);
if (!$commentInfo) {
throw new ValidateException('回复不存在');
}
$commentInfo->praise++;
/** @var UserRelationServices $service */
$service = app()->make(UserRelationServices::class);
$res = $service->getUserCount($uid, $id, UserRelationServices::TYPE_LIKE, UserRelationServices::CATEGORY_COMMENT);
if ($res) {
return true;
}
$this->transaction(function () use ($id, $uid, $service, $commentInfo) {
$res = $service->save([
'uid' => $uid,
'relation_id' => $id,
'type' => UserRelationServices::TYPE_LIKE,
'category' => UserRelationServices::CATEGORY_COMMENT,
'add_time' => time()
]);
$res = $res && $commentInfo->save();
if (!$res) {
throw new ValidateException('点赞失败');
}
});
event('product.reply.update', [$uid]);
return true;
}
/**
* 取消回复点赞
* @param int $id
* @param int $uid
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function unCommentPraise(int $id, int $uid)
{
$commentInfo = $this->dao->get($id, ['id', 'praise']);
if (!$commentInfo) {
throw new ValidateException('回复不存在');
}
$commentInfo->praise--;
/** @var UserRelationServices $service */
$service = app()->make(UserRelationServices::class);
$this->transaction(function () use ($id, $uid, $service, $commentInfo) {
$res = $service->delete([
'uid' => $uid,
'relation_id' => $id,
'type' => UserRelationServices::TYPE_LIKE,
'category' => UserRelationServices::CATEGORY_COMMENT
]);
$res = $res && $commentInfo->save();
if (!$res) {
throw new ValidateException('点赞失败');
}
});
event('product.reply.update', [$uid]);
return true;
}
}

View File

@@ -0,0 +1,677 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductReplyDao;
use app\services\BaseServices;
use app\services\product\sku\StoreProductAttrValueServices;
use app\services\supplier\SystemSupplierServices;
use app\services\user\level\SystemUserLevelServices;
use app\services\user\UserBillServices;
use app\services\user\UserRelationServices;
use app\services\user\UserServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Route as Url;
/**
* Class StoreProductReplyService
* @package app\services\product\product
* @mixin StoreProductReplyDao
*/
class StoreProductReplyServices extends BaseServices
{
/**
* @var StoreProductReplyDao
*/
#[Inject]
protected StoreProductReplyDao $dao;
/**
* 获取评论列表
* @param array $where
* @return array
*/
public function sysPage(array $where)
{
/** @var StoreProductReplyStoreProductServices $storeProductReplyStoreProductServices */
$storeProductReplyStoreProductServices = app()->make(StoreProductReplyStoreProductServices::class);
$data = $storeProductReplyStoreProductServices->getProductReplyList($where, [
'replyComment' => function ($query) use ($where) {
if ($where['type'] ?? 0) {
$query->where('uid', 0)->where('pid', 0)->where('type', $where['type'] ?? 0)->where('relation_id', $where['relation_id'] ?? 0);
} else {
$query->where('uid', 0)->where('pid', 0);
}
}
]);
foreach ($data['list'] as &$item) {
$item['time'] = time_tran(strtotime($item['add_time']));
$item['create_time'] = $item['add_time'];
$item['score'] = $item['product_score'] + $item['service_score'] + $item['delivery_score'];
$item['score'] = bcdiv($item['score'], '3', 0);
}
return $data;
}
/**
* 创建虚拟评论表单
* @param int $product_id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm(int $product_id)
{
if ($product_id == 0) {
$field[] = Form::frameImage('image', '商品', Url::buildUrl('admin/store.StoreProduct/index', array('fodder' => 'image')))->icon('ios-add')->width('960px')->height('560px')->modal(['footer-hide' => true])->Props(['srcKey' => 'image']);
} else {
$field[] = Form::hidden('product_id', $product_id);
}
$field[] = Form::frameImage('avatar', '用户头像', Url::buildUrl('admin/widget.images/index', array('fodder' => 'avatar')))->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true]);
$field[] = Form::input('nickname', '用户名称')->col(24);
$field[] = Form::input('comment', '评价文字')->type('textarea');
$field[] = Form::rate('product_score', '商品分数', 0)->allowHalf(false);
$field[] = Form::rate('service_score', '服务分数', 0)->allowHalf(false);
$field[] = Form::frameImages('pics', '评价图片', Url::buildUrl('admin/widget.images/index', array('fodder' => 'pics', 'type' => 'many', 'maxLength' => 8)))->maxLength(8)->icon('ios-add')->width('960px')->height('505px')->modal(['footer-hide' => true])->modal(['footer-hide' => true])->props(['closeBtn' => false, 'okBtn' => false]);
$field[] = Form::dateTime('add_time', '评论时间', '')->placeholder('请选择评论时间(不选择默认当前添加时间)');
return create_form('添加虚拟评论', $field, Url::buildUrl('/product/reply/save_fictitious_reply'), 'POST');
}
/**
* 添加自评
* @param array $data
* @param int $type
* @param int $relation_id
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function saveReply(array $data, int $type = 0, int $relation_id = 0)
{
$time = time();
$data['uid'] = 0;
$data['oid'] = 0;
$data['type'] = $type;
$data['relation_id'] = $relation_id;
$data['unique'] = uniqid();
$data['reply_type'] = 'product';
$data['add_time'] = empty($data['add_time']) ? $time : strtotime($data['add_time']);
$data['pics'] = json_encode($data['pics']);
if (isset($data['sku_unique']) && $data['sku_unique']) {
/** @var StoreProductAttrValueServices $productAttrValueServices */
$productAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$attrDetail = $productAttrValueServices->getone(['product_id' => $data['product_id'], 'unique' => $data['sku_unique']]);
$data['sku'] = $attrDetail ? $attrDetail['suk'] : '';
}
unset($data['image']);
if ($data['add_time'] > $time) {
throw new AdminException('评论时间应小于当前时间');
}
$res = $this->dao->save($data);
if (!$res) throw new AdminException('添加虚拟评论失败');
$this->dao->cacheTag()->clear();
if (isset($data['product_id'])) {
//商品评价
event('product.reply', [$data['product_id']]);
}
return true;
}
/**
* 回复评论
* @param int $replyId
* @param string $content
* @param int $type
* @param int $relation_id
* @return void
*/
public function setReply(int $replyId, string $content, int $type = 0, int $relation_id = 0)
{
if ($content == '') throw new AdminException('请输入回复内容');
$reply = $this->dao->get($replyId);
if (!$reply) {
throw new AdminException('评论不存在或已删除');
}
$save['content'] = $content;
$save['create_time'] = time();
$save['type'] = $type;
$save['relation_id'] = $relation_id;
$save['reply_id'] = $replyId;
/** @var StoreProductReplyCommentServices $service */
$service = app()->make(StoreProductReplyCommentServices::class);
$where = ['reply_id' => $replyId, 'uid' => 0, 'type' => $type, 'relation_id' => $relation_id, 'pid' => 0];
if ($service->count($where)) {
$res = $service->update($where, ['content' => $content, 'update_time' => time()]);
} else {
$res = $service->save($save);
}
if (!$res) throw new AdminException('回复失败,请稍后再试');
if ($type == $reply['type']) {//回复端 与商品端一致 修改回复状态
$this->dao->update($replyId, ['is_reply' => 1]);
}
$this->dao->cacheTag()->clear();
}
/**
* 删除
* @param int $id
*/
public function del(int $id)
{
$res = $this->dao->update($id, ['is_del' => 1]);
if (!$res) throw new AdminException('删除失败');
$this->dao->cacheTag()->clear();
}
/**
* @param int $productId
* @param int $limit
* @return mixed
* @throws \Throwable
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/3
*/
public function getRecProductReplyCache(int $productId, int $limit = 1)
{
return $this->dao->cacheTag()->remember('id:' . $productId . ':limit:' . $limit, function () use ($productId, $limit) {
return $this->getRecProductReply($productId, $limit);
}, 3600);
}
/**
* 获取最近的几条评论
* @param int $productId
* @param int $limit
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getRecProductReply(int $productId, int $limit = 1)
{
$page = $limit ? 1 : 0;
$list = $this->dao->getProductReply($productId, '*', $page, $limit);
if ($list) {
//用户是否是付费会员和等级会员
$uids = array_column($list, 'uid');
$uids = array_filter(array_unique($uids));
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$vipArray = $userServices->checkIsVip($uids);
foreach ($list as &$item) {
$item['suk'] = $item['sku'];
$item['nickname'] = anonymity($item['nickname']);
$item['merchant_reply_time'] = date('Y-m-d H:i', $item['merchant_reply_time']);
$item['add_time'] = time_tran($item['add_time']);
$item['star'] = bcadd((string)bcadd((string)$item['product_score'], (string)$item['service_score'], 2), (string)$item['delivery_score'], 2);
$item['star'] = bcdiv($item['star'], '3', 0);
$item['comment'] = $item['comment'] ?: '此用户没有填写评价';
$item['pics'] = $item['pics'] ? (is_string($item['pics']) ? json_decode($item['pics'], true) : $item['pics']) : [];
$item['level_name'] = $vipArray[$item['uid']]['level_name'] ?? '';
$item['vip_status'] = $vipArray[$item['uid']]['vip_status'] ?? '';
}
}
return $list;
}
/**
* 获取好评率
* @param int $id
* @return int|string
*/
public function getProductReplyChance(int $id)
{
$replyCount = $this->dao->replyCount($id);
if ($replyCount) {
$goodReply = $this->dao->replyCount($id, 1);
if ($goodReply) {
$replyCount = bcdiv((string)$goodReply, (string)$replyCount, 2);
$replyCount = bcmul((string)$replyCount, '100', 0);
} else {
$replyCount = 0;
}
} else {
$replyCount = 100;
}
return $replyCount;
}
/**
* 获取评论数据 评论总数 好评总数 好评率
* @param int $id
* @return array
*/
public function getProductReplyData(int $id)
{
return $this->dao->cacheTag()->remember('ReplyData_' . $id, function () use ($id) {
$goodReply = $replyChance = 0;
$replyCount = $this->dao->replyCount($id);
if ($replyCount) {
$goodReply = $this->dao->replyCount($id, 3);
if ($goodReply) {
$replyChance = bcmul((string)bcdiv((string)$goodReply, (string)$replyCount, 2), '100', 0);
} else {
$replyChance = 0;
}
} else {
$replyChance = 100;
}
return [$replyCount, $goodReply, $replyChance];
}, 3600);
}
/**
* 商品评论数量
* @param int $type
* @param int $relation_id
* @return int
*/
public function replyCount(int $type = 0, int $relation_id = 0)
{
return $this->dao->count(['is_reply' => 0, 'is_del' => 0, 'type' => $type, 'relation_id' => $relation_id]);
}
/**
* 获取商品评论数量
* @param int $id
* @return mixed
*/
public function productReplyCount(int $id)
{
$data['sum_count'] = $this->dao->replyCount($id);
$data['good_count'] = $this->dao->replyCount($id, 3);
$data['in_count'] = $this->dao->replyCount($id, 2);
$data['poor_count'] = $this->dao->replyCount($id, 1);
if ($data['sum_count'] != 0) {
$data['reply_chance'] = bcdiv($data['good_count'], $data['sum_count'], 2);
$data['reply_star'] = bcdiv(($this->dao->sum(['product_id' => $id, 'is_del' => 0], 'product_score') + $this->dao->sum(['product_id' => $id, 'is_del' => 0], 'service_score')), $data['sum_count'] * 2, 0);
} else {
$data['reply_chance'] = 100;
$data['reply_star'] = 5;
}
// $data['reply_star'] = bcmul($data['reply_chance'], 5, 0);
$data['reply_chance'] = $data['sum_count'] == 0 ? 100 : bcmul($data['reply_chance'], 100, 0);
return $data;
}
/**
* 获取商品评论列表
* @param int $id
* @param int $type
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getProductReplyList(int $id, int $type)
{
if (!$id) {
return [];
}
[$page, $limit] = $this->getPageValue();
$list = $this->dao->replyList($id, $type, $page, $limit);
foreach ($list as &$item) {
$item['suk'] = $item['sku'];
$item['nickname'] = anonymity($item['nickname']);
$item['merchant_reply_time'] = date('Y-m-d H:i', $item['merchant_reply_time']);
$item['add_time'] = date('Y-m-d H:i', $item['add_time']);
$item['star'] = bcadd((string)bcadd((string)$item['product_score'], (string)$item['service_score'], 2), (string)$item['delivery_score'], 2);
$item['star'] = bcdiv($item['star'], '3', 0);
$item['comment'] = $item['comment'] ?: '此用户没有填写评价';
$item['pics'] = is_string($item['pics']) ? json_decode($item['pics'], true) : $item['pics'];
}
return $list;
}
/**
* 评价点赞
* @param int $id
* @param int $uid
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function replyPraise(int $id, int $uid)
{
$relyInfo = $this->dao->get($id, ['id', 'praise']);
if (!$relyInfo) {
throw new ValidateException('点赞的评价不存在');
}
/** @var UserRelationServices $service */
$service = app()->make(UserRelationServices::class);
if ($service->getUserCount($uid, $id, UserRelationServices::TYPE_COLLECT, UserRelationServices::CATEGORY_REPLY)) {
return true;
}
$relyInfo->praise++;
$this->transaction(function () use ($uid, $relyInfo, $service, $id) {
$res = $service->save([
'uid' => $uid,
'relation_id' => $id,
'type' => UserRelationServices::TYPE_LIKE,
'category' => UserRelationServices::CATEGORY_REPLY,
'add_time' => time()
]);
$res = $res && $relyInfo->save();
if (!$res) {
throw new ValidateException('点赞失败');
}
});
event('product.reply.update', [$uid]);
$this->dao->cacheTag()->clear();
return true;
}
/**
* 取消点赞
* @param int $id
* @param int $uid
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function unReplyPraise(int $id, int $uid)
{
$relyInfo = $this->dao->get($id, ['id', 'praise']);
if (!$relyInfo) {
throw new ValidateException('点赞的评价不存在');
}
/** @var UserRelationServices $service */
$service = app()->make(UserRelationServices::class);
$relyInfo->praise--;
$this->transaction(function () use ($uid, $relyInfo, $service, $id) {
$res = $service->delete([
'uid' => $uid,
'relation_id' => $id,
'type' => UserRelationServices::TYPE_LIKE,
'category' => UserRelationServices::CATEGORY_REPLY
]);
$res = $res && $relyInfo->save();
if (!$res) {
throw new ValidateException('取消点赞失败');
}
});
event('product.reply.update', [$uid]);
$this->dao->cacheTag()->clear();
return true;
}
/**
* 获取评论详情
* @param int $id
* @param int $uid
* @return mixed
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getReplyInfo(int $id, int $uid)
{
$replyInfo = $this->dao->get($id, ['*']);
if (!$replyInfo) {
throw new ValidateException('查看的评论不存在');
}
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$productInfo = $productServices->get($replyInfo->product_id, ['id', 'store_name', 'image', 'is_presale_product']);
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$uids = $replyInfo->uid ? [$replyInfo->uid] : [];
$vipArray = $userService->checkIsVip($uids);
$userInfo = $userService->get($replyInfo->uid, ['nickname', 'uid', 'avatar', 'is_money_level']);
$userInfo = $userInfo ? $userInfo->toArray() : [];
$userInfo['level_name'] = $vipArray[$replyInfo->uid]['level_name'] ?? '';
$userInfo['vip_status'] = $vipArray[$replyInfo->uid]['vip_status'] ?? '';
$userInfo['nickname'] = anonymity($userInfo['nickname'] ?? '');
$replyInfo->nickname = anonymity($replyInfo['nickname'] ?? '');
$data['reply'] = $replyInfo->toArray();
$data['reply']['add_time'] = $data['reply']['add_time'] ? date('Y-m-d H:i:s', $data['reply']['add_time']) : '';
$data['reply']['suk'] = $replyInfo['sku'] ?? '';
/** @var StoreProductReplyCommentServices $commentService */
$commentService = app()->make(StoreProductReplyCommentServices::class);
$data['reply']['comment_sum'] = $commentService->count(['reply_id' => $id, 'pid' => 0]);
$data['product'] = $productInfo ? $productInfo->toArray() : [];
$data['user'] = $userInfo;
$data['star'] = bcdiv(bcadd($data['reply']['product_score'], $data['reply']['service_score'], 2), 2, 0);
/** @var UserRelationServices $make */
$make = app()->make(UserRelationServices::class);
$data['is_praise'] = !!$make->getUserCount($uid, $id, UserRelationServices::TYPE_LIKE, UserRelationServices::CATEGORY_REPLY);
//记录浏览量
$replyInfo->views_num++;
$replyInfo->save();
return $data;
}
/**
* 新版本获取商品评价
* @param int $id
* @param int $type
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getNewProductReplyList(int $id, int $type, int $uid)
{
if (!$id) {
return [];
}
[$page, $limit] = $this->getPageValue();
$list = $this->dao->replyList($id, $type, $page, $limit, ['userInfo',
'replyComment' => function ($query) {
$query->with([
'user' => function ($query) {
$query->field('uid,avatar,nickname');
}
])->where('pid', 0)->field(['uid', 'reply_id', 'content'])
->order('praise desc,create_time desc');
},
'productRelation' => function ($query) use ($uid) {
$query->where('uid', $uid)->where('type', UserRelationServices::TYPE_LIKE)->where('category', UserRelationServices::CATEGORY_REPLY)->field(['uid', 'relation_id']);
}
]);
if ($list) {
$siteLogoSquare = sys_config('site_logo_square');
$siteName = sys_config('site_name');
$supplierIds = $storeIds = [];
foreach ($list as $value) {
switch ($value['type']) {
case 0:
break;
case 2://供应商
$supplierIds[] = $value['relation_id'];
break;
}
}
$supplierIds = array_unique($supplierIds);
$supplierList = [];
if ($supplierIds) {
/** @var SystemSupplierServices $supplierServices */
$supplierServices = app()->make(SystemSupplierServices::class);
$supplierList = $supplierServices->getColumn([['id', 'in', $supplierIds], ['is_del', '=', 0]], 'id,supplier_name,avatar', 'id');
}
$replyId = array_column($list, 'id');
/** @var StoreProductReplyCommentServices $make */
$make = app()->make(StoreProductReplyCommentServices::class);
$replySum = $make->getReplyCommentCountList($replyId);
//是否付费会员和等级会员
$uids = array_column($list, 'uid');
$uids = array_filter(array_unique($uids));
/** @var UserServices $userServices */
$userServices = app()->make(UserServices::class);
$vipArray = $userServices->checkIsVip($uids);
foreach ($list as &$item) {
$item['suk'] = $item['sku'];
$item['nickname'] = anonymity($item['nickname']);
$item['merchant_reply_time'] = date('Y-m-d H:i', $item['merchant_reply_time']);
$item['add_time'] = date('Y-m-d H:i', $item['add_time']);
$item['star'] = bcadd((string)bcadd((string)$item['product_score'], (string)$item['service_score'], 2), (string)$item['delivery_score'], 2);
$item['star'] = bcdiv($item['star'], '3', 0);
$item['comment'] = $item['comment'] ?: '此用户没有填写评价';
$item['pics'] = $item['pics'] ? (is_string($item['pics']) ? json_decode($item['pics'], true) : $item['pics']) : [];
if (isset($item['replyComment']['user']['nickname'])) {
$item['replyComment']['user']['nickname'] = anonymity($item['replyComment']['user']['nickname']);
} else if (isset($item['replyComment']) && !$item['replyComment']['user'] && $item['replyComment']['uid'] === 0) {
$user = [];
switch ($item['replyComment']['type'] ?? 0) {
case 0:
$user = ['nickname' => $siteName, 'avatar' => $siteLogoSquare];
break;
case 2://供应商
$user = ['nickname' => $supplierList[$item['relation_id']]['supplier_name'] ?? '', 'avatar' => $supplierList[$item['relation_id']]['avatar'] ?? ''];
break;
default:
$user = ['nickname' => $siteName, 'avatar' => $siteLogoSquare];
break;
}
$item['replyComment']['user'] = $user;
}
if ($uid) {
$item['is_praise'] = !empty($item['productRelation']);
} else {
$item['is_praise'] = false;
}
if (isset($item['replyComment'])) {
foreach ($replySum as $value) {
if ($item['id'] === $value['reply_id']) {
$item['replyComment']['sum'] = $value['sum'];
}
}
}
$item['level_name'] = $vipArray[$item['uid']]['level_name'] ?? '';
$item['vip_status'] = $vipArray[$item['uid']]['vip_status'] ?? '';
}
}
return $list;
}
/**
* 计算商品评分
* @param int $id
* @return bool
*/
public function computedProductStar(int $id)
{
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$productInfo = $productServices->get($id, ['id', 'star']);
if ($productInfo) {
$starList = $this->dao->getColumn(['product_id' => $id, 'is_del' => 0], 'product_score,product_id');
if ($starList) {
$star = bcdiv((string)array_sum(array_column($starList, 'product_score')), (string)count($starList), 1);
$productServices->update(['id' => $id], ['star' => $star]);
$productServices->cacheTag()->clear();
}
}
return true;
}
/**
* 评论赠送积分和经验值
* @param $oid
* @param $uid
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/4/22
*/
public function replySendPointExp($oid, $uid)
{
$userServices = app()->make(UserServices::class);
$userInfo = $userServices->getUserInfo($uid);
if (!$userInfo) {
return true;
}
/** @var UserBillServices $userBillServices */
$userBillServices = app()->make(UserBillServices::class);
if (sys_config('reply_point_switch') == 1) {
$replayPointDay = sys_config('reply_point_day');
$replayPointOnce = sys_config('reply_point_once');
$replyDayGetPoint = $userBillServices->getBillSum([
'uid' => $uid,
'type' => 'reply_add_integral',
'time' => [strtotime(date('Y-m-d')), time()]
]);
if ($replayPointDay == 0 || $replyDayGetPoint < $replayPointDay) {
$balance = bcadd((string)$userInfo['integral'], (string)$replayPointOnce);
$userBillServices->income('reply_add_integral', $uid, (int)$replayPointOnce, (int)$balance, $oid);
$userServices->update($uid, ['integral' => $balance]);
}
}
if (sys_config('reply_exp_switch') == 1) {
$replayExpDay = sys_config('reply_exp_day');
$replayExpOnce = sys_config('reply_exp_once');
$replyDayGetExp = $userBillServices->getBillSum([
'uid' => $uid,
'type' => 'reply_add_exp',
'time' => [strtotime(date('Y-m-d')), time()]
]);
if ($replayExpDay == 0 || $replyDayGetExp < $replayExpDay) {
$balance = bcadd((string)$userInfo['exp'], (string)$replayExpOnce);
$userBillServices->income('reply_add_exp', $uid, (int)$replayExpOnce, (int)$balance, $oid);
$userServices->update($uid, ['exp' => $balance]);
}
}
return true;
}
/**
* 批量AI生成并保存商品评价
* @param array $data 评价数据列表
* @return bool
*/
public function saveAllAiReply($data)
{
try {
foreach ($data as $item) {
$saveData = [
'nickname' => $item['nickname'],
'avatar' => $item['avatar'],
'comment' => $item['comment'],
'pics' => [],
'reply_score' => $item['reply_score'],
'product_score' => $item['product_score'],
'service_score' => $item['service_score'],
'delivery_score' => $item['delivery_score'],
'product_id' => $item['product_id'],
'sku_unique' => $item['unique'],
'add_time' => $item['add_time'],
];
$this->saveReply($saveData);
}
} catch (\Exception $e) {
}
return true;
}
}

View File

@@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\product\product;
use app\services\BaseServices;
use app\dao\product\product\StoreProductReplyStoreProductDao;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
/**
*
* Class StoreProductReplyStoreProductServices
* @package app\services\product\product
* @mixin StoreProductReplyStoreProductDao
*/
class StoreProductReplyStoreProductServices extends BaseServices
{
/**
* @var StoreProductReplyStoreProductDao
*/
#[Inject]
protected StoreProductReplyStoreProductDao $dao;
/**
* 获取评论列表
* @param array $where
* @param array $with
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getProductReplyList(array $where, array $with = [])
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getProductReplyList($where, $page, $limit, $with);
$count = $this->dao->replyCount($where);
return compact('list', 'count');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductStockRecordDao;
use app\services\BaseServices;
use app\services\product\sku\StoreProductAttrValueServices;
use think\annotation\Inject;
/**
* 商品库存记录
* Class StoreProductStockRecordServices
* @package app\services\product\product
* @mixin StoreProductStockRecordDao
*/
class StoreProductStockRecordServices extends BaseServices
{
/**
* @var StoreProductStockRecordDao
*/
#[Inject]
protected StoreProductStockRecordDao $dao;
/**
* 保存入库、出库记录
* @param int $id
* @param array $attrs
* @param int $type
* @param int $store_id
* @return bool
*/
public function saveRecord(int $id, array $attrs, int $type = 0, int $store_id = 0)
{
if (!$attrs) {
return false;
}
try {
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
//原来规格数据
$attrValues = $storeProductAttrValueServices->getProductAttrValue(['product_id' => $id, 'type' => $type]);
if ($attrValues) $attrValues = array_combine(array_column($attrValues, 'unique'), $attrValues);
$time = time();
$dataAll = $data = [];
foreach ($attrs as $attr) {
$data = [
'store_id' => $store_id,
'product_id' => $id,
'unique' => $attr['unique'],
'cost_price' => $attrValues[$attr['unique']]['cost'] ?? 0,
'add_time' => $time,
];
if (!isset($attrValues[$attr['unique']]) || $attr['stock'] > $attrValues[$attr['unique']]['stock']) {
$data['pm'] = 1;
$data['number'] = !isset($attrValues[$attr['unique']]) ? $attr['stock'] : bcsub((string)$attr['stock'], (string)$attrValues[$attr['unique']]['stock'], 2);
} else {
$data['pm'] = 0;
$data['number'] = !isset($attrValues[$attr['unique']]) ? $attr['stock'] : bcsub((string)$attrValues[$attr['unique']]['stock'], (string)$attr['stock'], 2);
}
if ($data['number']) $dataAll[] = $data;
}
if ($dataAll) {
$this->dao->saveAll($dataAll);
}
} catch (\Throwable $e) {
}
return true;
}
}

View File

@@ -0,0 +1,114 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductUnitDao;
use app\services\BaseServices;
use crmeb\services\FormBuilder;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 商品单位
* Class StoreProductUnitServices
* @package app\services\product\product
* @mixin StoreProductUnitDao
*/
class StoreProductUnitServices extends BaseServices
{
/**
* @var StoreProductUnitDao
*/
#[Inject]
protected StoreProductUnitDao $dao;
/**
* 获取所有商品单位列表
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllUnitList(int $type = 0, int $relation_id = 0)
{
$where = [];
// $where['type'] = $type;
// $where['relation_id'] = $relation_id;
$where['status'] = 1;
$where['is_del'] = 0;
return $this->dao->getList($where, 'id,name');
}
/**
* 获取商品单位
* @param array $where
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUnitList(array $where, string $field = '*')
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $field, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 创建表单
* @param array $cataData
* @return array
*/
public function unitCateForm(array $cataData = [])
{
$f[] = FormBuilder::input('name', '单位名称:', $cataData['name'] ?? '')->maxlength(15)->required();
$f[] = FormBuilder::number('sort', '排序:', (int)($cataData['sort'] ?? 0))->min(0);
return $f;
}
/**
* 创建表单
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
*/
public function createForm()
{
return create_form('添加商品单位', $this->unitCateForm(), $this->url('/product/unit'), 'POST');
}
/**
* 修改分类标签表单
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updateForm(int $id)
{
$unit = $this->dao->get($id);
if (!$unit) {
throw new ValidateException('分类标签没有查到');
}
return create_form('编辑商品单位', $this->unitCateForm($unit->toArray()), $this->url('/product/unit/' . $id), 'PUT');
}
}

View File

@@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductVisitDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class StoreProductVisitServices
* @package app\services\product\product
* @mixin StoreProductVisitDao
*/
class StoreProductVisitServices extends BaseServices
{
/**
* @var StoreProductVisitDao
*/
#[Inject]
protected StoreProductVisitDao $dao;
}

View File

@@ -0,0 +1,161 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreProductWordsDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
/**
* 热词
* Class StoreProductWordsServices
* @package app\services\product\product
* @mixin StoreProductWordsDao
*/
class StoreProductWordsServices extends BaseServices
{
/**
* @var StoreProductWordsDao
*/
#[Inject]
protected StoreProductWordsDao $dao;
/**
* 获取所有商品单位列表
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllWordsList(int $type = 0, int $relation_id = 0)
{
$where = [];
// $where['type'] = $type;
// $where['relation_id'] = $relation_id;
$where['is_show'] = 1;
$where['is_del'] = 0;
return $this->dao->getList($where, 'id,name');
}
/**
* 获取商品单位
* @param array $where
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getWordsList(array $where, string $field = '*')
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $field, $page, $limit);
if ($list) {
foreach ($list as &$item) {
$item['add_time'] = date('Y-m-d H:i:s', time());
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 保存数据
* @param int $id
* @param array $data
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveData(int $id, array $data)
{
$words = $this->dao->getOne(['name' => $data['name'], 'is_del' => 0, 'type' => 0]);
if ($id) {
if ($words && $words['id'] != $id) {
throw new AdminException('热词名称已经存在');
}
if ($this->dao->update($id, $data)) {
$data['id'] = $id;
$this->cacheUpdate($data);
return true;
} else {
return false;
}
} else {
if ($words) {
throw new AdminException('热词已经存在,请勿重复添加');
}
$data['add_time'] = time();
if ($res = $this->dao->save($data)) {
$id = $res->id;
$data['id'] = $id;
$this->cacheUpdate($data);
return true;
} else {
return false;
}
}
}
/**
* 设置状态
* @param $id
* @param $is_show
*/
public function setShow(int $id, int $is_show)
{
$res = $this->dao->update($id, ['is_show' => $is_show]);
if (!$res) {
throw new AdminException('设置失败');
}
//设置缓存
if (!$is_show) {
$this->cacheDelById($id);
return;
}
$branInfo = $this->dao->cacheInfoById($id);
if ($branInfo) {
$branInfo['is_show'] = 1;
} else {
$branInfo = $this->dao->get($id);
if (!$branInfo) {
return;
}
$branInfo = $branInfo->toArray();
}
$this->dao->cacheUpdate($branInfo);
}
/**
* 删除数据
* @param int $id
*/
public function del(int $id)
{
$res = $this->dao->delete($id);
if (!$res) throw new AdminException('删除失败');
//更新缓存
$this->cacheDelById($id);
}
}

View File

@@ -0,0 +1,82 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\product;
use app\dao\product\product\StoreVisitDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class StoreVisitService
* @package app\services\product\product
* @mixin StoreVisitDao
*/
class StoreVisitServices extends BaseServices
{
/**
* @var StoreVisitDao
*/
#[Inject]
protected StoreVisitDao $dao;
/**
* 设置浏览信息
* @param $uid
* @param array $productIds
* @param string $product_type
* @param int $cate
* @param string $type
* @param string $content
* @param int $min
* @return bool
*/
public function setView($uid, $productIds = [], $product_type = 'product', $cate = 0, $type = '', $content = '', $min = 20)
{
if (!$productIds) {
return true;
}
if (!is_array($productIds)) {
$productIds = [$productIds];
}
$views = $this->dao->getColumn(['uid' => $uid, 'product_id' => $productIds, 'product_type' => $product_type], 'count,add_time,id', 'product_id');
$cate = explode(',', $cate)[0];
$dataAll = [];
$time = time();
foreach ($productIds as $key => $product_id) {
if (isset($views[$product_id]) && $type != 'search') {
$view = $views[$product_id] ?? [];
if ($view && ($view['add_time'] + $min) < $time) {
$this->dao->update($view['id'], ['count' => $view['count'] + 1, 'add_time' => time()]);
}
} else {
$data = [
'add_time' => $time,
'count' => 1,
'product_id' => $product_id,
'product_type' => $product_type,
'cate_id' => $cate,
'type' => $type,
'uid' => $uid,
'content' => $content
];
$dataAll[] = $data;
}
}
if ($dataAll) {
if (!$this->dao->saveAll($dataAll)) {
throw new ValidateException('添加失败');
}
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesFreeCityDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 包邮和城市数据连表业务处理层
* Class ShippingTemplatesFreeCityServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesFreeCityDao
*/
class ShippingTemplatesFreeCityServices extends BaseServices
{
/**
* @var ShippingTemplatesFreeCityDao
*/
#[Inject]
protected ShippingTemplatesFreeCityDao $dao;
}

View File

@@ -0,0 +1,217 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesFreeDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\utils\Arr;
use think\annotation\Inject;
/**
* 包邮
* Class ShippingTemplatesFreeServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesFreeDao
*/
class ShippingTemplatesFreeServices extends BaseServices
{
/**
* @var ShippingTemplatesFreeDao
*/
#[Inject]
protected ShippingTemplatesFreeDao $dao;
/**
* @param array $tempIds
* @param array $cityIds
* @param int $expire
* @return bool|mixed|null
* @throws \Throwable
*/
public function isFreeListCache(array $tempIds, array $cityIds, int $expire = 60)
{
return CacheService::redisHandler('apiShipping')->remember(md5('isFreeList' . json_encode([$tempIds, $cityIds])), function () use ($tempIds, $cityIds) {
return $this->dao->isFreeList($tempIds, $cityIds, 0, 'temp_id,number,price', 'temp_id');
}, $expire);
}
/**
* 添加包邮信息
* @param array $appointInfo
* @param int $group
* @param int $tempId
* @return bool
* @throws \Exception
*/
public function saveFreeV1(array $appointInfo, int $group = 0, int $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$placeList = [];
mt_srand();
foreach ($appointInfo as $item) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
foreach ($item['city_ids'] as $cityId) {
$placeList [] = [
'temp_id' => $tempId,
'city_id' => $cityId[count($cityId) - 1],
'value' => json_encode($cityId),
'number' => $item['number'] ?? 0,
'price' => $item['price'] ?? 0,
'group' => $group,
'uniqid' => $uniqid,
];
}
}
if (count($placeList)) {
return $res && $this->dao->saveAll($placeList);
} else {
return $res;
}
}
/**
* 添加包邮信息
* @param array $appointInfo
* @param int $group
* @param int $tempId
* @return bool
* @throws \Exception
*/
public function saveFree(array $appointInfo, int $group = 0, int $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$placeList = [];
mt_srand();
foreach ($appointInfo as $item) {
if (isset($item['place']) && is_array($item['place'])) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
foreach ($item['place'] as $value) {
if (isset($value['children']) && is_array($value['children'])) {
foreach ($value['children'] as $vv) {
if (!isset($vv['city_id'])) {
throw new AdminException('缺少城市id无法保存');
}
$placeList [] = [
'temp_id' => $tempId,
'province_id' => $value['city_id'] ?? 0,
'city_id' => $vv['city_id'] ?? 0,
'number' => $item['a_num'] ?? 0,
'price' => $item['a_price'] ?? 0,
'group' => $group,
'uniqid' => $uniqid,
];
}
}
}
}
}
if (count($placeList)) {
return $res && $this->dao->saveAll($placeList);
} else {
return $res;
}
}
/**
* @param int $tempId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getFreeListV1(int $tempId)
{
$freeList = $this->dao->getShippingList(['temp_id' => $tempId]);
return Arr::formatShipping($freeList);
}
/**
* 获得指定包邮城市地址
* @param int $tempId
* @return array
*/
public function getFreeList(int $tempId)
{
$freeIdList = $this->dao->getShippingGroupArray(['temp_id' => $tempId], 'uniqid', 'uniqid', '');
$freeData = [];
$infos = $this->dao->getShippingArray(['uniqid' => $freeIdList, 'temp_id' => $tempId], '*', 'uniqid');
foreach ($freeIdList as $uniqid) {
$info = $infos[$uniqid];
$freeData[] = [
'place' => $this->getFreeTemp($uniqid, $info['province_id']),
'a_num' => $info['number'] ? floatval($info['number']) : 0,
'a_price' => $info['price'] ? floatval($info['price']) : 0,
];
}
foreach ($freeData as &$item) {
$item['placeName'] = implode(';', array_column($item['place'], 'name'));
}
return $freeData;
}
/**
* 获取包邮的省份
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getFreeTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesFreeCityServices $service */
$service = app()->make(ShippingTemplatesFreeCityServices::class);
$infoList = $service->getUniqidList(['uniqid' => $uniqid]);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['province_id'],
'name' => $item['name'] ?? '全国',
'children' => $this->getCityTemp($uniqid, $provinceId)
];
}
return $childrenData;
}
/**
* 获取市区数据
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getCityTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesFreeCityServices $service */
$service = app()->make(ShippingTemplatesFreeCityServices::class);
$infoList = $service->getUniqidList(['uniqid' => $uniqid, 'province_id' => $provinceId], false);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['city_id'],
'name' => $item['name'] ?? '全国',
];
}
return $childrenData;
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesNoDeliveryCityDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 不送达和城市数据连表业务处理层
* Class ShippingTemplatesNoDeliveryCityServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesNoDeliveryCityDao
*/
class ShippingTemplatesNoDeliveryCityServices extends BaseServices
{
/**
* @var ShippingTemplatesNoDeliveryCityDao
*/
#[Inject]
protected ShippingTemplatesNoDeliveryCityDao $dao;
}

View File

@@ -0,0 +1,191 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesNoDeliveryDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\utils\Arr;
use think\annotation\Inject;
/**
* 不送达
* Class ShippingTemplatesNoDeliveryServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesNoDeliveryDao
*/
class ShippingTemplatesNoDeliveryServices extends BaseServices
{
/**
* @var ShippingTemplatesNoDeliveryDao
*/
#[Inject]
protected ShippingTemplatesNoDeliveryDao $dao;
/**
* 添加不送达信息
* @param array $noDeliveryInfo
* @param int $tempId
* @return bool|mixed
*/
public function saveNoDeliveryV1(array $noDeliveryInfo, int $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$placeList = [];
mt_srand();
foreach ($noDeliveryInfo as $item) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
foreach ($item['city_ids'] as $cityId) {
$placeList [] = [
'temp_id' => $tempId,
'city_id' => $cityId[count($cityId) - 1],
'value' => json_encode($cityId),
'uniqid' => $uniqid,
];
}
}
if (count($placeList)) {
return $res && $this->dao->saveAll($placeList);
} else {
return $res;
}
}
/**
* 添加不送达信息
* @param array $noDeliveryInfo
* @param int $tempId
* @return bool|mixed
*/
public function saveNoDelivery(array $noDeliveryInfo, int $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$placeList = [];
mt_srand();
foreach ($noDeliveryInfo as $item) {
if (isset($item['place']) && is_array($item['place'])) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
foreach ($item['place'] as $value) {
if (isset($value['children']) && is_array($value['children'])) {
foreach ($value['children'] as $vv) {
if (!isset($vv['city_id'])) {
throw new AdminException('缺少城市id无法保存');
}
$placeList [] = [
'temp_id' => $tempId,
'province_id' => $value['city_id'] ?? 0,
'city_id' => $vv['city_id'] ?? 0,
'uniqid' => $uniqid,
];
}
}
}
}
}
if (count($placeList)) {
return $res && $this->dao->saveAll($placeList);
} else {
return $res;
}
}
/**
* @param int $tempId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getNoDeliveryListV1(int $tempId)
{
$freeList = $this->dao->getShippingList(['temp_id' => $tempId]);
return Arr::formatShipping($freeList);
}
/**
* 获得指定包邮城市地址
* @param int $tempId
* @return array
*/
public function getNoDeliveryList(int $tempId)
{
$freeIdList = $this->dao->getShippingGroupArray(['temp_id' => $tempId], 'uniqid', 'uniqid', '');
$freeData = [];
$infos = $this->dao->getShippingArray(['uniqid' => $freeIdList, 'temp_id' => $tempId], '*', 'uniqid');
foreach ($freeIdList as $uniqid) {
$info = $infos[$uniqid];
$freeData[] = [
'place' => $this->getNoDeliveryTemp($uniqid, $info['province_id']),
];
}
foreach ($freeData as &$item) {
$item['placeName'] = implode(';', array_column($item['place'], 'name'));
}
return $freeData;
}
/**
* 获取不送达的省份
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getNoDeliveryTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesNoDeliveryCityServices $service */
$service = app()->make(ShippingTemplatesNoDeliveryCityServices::class);
$infoList = $service->getUniqidList(['uniqid' => $uniqid]);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['province_id'],
'name' => $item['name'] ?? '全国',
'children' => $this->getCityTemp($uniqid, $provinceId)
];
}
return $childrenData;
}
/**
* 获取市区数据
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getCityTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesNoDeliveryCityServices $service */
$service = app()->make(ShippingTemplatesNoDeliveryCityServices::class);
$infoList = $service->getUniqidList(['uniqid' => $uniqid, 'province_id' => $provinceId], false);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['city_id'],
'name' => $item['name'] ?? '全国',
];
}
return $childrenData;
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesRegionCityDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 根据地区设置邮费
* Class ShippingTemplatesRegionCityServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesRegionCityDao
*/
class ShippingTemplatesRegionCityServices extends BaseServices
{
/**
* @var ShippingTemplatesRegionCityDao
*/
#[Inject]
protected ShippingTemplatesRegionCityDao $dao;
}

View File

@@ -0,0 +1,255 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\shipping;
use app\dao\product\shipping\ShippingTemplatesRegionDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\utils\Arr;
use think\annotation\Inject;
/**
* 指定邮费
* Class ShippingTemplatesRegionServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesRegionDao
*/
class ShippingTemplatesRegionServices extends BaseServices
{
/**
* @var ShippingTemplatesRegionDao
*/
#[Inject]
protected ShippingTemplatesRegionDao $dao;
/**
* @param array $tempIds
* @param array $cityIds
* @param int $expire
* @return bool|mixed|null
* @throws \Throwable
*/
public function getTempRegionListCache(array $tempIds, array $cityIds, int $expire = 60)
{
return CacheService::redisHandler('apiShipping')->remember(md5('RegionList' . json_encode([$tempIds, $cityIds])), function () use ($tempIds, $cityIds) {
return $this->dao->getTempRegionList($tempIds, $cityIds, 'temp_id,first,first_price,continue,continue_price', 'temp_id');
}, $expire);
}
/**
* 添加运费信息
* @param array $regionInfo
* @param int $group
* @param int $tempId
* @return bool
* @throws \Exception
*/
public function saveRegionV1(array $regionInfo, int $group = 0, $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$regionList = [];
mt_srand();
foreach ($regionInfo as $item) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
if (isset($item['city_ids']) && $item['city_ids']) {
foreach ($item['city_ids'] as $value) {
$regionList[] = [
'temp_id' => $tempId,
'city_id' => $value ? $value[count($value) - 1] : 0,
'value' => json_encode($value),
'first' => $item['first'] ?? 0,
'first_price' => $item['first_price'] ?? 0,
'continue' => $item['continue'] ?? 0,
'continue_price' => $item['continue_price'] ?? 0,
'group' => $group,
'uniqid' => $uniqid,
];
}
}
}
return $res && $this->dao->saveAll($regionList);
}
/**
* 添加运费信息
* @param array $regionInfo
* @param int $group
* @param int $tempId
* @return bool
* @throws \Exception
*/
public function saveRegion(array $regionInfo, int $group = 0, $tempId = 0)
{
$res = true;
if ($tempId) {
if ($this->dao->count(['temp_id' => $tempId])) {
$res = $this->dao->delete($tempId, 'temp_id');
}
}
$regionList = [];
mt_srand();
foreach ($regionInfo as $item) {
if (isset($item['region']) && is_array($item['region'])) {
$uniqid = uniqid('adminapi') . rand(1000, 9999);
foreach ($item['region'] as $value) {
if (isset($value['children']) && is_array($value['children'])) {
foreach ($value['children'] as $vv) {
if (!isset($vv['city_id'])) {
throw new AdminException('缺少城市id无法保存');
}
$regionList[] = [
'temp_id' => $tempId,
'province_id' => $value['city_id'] ?? 0,
'city_id' => $vv['city_id'] ?? 0,
'first' => $item['first'] ?? 0,
'first_price' => $item['price'] ?? 0,
'continue' => $item['continue'] ?? 0,
'continue_price' => $item['continue_price'] ?? 0,
'group' => $group,
'uniqid' => $uniqid,
];
}
} else {
$regionList[0] = [
'temp_id' => $tempId,
'province_id' => 0,
'city_id' => 0,
'first' => $item['first'] ?? 0,
'first_price' => $item['price'] ?? 0,
'continue' => $item['continue'] ?? 0,
'continue_price' => $item['continue_price'] ?? 0,
'group' => $group,
'uniqid' => $uniqid,
];
}
}
}
}
return $res && $this->dao->saveAll($regionList);
}
/**
* @param int $tempId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getRegionListV1(int $tempId)
{
$freeList = $this->dao->getShippingList(['temp_id' => $tempId]);
return Arr::formatShipping($freeList);
}
/**
* 获取某个运费模板下的城市数据
* @param int $tempId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getRegionList(int $tempId)
{
$regionList = $this->dao->getShippingGroupArray(['temp_id' => $tempId], 'uniqid', 'uniqid', '');
$regionData = [];
$infos = $this->dao->getShippingArray(['uniqid' => $regionList, 'temp_id' => $tempId], '*', 'uniqid');
foreach ($regionList as $uniqid) {
$info = $infos[$uniqid];
if ($info['province_id'] == 0) {
$regionData[] = [
'region' => [
'city_id' => 0,
'name' => '默认全国',
],
'regionName' => '默认全国',
'first' => $info['first'] ? floatval($info['first']) : 0,
'price' => $info['first_price'] ? floatval($info['first_price']) : 0,
'continue' => $info['continue'] ? floatval($info['continue']) : 0,
'continue_price' => $info['continue_price'] ? floatval($info['continue_price']) : 0,
'uniqid' => $info['uniqid'],
];
} else {
$regionData[] = [
'region' => $this->getRegionTemp($uniqid, $info['province_id']),
'regionName' => '',
'first' => $info['first'] ? floatval($info['first']) : 0,
'price' => $info['first_price'] ? floatval($info['first_price']) : 0,
'continue' => $info['continue'] ? floatval($info['continue']) : 0,
'continue_price' => $info['continue_price'] ? floatval($info['continue_price']) : 0,
'uniqid' => $info['uniqid'],
];
}
}
foreach ($regionData as &$item) {
if (!$item['regionName']) {
$item['regionName'] = implode(';', array_map(function ($val) {
return $val['name'];
}, $item['region']));
}
}
return $regionData;
}
/**
* 获取省份下运费模板
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getRegionTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesRegionCityServices $services */
$services = app()->make(ShippingTemplatesRegionCityServices::class);
$infoList = $services->getUniqidList(['uniqid' => $uniqid]);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['province_id'],
'name' => $item['name'] ?? '全国',
'children' => $this->getCityTemp($uniqid, $item['province_id'])
];
}
return $childrenData;
}
/**
* 获取市区下的数据
* @param string $uniqid
* @param int $provinceId
* @return array
*/
public function getCityTemp(string $uniqid, int $provinceId)
{
/** @var ShippingTemplatesRegionCityServices $services */
$services = app()->make(ShippingTemplatesRegionCityServices::class);
$infoList = $services->getUniqidList(['uniqid' => $uniqid, 'province_id' => $provinceId], false);
$childrenData = [];
foreach ($infoList as $item) {
$childrenData[] = [
'city_id' => $item['city_id'],
'name' => $item['name'] ?? '全国',
];
}
return $childrenData;
}
}

View File

@@ -0,0 +1,189 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\services\product\shipping;
use app\services\BaseServices;
use app\dao\product\shipping\ShippingTemplatesDao;
use app\services\product\product\StoreProductServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use think\annotation\Inject;
/**
* 运费模板
* Class ShippingTemplatesServices
* @package app\services\product\shipping
* @mixin ShippingTemplatesDao
*/
class ShippingTemplatesServices extends BaseServices
{
/**
* 计费类型
* @var string[]
*/
protected array $group = [
1 => '按件数',
2 => '按重量',
3 => '按体积'
];
/**
* @var ShippingTemplatesDao
*/
#[Inject]
protected ShippingTemplatesDao $dao;
/**
* 获取运费模板列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getShippingList(array $where)
{
[$page, $limit] = $this->getPageValue();
$data = $this->dao->getShippingList($where, $page, $limit);
if ($data) {
$groupName = $this->group;
foreach ($data as &$item) {
$item['type'] = $groupName[$item['group']] ?? '';
}
}
$count = $this->dao->count($where);
return compact('data', 'count');
}
/**
* @param array $where
* @param $field
* @param string|null $key
* @param int $exprie
* @return bool|mixed|null
*/
public function getShippingColumnCache(array $where, $field, string $key = null, int $exprie = 60)
{
return CacheService::redisHandler('apiShipping')->remember(md5('Shipping_column' . json_encode($where)), function () use ($where, $field, $key) {
return $this->dao->getShippingColumn($where, $field, $key);
}, $exprie);
}
/**
* 获取需要修改的运费模板
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getShipping(int $id)
{
$templates = $this->dao->get($id);
if (!$templates) {
throw new AdminException('修改的模板不存在');
}
/** @var ShippingTemplatesFreeServices $freeServices */
$freeServices = app()->make(ShippingTemplatesFreeServices::class);
/** @var ShippingTemplatesRegionServices $regionServices */
$regionServices = app()->make(ShippingTemplatesRegionServices::class);
/** @var ShippingTemplatesNoDeliveryServices $noDeliveryServices */
$noDeliveryServices = app()->make(ShippingTemplatesNoDeliveryServices::class);
$data['appointList'] = $freeServices->getFreeListV1($id);
$data['templateList'] = $regionServices->getRegionListV1($id);
$data['noDeliveryList'] = $noDeliveryServices->getNoDeliveryListV1($id);
if (!isset($data['templateList'][0]['city_ids'])) {
$data['templateList'][0] = ['city_ids' => [[0]], 'regionName' => '默认全国'];
}
$data['formData'] = [
'name' => $templates->name,
'type' => $templates->getData('group'),
'appoint_check' => intval($templates->getData('appoint')),
'no_delivery_check' => intval($templates->getData('no_delivery')),
'sort' => intval($templates->getData('sort')),
];
return $data;
}
/**
* 保存或者修改运费模板
* @param int $id
* @param array $temp
* @param array $data
* @return mixed
*/
public function save(int $id, array $temp, array $data)
{
/** @var ShippingTemplatesRegionServices $regionServices */
$regionServices = app()->make(ShippingTemplatesRegionServices::class);
$this->transaction(function () use ($regionServices, $data, $id, $temp) {
if ($id) {
$res = $this->dao->update($id, $temp);
} else {
$res = $this->dao->save($temp);
}
if (!$res) {
throw new AdminException('保存失败');
}
if (!$id) $id = $res->id;
//设置区域配送
if (!$regionServices->saveRegionV1($data['region_info'], (int)$data['group'], (int)$id)) {
throw new AdminException('指定区域邮费添加失败!');
}
//设置指定包邮
if ($data['appoint']) {
/** @var ShippingTemplatesFreeServices $freeServices */
$freeServices = app()->make(ShippingTemplatesFreeServices::class);
if (!$freeServices->saveFreeV1($data['appoint_info'], (int)$data['group'], (int)$id)) {
throw new AdminException('指定包邮添加失败!');
}
}
//设置不送达
if ($data['no_delivery']) {
/** @var ShippingTemplatesNoDeliveryServices $noDeliveryServices */
$noDeliveryServices = app()->make(ShippingTemplatesNoDeliveryServices::class);
if (!$noDeliveryServices->saveNoDeliveryV1($data['no_delivery_info'], (int)$id)) {
throw new AdminException('指定不送达添加失败!');
}
}
});
return true;
}
/**
* 删除运费模板
* @param int $id
*/
public function detete(int $id)
{
$this->dao->delete($id);
/** @var ShippingTemplatesFreeServices $freeServices */
$freeServices = app()->make(ShippingTemplatesFreeServices::class);
/** @var ShippingTemplatesRegionServices $regionServices */
$regionServices = app()->make(ShippingTemplatesRegionServices::class);
/** @var ShippingTemplatesNoDeliveryServices $noDeliveryServices */
$noDeliveryServices = app()->make(ShippingTemplatesNoDeliveryServices::class);
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$this->transaction(function () use ($id, $freeServices, $regionServices, $noDeliveryServices, $storeProductServices) {
$freeServices->delete($id, 'temp_id');
$regionServices->delete($id, 'temp_id');
$noDeliveryServices->delete($id, 'temp_id');
$storeProductServices->update(['temp_id' => $id], ['temp_id' => 1]);
});
}
}

View File

@@ -0,0 +1,143 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\sku;
use app\dao\product\sku\StoreProductAttrResultDao;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
/**
* Class StoreProductAttrResultService
* @package app\services\product\sku
* @mixin StoreProductAttrResultDao
*/
class StoreProductAttrResultServices extends BaseServices
{
/**
* @var StoreProductAttrResultDao
*/
#[Inject]
protected StoreProductAttrResultDao $dao;
/**
* 获取属性规格
* @param array $where
* @return mixed
*/
public function getResult(array $where)
{
return json_decode($this->dao->value($where, 'result'), true);
}
/**
* 删除属性
* @param int $id
* @param int $type
* @return bool
*/
public function del(int $id, int $type)
{
return $this->dao->del($id, $type);
}
/**
* 设置属性
* @param array $data
* @param int $id
* @param int $type
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function setResult(array $data, int $id, int $type)
{
$result = $this->dao->get(['product_id' => $id, 'type' => $type]);
if ($result) {
$res = $this->dao->update($result['id'], ['result' => json_encode($data), 'change_time' => time()]);
} else {
$res = $this->dao->save(['product_id' => $id, 'result' => json_encode($data), 'change_time' => time(), 'type' => $type]);
}
if (!$res) throw new AdminException('规格保存失败');
return true;
}
/**
* 无属性添加默认属性
* @param int $id
* @param int $type
* @param array $productInfo
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function checkProductAttr(int $id, int $type = 0, array $productInfo = []):bool
{
if (!$id) {
return false;
}
try {
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
if (!$productInfo) {
$productInfo = $storeProductServices->getCacheProductInfo($id);
}
if (!$productInfo) {
return false;
}
if ($this->getResult(['product_id' => $id, 'type' => $type])) {
return true;
}
$attr = [
[
'value' => '规格',
'detailValue' => '',
'attrHidden' => '',
'detail' => ['默认']
]
];
$detail[0] = [
'value1' => '默认',
'detail' => ['规格' => '默认'],
'pic' => $productInfo['image'],
'price' => $productInfo['price'],
'settle_price' => $productInfo['settle_price'] ?? 0,
'cost' => $productInfo['cost'],
'ot_price' => $productInfo['ot_price'],
'stock' => $productInfo['stock'],
'bar_code' => '',
'weight' => 0,
'volume' => 0,
'brokerage' => 0,
'brokerage_two' => 0,
'code' => 0,
'write_times' => 1,
'write_valid' => 1,
'days' => 0,
'section_time' => []
];
/** @var StoreProductAttrServices $storeProductAttrServices */
$storeProductAttrServices = app()->make(StoreProductAttrServices::class);
$skuList = $storeProductAttrServices->validateProductAttr($attr, $detail, $id);
$storeProductAttrServices->saveProductAttr($skuList, $id, 0);
$storeProductServices->update($id, ['spec_type' => 0]);
} catch (\Throwable $e) {
}
return true;
}
}

View File

@@ -0,0 +1,606 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\sku;
use app\dao\product\sku\StoreProductAttrDao;
use app\services\BaseServices;
use app\services\order\StoreCartServices;
use app\services\product\product\StoreProductServices;
use app\services\user\channel\ChannelMerchantServices;
use crmeb\exceptions\AdminException;
use crmeb\services\CacheService;
use crmeb\traits\OptionTrait;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreProductAttrService
* @package app\services\product\sku
* @mixin StoreProductAttrDao
*/
class StoreProductAttrServices extends BaseServices
{
use OptionTrait;
/**
* @var StoreProductAttrDao
*/
#[Inject]
protected StoreProductAttrDao $dao;
/**
* 生成规格唯一值
* @param int $id
* @param string $sku
* @return false|string
*/
public function createAttrUnique(int $id, string $sku)
{
return substr(md5($id . $sku . uniqid(true)), 12, 8);
}
/**
* 根据根据前端规格数据获取sku
* @param array $value
* @param string
*/
public function getSku(array $value)
{
$sku = '';
if ($value) {
$detail = [];
$count = count($value['detail'] ?? []);
for ($i = 1; $i <= $count; $i++) {
$detail[] = trim($value['value' . $i]);
}
$sku = implode(',', $detail);
}
return $sku;
}
/**
* 添加商品属性数据判断
* @param array $attrList
* @param array $valueList
* @param int $productId
* @param int $type
* @param int $is_vip
* @param int $validate
* @return array
*/
public function validateProductAttr(array $attrList, array $valueList, int $productId, int $type = 0, int $is_vip = 0, int $validate = 1)
{
$attrValueList = [];
$attrNameList = [];
foreach ($attrList as $index => $attr) {
if (!isset($attr['value'])) {
throw new AdminException('请输入规则名称!');
}
$attr['value'] = trim($attr['value']);
if (!isset($attr['value'])) {
throw new AdminException('请输入规则名称!!');
}
if (!isset($attr['detail']) || !count($attr['detail'])) {
throw new AdminException('请输入属性名称!');
}
foreach ($attr['detail'] as $k => $attrValue) {
if (is_string($attrValue)) {
$attrValueTemp = $attrValue;
$attrValue = [];
$attrValue['value'] = $attrValueTemp;
$attrValue['pic'] = '';
} else {
$attrValue['value'] = trim($attrValue['value']);
$attrValue['pic'] = isset($attr['add_pic']) && $attr['add_pic'] ? trim($attrValue['pic']) : '';
}
if (empty($attrValue['value'])) {
throw new AdminException('请输入正确的属性');
}
$attr['detail'][$k] = $attrValue;
$attrValueList[] = $attrValue;
}
$attrNameList[] = $attr['value'];
$attrList[$index] = $attr;
}
$result = ['attr' => $attrList, 'value' => $valueList];
$attrCount = count($attrList);
foreach ($valueList as $index => $value) {
if (!isset($value['detail']) || count($value['detail']) != $attrCount) {
throw new AdminException('请填写正确的商品信息');
}
if (!isset($value['price']) || !is_numeric($value['price']) || floatval($value['price']) != $value['price']) {
throw new AdminException('请填写正确的商品价格');
}
if ($type == 4) {
if (!isset($value['integral']) || !is_numeric($value['integral']) || floatval($value['integral']) != $value['integral']) {
throw new AdminException('请填写正确的商品积分价格');
}
}
if (isset($value['price']) && $value['price'] <= 0 && isset($value['integral']) && $value['integral'] <= 0) {
throw new AdminException('积分商品兑换积分和价格不能同时为空');
}
if (!isset($value['stock']) || !is_numeric($value['stock']) || intval($value['stock']) != $value['stock']) {
throw new AdminException('请填写正确的商品库存');
}
//供应商结算价 不用成本价
if (!($value['settle_price'] ?? 0) && (!isset($value['cost']) || !is_numeric($value['cost']) || floatval($value['cost']) != $value['cost'])) {
throw new AdminException('请填写正确的商品成本价格');
}
if ($validate && (!isset($value['pic']) || empty($value['pic']))) {
throw new AdminException('请上传商品规格图片');
}
if ($is_vip && (!isset($value['vip_price']) || !$value['vip_price'])) {
throw new AdminException('会员价格不能为0');
}
foreach ($value['detail'] as $attrName => $attrValue) {
//如果attrName 存在空格 则这个规格key 会出现两次
unset($valueList[$index]['detail'][$attrName]);
$attrName = trim($attrName);
$attrValue = trim($attrValue);
if (!in_array($attrName, $attrNameList, true)) {
throw new AdminException($attrName . '规则不存在');
}
if (!in_array($attrValue, array_column($attrValueList, 'value'), true)) {
throw new AdminException($attrName . '属性不存在');
}
if (empty($attrName)) {
throw new AdminException('请输入正确的属性');
}
$valueList[$index]['detail'][$attrName] = $attrValue;
}
}
$attrGroup = [];
$valueGroup = [];
foreach ($attrList as $k => $value) {
$attrGroup[] = [
'product_id' => $productId,
'attr_name' => $value['value'],
'attr_values' => $value['detail'],
'type' => $type
];
}
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$skuArray = $storeProductAttrValueServices->getSkuArray(['product_id' => $productId, 'type' => $type], 'unique,vip_price,level_price,brokerage_two,brokerage,channel_level_price', 'suk');
$store_product_id = $this->getItem('store_product_id', 0);
$productSkuArray = [];
if ($store_product_id) {
$productSkuArray = $storeProductAttrValueServices->getSkuArray(['product_id' => $store_product_id, 'type' => 0], '*', 'suk');
}
$sort = 0;
$inventoryData = [];
foreach ($valueList as $k => $value) {
$sku = implode(',', array_map(fn($key) => $value['detail'][$key], array_column($attrList, 'value')));
$stock = $value['stock'];
if (isset($value['pm'])) {
//pm是 1 是增加 0 是减少
if ($value['pm'] == 1) {
$stock = (int)$value['stock'] + (int)$value['inventory'];
} else {
$stock = (int)$value['stock'] - (int)$value['inventory'];
if ($stock < 0) {
throw new AdminException('出库数量不能大于库存');
}
}
if ($value['inventory'] > 0) {
$inventoryData[$k]['pm'] = $value['pm'];
$inventoryData[$k]['inventory'] = $value['inventory'];
}
} else {
if ($value['stock'] > 0) {
$inventoryData[$k]['inventory'] = $value['stock'];
$inventoryData[$k]['pm'] = 1;
}
}
$valueGroup[$sku] = [
'product_id' => $productId,
'suk' => $sku,
'price' => $value['price'],
'integral' => $value['integral'] ?? 0,
'settle_price' => $value['settle_price'] ?? 0.00,//供应商结算价
'cost' => ($value['settle_price'] ?? 0.00) ?: $value['cost'],//供应端:去除成本价
'ot_price' => $value['ot_price'],
'stock' => $stock,
'unique' => $skuArray[$sku]['unique'] ?? $this->createAttrUnique($productId, $sku),
'image' => $value['pic'],
'bar_code' => $value['bar_code'] ?? '',
'weight' => $value['weight'] ?? 0,
'volume' => $value['volume'] ?? 0,
'brokerage' => $value['brokerage'] ?? 0,
'brokerage_two' => $value['brokerage_two'] ?? 0,
'type' => $type,
'quota' => $value['quota'] ?? 0,
'quota_show' => $value['quota'] ?? 0,
'vip_price' => $value['vip_price'] ?? 0,
'level_price' => $value['level_price'] ?? 0,
'channel_level_price' => $value['channel_level_price'] ?? 0,
'code' => $value['code'] ?? '',
'product_type' => $value['product_type'] ?? 0,
'is_default_select' => $value['is_default_select'] ?? 0,
'is_show' => $value['is_show'] ?? 1,
'virtual_list' => $productSkuArray[$sku]['virtual_list'] ?? $value['virtual_list'] ?? [],
'disk_info' => $productSkuArray[$sku]['disk_info'] ?? $value['disk_info'] ?? '',
'write_times' => $productSkuArray[$sku]['write_times'] ?? $value['write_times'] ?? 1,
'write_valid' => $productSkuArray[$sku]['write_valid'] ?? $value['write_valid'] ?? 1,
'write_days' => $productSkuArray[$sku]['write_days'] ?? $value['write_days'] ?? $value['days'] ?? 0,
'write_start' => ($productSkuArray[$sku]['section_time'][0] ?? $value['section_time'][0] ?? '') ? strtotime($productSkuArray[$sku]['section_time'][0] ?? $value['section_time'][0] ?? '') : 0,
'write_end' => ($productSkuArray[$sku]['section_time'][1] ?? $value['section_time'][1] ?? '') ? strtotime($productSkuArray[$sku]['section_time'][1] ?? $value['section_time'][1] ?? '') : 0,
'sort' => $sort
];
if (isset($inventoryData[$k]['inventory']) && $inventoryData[$k]['inventory'] > 0) {
$inventoryData[$k]['unique'] = $valueGroup[$sku]['unique'];
$inventoryData[$k]['product_id'] = $productId;
}
$sort = $sort + 1;
}
if (!count($attrGroup) || !count($valueGroup)) {
throw new AdminException('请设置至少一个属性!');
}
return compact('result', 'attrGroup', 'valueGroup', 'inventoryData');
}
/**
* 保存商品规格
* @param array $data
* @param int $id
* @param int $type
* @return bool|mixed|\think\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveProductAttr(array $data, int $id, int $type = 0)
{
$this->setAttr($data['attrGroup'], $id, $type);
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
$storeProductAttrResultServices->setResult($data['result'], $id, $type);
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$valueGroup = $data['valueGroup'] ?? [];
$updateSuks = array_column($valueGroup, 'suk');
$oldSuks = [];
$oldAttrValue = $storeProductAttrValueServices->getSkuArray(['product_id' => $id, 'type' => $type], '*', 'suk');
if ($oldAttrValue) $oldSuks = array_column($oldAttrValue, 'suk');
$delSuks = array_merge(array_diff($oldSuks, $updateSuks));
$dataAll = [];
$res1 = $res2 = $res3 = true;
foreach ($valueGroup as $item) {
$item['virtual_list'] = json_encode($item['virtual_list']);
$item['level_price'] = json_encode($item['level_price']);
$item['channel_level_price'] = json_encode($item['channel_level_price']);
if ($oldSuks && in_array($item['suk'], $oldSuks) && isset($oldAttrValue[$item['suk']])) {
$attrId = $oldAttrValue[$item['suk']]['id'];
unset($item['suk'], $item['unique']);
$res1 = $res1 && $storeProductAttrValueServices->update($attrId, $item);
} else {
$dataAll[] = $item;
}
}
if ($delSuks) {
$res2 = $storeProductAttrValueServices->del($id, $type, $delSuks);
}
if ($dataAll) {
$res3 = $storeProductAttrValueServices->saveAll($dataAll);
}
if ($res1 && $res2 && $res3) {
return $valueGroup;
} else {
throw new AdminException('商品规格信息保存失败');
}
}
/**
* 获取商品规格
* @param array $where
* @return array
*/
public function getProductAttr(array $where)
{
return $this->dao->getProductAttr($where);
}
/**
* 获取商品规格详情
* @param int $id
* @param int $uid
* @param int $cartNum //是否查询购物车数量
* @param int $type //活动类型 attr_value表
* @param int $productId
* @param array $productInfo
* @param int $discount //限时折扣
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductAttrDetailCache(int $id, int $uid, int $cartNum = 0, int $type = 0, int $productId = 0, array $productInfo = [], int $discount = -1, int $is_channel = 0)
{
$attrDetail = $this->dao->cacheTag()->remember('attr_' . $id . '_' . $type . '_' . $productId, function () use ($productId, $id, $type) {
$attrDetail = $this->dao->getProductAttr(['product_id' => $id, 'type' => $type]);
if (!$attrDetail && $type > 0 && $productId) {//活动商品未获取到规格信息
$attrDetail = $this->dao->getProductAttr(['product_id' => $productId, 'type' => 0]);
}
return $attrDetail;
}, 600);
/** @var StoreProductAttrValueServices $storeProductAttrValueService */
$storeProductAttrValueService = app()->make(StoreProductAttrValueServices::class);
$_values = $this->dao->cacheTag()->remember('attr_value_' . $id . '_' . $type, function () use ($storeProductAttrValueService, $id, $type) {
return $storeProductAttrValueService->getProductAttrValue(['product_id' => $id, 'type' => $type]);
}, 600);
$_values = $storeProductAttrValueService->getProductAttrValue(['product_id' => $id, 'type' => $type]);
if ($productId == 0) {
$productId = $id;
}
/** @var StoreProductServices $storeProductService */
$storeProductService = app()->make(StoreProductServices::class);
$vip_price = true;
if (!$storeProductService->vipIsOpen(!!($productInfo['is_vip'] ?? 0))) $vip_price = false;
$cartNumList = [];
$activityAttr = [];
if ($cartNum) {
/** @var StoreCartServices $storeCartService */
$storeCartService = app()->make(StoreCartServices::class);
$unique = array_column($_values, 'unique');
$cartNumList = $storeCartService->cacheTag('Cart_Nums_' . $uid)->remember(md5(json_encode($unique)),
function () use ($storeCartService, $unique, $id, $uid) {
return $storeCartService->getUserCartNums($unique, $id, $uid);
}
, 600);
}
$values = [];
$field = $type ? 'stock,price' : 'stock';
$storeProducts = $this->dao->cacheTag()->remember('attr_sku_' . $productId . '_' . $type, function () use ($storeProductAttrValueService, $productId, $field) {
return $storeProductAttrValueService->getSkuArray(['product_id' => $productId, 'type' => 0], $field, 'suk');
});
$channelDiscount = false;
if ($is_channel) {
$channelDiscountInfo = app()->make(ChannelMerchantServices::class)->isChannel($uid, true);
$channelDiscount = $channelDiscountInfo ? $channelDiscountInfo['discount'] : false;
if ($channelDiscount === false) {
throw new ValidateException('您不是采购商');
}
}
foreach ($_values as $value) {
if ($cartNum) {
$value['cart_num'] = $cartNumList[$value['unique']] ?? 0;
}
if (!$vip_price) $value['vip_price'] = 0;
$value['product_stock'] = $storeProducts[$value['suk']]['stock'] ?? 0;
if ($discount != -1) $value['price'] = bcmul((string)$value['price'], (string)bcdiv((string)$discount, '100', 2), 2);
if ($type) {
$value['product_price'] = $storeProducts[$value['suk']]['price'] ?? 0;
$attrs = explode(',', $value['suk']);
$count = count($attrs);
for ($i = 0; $i < $count; $i++) {
$activityAttr[$i][] = $attrs[$i];
}
}
if ($channelDiscount) {
if ($productInfo['channel_type'] == 1) {
$value['channel_price'] = bcmul($value['price'], $channelDiscount, 2);
} else {
foreach (json_decode($value['channel_level_price'], true) as $channel_level_price) {
if (isset($channelDiscountInfo) && $channel_level_price['id'] == $channelDiscountInfo['channel_type']) {
$value['channel_price'] = $channel_level_price['price'];
}
}
}
}
$value['small_image'] = get_thumb_water($value['image']);
$value['stock'] = $value['is_show'] == 1 ? $value['stock'] : 0;
$value['stock'] = max($value['stock'], 0);
$values[$value['suk']] = $value;
}
$attrResult = app()->make(StoreProductAttrResultServices::class)->getResult(['product_id' => $id, 'type' => $type]);
$attrPics = [];
foreach ($attrResult['attr'] as $resultAttr) {
foreach ($resultAttr['detail'] as $detail) {
if (is_string($detail)) {
$detail = [
'value' => $detail,
'pic' => '',
];
}
$attrPics[$resultAttr['value']][$detail['value']] = $detail['pic'] ?? '';
}
}
foreach ($attrDetail as $k => $v) {
$attr = $v['attr_values'];
//活动商品只展示参与活动sku
if ($type && $activityAttr && $a = array_merge(array_intersect($v['attr_values'], $activityAttr[$k]))) {
$attrDetail[$k]['attr_values'] = $a;
$attr = $a;
}
$is_pic = 0;
foreach ($attr as $kk => $vv) {
$attrDetail[$k]['attr_value'][$kk]['attr'] = $vv;
$attrDetail[$k]['attr_value'][$kk]['check'] = false;
$attrDetail[$k]['attr_value'][$kk]['pic'] = $attrPics[$v['attr_name']][$vv] ?? '';
if ($attrDetail[$k]['attr_value'][$kk]['pic']) {
$is_pic = 1;
}
}
$attrDetail[$k]['is_pic'] = $is_pic;
}
return [$attrDetail, $values];
}
/**
* 获取商品规格详情
* @param int $id
* @param int $uid
* @param int $cartNum //是否查询购物车数量
* @param int $type //活动类型 attr_value表
* @param int $productId
* @param array $productInfo
* @param int $discount //限时折扣
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductAttrDetail(int $id, int $uid, int $cartNum = 0, int $type = 0, int $productId = 0, array $productInfo = [], int $discount = -1, $is_channel = 0)
{
$attrDetail = $this->dao->getProductAttr(['product_id' => $id, 'type' => $type]);
if (!$attrDetail && $type > 0 && $productId) {//活动商品未获取到规格信息
$attrDetail = $this->dao->getProductAttr(['product_id' => $productId, 'type' => 0]);
}
/** @var StoreProductAttrValueServices $storeProductAttrValueService */
$storeProductAttrValueService = app()->make(StoreProductAttrValueServices::class);
$_values = $storeProductAttrValueService->getProductAttrValue(['product_id' => $id, 'type' => $type]);
if ($productId == 0) $productId = $id;
/** @var StoreProductServices $storeProductService */
$storeProductService = app()->make(StoreProductServices::class);
if (!$productInfo) {
$productInfo = $storeProductService->get($productId, ['is_vip']);
}
$vip_price = true;
if (!$storeProductService->vipIsOpen(!!$productInfo['is_vip'])) $vip_price = false;
$cartNumList = [];
$activityAttr = [];
if ($cartNum) {
/** @var StoreCartServices $storeCartService */
$storeCartService = app()->make(StoreCartServices::class);
//真实用户
if ($uid) {
$cartNumList = $storeCartService->getUserCartNums(array_column($_values, 'unique'), $id, $uid);
} else {
//虚拟用户
$touristUid = $this->getItem('touristUid');
if ($touristUid) {
$cartNumList = $storeCartService->getUserCartNums(array_column($_values, 'unique'), $id, $touristUid, 'tourist_uid');
}
}
}
$values = [];
$field = $type ? 'stock,price' : 'stock';
$channelDiscount = false;
if ($is_channel && $uid) {
$channelDiscount = app()->make(ChannelMerchantServices::class)->isChannel($uid, true);
$channelDiscount = $channelDiscount ? $channelDiscount['discount'] : false;
if ($channelDiscount === false) {
throw new ValidateException('请选择采购商用户');
}
}
$storeProducts = $storeProductAttrValueService->getSkuArray(['product_id' => $productId, 'type' => 0], $field, 'suk');
foreach ($_values as $value) {
if ($cartNum) {
$value['cart_num'] = $uid || $touristUid ? ($cartNumList[$value['unique']] ?? 0) : 0;
}
if (!$vip_price) $value['vip_price'] = 0;
$value['product_stock'] = $storeProducts[$value['suk']]['stock'] ?? 0;
if ($channelDiscount) {
$value['channel_price'] = bcmul($value['price'], $channelDiscount, 2);
} else {
if ($discount != -1) $value['price'] = bcmul((string)$value['price'], (string)bcdiv((string)$discount, '100', 2), 2);
}
if ($type) {
$value['product_price'] = $storeProducts[$value['suk']]['price'] ?? 0;
$attrs = explode(',', $value['suk']);
$count = count($attrs);
for ($i = 0; $i < $count; $i++) {
$activityAttr[$i][] = $attrs[$i];
}
if ($value['product_stock'] <= 0) {//原商品规格无库存
$value['stock'] = 0;
}
}
$value['small_image'] = get_thumb_water($value['image']);
$values[$value['suk']] = $value;
}
$attrResult = app()->make(StoreProductAttrResultServices::class)->getResult(['product_id' => $id, 'type' => $type]);
$attrPics = [];
if (isset($attrResult['attr'])) {
foreach ($attrResult['attr'] as $resultAttr) {
foreach ($resultAttr['detail'] as $detail) {
if (is_string($detail)) {
$detail = [
'value' => $detail,
'pic' => '',
];
}
$attrPics[$resultAttr['value']][$detail['value']] = $detail['pic'] ?? '';
}
}
}
foreach ($attrDetail as $k => $v) {
$attr = $v['attr_values'];
//活动商品只展示参与活动sku
if ($type && $activityAttr && $a = array_merge(array_intersect($v['attr_values'], $activityAttr[$k]))) {
$attrDetail[$k]['attr_values'] = $a;
$attr = $a;
}
$is_pic = 0;
foreach ($attr as $kk => $vv) {
$attrDetail[$k]['attr_value'][$kk]['attr'] = $vv;
$attrDetail[$k]['attr_value'][$kk]['check'] = false;
$attrDetail[$k]['attr_value'][$kk]['pic'] = $attrPics[$v['attr_name']][$vv] ?? '';
if ($attrDetail[$k]['attr_value'][$kk]['pic']) {
$is_pic = 1;
}
}
$attrDetail[$k]['is_pic'] = $is_pic;
}
return [$attrDetail, $values];
}
/**
* 删除一条数据
* @param int $id
* @param int $type
*/
public function del(int $id, int $type)
{
$this->dao->del($id, $type);
}
/**
* 设置规格
* @param array $data
* @param int $id
* @param int $type
* @return bool
* @throws \Exception
*/
public function setAttr(array $data, int $id, int $type)
{
foreach ($data as &$item) {
$item['attr_values'] = array_column($item['attr_values'], 'value');
}
if ($data) {
$this->dao->del($id, $type);
$res = $this->dao->saveAll($data);
if (!$res) throw new AdminException('规格保存失败');
}
return true;
}
}

View File

@@ -0,0 +1,647 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\sku;
use app\dao\product\sku\StoreProductAttrValueDao;
use app\jobs\product\ProductStockTips;
use app\jobs\product\ProductStockValueTips;
use app\model\product\stock\StockRecord;
use app\services\activity\bargain\StoreBargainServices;
use app\services\activity\combination\StoreCombinationServices;
use app\services\activity\discounts\StoreDiscountsServices;
use app\services\activity\integral\StoreIntegralServices;
use app\services\activity\seckill\StoreSeckillServices;
use app\services\BaseServices;
use app\services\product\product\StoreProductStockRecordServices;
use app\webscoket\SocketPush;
use crmeb\exceptions\AdminException;
use app\services\product\product\StoreProductServices;
use crmeb\services\CacheService;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* Class StoreProductAttrValueService
* @package app\services\product\sku
* @mixin StoreProductAttrValueDao
*/
class StoreProductAttrValueServices extends BaseServices
{
/**
* @var StoreProductAttrValueDao
*/
#[Inject]
protected StoreProductAttrValueDao $dao;
/**
* 获取单规格规格
* @param array $where
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getOne(array $where, string $field = '*')
{
return $this->dao->getOne($where, $field);
}
/**
* 获取商品规格信息
* @param int $id
* @param int $pid
* @param int $type
* @return array
*/
public function attrList(int $id, int $pid, int $type = 0)
{
/** @var StoreProductAttrResultServices $storeProductAttrResultServices */
$storeProductAttrResultServices = app()->make(StoreProductAttrResultServices::class);
$result = $storeProductAttrResultServices->value(['product_id' => $id, 'type' => $type], 'result');
$items = json_decode($result, true)['attr'];
$productAttr = $this->getAttr($items, $pid, 0);
$activityAttr = $this->getAttr($items, $id, $type);
foreach ($productAttr as $pk => &$pv) {
if ($type == 3) {
$sv['ot_price'] = isset($pv['_checked']) && $pv['_checked'] ? $pv['ot_price'] : $pv['price'];
}
foreach ($activityAttr as &$sv) {
if ($pv['detail'] == $sv['detail']) {
if ($type == 3) {
$sv['ot_price'] = isset($sv['_checked']) && $sv['_checked'] ? $sv['ot_price'] : $sv['price'];
}
$productAttr[$pk] = $sv;
}
}
$productAttr[$pk]['detail'] = json_decode($productAttr[$pk]['detail']);
}
$attrs['items'] = $items;
$attrs['value'] = $productAttr;
foreach ($items as $key => $item) {
$header[] = ['title' => $item['value'], 'key' => 'value' . ($key + 1), 'align' => 'center', 'minWidth' => 80];
}
$header[] = ['title' => '图片', 'slot' => 'pic', 'align' => 'center', 'minWidth' => 120];
$header[] = ['title' => '成本价', 'key' => 'cost', 'align' => 'center', 'minWidth' => 80];
if ($type == 3) {
$header[] = ['title' => '日常售价', 'key' => 'ot_price', 'align' => 'center', 'minWidth' => 80];
} elseif ($type == 4) {
$header[] = ['title' => '金额', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
} else {
$header[] = ['title' => '划线价', 'key' => 'ot_price', 'align' => 'center', 'minWidth' => 80];
}
if ($type == 1) {
$header[] = ['title' => '秒杀价', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
} elseif ($type == 2) {
$header[] = ['title' => '砍价起始金额', 'slot' => 'price', 'align' => 'center', 'minWidth' => 80];
$header[] = ['title' => '砍价最低价', 'slot' => 'min_price', 'align' => 'center', 'minWidth' => 80];
} elseif ($type == 3) {
$header[] = ['title' => '拼团价', 'key' => 'price', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
} elseif ($type == 4) {
$header[] = ['title' => '兑换积分', 'key' => 'integral', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
}
$header[] = ['title' => '库存', 'key' => 'stock', 'align' => 'center', 'minWidth' => 80];
if (in_array($type, [1, 3, 4])) {
$header[] = ['title' => $type == 4 ? '兑换次数' : '限量', 'key' => 'quota', 'type' => 1, 'align' => 'center', 'minWidth' => 80];
} else {
$header[] = ['title' => '限量', 'slot' => 'quota', 'align' => 'center', 'minWidth' => 80];
}
$header[] = ['title' => '重量(KG)', 'key' => 'weight', 'align' => 'center', 'minWidth' => 80];
$header[] = ['title' => '体积(m³)', 'key' => 'volume', 'align' => 'center', 'minWidth' => 80];
$header[] = ['title' => '商品条形码', 'key' => 'bar_code', 'align' => 'center', 'minWidth' => 80];
$header[] = ['title' => '商品编号', 'key' => 'code', 'align' => 'center', 'minWidth' => 80];
$attrs['header'] = $header;
return $attrs;
}
/**
* 获取规格
* @param $attr
* @param $id
* @param $type
* @return array
*/
public function getattr($attr, $id, $type)
{
foreach ($attr as $k => $v) {
foreach ($v['detail'] as $key => $item) {
if (isset($item['value'])) {
$attr[$k]['detail'][$key] = $item['value'];
}
}
}
$value = attr_format($attr)[1];
$valueNew = [];
$count = 0;
if ($type == 2) {
/** @var StoreBargainServices $storeBargainServices */
$storeBargainServices = app()->make(StoreBargainServices::class);
$min_price = $storeBargainServices->value(['id' => $id], 'min_price');
} else {
$min_price = 0;
}
$sukValueArr = $this->getSkuArray(['product_id' => $id, 'type' => $type], 'bar_code,code,cost,price,ot_price,stock,image as pic,weight,volume,brokerage,brokerage_two,quota,quota_show,settle_price,integral', 'suk');
foreach ($value as $key => $item) {
$detail = $item['detail'];
// sort($item['detail'], SORT_STRING);
$suk = implode(',', $item['detail']);
$sukValue = $sukValueArr[$suk] ?? [];
if ($sukValue) {
foreach (array_values($detail) as $k => $v) {
$valueNew[$count]['value' . ($k + 1)] = $v;
}
$valueNew[$count]['detail'] = json_encode($detail);
$valueNew[$count]['pic'] = $sukValue['pic'] ?? '';
$valueNew[$count]['price'] = $sukValue['price'] ? floatval($sukValue['price']) : 0;
$valueNew[$count]['integral'] = $sukValue['integral'] ?: 0;
$valueNew[$count]['settle_price'] = $sukValue['cost'] ? floatval($sukValue['settle_price']) : 0;
$valueNew[$count]['cost'] = $sukValue['cost'] ? floatval($sukValue['cost']) : 0;
$valueNew[$count]['ot_price'] = isset($sukValue['ot_price']) ? floatval($sukValue['ot_price']) : 0;
$valueNew[$count]['stock'] = $sukValue['stock'] ? intval($sukValue['stock']) : 0;
$valueNew[$count]['quota'] = isset($sukValue['quota_show']) && $sukValue['quota_show'] ? intval($sukValue['quota_show']) : 0;
$valueNew[$count]['code'] = $sukValue['code'] ?? '';
$valueNew[$count]['bar_code'] = $sukValue['bar_code'] ?? '';
$valueNew[$count]['weight'] = $sukValue['weight'] ? floatval($sukValue['weight']) : 0;
$valueNew[$count]['volume'] = $sukValue['volume'] ? floatval($sukValue['volume']) : 0;
$valueNew[$count]['brokerage'] = $sukValue['brokerage'] ? floatval($sukValue['brokerage']) : 0;
$valueNew[$count]['brokerage_two'] = $sukValue['brokerage_two'] ? floatval($sukValue['brokerage_two']) : 0;
switch ($type) {
case 1://秒杀
$valueNew[$count]['_checked'] = true;
break;
case 2://砍价
$valueNew[$count]['min_price'] = $min_price ? floatval($min_price) : 0;
$valueNew[$count]['opt'] = true;
break;
case 3://拼团
$valueNew[$count]['_checked'] = true;
break;
case 4://积分
$valueNew[$count]['integral'] = isset($sukValue['integral']) ? floatval($sukValue['integral']) : 0;
$valueNew[$count]['_checked'] = true;
break;
default:
$valueNew[$count]['_checked'] = false;
$valueNew[$count]['opt'] = false;
break;
}
$count++;
}
}
return $valueNew;
}
/**
* 根据活动商品unique查看原商品unique
* @param string $unique
* @param int $activity_id
* @param int $type
* @param array|string[] $field
* @return array|mixed|string|\think\Model|null
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUniqueByActivityUnique(string $unique, int $activity_id, int $type = 1, array $field = ['unique'])
{
if ($type == 0) return $unique;
$attrValue = $this->dao->get(['unique' => $unique, 'product_id' => $activity_id, 'type' => $type], ['id', 'suk', 'product_id']);
if (!$attrValue) {
return '';
}
switch ($type) {
case 1://秒杀
/** @var StoreSeckillServices $activityServices */
$activityServices = app()->make(StoreSeckillServices::class);
break;
case 2://砍价
/** @var StoreBargainServices $activityServices */
$activityServices = app()->make(StoreBargainServices::class);
break;
case 3://拼团
/** @var StoreCombinationServices $activityServices */
$activityServices = app()->make(StoreCombinationServices::class);
break;
case 4://积分
/** @var StoreIntegralServices $activityServices */
$activityServices = app()->make(StoreIntegralServices::class);
break;
case 5://套餐
/** @var StoreDiscountsServices $activityServices */
$activityServices = app()->make(StoreDiscountsServices::class);
break;
default:
/** @var StoreProductServices $activityServices */
$activityServices = app()->make(StoreProductServices::class);
break;
}
$product_id = $activityServices->value(['id' => $activity_id], 'product_id');
if (!$product_id) {
return '';
}
if (count($field) == 1) {
return $this->dao->value(['suk' => $attrValue['suk'], 'product_id' => $product_id, 'type' => 0], $field[0] ?? 'unique');
} else {
return $this->dao->get(['suk' => $attrValue['suk'], 'product_id' => $product_id, 'type' => 0], $field);
}
}
/**
* 删除一条数据
* @param int $id
* @param int $type
* @param array $suk
* @return bool
*/
public function del(int $id, int $type, array $suk = [])
{
return $this->dao->del($id, $type, $suk);
}
/**
* 批量保存
* @param array $data
*/
public function saveAll(array $data)
{
$res = $this->dao->saveAll($data);
if (!$res) throw new AdminException('规格保存失败');
return $res;
}
/**
* 获取sku
* @param array $where
* @param string $field
* @param string $key
* @return array
*/
public function getSkuArray(array $where, string $field = 'id,unique,bar_code,cost,price,integral,ot_price,stock,image as pic,weight,volume,brokerage,brokerage_two,quota,product_id,code,settle_price,level_price,vip_price,channel_level_price', string $key = 'suk')
{
return $this->dao->getColumn($where, $field, $key, false, 'sort asc,id asc');
}
/**
* 交易排行榜
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function purchaseRanking()
{
$dlist = $this->dao->attrValue();
/** @var StoreProductServices $proServices */
$proServices = app()->make(StoreProductServices::class);
$slist = $proServices->getProductLimit(['is_del' => 0], 20, 'id as product_id,store_name,sales * price as val');
$data = array_merge($dlist, $slist);
$last_names = array_column($data, 'val');
array_multisort($last_names, SORT_DESC, $data);
$list = array_splice($data, 0, 20);
return $list;
}
/**
* 获取商品的属性数量
* @param $product_id
* @param $unique
* @param $type
* @return int
*/
public function getAttrvalueCount($product_id, $unique, $type)
{
return $this->dao->count(['product_id' => $product_id, 'unique' => $unique, 'type' => $type]);
}
/**
* 获取唯一值下的库存
* @param string $unique
* @return int
*/
public function uniqueByStock(string $unique)
{
if (!$unique) return 0;
return $this->dao->uniqueByStock($unique);
}
/**
* 减销量,加库存
* @param $productId
* @param $unique
* @param $num
* @param int $type
* @return mixed
*/
public function decProductAttrStock($productId, $unique, $num, $type = 0)
{
$res = $this->dao->decStockIncSales([
'product_id' => $productId,
'unique' => $unique,
'type' => $type
], $num);
if ($res) {
$this->workSendStock($productId, $unique, $type);
}
return $res;
}
/**
* 减少销量增加库存
* @param $productId
* @param $unique
* @param $num
* @return bool
*/
public function incProductAttrStock(int $productId, string $unique, int $num, int $type = 0)
{
return $this->dao->incStockDecSales(['unique' => $unique, 'product_id' => $productId, 'type' => $type], $num);
}
/**
* 库存预警消息提醒
* @param int $productId
* @param string $unique
* @param int $type
*/
public function workSendStock(int $productId, string $unique, int $type)
{
ProductStockValueTips::dispatch([$productId, $unique, $type]);
}
/**
* 更新sum_stock
* @param array $uniques
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updateSumStock(array $uniques)
{
$stockSumData = [];
$this->dao->getList(['unique' => $uniques])->map(function ($item) use ($stockSumData) {
if (isset($stockSumData[$item->unique])) {
$data['sum_stock'] = $item->stock + $stockSumData[$item->unique];
} else {
$data['sum_stock'] = $item->stock;
}
$this->dao->update(['product_id' => $item['product_id'], 'unique' => $item['unique'], 'type' => $item['type']], $data);
});
}
/**
* 批量快速修改商品规格库存
* @param int $id
* @param array $data
* @return int|string
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveProductAttrsStock(int $id, array $data, int $admin_id = 0)
{
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$product = $productServices->get($id);
if (!$product) {
throw new ValidateException('商品不存在');
}
$attrs = $this->dao->getProductAttrValue(['product_id' => $id, 'type' => 0]);
if ($attrs) $attrs = array_combine(array_column($attrs, 'unique'), $attrs);
$dataAll = $update = [];
$stock = 0;
$time = time();
$inInventory = [];
$outInventory = [];
foreach ($data as $attr) {
if (!isset($attrs[$attr['unique']])) continue;
if ($attr['pm']) {
if ($attr['stock']) {
$inInventory[] = [
'pm' => $attr['pm'],
'unique' => $attr['unique'],
'inventory' => $attr['stock'],
'product_id' => $id
];
}
$stock = bcadd((string)$stock, (string)$attr['stock'], 0);
$update['stock'] = bcadd((string)$attrs[$attr['unique']]['stock'], (string)$attr['stock'], 0);
$update['sum_stock'] = bcadd((string)$attrs[$attr['unique']]['sum_stock'], (string)$attr['stock'], 0);
} else {
$stock = bcsub((string)$stock, (string)$attr['stock'], 0);
$update['stock'] = bcsub((string)$attrs[$attr['unique']]['stock'], (string)$attr['stock'], 0);
if ($attr['stock']) {
$_outInventory = [
'pm' => $attr['pm'],
'unique' => $attr['unique'],
'product_id' => $id
];
if ($update['stock'] > 0) {
$_outInventory['inventory'] = $attr['stock'];
} else {
$_outInventory['inventory'] = $attrs[$attr['unique']]['stock'];
}
$outInventory[] = $_outInventory;
}
$update['sum_stock'] = bcsub((string)$attrs[$attr['unique']]['sum_stock'], (string)$attr['stock'], 0);
}
// $update['stock'] = $update['stock'] > 0 ? $update['stock'] : 0;
$this->dao->update(['id' => $attrs[$attr['unique']]['id']], $update);
$dataAll[] = [
'product_id' => $id,
'unique' => $attr['unique'],
'cost_price' => $attrs[$attr['unique']]['cost'] ?? 0,
'number' => $attr['stock'],
'pm' => $attr['pm'] ? 1 : 0,
'add_time' => $time,
];
}
$product_stock = $stock ? bcadd((string)$product['stock'], (string)$stock, 0) : bcsub((string)$product['stock'], (string)$stock, 0);
// $product_stock = $product_stock > 0 ? $product_stock : 0;
//修改商品库存
$productServices->update($id, ['stock' => $product_stock]);
//添加库存记录$product_stock
if ($dataAll) {
/** @var StoreProductStockRecordServices $storeProductStockRecordServces */
$storeProductStockRecordServces = app()->make(StoreProductStockRecordServices::class);
$storeProductStockRecordServces->saveAll($dataAll);
}
if ($admin_id) {
if (count($inInventory) > 0) {
event('product.stock.record', [StockRecord::STOCK_TYPE_OTHER_IN, StockRecord::TYPE_IN, $inInventory, $admin_id, '']);
}
if (count($outInventory) > 0) {
event('product.stock.record', [StockRecord::STOCK_TYPE_OTHER_OUT, StockRecord::TYPE_OUT, $outInventory, $admin_id, '']);
}
}
//清除缓存
$productServices->cacheTag()->clear();
/** @var StoreProductAttrServices $attrService */
$attrService = app()->make(StoreProductAttrServices::class);
$attrService->cacheTag()->clear();
//警戒库存
$productServices->workSendStock($id);
return $product_stock;
}
/**
* 查询库存预警产品ids
* @param array $where
* @return array
*/
public function getGroupId(array $where)
{
$res1 = [];
$res2 = $this->dao->getGroupData('product_id', 'product_id', $where);
foreach ($res2 as $id) {
$res1[] = $id['product_id'];
}
return $res1;
}
/**
* 批量修改库存价格
* @param $id
* @param $data
* @return true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2024/1/25 10:01
*/
public function updateAttrs($id, $data)
{
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
$product = $productServices->get($id);
if (!$product) {
throw new ValidateException('商品不存在');
}
$attrs = $this->dao->getProductAttrValue(['product_id' => $id, 'type' => 0]);
$data = array_combine(array_column($data, 'unique'), $data);
$dataAll = $update = [];
$product_stock = $product_price = $product_ot_price = $product_cost = 0;
$time = time();
foreach ($attrs as $item) {
$attr = $data[$item['unique']] ?? [];
if ($attr) {
$this->update($item['id'], [
'price' => $attr['price'],
'stock' => $attr['stock'],
'sum_stock' => $attr['stock'],
'cost' => $attr['cost'],
'ot_price' => $attr['ot_price']
]);
$number = bcsub((string)$attr['stock'], (string)$item['stock'], 0);
$dataAll[] = [
'product_id' => $id,
'unique' => $attr['unique'],
'cost_price' => $attr['cost'] ?? 0,
'number' => abs($number),
'pm' => $number > 0 ? 1 : 0,
'add_time' => $time,
];
}
$product_array = $attr ?: $item;
// 计算商品库存
$product_stock = bcadd((string)$product_stock, (string)$product_array['stock'], 0);
// 更新商品价格
$product_price = max($product_price, $product_array['price']);
// 更新商品原价
$product_ot_price = max($product_ot_price, $product_array['ot_price']);
// 更新商品成本
$product_cost = max($product_cost, $product_array['cost']);
}
// 修改商品库存等信息
$productServices->update($id, [
'stock' => $product_stock,
'price' => $product_price,
'ot_price' => $product_ot_price,
'cost' => $product_cost,
]);
if ($dataAll) {
/** @var StoreProductStockRecordServices $storeProductStockRecordServces */
$storeProductStockRecordServces = app()->make(StoreProductStockRecordServices::class);
$storeProductStockRecordServces->saveAll($dataAll);
}
// 清除缓存
$productServices->cacheTag()->clear();
/** @var StoreProductAttrServices $attrService */
$attrService = app()->make(StoreProductAttrServices::class);
$attrService->cacheTag()->clear();
return true;
}
/**
* 购买普通商品扣除活动的总库存显示
* @param $productId
* @param $unique
* @param $num
* @return bool
* @author wuhaotian
* @email 442384644@qq.com
* @date 2025/9/18
*/
public function decMarketingProductStock($productId, $unique, $num)
{
try {
$sku = $this->dao->value(['product_id' => $productId, 'unique' => $unique], 'suk');
$seckillIds = app()->make(StoreSeckillServices::class)->getColumn(['product_id' => $productId, 'is_del' => 0], 'id');
$bargainIds = app()->make(StoreBargainServices::class)->getColumn(['product_id' => $productId, 'is_del' => 0], 'id');
$combinationIds = app()->make(StoreCombinationServices::class)->getColumn(['product_id' => $productId, 'is_del' => 0], 'id');
if (count($seckillIds)) {
$this->dao->decUpdate([['product_id', 'in', $seckillIds], ['suk', '=', $sku], ['type', '=', 1]], 'stock', $num);
}
if (count($bargainIds)) {
$this->dao->decUpdate([['product_id', 'in', $bargainIds], ['suk', '=', $sku], ['type', '=', 2]], 'stock', $num);
}
if (count($combinationIds)) {
$this->dao->decUpdate([['product_id', 'in', $combinationIds], ['suk', '=', $sku], ['type', '=', 3]], 'stock', $num);
}
} catch (\Exception $e) {
}
return true;
}
/**
* 查询规格列表
* @param array $where
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/18 16:42
*/
public function getAttrValueList(array $where = [])
{
[$page, $limit] = $this->getPageValue();
$query = $this->dao->joinAttrSearch($where);
$count = $query->count();
$list = $query->field('a.*,p.store_name')->page($page, $limit)->order('p.sort desc,p.id desc')->select()->toArray();
// $this->dao->getAttrList($where,'*', $page, $limit,'id desc',['product']);
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,174 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\sku;
use app\dao\product\sku\StoreProductRuleDao;
use app\services\BaseServices;
use crmeb\exceptions\AdminException;
use think\annotation\Inject;
/**
* Class StoreProductRuleService
* @package app\services\product\sku
* @mixin StoreProductRuleDao
*/
class StoreProductRuleServices extends BaseServices
{
/**
* @var StoreProductRuleDao
*/
#[Inject]
protected StoreProductRuleDao $dao;
/**
* 获取规格模板
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getRule(int $type = 0, int $relation_id = 0 )
{
$list = $this->dao->getList(['type' => $type, 'relation_id' => $relation_id]);
foreach ($list as &$item) {
$attr_name = $attr_value = [];
if ($item['rule_value']) {
$item['rule_value'] = $specs = json_decode($item['rule_value'], true);
foreach ($item['rule_value'] as $ruleKey => $ruleItem) {
foreach ($ruleItem['detail'] as $valueKey => $valueItem) {
if (is_string($valueItem)) {
$item['rule_value'][$ruleKey]['detail'][$valueKey] = ['value' => $valueItem, 'pic' => ''];
$item['rule_value'][$ruleKey]['add_pic'] = 0;
}
}
}
if ($specs) {
foreach ($specs as $key => $value) {
$attr_name[] = $value['value'];
$attr_value[] = implode(',', $value['detail']);
}
} else {
$attr_name[] = '';
$attr_value[] = '';
}
$item['attr_name'] = implode(',', $attr_name);
$item['attr_value'] = $attr_value;
}
}
return $list;
}
/**
* 获取商品规格列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where = [])
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, $page, $limit);
foreach ($list as &$item) {
$attr_name = $attr_value = [];
if ($item['rule_value']) {
$specs = json_decode($item['rule_value'], true);
if ($specs) {
foreach ($specs as $key => $value) {
$attr_name[] = $value['value'];
$attr_value[] = implode(',', $value['detail']);
}
} else {
$attr_name[] = '';
$attr_value[] = '';
}
$item['attr_name'] = implode(',', $attr_name);
$item['attr_value'] = $attr_value;
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 保存数据
* @param int $id
* @param array $data
* @param int $type
* @param int $relation_id
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function save(int $id, array $data, int $type = 0, int $relation_id = 0)
{
$data['type'] = $type;
$data['relation_id'] = $relation_id;
$data['rule_value'] = json_encode($data['spec']);
unset($data['spec']);
$rule = $this->dao->getOne(['rule_name' => $data['rule_name'], 'type' => $type, 'relation_id' => $relation_id]);
if ($id) {
if ($rule && $rule['id'] != $id) {
throw new AdminException('分类名称已存在');
}
$res = $this->dao->update($id, $data);
} else {
if ($rule) {
throw new AdminException('分类名称已存在');
}
$res = $this->dao->save($data);
}
if (!$res) throw new AdminException('保存失败');
}
/**
* 获取一条数据
* @param int $id
* @return array
*/
public function getInfo(int $id)
{
$info = $this->dao->get($id);
$info['spec'] = json_decode($info['rule_value'], true);
return compact('info');
}
/**
* 删除数据
* @param string $ids
*/
public function del(string $ids)
{
if ($ids == '') throw new AdminException('请至少选择一条数据');
$this->dao->del($ids);
}
/**
* @param array $where
* @param string $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductRuleList(array $where, $field = "*")
{
return $this->dao->getProductRuleList($where, $field);
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace app\services\product\sku;
use app\dao\product\sku\StoreProductVirtualDao;
use app\services\BaseServices;
use app\services\product\product\StoreProductServices;
use think\annotation\Inject;
/**
* 规格卡密信息
* Class StoreProductVirtualServices
* @package app\services\product\sku
* @mixin StoreProductVirtualDao
*/
class StoreProductVirtualServices extends BaseServices
{
/**
* @var StoreProductVirtualDao
*/
#[Inject]
protected StoreProductVirtualDao $dao;
/**
* 规格中获取卡密列表
* @param $unique
* @param $product_id
* @return array
*/
public function getArr($unique, $product_id)
{
$res = $this->dao->getColumn(['attr_unique' => $unique, 'product_id' => $product_id], 'card_no,card_pwd');
$data = [];
foreach ($res as $item) {
$data[] = ['key' => $item['card_no'], 'value' => $item['card_pwd']];
}
return $data;
}
/**
* 获取订单发送卡密列表
* @param array $where
* @param int $limit
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getOrderCardList(array $where, int $limit = 1)
{
return $this->dao->getList($where, '*', 0, $limit);
}
/**
* 保存商品规格(虚拟卡密信息)
* @param int $id
* @param array $valueGroup
* @param int $store_id
* @return bool
*/
public function saveProductVirtual(int $id, array $valueGroup, int $store_id = 0)
{
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
/** @var StoreProductServices $storeProductServices */
$storeProductServices = app()->make(StoreProductServices::class);
$cardList = $this->dao->getList(['product_id' => $id, 'store_id' => $store_id], "card_no");
$cardList = empty($cardList) ? [] : array_column($cardList, "card_no");
$count = 0;
foreach ($valueGroup as $item) {
if (isset($item['product_type']) && $item['product_type'] == 1) {
$num = 0;
if (isset($item['disk_info']) && $item['disk_info']) {
$num = $item['stock'];
$count += $num;
} elseif (isset($item['virtual_list']) && count($item['virtual_list'])) {
$num = $this->dao->count(['store_id' => $store_id, 'product_id' => $id, 'attr_unique' => $item['unique'], 'uid' => 0]) ?? 0;
$count += $num;
$data = [];
foreach ($item['virtual_list'] as $items) {
if (!in_array($items['key'], $cardList)) {
$data[] = [
'product_id' => $id,
'attr_unique' => $item['unique'],
'card_no' => $items['key'],
'card_pwd' => $items['value'],
'card_unique' => md5($item['unique'] . ',' . $items['key'] . ',' . $items['value'])
];
$num++;
$count++;
}
}
}
if (!empty($data)) {
$this->dao->saveAll($data);
}
$storeProductAttrValueServices->update(['product_id' => $id, 'unique' => $item['unique']], ['stock' => $num]);
}
}
$storeProductServices->update($id, ['stock' => $count]);
unset($item);
return true;
}
}

View File

@@ -0,0 +1,133 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\specs;
use app\dao\product\specs\StoreProductSpecsDao;
use app\services\BaseServices;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 商品参数
* Class StoreProductSpecsServices
* @package app\services\product\ensure
* @mixin StoreProductSpecsDao
*/
class StoreProductSpecsServices extends BaseServices
{
/**
* 商品参数字段
* @var array
*/
protected array $specs = [
'id' => 0,
'temp_id' => 0,
'name' => '',
'value' => '',
'sort' => 0
];
/**
* @var StoreProductSpecsDao
*/
#[Inject]
protected StoreProductSpecsDao $dao;
/**
* 获取参数模版列表(带参数)
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getSpecsTemplateList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getList($where, '*', $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 检测
* @param array $data
* @return array
*/
public function checkSpecsData(array $data)
{
$data = array_merge($this->specs, array_intersect_key($data, $this->specs));
if (!isset($data['name']) || !$data['name']) {
throw new ValidateException('请填写参数名称');
}
return $data;
}
/**
* 修改参数模版(商品参数)
* @param int $id
* @param array $specsArr
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updateData(int $id, array $specsArr)
{
$this->dao->delete(['temp_id' => $id]);
$insert = [];
$time = time();
foreach ($specsArr as $specs) {
$specs = $this->checkSpecsData($specs);
$specs['temp_id'] = $id;
if (isset($specs['id'])) {
unset($specs['id']);
}
$specs['add_time'] = $time;
$insert[] = $specs;
}
if ($insert) {
if (!$this->dao->saveAll($insert)) {
throw new ValidateException('新增商品参数失败');
}
}
return true;
}
/**
* 保存参数模版(商品参数)
* @param int $id
* @param array $specsArr
* @return bool
*/
public function saveData(int $id, array $specsArr)
{
if (!$specsArr) return true;
$dataAll = [];
$time = time();
foreach ($specsArr as $specs) {
$specs = $this->checkSpecsData($specs);
$specs['temp_id'] = $id;
$specs['add_time'] = $time;
$dataAll[] = $specs;
}
if ($dataAll) {
$this->dao->saveAll($dataAll);
}
return true;
}
}

View File

@@ -0,0 +1,85 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\specs;
use app\dao\other\CategoryDao;
use app\services\BaseServices;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
/**
* 商品参数模版
* Class StoreProductSpecsTemplateServices
* @package app\services\product\specs
* @mixin CategoryDao
*/
class StoreProductSpecsTemplateServices extends BaseServices
{
/**
* 在分类库中3
*/
const GROUP = 3;
/**
* @var CategoryDao
*/
#[Inject]
protected CategoryDao $dao;
/**
* 获取所有参数模版
* @param array $where
* @param int $type
* @param int $relation_id
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getProductSpecsTemplateList(array $where, int $type = 0, int $relation_id = 0) : array
{
[$page, $limit] = $this->getPageValue();
$where = array_merge($where, ['type' => $type, 'relation_id' => $relation_id, 'group' => 3]);
$count = $this->dao->count($where);
$list = $this->dao->getCateList($where, $page, $limit, ['*']);
if ($list) {
foreach ($list as &$item) {
$item['add_time'] = $item['add_time'] ? date('Y-m-d H:i:s', $item['add_time']) : '';
}
}
return compact('list', 'count');
}
/**
* 获取所有商品参数
* @param int $type
* @param int $relation_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getAllSpecs(int $type = 0, int $relation_id = 0)
{
$where = [];
// $where['relation_id'] = $relation_id;
// $where['type'] = $type;
$where['group'] = 3;
return $this->dao->getCateList($where, 0, 0, ['id', 'name'], ['specs' => function($query) {
$query->where('status', 1);
}]);
}
}

View File

@@ -0,0 +1,398 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\stock;
use app\dao\product\stock\StockInventoryDao;
use app\dao\product\stock\StockRecordDao;
use app\dao\product\stock\StockRecordItemDao;
use app\model\product\stock\StockRecord;
use app\services\BaseServices;
use app\services\order\StoreOrderRefundServices;
use app\services\order\StoreOrderServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrValueServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Route as Url;
/**
* 库存管理服务类
* Class StockRecordServices
* @package app\services\stock
* @mixin StockInventoryDao
*/
class StockInventoryServices extends BaseServices
{
/**
* @var StockInventoryDao
*/
#[Inject]
protected StockInventoryDao $dao;
/**
* 获取出入库记录列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStockRecordList(array $where, array $with = [], $limits = 0): array
{
[$page, $limit] = $this->getPageValue();
$limit = $limits ?: $limit;
$with = is_array($with) ? array_merge($with, ['admin']) : ['admin'];
$list = $this->dao->getList($where, '*', $page, $limit, $with);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 创建
* @param array $data
* @return bool
* @throws \Exception
*/
public function createStockRecord(array $data, int $adminId, $id = 0): bool
{
// 验证数据
$this->validateStockRecordData($data);
return $this->transaction(function () use ($data, $adminId, $id) {
$id = $this->saveStockInventoryRecord($data, $adminId, $id);
$itemDao = app()->make(StockRecordItemDao::class);
if ($id) {
$itemDao->delete(['record_id' => $id, 'type' => 2]);
}
$items = [];
$good_stock = $good_inventory_stock = $defective_stock = $defective_inventory_stock = 0;
$out_library_items = [];
$enter_library_items = [];
$storeProductServices = app()->make(StoreProductServices::class);
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$product_ids = array_unique(array_column($data['product'], 'product_id'));
$uniques = array_unique(array_column($data['product'], 'unique'));
$product_list = $storeProductServices->search([])->whereIn('id', $product_ids)->column('id,store_name,code', 'id');
$attr_list = $storeProductAttrValueServices->search([])->whereIn('unique', $uniques)->column('unique,suk,image,bar_code', 'unique');
foreach ($data['product'] as $item) {
$item['product_name'] = $product_list[$item['product_id']]['store_name'] ?? '';
$item['product_code'] = $product_list[$item['product_id']]['code'] ?? '';
$item['product_image'] = $attr_list[$item['unique']]['image'] ?? '';
$item['product_suk'] = $attr_list[$item['unique']]['suk'] ?? '';
$item['product_bar_code'] = $attr_list[$item['unique']]['bar_code'] ?? '';
$this->processProductItem($item, $out_library_items, $enter_library_items, $good_stock, $good_inventory_stock, $defective_stock, $defective_inventory_stock, $items, $id);
}
$this->dao->update($id, [
'good_stock' => $good_stock,
'good_inventory_stock' => $good_inventory_stock,
'defective_stock' => $defective_stock,
'defective_inventory_stock' => $defective_inventory_stock
]);
if (!$itemDao->saveAllItems($items)) {
throw new AdminException('创建商品详情失败');
}
if ($data['status'] == 1) {
$stockRecordServices = app()->make(StockRecordServices::class);
if (!empty($out_library_items)) {
$out_library = [
'type' => StockRecord::TYPE_OUT,
'stock_type' => StockRecord::STOCK_TYPE_LOSS,
'record_date' => time(),
'product' => $out_library_items,
'remark' => '盘点亏损'
];
$stockRecordServices->createStockRecord($out_library, $adminId);
}
if (!empty($enter_library_items)) {
$enter_library = [
'type' => StockRecord::TYPE_IN,
'stock_type' => StockRecord::STOCK_TYPE_PROFIT,
'record_date' => time(),
'product' => $enter_library_items,
'remark' => '盘点报溢'
];
$stockRecordServices->createStockRecord($enter_library, $adminId);
}
}
return true;
});
}
/**
* 创建盘点记录
* @param array $data
* @param int $adminId
* @param $id
* @return mixed
*/
private function saveStockInventoryRecord(array $data, int $adminId, $id): mixed
{
if ($id) {
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('盘点记录不存在');
}
$info->remark = $data['remark'] ?? '';
$info->operator_id = $adminId;
$info->status = $data['status'];
$info->save();
} else {
// 生成单号
$recordNo = app()->make(StockRecordServices::class)->generateUniqueRecordNo(3);
// 创建主记录
$recordData = [
'record_no' => $recordNo,
'remark' => $data['remark'] ?? '',
'operator_id' => $adminId,
'status' => $data['status'],
'create_time' => time(),
'update_time' => time(),
];
$record = $this->dao->save($recordData);
if (!$record) {
throw new AdminException('创建盘点记录失败');
}
$id = $record->id;
}
return $id;
}
/**
* 处理商品项
* @param array $item
* @param array $out_library_items
* @param array $enter_library_items
* @param $good_stock
* @param $good_inventory_stock
* @param $defective_stock
* @param $defective_inventory_stock
* @param array $items
* @param int $id
* @return void
* User: liusl
* DateTime: 2025/9/22 15:49
*/
private function processProductItem(array $item, array &$out_library_items, array &$enter_library_items, &$good_stock, &$good_inventory_stock, &$defective_stock, &$defective_inventory_stock, array &$items, int $id)
{
$poor_good_stock = $poor_defective_stock = 0;
if ($item['good_inventory_stock'] !== null) {
$poor_good_stock = bcsub($item['good_inventory_stock'], $item['good_stock'], 0);
}
if ($item['defective_inventory_stock'] !== null) {
$poor_defective_stock = bcsub($item['defective_inventory_stock'], $item['defective_stock'], 0);
}
$_enter_library_items = [];
$_out_library_items = [];
if ($poor_good_stock > 0) {
$good_stock += (int)$poor_good_stock;
$_enter_library_items = [
'product_id' => $item['product_id'],
'unique' => $item['unique'] ?? 0,
'good_stock' => $poor_good_stock,
'defective_stock' => 0,
];
} elseif ($poor_good_stock < 0) {
$good_inventory_stock += (int)$poor_good_stock;
$_out_library_items = [
'product_id' => $item['product_id'],
'unique' => $item['unique'] ?? 0,
'good_stock' => bcmul($poor_good_stock, -1, 0),
'defective_stock' => 0,
];
}
if ($poor_defective_stock > 0) {
$defective_stock += (int)$poor_defective_stock;
if ($_enter_library_items) {
$_enter_library_items['defective_stock'] = $poor_defective_stock;
} else {
$_enter_library_items = [
'product_id' => $item['product_id'],
'unique' => $item['unique'] ?? 0,
'good_stock' => 0,
'defective_stock' => $poor_defective_stock,
];
}
} elseif ($poor_defective_stock < 0) {
$defective_inventory_stock += (int)$poor_defective_stock;
if ($_out_library_items) {
$_out_library_items['defective_stock'] = bcmul($poor_defective_stock, -1, 0);
} else {
$_out_library_items = [
'product_id' => $item['product_id'],
'unique' => $item['unique'] ?? 0,
'good_stock' => 0,
'defective_stock' => bcmul($poor_defective_stock, -1, 0),
];
}
}
if (count($_out_library_items) > 0) {
$out_library_items[] = $_out_library_items;
}
if (count($_enter_library_items) > 0) {
$enter_library_items[] = $_enter_library_items;
}
$items[] = [
'type' => 2,
'record_id' => $id,
'product_id' => $item['product_id'],
'unique' => $item['unique'] ?? 0,
'good_stock' => $item['good_inventory_stock'] !== null ? ($item['good_stock'] ?? 0) : 0,
'good_inventory_stock' => $item['good_inventory_stock'] ?? 0,
'defective_stock' => $item['defective_inventory_stock'] !== null ? ($item['defective_stock'] ?? 0) : 0,
'defective_inventory_stock' => $item['defective_inventory_stock'] ?? 0,
'product_name' => $item['product_name'] ?? '',
'product_image' => $item['product_image'] ?? '',
'product_code' => $item['product_code'] ?? '',
'product_suk' => $item['product_suk'] ?? '',
'product_bar_code' => $item['product_bar_code'] ?? '',
'create_time' => time(),
'update_time' => time(),
];
}
/**
* 审核出入库记录
* @param int $id
* @param int $status
* @param string $remark
* @return bool
* @throws \Exception
*/
public function auditStockRecord(int $id, int $status, string $remark = ''): bool
{
$record = $this->dao->get($id);
if (!$record) {
throw new AdminException('盘点记录不存在');
}
Db::startTrans();
try {
// 更新记录状态
$updateData = [
'status' => $status,
'audit_remark' => $remark,
'audit_time' => time(),
'update_time' => time(),
];
if (!$this->dao->update($id, $updateData)) {
throw new AdminException('更新记录状态失败');
}
Db::commit();
return true;
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
}
/**
* 验证出入库记录数据
* @param array $data
* @throws AdminException
*/
protected function validateStockRecordData(array $data): void
{
if (empty($data['product']) || !is_array($data['product'])) {
throw new AdminException('商品信息不能为空');
}
foreach ($data['product'] as $item) {
if (empty($item['product_id'])) {
throw new AdminException('商品ID不能为空');
}
}
}
/**
* 获取详情
* @param $id
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/17 16:50
*/
/**
* 获取盘点详情
* @param int $id 盘点记录ID
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function read($id)
{
$info = $this->dao->get($id, ['*'], ['admin']);
if (!$info) {
throw new AdminException('记录不存在');
}
$info = $info->toArray();
$product = app()->make(StockRecordItemServices::class)->search(['record_id' => $id, 'type' => 2])->select()->toArray();
if (!is_array($product)) {
$product = [];
}
$info['product'] = $product;
return $info;
}
/**
* 备注表单
* @param $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/18 14:42
*/
public function remarkFrom($id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('记录不存在');
}
$field[] = Form::textarea('remark', '备注', $info['remark'] ?? '');
return create_form('添加备注', $field, Url::buildUrl('/stock/inventory/remark_save/' . $id), 'POST');
}
}

View File

@@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\stock;
use app\dao\product\stock\StockRecordItemDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 库存管理服务类
* Class StockRecordServices
* @package app\services\stock
* @mixin StockRecordItemDao
*/
class StockRecordItemServices extends BaseServices
{
/**
* @var StockRecordItemDao
*/
#[Inject]
protected StockRecordItemDao $dao;
}

View File

@@ -0,0 +1,862 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\product\stock;
use app\dao\product\stock\StockRecordDao;
use app\dao\product\stock\StockRecordItemDao;
use app\model\product\stock\StockRecord;
use app\services\BaseServices;
use app\services\order\StoreOrderRefundServices;
use app\services\order\StoreOrderServices;
use app\services\product\product\StoreProductServices;
use app\services\product\sku\StoreProductAttrValueServices;
use crmeb\exceptions\AdminException;
use crmeb\services\FormBuilder as Form;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Route as Url;
/**
* 库存管理服务类
* Class StockRecordServices
* @package app\services\stock
* @mixin StockRecordDao
*/
class StockRecordServices extends BaseServices
{
/**
* @var StockRecordDao
*/
#[Inject]
protected StockRecordDao $dao;
/**
* 获取出入库记录列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStockRecordList(array $where, $with = [], $limits = 0): array
{
[$page, $limit] = $this->getPageValue();
$limit = $limits ?: $limit;
$with = is_array($with) ? array_merge($with, ['admin']) : ['admin'];
$unique = $where['unique'] ?? '';
if ($unique) {
$with[] = 'items';
}
$list = $this->dao->getList($where, '*', $page, $limit, $with);
$count = $this->dao->getCount($where);
$_items = [];
if ($unique) {
$itemsList = array_column($list, 'items');
foreach ($itemsList as $v) {
foreach ($v as $item) {
if ($item && isset($item['unique']) && $item['unique'] == $unique) {
if (!isset($_items[$item['record_id']])) {
$_items[$item['record_id']] = [
'good_stock' => $item['good_stock'],
'defective_stock' => $item['defective_stock'],
];
} else {
$_items[$item['record_id']]['good_stock'] += $item['good_stock'];
$_items[$item['record_id']]['defective_stock'] += $item['defective_stock'];
}
}
}
}
}
foreach ($list as &$item) {
if ($unique) {
$item['good_stock'] = $_items[$item['id']]['good_stock'] ?? 0;
$item['defective_stock'] = $_items[$item['id']]['defective_stock'] ?? 0;
}
$item['stock_type_name'] = StockRecord::getStockTypeName($item['stock_type']);
// unset($item['items']);
if ($item['stock_type'] == 'sale') $item['admin_name'] = '用户购买';
}
return compact('list', 'count');
}
/**
* 创建出入库记录
* @param array $data
* @return bool
* @throws \Exception
*/
public function createStockRecord(array $data, int $adminId, $is_stock = false): bool
{
// 验证数据
$this->validateStockRecordData($data);
$id = $this->transaction(function () use ($data, $adminId) {
// 生成单号
$recordNo = $this->generateUniqueRecordNo($data['type']);
// 创建主记录
$recordData = [
'record_no' => $recordNo,
'type' => $data['type'],
'stock_type' => $data['stock_type'],
'after_sale_no' => $data['after_sale_no'] ?? '',
'record_date' => is_numeric($data['record_date']) ? $data['record_date'] : strtotime($data['record_date']),
'remark' => $data['remark'] ?? '',
'operator_id' => $adminId,
'create_time' => time(),
'update_time' => time(),
];
$recordId = $this->dao->save($recordData);
if (!$recordId) {
throw new AdminException('创建出入库记录失败');
}
// 创建商品详情
$itemDao = app()->make(StockRecordItemDao::class);
$items = [];
$storeProductServices = app()->make(StoreProductServices::class);
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
$product_ids = array_unique(array_column($data['product'], 'product_id'));
$uniques = array_unique(array_column($data['product'], 'unique'));
$product_list = $storeProductServices->search([])->whereIn('id', $product_ids)->column('id,store_name,code', 'id');
$attr_list = $storeProductAttrValueServices->search([])->whereIn('unique', $uniques)->column('unique,suk,image,bar_code', 'unique');
foreach ($data['product'] as $item) {
$stock = $this->calculateStockChanges($data['type'], $data['stock_type'], $item['good_stock'] ?? 0, $item['defective_stock'] ?? 0);
$items[] = [
'record_id' => $recordId->id,
'product_id' => $item['product_id'],
'record_date' => $recordData['record_date'],
'unique' => $item['unique'] ?? 0,
'stock_type' => $data['stock_type'],
'good_stock' => $stock['good_stock_change'] ?? 0,
'defective_stock' => $stock['defective_stock_change'] ?? 0,
'product_name' => $product_list[$item['product_id']]['store_name'] ?? '',
'product_code' => $product_list[$item['product_id']]['code'] ?? '',
'product_image' => $attr_list[$item['unique']]['image'] ?? '',
'product_suk' => $attr_list[$item['unique']]['suk'] ?? '',
'product_bar_code' => $attr_list[$item['unique']]['bar_code'] ?? '',
'create_time' => time(),
'update_time' => time(),
];
}
if (!$itemDao->saveAllItems($items)) {
throw new AdminException('创建商品详情失败');
}
return $recordId->id;
});
if (!$is_stock) {
event('product.stock.create', [$id]);
}
return true;
}
/**
* 根据出入库类型计算库存变化量
* @param int $recordType 记录类型 1=入库 2=出库
* @param string $stockType 库存类型
* @param int $goodStock 良品数量
* @param int $defectiveStock 残次品数量
* @return array
*/
public function calculateStockChanges(int $recordType, string $stockType, $goodStock, $defectiveStock): array
{
switch ($stockType) {
case StockRecord::STOCK_TYPE_DEFECTIVE_TO_GOOD: // 残次品转良品入库
// 良品增加(正数),残次品减少(负数)
$goodStockChange = $goodStock;
$defectiveStockChange = -$goodStock;
break;
case StockRecord::STOCK_TYPE_GOOD_TO_DEFECTIVE: // 良品转残次品出库
// 良品减少(负数),残次品增加(正数)
$goodStockChange = -$goodStock;
$defectiveStockChange = $goodStock;
break;
default:
// 普通入库和出库逻辑
if ($recordType == StockRecord::TYPE_IN) {
// 入库:良品和残次品都是正数
$goodStockChange = $goodStock;
$defectiveStockChange = $defectiveStock;
} else {
// 出库:良品和残次品都是负数
$goodStockChange = $goodStock ? -$goodStock : 0;
$defectiveStockChange = $defectiveStock ? -$defectiveStock : 0;
}
break;
}
return [
'good_stock_change' => $goodStockChange,
'defective_stock_change' => $defectiveStockChange
];
}
/**
* 获取商品出入库明细
* @param array $where
* @return array
*/
public function getProductStockDetails(array $where): array
{
[$page, $limit] = $this->getPageValue();
$itemDao = app()->make(StockRecordItemDao::class);
$list = $itemDao->getProductStockDetails($where, $page, $limit);
$count = $itemDao->getProductStockDetailsCount($where);
return compact('list', 'count');
}
/**
* 验证出入库记录数据
* @param array $data
* @throws AdminException
*/
protected
function validateStockRecordData(array $data): void
{
if (empty($data['type']) || !in_array($data['type'], [StockRecord::TYPE_IN, StockRecord::TYPE_OUT])) {
throw new AdminException('出入库类型错误');
}
// 验证stock_type字段
if (empty($data['stock_type'])) {
throw new AdminException('出入库子类型不能为空');
}
$validStockTypes = [
StockRecord::STOCK_TYPE_PURCHASE,
StockRecord::STOCK_TYPE_RETURN,
StockRecord::STOCK_TYPE_OTHER_IN,
StockRecord::STOCK_TYPE_DEFECTIVE_TO_GOOD,
StockRecord::STOCK_TYPE_EXPIRED_RETURN,
StockRecord::STOCK_TYPE_PROFIT,
StockRecord::STOCK_TYPE_USE_OUT,
StockRecord::STOCK_TYPE_SCRAP_OUT,
StockRecord::STOCK_TYPE_GOOD_TO_DEFECTIVE,
StockRecord::STOCK_TYPE_OTHER_OUT,
StockRecord::STOCK_TYPE_SALE,
StockRecord::STOCK_TYPE_LOSS,
];
if (!in_array($data['stock_type'], $validStockTypes)) {
throw new AdminException('出入库子类型无效');
}
if (empty($data['record_date'])) {
throw new AdminException('出入库日期不能为空');
}
if (empty($data['product']) || !is_array($data['product'])) {
throw new AdminException('商品信息不能为空');
}
foreach ($data['product'] as $item) {
if (empty($item['product_id'])) {
throw new AdminException('商品ID不能为空');
}
if(isset($item['good_stock']) && isset($item['defective_stock']) && !$item['good_stock'] && !$item['defective_stock']){
throw new AdminException('请填写出入库数量');
}
}
}
/**
* 生成唯一的单号
* @param int $type
* @return string
*/
public function generateUniqueRecordNo(int $type): string
{
$maxAttempts = 10;
$attempts = 0;
do {
$recordNo = $this->dao->generateRecordNo($type);
$exists = $this->dao->checkRecordNoExists($recordNo);
$attempts++;
} while ($exists && $attempts < $maxAttempts);
if ($exists) {
throw new AdminException('生成单号失败,请重试');
}
return $recordNo;
}
/**
* 导出出入库记录
* @param array $where
* @return array
*/
public
function exportStockRecords(array $where): array
{
$list = $this->dao->getList($where, '*', 1, 0, ['items']);
$exportData = [];
foreach ($list as $record) {
foreach ($record['items'] as $item) {
$exportData[] = [
'record_no' => $record['record_no'],
'type' => $record['type'] == 1 ? '入库' : '出库',
'product_id' => $item['product_id'],
'sku_id' => $item['sku_id'],
'quantity' => $item['quantity'],
'unit_price' => $item['unit_price'],
'total_price' => $item['total_price'],
'record_date' => date('Y-m-d H:i:s', $record['record_date']),
'status' => $this->getStatusText($record['status']),
'remark' => $record['remark']
];
}
}
return $exportData;
}
/**
* 获取详情
* @param $id
* @return array
* @throws \ReflectionException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/17 16:50
*/
public function read($id, $unique = '')
{
$info = $this->dao->get($id, ['*'], ['admin']);
if (!$info) {
throw new AdminException('记录不存在');
}
$info = $info->toArray();
$info['stock_type_name'] = StockRecord::getStockTypeName($info['stock_type']);
$product = app()->make(StockRecordItemServices::class)->search(['record_id' => $id, 'unique' => $unique, 'type' => 1])->select()->toArray();
if (!is_array($product)) {
$product = [];
}
foreach ($product as &$item){
$item['good_stock'] = abs($item['good_stock']);
$item['defective_stock'] = abs($item['defective_stock']);
}
$info['product'] = $product;
return $info;
}
/**
* 备注表单
* @param $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/18 14:42
*/
public
function remarkFrom($id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new AdminException('记录不存在');
}
$field[] = Form::textarea('remark', '备注:', $info['remark'] ?? '');
return create_form('添加备注', $field, Url::buildUrl('/stock/record/remark_save/' . $id), 'POST');
}
/**
* 获取售后单商品信息列表
* @param string $order_id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/9/17 16:15
*/
public
function getRefundList(string $order_id)
{
$storeOrderRefundServices = app()->make(StoreOrderRefundServices::class);
if ($this->dao->get(['after_sale_no' => $order_id, 'type' => 1])) {
throw new AdminException('该订单已经入库过了');
}
$order = $storeOrderRefundServices->get(['order_id' => $order_id], ['*']);
if (!$order) throw new ValidateException('订单不存在');
$order = $order->toArray();
/** @var StoreOrderServices $orderServices */
$orderServices = app()->make(StoreOrderServices::class);
$orderInfo = $orderServices->get($order['store_order_id'], ['*'], ['invoice', 'virtual']);
$orderInfo = $orderInfo->toArray();
$orderInfo = $orderServices->tidyOrder($orderInfo, true, true);
$cartInfo = $orderInfo['cartInfo'];
return $cartInfo;
}
/**
* 获取库存类型定义
* @return array
*/
private function getStockTypes(): array
{
return [
'in' => ['purchase', 'return', 'other_in', 'defective_to_good', 'profit'],
'out' => ['expired_return', 'use_out', 'scrap_out', 'good_to_defective', 'other_out', 'sale', 'loss']
];
}
/**
* 构建基础查询模型
* @param array $where
* @return mixed
*/
private function buildBaseQuery(array $where)
{
/** @var StockRecordItemDao $stockRecordItemDao */
$stockRecordItemDao = app()->make(StockRecordItemDao::class);
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
// 构建基础查询
$model = $stockRecordItemDao->search(['type' => 1, 'keywords' => $where['product_name'] ?? '']);
$where['record_date'] = $where['record_date'] && is_string($where['record_date']) ? explode('-', $where['record_date']) : [];
if (count($where['record_date']) == 2 && $where['record_date'][0] && $where['record_date'][1]) {
$startTime = strtotime($where['record_date'][0]);
$endTime = strtotime($where['record_date'][1]) + 86400 - 1;
$model = $model->whereBetween('record_date', [$startTime, $endTime]);
}
return $model;
}
/**
* 获取库存类型名称映射
* @return array
*/
private function getStockTypeNames(): array
{
return [
'purchase' => '采购入库',
'return' => '退货入库',
'other_in' => '其他入库',
'defective_to_good' => '残次品转良',
'expired_return' => '过期退货',
'profit' => '盘盈入库',
'use_out' => '试用出库',
'scrap_out' => '报废出库',
'good_to_defective' => '良品转残次品',
'other_out' => '其他出库',
'sale' => '销售出库',
'loss' => '盘亏出库'
];
}
/**
* 获取出入库统计数据列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStockStatisticsList(array $where): array
{
[$page, $limit] = $this->getPageValue();
/** @var StoreProductServices $productServices */
$productServices = app()->make(StoreProductServices::class);
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
// 获取库存类型定义
$stockTypes = $this->getStockTypes();
$inStockTypes = $stockTypes['in'];
$outStockTypes = $stockTypes['out'];
// 构建基础查询
$model = $this->buildBaseQuery($where);
if ($model === null) {
return ['list' => [], 'count' => 0];
}
// 根据stock_type参数筛选入库或出库
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
if ($where['stock_type'] == 1) {
// 查询入库统计
$model = $model->whereIn('stock_type', $inStockTypes);
} elseif ($where['stock_type'] == 2) {
// 查询出库统计
$model = $model->whereIn('stock_type', $outStockTypes);
}
}
// 按商品规格唯一值分组统计,分别统计各种类型的数量
$fieldArray = [
'product_id',
'product_suk',
'product_name',
'product_image',
'product_bar_code',
'unique',
'SUM(CASE
WHEN stock_type = "defective_to_good" THEN good_stock
WHEN stock_type = "good_to_defective" THEN defective_stock
ELSE good_stock + defective_stock
END) as total_stock',
'product_bar_code'
];
// 根据查询类型添加对应的统计字段
if ($where['stock_type'] == 1) {
// 入库统计
foreach ($inStockTypes as $type) {
if ($type === 'defective_to_good') {
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock ELSE 0 END) as {$type}_stock";
} else {
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock + defective_stock ELSE 0 END) as {$type}_stock";
}
}
} else {
// 出库统计
foreach ($outStockTypes as $type) {
if ($type === 'good_to_defective') {
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN defective_stock ELSE 0 END) as {$type}_stock";
} else {
$fieldArray[] = "SUM(CASE WHEN stock_type = '{$type}' THEN good_stock + defective_stock ELSE 0 END) as {$type}_stock";
}
}
}
$statistics = $model->field($fieldArray)
->group('product_id, unique')
->page($page, $limit)
->select()
->toArray();
// 获取总数 - 重新构建查询
$countModel = $this->buildBaseQuery($where);
if ($countModel === null) {
return ['list' => [], 'count' => 0];
}
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
if ($where['stock_type'] == 1) {
$countModel = $countModel->whereIn('stock_type', $inStockTypes);
} elseif ($where['stock_type'] == 2) {
$countModel = $countModel->whereIn('stock_type', $outStockTypes);
}
}
// 获取分组后的总数
$countResult = $countModel->field('product_id, unique')->group('product_id, unique')->select();
$count = count($countResult);
// 获取类型名称映射
$typeNames = $this->getStockTypeNames();
// 组装返回数据
$list = [];
foreach ($statistics as $item) {
$result = [
'product_id' => $item['product_id'],
'product_name' => $item['product_name'],
'unique' => $item['unique'],
'bar_code' => $item['product_bar_code'] ?? '',
'sku_name' => $item['product_suk'],
'total_stock' => $item['total_stock']
];
$_result[] = [
'prop' => abs((int)$item['total_stock']),
'label' => '总库存'
];
// 添加各种类型的统计数据
$allTypes = array_merge($inStockTypes, $outStockTypes);
foreach ($allTypes as $type) {
$stockKey = $type . '_stock';
if (isset($item[$stockKey])) {
$_result[] = [
'prop' => abs((int)$item[$stockKey]),
'label' => $typeNames[$type]
];
}
}
$result['result'] = $_result;
$list[] = $result;
}
return compact('list', 'count');
}
/**
* 获取出入库整体统计数据
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getStockOverallStatistics(array $where): array
{
// 获取库存类型定义
$stockTypes = $this->getStockTypes();
$inStockTypes = $stockTypes['in'];
$outStockTypes = $stockTypes['out'];
// 构建基础查询
$model = $this->buildBaseQuery($where);
if ($model === null) {
return $this->getEmptyStatisticsCards($where);
}
// 根据stock_type参数筛选入库或出库
if (isset($where['stock_type']) && $where['stock_type'] !== '') {
if ($where['stock_type'] == 1) {
// 只查询入库统计
$model = $model->whereIn('stock_type', $inStockTypes);
} elseif ($where['stock_type'] == 2) {
// 只查询出库统计
$model = $model->whereIn('stock_type', $outStockTypes);
}
}
// 根据查询类型构建不同的统计字段
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
// 只统计入库
$fieldArray = [
'SUM(CASE
WHEN stock_type = "defective_to_good" THEN good_stock
WHEN stock_type IN ("' . implode('","', array_diff($inStockTypes, ['defective_to_good'])) . '") THEN good_stock + defective_stock
ELSE 0
END) as total_in_stock',
'SUM(CASE WHEN stock_type = "purchase" THEN good_stock + defective_stock ELSE 0 END) as purchase_stock',
'SUM(CASE WHEN stock_type = "profit" THEN good_stock + defective_stock ELSE 0 END) as profit_stock',
'SUM(CASE WHEN stock_type = "return" THEN good_stock + defective_stock ELSE 0 END) as return_stock',
'SUM(CASE WHEN stock_type = "other_in" THEN good_stock + defective_stock ELSE 0 END) as other_in_stock',
'SUM(CASE WHEN stock_type = "defective_to_good" THEN good_stock ELSE 0 END) as defective_to_good_stock'
];
} else {
// 只统计出库
$fieldArray = [
'SUM(CASE
WHEN stock_type = "good_to_defective" THEN defective_stock
WHEN stock_type IN ("' . implode('","', array_diff($outStockTypes, ['good_to_defective'])) . '") THEN good_stock + defective_stock
ELSE 0
END) as total_out_stock',
'SUM(CASE WHEN stock_type = "sale" THEN good_stock + defective_stock ELSE 0 END) as sale_stock',
'SUM(CASE WHEN stock_type = "loss" THEN good_stock + defective_stock ELSE 0 END) as loss_stock',
'SUM(CASE WHEN stock_type = "expired_return" THEN good_stock + defective_stock ELSE 0 END) as expired_return_stock',
'SUM(CASE WHEN stock_type = "use_out" THEN good_stock + defective_stock ELSE 0 END) as use_out_stock',
'SUM(CASE WHEN stock_type = "scrap_out" THEN good_stock + defective_stock ELSE 0 END) as scrap_out_stock',
'SUM(CASE WHEN stock_type = "other_out" THEN good_stock + defective_stock ELSE 0 END) as other_out_stock',
'SUM(CASE WHEN stock_type = "good_to_defective" THEN defective_stock ELSE 0 END) as good_to_defective_stock'
];
}
// 执行统计查询
$statistics = $model->field($fieldArray)->find();
if (!$statistics) {
return $this->getEmptyStatisticsCards($where);
}
// 转换为数组
$stats = $statistics->toArray();
// 根据查询类型组装返回数据
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
// 只返回入库统计卡片
return [
['name' => '总入库数', 'type' => 1, 'field' => '件', 'count' => (int)$stats['total_in_stock'], 'className' => 'icondingdanjine', 'col' => 8],
['name' => '采购入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['purchase_stock'], 'className' => 'iconshouyintai-shouyin1', 'col' => 8],
['name' => '盘盈入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['profit_stock'], 'className' => 'iconhexiaodingdanjine', 'col' => 8],
['name' => '退货入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['return_stock'], 'className' => 'iconshouhou_tuikuan', 'col' => 8],
['name' => '其他入库', 'type' => 1, 'field' => '件', 'count' => (int)$stats['other_in_stock'], 'className' => 'icontuikuandingdanliang', 'col' => 8],
['name' => '残次品转良品', 'type' => 1, 'field' => '件', 'count' => (int)$stats['defective_to_good_stock'], 'className' => 'iconfenpeidingdanjine', 'col' => 8],
];
} else {
// 只返回出库统计卡片
return [
['name' => '总出库数', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['total_out_stock']), 'className' => 'icondingdanjine', 'col' => 6],
['name' => '销售出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['sale_stock']), 'className' => 'iconzaishoushangpin', 'col' => 6],
['name' => '盘亏出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['loss_stock']), 'className' => 'icondaishenhe-shequneirong', 'col' => 6],
['name' => '过期退货', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['expired_return_stock']), 'className' => 'iconshouyintai-tuihuo1', 'col' => 6],
['name' => '试用出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['use_out_stock']), 'className' => 'iconshouhou-tuikuan-lv', 'col' => 6],
['name' => '报废出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['scrap_out_stock']), 'className' => 'iconjingjiekucun', 'col' => 6],
['name' => '其他出库', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['other_out_stock']), 'className' => 'icontuikuandingdanliang', 'col' => 6],
['name' => '良品转残次品', 'type' => 1, 'field' => '件', 'count' => abs((int)$stats['good_to_defective_stock']), 'className' => 'iconfenpeidingdanjine', 'col' => 6],
];
}
}
/**
* 获取空的统计数据卡片
* @param array $where
* @return array
*/
private function getEmptyStatisticsCards(array $where = []): array
{
// 根据查询类型返回对应的空数据卡片
if (isset($where['stock_type']) && $where['stock_type'] == 1) {
// 只返回入库空数据卡片
return [
['name' => '总入库数', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icondingdanjine', 'col' => 8],
['name' => '采购入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouyintai-shouyin1', 'col' => 8],
['name' => '盘盈入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconhexiaodingdanjine', 'col' => 8],
['name' => '退货入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouhou_tuikuan', 'col' => 8],
['name' => '其他入库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icontuikuandingdanliang', 'col' => 8],
['name' => '残次品转良品', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconfenpeidingdanjine', 'col' => 8]
];
} else {
// 只返回出库空数据卡片
return [
['name' => '总出库数', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconchuku', 'col' => 6],
['name' => '销售出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconzaishoushangpin', 'col' => 6],
['name' => '盘亏出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icondaishenhe-shequneirong', 'col' => 6],
['name' => '过期退货', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouyintai-tuihuo1', 'col' => 6],
['name' => '试用出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconshouhou-tuikuan-lv', 'col' => 6],
['name' => '报废出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconjingjiekucun', 'col' => 6],
['name' => '其他出库', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'icontuikuandingdanliang', 'col' => 6],
['name' => '良品转残次品', 'type' => 1, 'field' => '件', 'count' => 0, 'className' => 'iconfenpeidingdanjine', 'col' => 6]
];
}
}
/**
* 明细导出查询
* @param array $where
* @param $with
* @param int $limits
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* User: liusl
* DateTime: 2025/10/11 09:38
*/
public function getStockDetailsList(array $where, int $limits = 0): array
{
[$page, $limit] = $this->getPageValue();
$limit = $limits ?: $limit;
$list = $this->dao->getList($where, '*', $page, $limit, ['admin']);
$ids = array_column($list, 'id');
$items = app()->make(StockRecordItemServices::class)->search(['record_id' => $ids, 'unique' => $where['unique']])->select()->toArray();
$_items = [];
foreach ($items as $item) {
$_items[$item['record_id']][] = $item;
}
foreach ($list as &$item) {
$item['items'] = $_items[$item['id']] ?? [];
$item['stock_type_name'] = StockRecord::getStockTypeName($item['stock_type']);
}
return $list;
}
/**
* 获取库存统计概览数据
* @param array $where 查询条件
* @return array
*/
public function getProductStatistics($where = [])
{
// 获取当前库存数量(不受时间筛选影响)
$currentStock = $this->getCurrentStock($where);
//警戒库存商品
$store_stock = sys_config('store_stock', 0);
$productIds = app()->make(StoreProductServices::class)->search(['status' => 5])->column('id');
$attrStock = app()->make(StoreProductAttrValueServices::class)->search(['product_id' => $productIds])->where('stock', '<=', $store_stock)->sum('stock');
return [
['name' => '良品库存', 'field' => '件', 'count' => $currentStock['good_stock'] ?? 0, 'className' => 'ios-cube', 'col' => 8],
// ['name' => '良品入库数量', 'field' => '件', 'count' => $stockMovement['good_in_stock'] ?? 0, 'className' => 'ios-arrow-down', 'col' => 8],
// ['name' => '良品出库数量', 'field' => '件', 'count' => $stockMovement['good_out_stock'] ?? 0, 'className' => 'ios-arrow-up', 'col' => 8],
['name' => '残次品库存', 'field' => '件', 'count' => $currentStock['defective_stock'] ?? 0, 'className' => 'ios-arrow-round-down', 'col' => 8],
['name' => '警戒库存', 'field' => '件', 'count' => $attrStock ?? 0, 'className' => 'ios-warning', 'col' => 8],
// ['name' => '残次品出库数量', 'field' => '件', 'count' => $stockMovement['defective_out_stock'] ?? 0, 'className' => 'ios-arrow-round-up', 'col' => 8],
];
}
/**
* 获取当前库存数量(不受时间筛选影响)
* @param array $where
* @return array
*/
private function getCurrentStock($where = [])
{
/** @var StoreProductAttrValueServices $attrValueServices */
$attrValueServices = app()->make(StoreProductAttrValueServices::class);
unset($where['time']);
$where['type'] = 0;
// 查询当前库存
$stockData = $attrValueServices->joinAttrSearch($where)
->field('SUM(a.stock) as good_stock, SUM(a.defective_stock) as defective_stock')
->find();
return $stockData ? $stockData->toArray() : ['good_stock' => 0, 'defective_stock' => 0];
}
/**
* 获取出入库统计数量(根据时间筛选)
* @param array $where
* @return array
*/
private function getStockMovement($where = [])
{
/** @var StockRecordItemDao $itemDao */
$itemDao = app()->make(StockRecordItemDao::class);
$query = $itemDao->search($where);
// 定义入库和出库类型
$inStockTypes = ['purchase', 'return', 'other_in', 'defective_to_good', 'profit'];
$outStockTypes = ['expired_return', 'use_out', 'scrap_out', 'good_to_defective', 'other_out', 'sale', 'loss'];
// 统计入库和出库数量
$statistics = $query->field([
// 良品入库数量
'SUM(CASE WHEN stock_type IN ("' . implode('","', $inStockTypes) . '") THEN good_stock ELSE 0 END) as good_in_stock',
// 良品出库数量
'SUM(CASE WHEN stock_type IN ("' . implode('","', $outStockTypes) . '") THEN good_stock ELSE 0 END) as good_out_stock',
// 残次品入库数量
'SUM(CASE WHEN stock_type IN ("' . implode('","', $inStockTypes) . '") THEN defective_stock ELSE 0 END) as defective_in_stock',
// 残次品出库数量
'SUM(CASE WHEN stock_type IN ("' . implode('","', $outStockTypes) . '") THEN defective_stock ELSE 0 END) as defective_out_stock'
])->find();
return $statistics ? $statistics->toArray() : [
'good_in_stock' => 0,
'good_out_stock' => 0,
'defective_in_stock' => 0,
'defective_out_stock' => 0
];
}
}