- 2023-10-25
- 2,635
- 40
针对v3.2.24版本,修改如下文件:
1.app/Controller/Admin/Api/Store.php
2.app/Controller/User/Api/Index.php
3.app/Service/Bind/Shop.php
4.assets/admin/controller/shared/store.js
1.app/Controller/Admin/Api/Store.php
代码:
<?php
declare(strict_types=1);
namespace App\Controller\Admin\Api;
use App\Controller\Base\API\Manage;
use App\Entity\Query\Delete;
use App\Entity\Query\Get;
use App\Entity\Query\Save;
use App\Interceptor\ManageSession;
use App\Model\ManageLog;
use App\Model\Shared;
use App\Service\Image;
use App\Service\Query;
use App\Util\Date;
use App\Util\Ini;
use App\Util\Str;
use Kernel\Annotation\Inject;
use Kernel\Annotation\Interceptor;
use Kernel\Context\Interface\Request;
use Kernel\Exception\JSONException;
use Kernel\Waf\Filter;
#[Interceptor(ManageSession::class, Interceptor::TYPE_API)]
class Store extends Manage
{
#[Inject]
private Query $query;
#[Inject]
private \App\Service\Shared $shared;
#[Inject]
private Image $image;
/**
* @return array
*/
public function data(): array
{
$map = $_POST;
$get = new Get(Shared::class);
$get->setPaginate((int)$this->request->post("page"), (int)$this->request->post("limit"));
$get->setWhere($map);
$data = $this->query->get($get);
return $this->json(data: $data);
}
/**
* @return array
* @throws JSONException
*/
public function save(): array
{
$map = $_POST;
if (!$map['domain']) {
throw new JSONException("店铺地址不能为空");
}
if (!$map['app_id']) {
throw new JSONException("商户ID不能为空");
}
if (!$map['app_key']) {
throw new JSONException("商户密钥不能为空");
}
$map['domain'] = trim($map['domain'], "/");
$connect = $this->shared->connect($map['domain'], $map['app_id'], $map['app_key'], (int)$map['type']);
$map['name'] = strip_tags((string)$connect['shopName']);
$map['balance'] = (float)$connect['balance'];
$save = new Save(Shared::class);
$save->setMap($map);
$save->enableCreateTime();
$save = $this->query->save($save);
if (!$save) {
throw new JSONException("保存失败,请检查信息填写是否完整");
}
ManageLog::log($this->getManage(), "[修改/新增]共享店铺");
return $this->json(200, '(^∀^)保存成功');
}
/**
* @return array
* @throws JSONException
*/
public function connect(): array
{
$id = (int)$_POST['id'];
$shared = Shared::query()->find($id);
if (!$shared) {
throw new JSONException("未找到该店铺");
}
$connect = $this->shared->connect($shared->domain, $shared->app_id, $shared->app_key, $shared->type);
$shared->name = strip_tags((string)$connect['shopName']);
$shared->balance = (float)$connect['balance'];
$shared->save();
return $this->json(200, 'success');
}
/**
* @return array
* @throws JSONException
*/
public function items(): array
{
$id = (int)$_POST['id'];
$shared = Shared::query()->find($id);
if (!$shared) {
throw new JSONException("未找到该店铺");
}
$items = $this->shared->items($shared);
$gan = function (&$items) use (&$gan) {
foreach ($items as $key => $val) {
$items[$key]["name"] = strip_tags((string)$val['name']);
if (isset($val['children']) && !empty($val['children'])) {
$gan($items[$key]["children"]);
}
}
};
$gan($items);
foreach ($items as $key => $item) {
$items[$key]['id'] = 0;
}
return $this->json(200, 'success', $items);
}
/**
* @throws JSONException
*/
public function addItem(Request $request): array
{
$map = $request->post(flags: Filter::NORMAL);
$categoryId = (int)$map['category_id'];
$storeId = (int)$_GET['storeId'];
$items = (array)$map['items'];
$premium = (float)$map['premium']; // 加价金额
$premiumType = (int)$map['premium_type']; // 加价模式
$sharedSync = (int)($map['shared_sync'] ?? 0) == 0 ? 0 : 1; // 主从同步
$inventorySync = (int)($map['inventory_sync'] ?? 1) == 0 ? 0 : 1; // 数量同步,默认开启
$imageDownload = (bool)$map['image_download'];
$shelves = (int)$map['shelves'] == 0 ? 0 : 1; // 立即上架
$shared = Shared::query()->find($storeId);
if (!$shared) {
throw new JSONException("未找到该店铺");
}
$date = Date::current();
$count = count($items);
$success = 0;
$error = 0;
foreach ($items as $item) {
try {
$commodity = new \App\Model\Commodity();
$commodity->category_id = $categoryId;
$commodity->name = $item['name'];
$commodity->description = $item['description'];
//正则处理
preg_match_all('#<img.*?src="(/.*?)"#', $commodity->description, $matchs);
$list = (array)$matchs[1];
if (count($list) > 0) {
foreach ($list as $e) {
//远端图片下载
if ($imageDownload) {
$download = $this->image->downloadRemoteImage($shared->domain . $e);
$commodity->description = str_replace($e, $download[0], $commodity->description);
} else {
$commodity->description = str_replace($e, $shared->domain . $e, $commodity->description);
}
}
}
//远端cover下载
if ($imageDownload) {
$download = $this->image->downloadRemoteImage($shared->domain . $item['cover']);
$commodity->cover = $download[0];
} else {
$commodity->cover = $shared->domain . $item['cover'];
}
$commodity->status = $shelves;
$commodity->owner = 0;
$commodity->create_time = $date;
$commodity->api_status = 0;
$commodity->code = strtoupper(Str::generateRandStr(16));
$commodity->delivery_way = 1;
$commodity->contact_type = $item['contact_type'];
$commodity->password_status = $item['password_status'];
$commodity->sort = 0;
$commodity->coupon = 0;
$commodity->shared_id = $storeId;
$commodity->shared_code = $item['code'];
$commodity->shared_premium = $premium;
$commodity->shared_premium_type = $premiumType;
$commodity->shared_sync = $sharedSync;
$commodity->inventory_sync = $inventorySync;
$commodity->seckill_status = $item['seckill_status'] ?? 0;
if ($commodity->seckill_status == 1) {
$commodity->seckill_start_time = $item['seckill_start_time'] ?? null;
$commodity->seckill_end_time = $item['seckill_end_time'] ?? null;
}
$commodity->draft_status = $item['draft_status'] ?? 0;
if ($commodity->draft_status) {
$commodity->draft_premium = $this->shared->AdjustmentAmount($premiumType, $premium, $item['draft_premium'] ?? 0);
}
//2022/01/05新增
$commodity->inventory_hidden = $item['inventory_hidden'] ?? 0;
$commodity->only_user = $item['only_user'] ?? 0;
$commodity->purchase_count = $item['purchase_count'] ?? 0;
$commodity->widget = $item['widget'] ?? null;
$commodity->minimum = $item['minimum'] ?? 0;
$commodity->maximum = $item['maximum'] ?? 0;
if (array_key_exists('stock', $item)) {
$commodity->stock = (int)$item['stock'];
}
//自动加价
$config = $this->shared->AdjustmentPrice((string)$item['config'], $item['price'], $item['user_price'], $premiumType, $premium);
$commodity->config = Ini::toConfig($config['config']);
$commodity->price = $config['price'];
$commodity->user_price = $config['user_price'];
$commodity->save();
$success++;
} catch (\Exception $e) {
$error++;
}
}
ManageLog::log($this->getManage(), "[店铺共享]进行了克隆商品({$shared->name}),总数量:{$count},成功:{$success},失败:{$error}");
return $this->json(200, "拉取结束,总数量:{$count},成功:{$success},失败:{$error}");
}
/**
* @return array
* @throws JSONException
*/
public function del(): array
{
$deleteBatchEntity = new Delete(Shared::class, $_POST['list']);
$count = $this->query->delete($deleteBatchEntity);
if ($count == 0) {
throw new JSONException("没有移除任何数据");
}
ManageLog::log($this->getManage(), "[店铺共享]删除操作,共计:" . count($_POST['list']));
return $this->json(200, '(^∀^)移除成功');
}
}
2.app/Controller/User/Api/Index.php
代码:
<?php
declare(strict_types=1);
namespace App\Controller\User\Api;
use App\Consts\Hook;
use App\Controller\Base\API\User;
use App\Entity\Query\Get;
use App\Interceptor\UserVisitor;
use App\Interceptor\Waf;
use App\Model\Card;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\Order;
use App\Model\Pay;
use App\Model\UserCommodity;
use App\Service\Query;
use App\Service\Shared;
use App\Service\Shop;
use App\Util\Client;
use App\Util\FileCache;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Annotation\Interceptor;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Util\Decimal;
#[Interceptor([Waf::class, UserVisitor::class])]
class Index extends User
{
#[Inject]
private Shared $shared;
#[Inject]
private Query $query;
#[Inject]
private \App\Service\Order $order;
#[Inject]
private Shop $shop;
/**
* @return array
*/
public function data(): array
{
$category = $this->shop->getCategory($this->getUserGroup());
hook(Hook::USER_API_INDEX_CATEGORY_LIST, $category);
return $this->json(200, "success", $category);
}
/**
* @return array
* @throws JSONException
* @throws RuntimeException
*/
public function commodity(): array
{
$keywords = (string)$_GET['keywords'];
$limit = (int)$_GET['limit'];
$page = (int)$_GET['page'];
$categoryId = $_GET['categoryId'];
FileCache::clearCache('shop');
$commodity = Commodity::query()
->with(['owner' => function (Relation $relation) {
$relation->select(["id", "username", "avatar"]);
}, 'category' => function (Relation $relation) {
$relation->select(["id", "name", "icon"]);
}]);
if ($categoryId == 'recommend') {
$commodity = $commodity->where("recommend", 1);
} elseif ($categoryId != 0) {
$commodity = $commodity->where("category_id", $categoryId);
}
if ($keywords != "") {
$commodity = $commodity->where('name', 'like', '%' . $keywords . '%');
}
$bus = \App\Model\Business::get();
$userCommodityMap = []; //自定义名称的MAP
if ($bus) {
//商家
if ($bus->master_display == 0) {
$commodity = $commodity->where("owner", $bus->user_id);
} else {
//查出所有自己定义的商品
$userCommodity = UserCommodity::query()->where("user_id", $bus->user_id)->get();
//隐藏的分类ID
$hideCommodity = [];
foreach ($userCommodity as $userComm) {
if ($userComm->status == 0) {
$hideCommodity[] = $userComm->commodity_id;
} else {
$userCommodityMap[$userComm->commodity_id] = $userComm;
}
}
$commodity = $commodity->whereNotIn("id", $hideCommodity)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
}
} else {
//主站
if (Config::get("substation_display") == 1) {
$let = "(`owner`=0 or ";
//显示商家
$list = (array)json_decode(Config::get("substation_display_list"), true);
foreach ($list as $userId) {
$let .= "`owner`={$userId} or ";
}
$let = trim(trim($let), "or") . ")";
$commodity = $commodity->whereRaw($let);
} else {
$commodity = $commodity->where("owner", 0);
}
}
$commodity = $commodity
->where("status", 1)
->orderBy("sort", "asc")
->select([
'id', 'name', 'cover',
'status', 'delivery_way', 'price',
'user_price', 'shared_code', 'inventory_sync',
'level_disable', 'level_price', 'hide', 'owner', 'inventory_hidden', "recommend", 'category_id', 'stock', 'shared_id'
])
->withCount(['order as order_sold' => function (Builder $relation) {
$relation->where("delivery_status", 1);
}]);
if ($limit == 0) {
$commodity = $commodity
->get();
$total = count($commodity);
$data = $commodity->toArray();
} else {
$commodity = $commodity
->paginate($limit, ["*"], "", $page);
$total = $commodity->total();
$data = $commodity->toArray()['data'];
}
$user = $this->getUser();
$userGroup = $this->getUserGroup();
//取得分类
$category = $this->shop->getCategory($userGroup);
$cates = [];
foreach ($category as $cate) {
$cates[] = (string)$cate['id'];
}
//最终的商品数据遍历
foreach ($data as $key => $val) {
$parseGroupConfig = Commodity::parseGroupConfig($val['level_price'], $userGroup);
if (!in_array((string)$val['category_id'], $cates) || $val['hide'] == 1 && (!$parseGroupConfig || !isset($parseGroupConfig['show']) || $parseGroupConfig['show'] != 1)) {
//隐藏商品
unset($data[$key]);
continue;
}
if ($val['shared_id'] && (int)$val['inventory_sync'] === 1) {
$shopCache = FileCache::getJsonFile("shop", $val['id'] . "_shared");
if (!empty($shopCache)) {
$inventory = $shopCache;
} else {
$com = Commodity::query()->with(['shared'])->find($val['id']);
if (!$com || !$com->shared) {
unset($data[$key]);
continue;
}
try {
$inventory = $this->shared->inventory($com->shared, $com);
FileCache::setJsonFile("shop", $val['id'] . "_shared", $inventory, 300);
} catch (\Throwable $e) {
FileCache::setJsonFile("shop", $val['id'] . "_shared", [
"count" => 0,
"delivery_way" => 0
], 300);
@file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] inventory fetch failed, commodity {$val['id']}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
unset($data[$key]);
continue;
}
}
$stockCount = (int)($inventory['count'] ?? 0);
$deliveryWay = $inventory['delivery_way'] ?? $data[$key]['delivery_way'];
if ($stockCount === 0 && (int)$deliveryWay === 0) {
unset($data[$key]);
continue;
}
$data[$key]['stock'] = $stockCount;
$data[$key]['delivery_way'] = $deliveryWay;
} elseif ($val['delivery_way'] == 0 && !$val['shared_id']) {
$data[$key]['stock'] = Card::query()->where("status", 0)->where("commodity_id", $val['id'])->count();
}
//如果登录后,则自动计算登录后的价格
if ($user) {
$tradeAmount = $this->order->valuation(commodity: $commodity[$key], group: $userGroup);
$data[$key]['price'] = $tradeAmount;
$data[$key]['user_price'] = $tradeAmount;
}
unset(
$data[$key]['level_price'],
$data[$key]['level_disable']
);
if (!$val['cover']) {
$data[$key]['cover'] = "/favicon.ico";
}
//分站自定义名称和价格
if (isset($userCommodityMap[$val['id']])) {
$var = $userCommodityMap[$val['id']];
if ($var->premium > 0) {
$data[$key]['price'] = (new Decimal($data[$key]['price'], 2))->mul($var->premium / 100)->add($data[$key]['price'])->getAmount();
$data[$key]['user_price'] = (new Decimal($data[$key]['user_price'], 2))->mul($var->premium / 100)->add($data[$key]['user_price'])->getAmount();
}
if ($var->name) {
$data[$key]['name'] = $var->name;
}
}
if ($data[$key]['stock'] === null) {
$data[$key]['stock'] = 0;
}
//隐藏库存
$data[$key]['stock_state'] = $this->shop->getStockState($data[$key]['stock']);
if ($val['inventory_hidden'] == 1) {
$data[$key]['stock'] = $this->shop->getHideStock($data[$key]['stock']);
}
}
$data = array_values($data);
hook(Hook::USER_API_INDEX_COMMODITY_LIST, $data);
$json = $this->json(200, "success", $data);
$json['total'] = $total;
return $json;
}
/**
* @param int $commodityId
* @return array
*/
public function commodityDetail(int $commodityId): array
{
$array = $this->shop->getItem($commodityId, $this->getUser(), $this->getUserGroup());
hook(Hook::USER_API_INDEX_COMMODITY_DETAIL_INFO, $array);
$array['stock_state'] = $this->shop->getStockState($array['stock']);
if ($array['inventory_hidden'] == 1) {
$array['stock'] = $this->shop->getHideStock($array['stock']);
}
return $this->json(200, 'success', $array);
}
/**
* @return array
* @throws JSONException
*/
public function card(): array
{
$map = $this->request->post();
/**
* @var Commodity $commodity
*/
$commodity = Commodity::with(['shared'])->find($map['item_id']);
$limit = $map['limit'] ?? 10;
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("该商品暂未上架");
}
if ($commodity->draft_status != 1) {
throw new JSONException("该商品不支持预选");
}
if ($commodity->shared) {
$data = $this->shared->draftCard($commodity->shared, $commodity->shared_code, $map);
//加价算法
foreach ($data['list'] as &$item) {
if ($item['draft_premium'] > 0) {
$item['draft_premium'] = $this->shared->AdjustmentAmount($commodity->shared_premium_type, $commodity->shared_premium, $item['draft_premium']);
}
}
} else {
$get = new Get(Card::class);
$get->setPaginate((int)$this->request->post("page"), (int)$limit);
$get->setWhere($map);
$get->setColumn('id', 'draft', 'draft_premium');
$data = $this->query->get($get, function (Builder $builder) use ($map) {
$builder = $builder->where("commodity_id", $map['item_id'])->where("status", 0);
if (!empty($map['race'])) {
$builder = $builder->where("race", $map['race']);
}
if (!empty($map['sku']) && is_array($map['sku'])) {
foreach ($map['sku'] as $k => $v) {
$builder = $builder->where("sku->{$k}", $v);
}
}
return $builder;
});
}
//分站处理
if (\App\Model\Business::state()) {
foreach ($data['list'] as &$item) {
if ($item['draft_premium'] > 0) {
$item['draft_premium'] = $this->shop->getSubstationPrice($commodity, $item['draft_premium']);
}
}
}
if ($commodity->level_disable != 1) {
//折扣处理
foreach ($data['list'] as &$item) {
if ($item['draft_premium'] > 0) {
$item['draft_premium'] = $this->order->getValuationPrice($commodity->id, $item['draft_premium'], $this->getUserGroup());
}
}
}
return $this->json(data: $data);
}
/**
* @return array
*/
public
function valuation(): array
{
$price = $this->order->valuation(
commodity: (int)$this->request->post("item_id"),
num: (int)$this->request->post("num"),
race: (string)$this->request->post("race"),
sku: (array)$this->request->post("sku"),
cardId: (int)$this->request->post("card_id"),
coupon: (string)$this->request->post("coupon"),
group: $this->getUserGroup()
);
$price = $this->shop->getSubstationPrice((int)$this->request->post("item_id"), $price);
return $this->json(data: ["price" => $price]);
}
/**
* @return array
*/
public
function stock(): array
{
$commodity = Commodity::with(['shared'])->find((int)$this->request->post("item_id"));
$stock = $this->shop->getItemStock($commodity, (string)$this->request->post("race"), (array)$this->request->post("sku"));
$array = ["stock" => $stock];
$array['stock_state'] = $this->shop->getStockState($stock);
if ($commodity->inventory_hidden == 1) {
$array['stock'] = $this->shop->getHideStock($stock);
}
return $this->json(data: $array);
}
/**
* @return array
*/
public
function pay(): array
{
$equipment = 2;
if (Client::isMobile()) {
$equipment = 1;
}
if (Client::isWeChat()) {
$equipment = 3;
}
$let = "(`equipment`=0 or `equipment`={$equipment})";
if (!$this->getUser()) {
$let .= " and id!=1";
}
$pay = Pay::query()->orderBy("sort", "asc")->where("commodity", 1)->whereRaw($let)->get(['id', 'name', 'icon', 'handle'])->toArray();
hook(Hook::USER_API_INDEX_PAY_LIST, $pay);
return $this->json(200, 'success', $pay);
}
/**
* @param string $keywords
* @return array
*/
public function query(string $keywords): array
{
$keywords = trim($keywords);
$get = new Get(Order::class);
$get->setPaginate((int)$this->request->post("page"), (int)$this->request->post("limit"));
$get->setColumn('id', 'trade_no', 'sku', 'secret', 'user_id', 'password', 'amount', 'pay_id', 'commodity_id', 'create_time', 'pay_time', 'delivery_status', 'status', 'card_num', 'contact', "race");
$data = $this->query->get($get, function (Builder $builder) use ($keywords) {
$builder = $builder->with(['pay' => function (Relation $relation) {
$relation->select(['id', 'name', 'icon']);
}, 'commodity' => function (Relation $relation) {
$relation->select(['id', 'name', 'cover', 'password_status', 'leave_message']);
}]);
if (preg_match('/^\d{18}$/', $keywords)) {
$builder = $builder->where("trade_no", $keywords);
} else {
$builder = $builder->where("contact", $keywords);
}
return $builder;
});
foreach ($data['list'] as &$item) {
if ($item['status'] != 1) {
unset($item['commodity']['leave_message']);
unset($item['secret']);
}
if (!empty($item['password'])) {
$item['password'] = true;
unset($item['secret']);
}
}
hook(Hook::USER_API_INDEX_QUERY_LIST, $data);
return $this->json(data: $data);
}
/**
* @param string $tradeNo
* @param string $password
* @return array
* @throws JSONException
*/
public function secret(string $tradeNo, string $password): array
{
$order = Order::with(['commodity'])->where("trade_no", $tradeNo)->first();
if (!$order) {
throw new JSONException("未查询到相关信息");
}
if (!empty($order->password)) {
if ($password != $order->password) {
throw new JSONException("密码错误");
}
}
if ($order->status != 1) {
throw new JSONException("订单还未支付");
}
$widget = (array)json_decode((string)$order->widget, true);
if (empty($widget)) {
$widget = null;
}
hook(Hook::USER_API_INDEX_QUERY_SECRET, $order);
return $this->json(data: [
'secret' => $order->secret,
'widget' => $widget,
'leave_message' => $order?->commodity?->leave_message
]);
}
}
3.app/Service/Bind/Shop.php
代码:
<?php
declare(strict_types=1);
namespace App\Service\Bind;
use App\Consts\Hook;
use App\Model\Business;
use App\Model\Card;
use App\Model\Category;
use App\Model\Commodity;
use App\Model\Config;
use App\Model\User;
use App\Model\UserCategory;
use App\Model\UserCommodity;
use App\Model\UserGroup;
use App\Service\Shared;
use App\Util\Client;
use App\Util\Ini;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Kernel\Annotation\Inject;
use Kernel\Exception\JSONException;
use Kernel\Exception\RuntimeException;
use Kernel\Plugin\Entity\Stock;
use Kernel\Util\Decimal;
class Shop implements \App\Service\Shop
{
#[Inject]
private Shared $shared;
#[Inject]
private \App\Service\Order $order;
/**
* @param UserGroup|null $group
* @return array
* @throws RuntimeException
*/
public function getCategory(?UserGroup $group): array
{
$category = Category::query()->withCount(['children as commodity_count' => function (Builder $builder) {
$builder->where("status", 1);
}])->where("status", 1)->orderBy("sort", "asc");
$bus = Business::get();
$userCategoryMap = []; //自定义名称的MAP
$master = true;
if ($bus) {
$master = false;
//商家
if ($bus->master_display == 0) {
$category = $category->where("owner", $bus->user_id);
} else {
//查询出所有不显示的ID
$userCategory = UserCategory::query()->where("user_id", $bus->user_id)->get();
//隐藏的分类ID
$hideCategory = [];
foreach ($userCategory as $userCate) {
if ($userCate->status == 0) {
$hideCategory[] = $userCate->category_id;
} else {
$userCategoryMap[$userCate->category_id] = $userCate;
}
}
$category = $category->whereNotIn("id", $hideCategory)->whereRaw("(`owner`=0 or `owner`={$bus->user_id})");
}
} else {
//主站
if (Config::get("substation_display") == 1) {
//显示商家
$list = (array)json_decode(Config::get("substation_display_list"), true);
$let = "(`owner`=0 or ";
foreach ($list as $userId) {
$let .= "`owner`={$userId} or ";
}
$let = trim(trim($let), "or") . ")";
$category = $category->whereRaw($let);
} else {
$category = $category->where("owner", 0);
}
}
//拿到最终的分类数据
$category = $category->get();
foreach ($category as $index => $item) {
$levelConfig = $item->getLevelConfig($group);
if ($item->hide == 1 && (!$levelConfig || !isset($levelConfig['show']) || (int)$levelConfig['show'] != 1)) {
unset($category[$index]);
continue;
}
if (isset($userCategoryMap[$item->id])) {
$var = $userCategoryMap[$item->id];
if ($var->name) {
$category[$index]['name'] = $var->name;
}
}
if (!$item->icon) {
$category[$index]['icon'] = '/favicon.ico';
}
}
$array = $category->toArray();
$array = array_values($array);
$commodityRecommend = Config::get("commodity_recommend");
if ($commodityRecommend == 1 && $master) {
array_unshift($array, [
"id" => 'recommend',
"name" => Config::get("commodity_name"),
"sort" => 1,
"create_time" => "-",
"owner" => 0,
"icon" => "/assets/static/images/recommend.png",
"status" => 1,
"hide" => 0,
"user_level_config" => null,
"commodity_count" => Commodity::query()->where("status", 1)->where("recommend", 1)->count(),
]);
}
return $array;
}
/**
* @param int|string $commodityId
* @param User|null $user
* @param UserGroup|null $group
* @return array
* @throws JSONException
* @throws RuntimeException
*/
public function getItem(int|string $commodityId, ?User $user = null, ?UserGroup $group = null): array
{
$commodity = Commodity::query()->with(['owner' => function (Relation $relation) {
$relation->select(["id", "username", "avatar"]);
}])
->select(["id", "name", "description",
"only_user", "purchase_count", "category_id", "cover", "price", "user_price",
"status", "owner", "delivery_way", "contact_type", "password_status", "level_price",
"level_disable", "coupon", "shared_id", "shared_code", "shared_premium", "shared_premium_type", "seckill_status",
"seckill_start_time", "seckill_end_time", "draft_status", "draft_premium", "inventory_hidden",
"widget", "minimum", "maximum", "shared_sync", "config", "stock", "code"])
->withCount(['order as order_sold' => function (Builder $relation) {
$relation->where("delivery_status", 1);
}]);
if (is_int($commodityId)) {
$commodity = $commodity->find($commodityId);
} else {
$commodity = $commodity->where("code", $commodityId)->first();
}
if (!$commodity) {
throw new JSONException("商品不存在");
}
if ($commodity->status != 1) {
throw new JSONException("该商品暂未上架");
}
/**
* @var Shared $shared
*/
$shared = \App\Model\Shared::query()->find($commodity->shared_id);
$inventory = null;
if ($shared) {
try {
$inventory = $this->shared->inventory($shared, $commodity);
$commodity->stock = (int)($inventory['count'] ?? $commodity->stock);
$commodity->delivery_way = $inventory['delivery_way'] ?? $commodity->delivery_way;
$commodity->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
} catch (\Throwable $e) {
$commodity->stock = $commodity->stock ?? 0;
}
//远端同步
if ($commodity->shared_sync == 1 && is_array($inventory)) {
/**
* @var Commodity $new
*/
$new = Commodity::query()->find($commodity->id);
$configSource = $inventory['config'] ?? $commodity->config;
$configString = is_array($configSource) ? Ini::toConfig($configSource) : (string)$configSource;
$priceSource = $inventory['price'] ?? $commodity->price;
$userPriceSource = $inventory['user_price'] ?? $commodity->user_price;
$base = $this->shared->AdjustmentPrice($configString, (string)$priceSource, (string)$userPriceSource, $new->shared_premium_type, $new->shared_premium);
$commodity->price = $new->price = $base['price'];
$commodity->user_price = $new->user_price = $base['user_price'];
$commodity->config = $new->config = Ini::toConfig($base['config']);
$commodity->draft_status = $new->draft_status = $inventory['draft_status'] ?? $commodity->draft_status;
$commodity->draft_premium = $new->draft_premium = $inventory['draft_premium'] ?? $commodity->draft_premium;
$commodity->seckill_status = $new->seckill_status = $inventory['seckill_status'] ?? $commodity->seckill_status;
$commodity->seckill_start_time = $new->seckill_start_time = $inventory['seckill_start_time'] ?? $commodity->seckill_start_time;
$commodity->seckill_end_time = $new->seckill_end_time = $inventory['seckill_end_time'] ?? $commodity->seckill_end_time;
$commodity->widget = $new->widget = $commodity->widget;
$commodity->minimum = $new->minimum = $inventory['minimum'] ?? $commodity->minimum;
$commodity->maximum = $new->maximum = $inventory['maximum'] ?? $commodity->maximum;
$commodity->stock = $new->stock = (int)($inventory['count'] ?? $commodity->stock);
$commodity->contact_type = $new->contact_type = $inventory['contact_type'] ?? $commodity->contact_type;
$new->save();
}
} else if ($commodity->delivery_way == 0) {
$commodity->stock = Card::query()->where("commodity_id", $commodity->id)->where("status", 0)->count();
}
//解析商品配置
$this->order->parseConfig($commodity, $group);
//处理分站
$this->substationPriceIncrease($commodity);
$commodity->service_url = Config::get("service_url");
$commodity->service_qq = Config::get("service_qq");
if ($commodity->draft_status == 1 && $commodity->draft_premium > 0 && $commodity->level_disable != 1) {
$commodity->draft_premium = $this->order->getValuationPrice($commodity->id, $commodity->draft_premium, $group);
}
$array = $commodity->toArray();
if ($array["owner"]) {
$business = Business::query()->where("user_id", $array["owner"]['id'])->first();
if ($business) {
$array['service_url'] = $business->service_url;
$array['service_qq'] = $business->service_qq;
}
}
if (!$array['cover']) {
$array['cover'] = "/favicon.ico";
}
$array['share_url'] = Client::getUrl() . "/item/{$array['id']}";
$array['login'] = (bool)$user;
if ($array['login']) {
$array['share_url'] .= "?from={$user->id}";
}
//获取网站是否需要验证码
$array['trade_captcha'] = (int)Config::get("trade_verification");
if ($commodity->widget) {
$array['widget'] = json_decode($commodity->widget, true);
}
return $array;
}
/**
* @param int|string|null $stock
* @return string
*/
public function getHideStock(int|string|null $stock): string
{
$stock = (int)$stock;
return match (true) {
$stock <= 0 => "已售罄",
$stock <= 5 => "即将售罄",
$stock <= 20 => "一般",
$stock <= 100 => "充足",
default => "非常多"
};
}
/**
* @param int|string|null $stock
* @return int
*/
public function getStockState(int|string|null $stock): int
{
$stock = (int)$stock;
return match (true) {
$stock <= 0 => 0,
$stock <= 5 => 1,
$stock <= 20 => 2,
$stock <= 100 => 3,
default => 4
};
}
/**
* @param int|Commodity|string $commodity
* @param string|null $race
* @param array|null $sku
* @return string
* @throws JSONException
*/
public function getItemStock(int|Commodity|string $commodity, ?string $race = null, ?array $sku = []): string
{
if (is_int($commodity)) {
$commodity = Commodity::with(['shared'])->find($commodity);
} elseif (is_string($commodity)) {
$commodity = Commodity::with(['shared'])->where("code", $commodity)->first();
}
if (!$commodity) throw new JSONException("商品不存在");
if (($hook = \hook(Hook::SERVICE_SHOP_GET_ITEM_STOCK, $commodity, $race, $sku)) instanceof Stock) return $hook->getStock();
//对接商品
if ($commodity->shared) {
return $this->getSharedStock($commodity, $race, $sku);
} else if ($commodity->delivery_way == 0) {
//库存
$card = Card::query()->where("commodity_id", $commodity->id)->where("status", 0);
if ($race) $card = $card->where("race", $race);
if (!empty($sku)) {
foreach ($sku as $k => $v) {
$card = $card->where("sku->{$k}", $v);
}
}
return (string)$card->count();
}
return (string)$commodity->stock;
}
/**
* @param int $id
* @param string|null $race
* @param array|null $sku
* @return string
*/
public function getSharedStockHash(int $id, ?string $race = null, ?array $sku = []): string
{
return md5($id . $race . json_encode($sku ?: []));
}
/**
* @param int|Commodity $commodity
* @param string|null $race
* @param array|null $sku
* @return void
* @throws JSONException
*/
public function updateSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): void
{
if (is_int($commodity)) {
$commodity = Commodity::query()->find($commodity);
}
if (!$commodity) throw new JSONException("商品不存在");
$hash = $this->getSharedStockHash($commodity->id, $race, $sku);
$stock = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
unset($stock[$hash]);
Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $stock]);
}
/**
* @param int|Commodity $commodity
* @param string|null $race
* @param array|null $sku
* @return string|null
* @throws JSONException
*/
public function getSharedStock(int|Commodity $commodity, ?string $race = null, ?array $sku = []): string|null
{
if (is_int($commodity)) {
$commodity = Commodity::query()->find($commodity);
}
if (!$commodity) throw new JSONException("商品不存在");
$hash = $this->getSharedStockHash($commodity->id, $race, $sku);
if (!is_array($commodity->shared_stock) || !isset($commodity->shared_stock[$hash])) {
try {
$stock = $this->shared->getItemStock($commodity->shared, $commodity->shared_code, $race, $sku);
} catch (\Throwable $e) {
// fall back to local stock when the remote接口无法访问,避免前台直接报错
@file_put_contents(BASE_PATH . "/runtime/shared_inventory.log", "[" . date('Y-m-d H:i:s') . "] stock fetch failed, commodity {$commodity->id}: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
$fallback = $commodity->stock ?? 0;
return (string)$fallback;
}
$array = is_array($commodity->shared_stock) ? $commodity->shared_stock : [];
$array[$hash] = $stock;
Commodity::query()->where("id", $commodity->id)->update(["shared_stock" => $array]);
return $stock;
}
return $commodity->shared_stock[$hash];
}
/**
* @param Commodity|int|string $commodity
* @param int $cardId
* @return array
* @throws JSONException
*/
public function getDraft(Commodity|int|string $commodity, int $cardId): array
{
if (is_int($commodity)) {
$commodity = Commodity::query()->find($commodity);
}
if (!$commodity) throw new JSONException("商品不存在");
$card = Card::query()->where("commodity_id", $commodity->id)->where("id", $cardId)->first();
if (!$card) {
throw new JSONException("预选的宝贝不存在");
}
if ($commodity->id != $card->commodity_id) {
throw new JSONException("此预告信息不属于此商品");
}
if ($card->status != 0) {
throw new JSONException("此宝贝已被他人抢走");
}
return ["draft_premium" => $card->draft_premium];
}
/**
* @param Commodity $commodity
* @return void
*/
public function substationPriceIncrease(Commodity &$commodity): void
{
$business = Business::get();
if (!$business) {
return;
}
/**
* @var UserCommodity $userCommodity
*/
$userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();
if (!$userCommodity) {
return;
}
if ($userCommodity->name) {
$commodity->name = $userCommodity->name;
}
$config = $commodity->config ?: [];
if ($userCommodity->premium > 0) {
$commodity->price = (new Decimal($commodity->price))->mul($userCommodity->premium / 100)->add($commodity->price)->getAmount();
$commodity->user_price = (new Decimal($commodity->user_price))->mul($userCommodity->premium / 100)->add($commodity->user_price)->getAmount();
if ($commodity->draft_premium > 0) {
$commodity->draft_premium = (new Decimal($commodity->draft_premium))->mul($userCommodity->premium / 100)->add($commodity->draft_premium)->getAmount();
}
if (is_array($config['category'])) {
foreach ($config['category'] as &$price) {
$price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
}
}
if (is_array($config['wholesale'])) {
foreach ($config['wholesale'] as &$price) {
$price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
}
}
if (is_array($config['category_wholesale'])) {
foreach ($config['category_wholesale'] as &$arr) {
foreach ($arr as &$price) {
$price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
}
}
}
if (is_array($config['sku'])) {
foreach ($config['sku'] as &$arr) {
foreach ($arr as &$price) {
$price = (new Decimal($price))->mul($userCommodity->premium / 100)->add($price)->getAmount();
}
}
}
}
$commodity->config = $config;
}
/**
* @param Commodity|int $commodity
* @param int|string|float $amount
* @return string
* @throws JSONException
*/
public function getSubstationPrice(Commodity|int $commodity, int|string|float $amount): string
{
if (is_int($commodity)) {
$commodity = Commodity::query()->find($commodity);
}
if (!$commodity) {
throw new JSONException("商品不存在");
}
$business = Business::get();
if (!$business) {
return (string)$amount;
}
/**
* @var UserCommodity $userCommodity
*/
$userCommodity = UserCommodity::query()->where("user_id", $business->user_id)->where("commodity_id", $commodity->id)->first();
if (!$userCommodity) {
return (string)$amount;
}
if ($userCommodity->premium > 0) {
return (new Decimal($amount))->mul($userCommodity->premium / 100)->add($amount)->getAmount();
}
return (string)$amount;
}
}
4.assets/admin/controller/shared/store.js
代码:
!function () {
let table;
table = new Table("/admin/api/store/data", "#shared-store-table");
const modal = (title, assign = {}) => {
component.popup({
submit: '/admin/api/store/save',
tab: [
{
name: title,
form: [
{
title: "协议",
name: "type",
type: "select",
placeholder: "请选择协议",
dict: "_shared_type",
default: 0,
required: true
},
{
title: "店铺地址",
name: "domain",
type: "input",
placeholder: "需要带http://或者https://(推荐,如果支持)",
required: true
},
{
title: "商户ID", name: "app_id", type: "input", placeholder: "请输入商户ID",
required: true
},
{
title: "商户密钥", name: "app_key", type: "input", placeholder: "请输入商户密钥",
required: true
},
]
},
],
autoPosition: true,
height: "auto",
assign: assign,
width: "580px",
done: (res) => {
table.refresh();
}
});
}
table.setColumns([
{checkbox: true}
, {
field: 'name', title: '店铺名称', formatter: (a, b) => {
return `<span class="table-item"><img src="${b.domain}/favicon.ico" class="table-item-icon"><span class="table-item-name">${a}</span></span>`;
}
}, {
field: 'domain', title: '店铺地址' , formatter : format.link
}, {
field: 'balance', title: '余额(缓存)', formatter: _ => format.money(_, "green")
}, {
field: 'status', title: '状态', formatter: function (val, item) {
return '<span class="connect-' + item.id + '"><span class="badge badge-light-primary">连接中..</span></span>'
}
}, {
field: 'type', title: '协议', dict: "_shared_type"
},
{
field: 'operation', title: '操作', type: 'button', buttons: [
{
icon: 'fa-duotone fa-regular fa-link',
tips: "接入货源",
class: "text-primary",
click: (event, value, row, index) => {
util.post("/admin/api/store/items", {id: row.id}, res => {
let items = {};
res.data.forEach(value => {
value.children.forEach(item => {
items[item.id] = item;
});
});
component.popup({
submit: (result, index) => {
if (result.auth.length == 0) {
layer.msg("至少选择一个远端店铺的商品");
return;
}
result.items = [];
result.auth.forEach(itemId => {
if (parseInt(itemId) == 0) {
return;
}
let item = items[itemId];
result.items.push(item);
});
delete result.auth;
result.store_id = row.id;
util.post('/admin/api/store/addItem?storeId=' + row.id, result, res => {
message.alert(res.msg, "success")
});
},
tab: [
{
name: util.icon("fa-duotone fa-regular fa-link") + " 接入货源",
form: [
{
title: "商品分类",
name: "category_id",
type: "treeSelect",
placeholder: "请选择商品分类",
dict: `category->owner=0,id,name,pid&tree=true`,
required: true,
parent: false
},
{
title: "远端图片本地化",
name: "image_download",
type: "switch",
tips: "启用后,导入对方商品时,会自动将对方所有图片资源下载至本地"
},
{
title: "远端信息同步",
name: "shared_sync",
type: "switch",
tips: "启用后,远端商品信息会实时同步本地,远端价发生变化会立即同步"
},
{
title: "远端库存同步",
name: "inventory_sync",
type: "switch",
default: true,
tips: "启用后,远端商品库存会定期/实时读取,前台展示远端库存"
},
{
title: "立即上架",
name: "shelves",
type: "switch",
tips: "开启后,入库完毕后会立即上架"
},
{
title: "加价模式",
name: "premium_type",
type: "radio",
dict: [
{id: 0, name: "普通金额加价"},
{id: 1, name: "百分比加价(99%的人选择)"}
],
default: 1,
required: true
},
{
title: "加价数额",
name: "premium",
type: "input",
placeholder: "加价金额/百分比(小数代替)",
required: true
},
{title: "远程商品", name: "auth", type: "treeCheckbox", dict: res.data}
]
}
],
assign: {},
autoPosition: true,
height: "auto",
width: "780px",
done: () => {
}
});
});
}
}, {
icon: 'fa-duotone fa-regular fa-pen-to-square',
class: "text-primary",
click: (event, value, row, index) => {
modal(util.icon("fa-duotone fa-regular fa-pen-to-square me-1") + " 修改远端店铺", row);
}
},
{
icon: 'fa-duotone fa-regular fa-trash-can',
class: "text-danger",
click: (event, value, row, index) => {
message.ask("您确定要移除此远端店铺吗,此操作无法恢复", () => {
util.post('/admin/api/store/del', {list: [row.id]}, res => {
message.success("删除成功");
table.refresh();
});
});
}
}
]
},
]);
table.setPagination(15, [15, 30]);
table.onComplete((a, b, c) => {
c?.data?.list?.forEach(item => {
$.post("/admin/api/store/connect", {id: item.id}, run => {
let ins = $(".connect-" + item.id);
if (run.code == 200) {
ins.html(format.badge("正常", "a-badge-success"));
$(".items-" + item.id).show();
} else {
ins.html(format.badge(run.msg, "a-badge-danger"));
}
});
});
});
table.render();
$('.btn-app-create').click(function () {
modal(`${util.icon("fa-duotone fa-regular fa-link")} 添加远端店铺`);
});
}();