|
|
@@ -0,0 +1,710 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace bizHd\homePageConfig\classes;
|
|
|
+
|
|
|
+use bizHd\goods\classes\GoodsClass;
|
|
|
+use bizHd\goods\classes\GoodsCategoryClass;
|
|
|
+use bizHd\goods\classes\GoodsUseCaseClass;
|
|
|
+use common\components\imgUtil;
|
|
|
+use common\components\util;
|
|
|
+use Yii;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 门店首页其余模块配置(金刚区/秒杀/团购/热门/上新/下拉商品)
|
|
|
+ * 数据按 mainId 存 Redis,供 hdApp 配置、mallApp 读取展示。
|
|
|
+ */
|
|
|
+class HomePageModuleClass
|
|
|
+{
|
|
|
+ const REDIS_NAV_GRID = '_home_page_config_nav_grid';
|
|
|
+ const REDIS_SECKILL = '_home_page_config_seckill';
|
|
|
+ const REDIS_GROUP_BUY = '_home_page_config_group_buy';
|
|
|
+ const REDIS_HOT = '_home_page_config_hot';
|
|
|
+ const REDIS_NEW = '_home_page_config_new';
|
|
|
+ const REDIS_PULL_GOODS = '_home_page_config_pull_goods';
|
|
|
+
|
|
|
+ const NAV_OSS_ROOT = 'uploads_home_nav';
|
|
|
+ const MAX_NAV_COUNT = 20;
|
|
|
+
|
|
|
+ /** 关联:商品 */
|
|
|
+ const LINK_GOODS = 2;
|
|
|
+ /** 关联:分类 */
|
|
|
+ const LINK_CATEGORY = 3;
|
|
|
+ /** 关联:场景 */
|
|
|
+ const LINK_USE_CASE = 4;
|
|
|
+
|
|
|
+ /** 排序:按商品排序 */
|
|
|
+ const SORT_PRODUCT = 1;
|
|
|
+ /** 排序:按销量 */
|
|
|
+ const SORT_SALES = 2;
|
|
|
+ /** 排序:按上新时间 */
|
|
|
+ const SORT_NEW = 3;
|
|
|
+
|
|
|
+ /** 排列规则合法取值:1排1列/1排2列/1排3列 */
|
|
|
+ const LAYOUT_COLS_OPTIONS = [1, 2, 3];
|
|
|
+ /** 排列规则默认值 */
|
|
|
+ const DEFAULT_LAYOUT_COLS = 3;
|
|
|
+ /** 首页展示商品解析默认条数上限 */
|
|
|
+ const DISPLAY_GOODS_LIMIT = 20;
|
|
|
+
|
|
|
+ public static function redisKey($mainId, $suffix)
|
|
|
+ {
|
|
|
+ return intval($mainId) . $suffix;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function getJson($mainId, $suffix)
|
|
|
+ {
|
|
|
+ $raw = Yii::$app->redis->executeCommand('GET', [self::redisKey($mainId, $suffix)]);
|
|
|
+ if (empty($raw)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $data = json_decode($raw, true);
|
|
|
+ return is_array($data) ? $data : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function setJson($mainId, $suffix, $payload)
|
|
|
+ {
|
|
|
+ Yii::$app->redis->executeCommand('SET', [
|
|
|
+ self::redisKey($mainId, $suffix),
|
|
|
+ json_encode($payload, JSON_UNESCAPED_UNICODE),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function decodeList($raw)
|
|
|
+ {
|
|
|
+ if (is_string($raw)) {
|
|
|
+ $decoded = json_decode($raw, true);
|
|
|
+ return is_array($decoded) ? $decoded : [];
|
|
|
+ }
|
|
|
+ return is_array($raw) ? $raw : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 金刚区 --------------------
|
|
|
+
|
|
|
+ public static function getNavGrid($mainId)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $saved = self::getJson($mainId, self::REDIS_NAV_GRID);
|
|
|
+ $cols = isset($saved['cols']) ? intval($saved['cols']) : 5;
|
|
|
+ if (!in_array($cols, [4, 5], true)) {
|
|
|
+ $cols = 5;
|
|
|
+ }
|
|
|
+ $list = [];
|
|
|
+ foreach (self::decodeList($saved['list'] ?? []) as $item) {
|
|
|
+ if (!is_array($item)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $list[] = [
|
|
|
+ 'id' => strval($item['id'] ?? ''),
|
|
|
+ 'name' => strval($item['name'] ?? ''),
|
|
|
+ 'icon' => strval($item['icon'] ?? ''),
|
|
|
+ 'iconType' => intval($item['iconType'] ?? 1),
|
|
|
+ 'enabled' => !empty($item['enabled']) ? 1 : 0,
|
|
|
+ 'type' => intval($item['type'] ?? self::LINK_CATEGORY),
|
|
|
+ 'value' => strval($item['value'] ?? ''),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return [
|
|
|
+ 'enabled' => HomePageConfigClass::getModuleEnabled($mainId, 'navGrid'),
|
|
|
+ 'cols' => $cols,
|
|
|
+ 'list' => $list,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function saveNavGrid($mainId, $data)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $enabled = !empty($data['enabled']) ? 1 : 0;
|
|
|
+ $cols = isset($data['cols']) ? intval($data['cols']) : 5;
|
|
|
+ if (!in_array($cols, [4, 5], true)) {
|
|
|
+ util::fail('排列规则无效');
|
|
|
+ }
|
|
|
+ $rawList = self::decodeList($data['list'] ?? []);
|
|
|
+ if (count($rawList) > self::MAX_NAV_COUNT) {
|
|
|
+ util::fail('最多可配置' . self::MAX_NAV_COUNT . '个导航');
|
|
|
+ }
|
|
|
+ $list = [];
|
|
|
+ foreach ($rawList as $index => $item) {
|
|
|
+ if (!is_array($item)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $name = trim(strval($item['name'] ?? ''));
|
|
|
+ $icon = trim(strval($item['icon'] ?? ''));
|
|
|
+ $type = intval($item['type'] ?? 0);
|
|
|
+ $value = trim(strval($item['value'] ?? ''));
|
|
|
+ $no = $index + 1;
|
|
|
+ if ($name === '' || $icon === '' || $value === '') {
|
|
|
+ util::fail("请完善第{$no}个导航项");
|
|
|
+ }
|
|
|
+ if (!in_array($type, [self::LINK_CATEGORY, self::LINK_USE_CASE], true)) {
|
|
|
+ util::fail("第{$no}个导航关联类型无效");
|
|
|
+ }
|
|
|
+ $list[] = [
|
|
|
+ 'id' => strval($item['id'] ?? ('nav_' . ($index + 1))),
|
|
|
+ 'name' => mb_substr($name, 0, 20),
|
|
|
+ 'icon' => $icon,
|
|
|
+ 'iconType' => intval($item['iconType'] ?? 1) === 2 ? 2 : 1,
|
|
|
+ 'enabled' => !empty($item['enabled']) ? 1 : 0,
|
|
|
+ 'type' => $type,
|
|
|
+ 'value' => $value,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ self::setJson($mainId, self::REDIS_NAV_GRID, ['cols' => $cols, 'list' => $list]);
|
|
|
+ HomePageConfigClass::updateModuleEnabled($mainId, 'navGrid', $enabled);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 秒杀 --------------------
|
|
|
+
|
|
|
+ public static function getSeckill($mainId, $refreshStock = true)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $saved = self::getJson($mainId, self::REDIS_SECKILL);
|
|
|
+ $data = self::normalizeActivityBase($saved);
|
|
|
+ $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'seckill');
|
|
|
+ $data['goods'] = self::normalizeSeckillGoods($saved['goods'] ?? [], $mainId, $refreshStock);
|
|
|
+ $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
|
|
|
+ return $data;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function saveSeckill($mainId, $data)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $base = self::validateActivityBase($data);
|
|
|
+ $goods = self::validateSeckillGoods($mainId, self::decodeList($data['goods'] ?? []));
|
|
|
+ self::setJson($mainId, self::REDIS_SECKILL, array_merge($base, ['goods' => $goods]));
|
|
|
+ HomePageConfigClass::updateModuleEnabled($mainId, 'seckill', !empty($data['enabled']) ? 1 : 0);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验并规范化秒杀商品;秒杀库存不得超过实际商品库存
|
|
|
+ */
|
|
|
+ public static function validateSeckillGoods($mainId, $rawList)
|
|
|
+ {
|
|
|
+ $list = [];
|
|
|
+ foreach ($rawList as $index => $item) {
|
|
|
+ if (!is_array($item)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $no = $index + 1;
|
|
|
+ $goodsId = intval($item['goodsId'] ?? 0);
|
|
|
+ $price = floatval($item['price'] ?? 0);
|
|
|
+ $stock = intval($item['stock'] ?? 0);
|
|
|
+ $limit = intval($item['limit'] ?? 0);
|
|
|
+ if ($goodsId <= 0) {
|
|
|
+ util::fail("请选择第{$no}个秒杀商品");
|
|
|
+ }
|
|
|
+ if ($price <= 0) {
|
|
|
+ util::fail("请填写第{$no}个秒杀价格");
|
|
|
+ }
|
|
|
+ if ($stock <= 0) {
|
|
|
+ util::fail("请填写第{$no}个秒杀库存");
|
|
|
+ }
|
|
|
+ if ($limit <= 0) {
|
|
|
+ util::fail("请填写第{$no}个单人限购");
|
|
|
+ }
|
|
|
+ $goods = GoodsClass::getById($goodsId, true);
|
|
|
+ if (empty($goods) || intval($goods->mainId ?? 0) !== intval($mainId)) {
|
|
|
+ util::fail("第{$no}个秒杀商品无效");
|
|
|
+ }
|
|
|
+ $realStock = intval($goods->stock ?? 0);
|
|
|
+ // 秒杀库存不得超过实际库存
|
|
|
+ if ($stock > $realStock) {
|
|
|
+ util::fail("第{$no}个秒杀库存不能超过商品实际库存({$realStock})");
|
|
|
+ }
|
|
|
+ $status = !empty($item['status']) ? 1 : 0;
|
|
|
+ // 实际库存已低于秒杀库存:自动下架该秒杀商品
|
|
|
+ if ($realStock < $stock) {
|
|
|
+ $status = 0;
|
|
|
+ }
|
|
|
+ $list[] = [
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'price' => round($price, 2),
|
|
|
+ 'stock' => $stock,
|
|
|
+ 'limit' => $limit,
|
|
|
+ 'status' => $status,
|
|
|
+ 'name' => strval($goods->name ?? ($item['name'] ?? '')),
|
|
|
+ 'cover' => strval($goods->shortCover ?? ($goods->cover ?? ($item['cover'] ?? ''))),
|
|
|
+ 'originPrice' => floatval($goods->price ?? ($item['originPrice'] ?? 0)),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取时刷新秒杀商品状态:实际库存低于秒杀库存则下架,并回写 Redis
|
|
|
+ */
|
|
|
+ public static function normalizeSeckillGoods($rawList, $mainId, $refreshStock = true)
|
|
|
+ {
|
|
|
+ $list = [];
|
|
|
+ $changed = false;
|
|
|
+ foreach (self::decodeList($rawList) as $item) {
|
|
|
+ if (!is_array($item) || empty($item['goodsId'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $row = [
|
|
|
+ 'goodsId' => intval($item['goodsId']),
|
|
|
+ 'price' => floatval($item['price'] ?? 0),
|
|
|
+ 'stock' => intval($item['stock'] ?? 0),
|
|
|
+ 'limit' => intval($item['limit'] ?? 0),
|
|
|
+ 'status' => !empty($item['status']) ? 1 : 0,
|
|
|
+ 'name' => strval($item['name'] ?? ''),
|
|
|
+ 'cover' => strval($item['cover'] ?? ''),
|
|
|
+ 'originPrice' => floatval($item['originPrice'] ?? 0),
|
|
|
+ 'realStock' => intval($item['stock'] ?? 0),
|
|
|
+ ];
|
|
|
+ if ($refreshStock) {
|
|
|
+ $goods = GoodsClass::getById($row['goodsId'], true);
|
|
|
+ if (!empty($goods) && intval($goods->mainId ?? 0) === intval($mainId)) {
|
|
|
+ $realStock = intval($goods->stock ?? 0);
|
|
|
+ $row['realStock'] = $realStock;
|
|
|
+ $row['name'] = strval($goods->name ?? $row['name']);
|
|
|
+ $row['cover'] = strval($goods->shortCover ?? ($goods->cover ?? $row['cover']));
|
|
|
+ $row['originPrice'] = floatval($goods->price ?? $row['originPrice']);
|
|
|
+ if ($realStock < $row['stock'] && $row['status'] == 1) {
|
|
|
+ $row['status'] = 0;
|
|
|
+ $changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $list[] = $row;
|
|
|
+ }
|
|
|
+ if ($changed && $refreshStock) {
|
|
|
+ $saved = self::getJson($mainId, self::REDIS_SECKILL);
|
|
|
+ $persist = [];
|
|
|
+ foreach ($list as $g) {
|
|
|
+ $persist[] = [
|
|
|
+ 'goodsId' => $g['goodsId'],
|
|
|
+ 'price' => $g['price'],
|
|
|
+ 'stock' => $g['stock'],
|
|
|
+ 'limit' => $g['limit'],
|
|
|
+ 'status' => $g['status'],
|
|
|
+ 'name' => $g['name'],
|
|
|
+ 'cover' => $g['cover'],
|
|
|
+ 'originPrice' => $g['originPrice'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ $saved['goods'] = $persist;
|
|
|
+ self::setJson($mainId, self::REDIS_SECKILL, $saved);
|
|
|
+ }
|
|
|
+ return $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 团购 --------------------
|
|
|
+
|
|
|
+ public static function getGroupBuy($mainId, $refreshStock = true)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $saved = self::getJson($mainId, self::REDIS_GROUP_BUY);
|
|
|
+ $data = self::normalizeActivityBase($saved);
|
|
|
+ $data['enabled'] = HomePageConfigClass::getModuleEnabled($mainId, 'groupBuy');
|
|
|
+ $data['goods'] = self::normalizeGroupBuyGoods($saved['goods'] ?? [], $mainId, $refreshStock);
|
|
|
+ $data['status'] = self::calcActivityStatus($data['startTime'], $data['endTime'], $data['enabled']);
|
|
|
+ return $data;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function saveGroupBuy($mainId, $data)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ if ($mainId <= 0) {
|
|
|
+ util::fail('无效门店');
|
|
|
+ }
|
|
|
+ $base = self::validateActivityBase($data);
|
|
|
+ $goods = self::validateGroupBuyGoods($mainId, self::decodeList($data['goods'] ?? []));
|
|
|
+ self::setJson($mainId, self::REDIS_GROUP_BUY, array_merge($base, ['goods' => $goods]));
|
|
|
+ HomePageConfigClass::updateModuleEnabled($mainId, 'groupBuy', !empty($data['enabled']) ? 1 : 0);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function validateGroupBuyGoods($mainId, $rawList)
|
|
|
+ {
|
|
|
+ $list = [];
|
|
|
+ foreach ($rawList as $index => $item) {
|
|
|
+ if (!is_array($item)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $no = $index + 1;
|
|
|
+ $goodsId = intval($item['goodsId'] ?? 0);
|
|
|
+ $price = floatval($item['price'] ?? 0);
|
|
|
+ $stock = intval($item['stock'] ?? 0);
|
|
|
+ $limit = intval($item['limit'] ?? 0);
|
|
|
+ $groupSize = intval($item['groupSize'] ?? 3);
|
|
|
+ if (!in_array($groupSize, [2, 3, 5], true)) {
|
|
|
+ util::fail("第{$no}个成团人数无效");
|
|
|
+ }
|
|
|
+ if ($goodsId <= 0 || $price <= 0 || $stock <= 0 || $limit <= 0) {
|
|
|
+ util::fail("请完善第{$no}个团购商品");
|
|
|
+ }
|
|
|
+ $goods = GoodsClass::getById($goodsId, true);
|
|
|
+ if (empty($goods) || intval($goods->mainId ?? 0) !== intval($mainId)) {
|
|
|
+ util::fail("第{$no}个团购商品无效");
|
|
|
+ }
|
|
|
+ $realStock = intval($goods->stock ?? 0);
|
|
|
+ if ($stock > $realStock) {
|
|
|
+ util::fail("第{$no}个团购库存不能超过商品实际库存({$realStock})");
|
|
|
+ }
|
|
|
+ $virtualGroup = !empty($item['virtualGroup']) ? 1 : 0;
|
|
|
+ $virtualMinutes = intval($item['virtualMinutes'] ?? 0);
|
|
|
+ if ($virtualGroup && $virtualMinutes <= 0) {
|
|
|
+ util::fail("请填写第{$no}个虚拟成团时间");
|
|
|
+ }
|
|
|
+ $list[] = [
|
|
|
+ 'goodsId' => $goodsId,
|
|
|
+ 'price' => round($price, 2),
|
|
|
+ 'stock' => $stock,
|
|
|
+ 'limit' => $limit,
|
|
|
+ 'groupSize' => $groupSize,
|
|
|
+ 'virtualGroup' => $virtualGroup,
|
|
|
+ 'virtualMinutes' => $virtualMinutes,
|
|
|
+ 'autoRefund' => !empty($item['autoRefund']) ? 1 : 0,
|
|
|
+ 'status' => ($realStock < $stock) ? 0 : (!empty($item['status']) ? 1 : 0),
|
|
|
+ 'name' => strval($goods->name ?? ($item['name'] ?? '')),
|
|
|
+ 'cover' => strval($goods->shortCover ?? ($goods->cover ?? ($item['cover'] ?? ''))),
|
|
|
+ 'originPrice' => floatval($goods->price ?? ($item['originPrice'] ?? 0)),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function normalizeGroupBuyGoods($rawList, $mainId, $refreshStock = true)
|
|
|
+ {
|
|
|
+ $list = [];
|
|
|
+ $changed = false;
|
|
|
+ foreach (self::decodeList($rawList) as $item) {
|
|
|
+ if (!is_array($item) || empty($item['goodsId'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $row = [
|
|
|
+ 'goodsId' => intval($item['goodsId']),
|
|
|
+ 'price' => floatval($item['price'] ?? 0),
|
|
|
+ 'stock' => intval($item['stock'] ?? 0),
|
|
|
+ 'limit' => intval($item['limit'] ?? 0),
|
|
|
+ 'groupSize' => intval($item['groupSize'] ?? 3),
|
|
|
+ 'virtualGroup' => !empty($item['virtualGroup']) ? 1 : 0,
|
|
|
+ 'virtualMinutes' => intval($item['virtualMinutes'] ?? 0),
|
|
|
+ 'autoRefund' => !empty($item['autoRefund']) ? 1 : 0,
|
|
|
+ 'status' => !empty($item['status']) ? 1 : 0,
|
|
|
+ 'name' => strval($item['name'] ?? ''),
|
|
|
+ 'cover' => strval($item['cover'] ?? ''),
|
|
|
+ 'originPrice' => floatval($item['originPrice'] ?? 0),
|
|
|
+ 'realStock' => intval($item['stock'] ?? 0),
|
|
|
+ ];
|
|
|
+ if ($refreshStock) {
|
|
|
+ $goods = GoodsClass::getById($row['goodsId'], true);
|
|
|
+ if (!empty($goods) && intval($goods->mainId ?? 0) === intval($mainId)) {
|
|
|
+ $realStock = intval($goods->stock ?? 0);
|
|
|
+ $row['realStock'] = $realStock;
|
|
|
+ $row['name'] = strval($goods->name ?? $row['name']);
|
|
|
+ $row['cover'] = strval($goods->shortCover ?? ($goods->cover ?? $row['cover']));
|
|
|
+ $row['originPrice'] = floatval($goods->price ?? $row['originPrice']);
|
|
|
+ if ($realStock < $row['stock'] && $row['status'] == 1) {
|
|
|
+ $row['status'] = 0;
|
|
|
+ $changed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $list[] = $row;
|
|
|
+ }
|
|
|
+ if ($changed && $refreshStock) {
|
|
|
+ $saved = self::getJson($mainId, self::REDIS_GROUP_BUY);
|
|
|
+ $persist = [];
|
|
|
+ foreach ($list as $g) {
|
|
|
+ unset($g['realStock']);
|
|
|
+ $persist[] = $g;
|
|
|
+ }
|
|
|
+ $saved['goods'] = $persist;
|
|
|
+ self::setJson($mainId, self::REDIS_GROUP_BUY, $saved);
|
|
|
+ }
|
|
|
+ return $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 热门/上新/下拉 --------------------
|
|
|
+
|
|
|
+ public static function getGoodsSection($mainId, $moduleKey)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ $suffix = self::sectionSuffix($moduleKey);
|
|
|
+ $saved = self::getJson($mainId, $suffix);
|
|
|
+ $defaults = [
|
|
|
+ 'hot' => '热门推荐',
|
|
|
+ 'new' => '今日上新',
|
|
|
+ 'pullGoods' => '更多商品',
|
|
|
+ ];
|
|
|
+ return [
|
|
|
+ 'enabled' => HomePageConfigClass::getModuleEnabled($mainId, $moduleKey),
|
|
|
+ 'name' => strval($saved['name'] ?? ($defaults[$moduleKey] ?? '')),
|
|
|
+ 'type' => intval($saved['type'] ?? self::LINK_GOODS),
|
|
|
+ 'value' => strval($saved['value'] ?? ''),
|
|
|
+ 'sort' => intval($saved['sort'] ?? self::SORT_PRODUCT),
|
|
|
+ 'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function saveGoodsSection($mainId, $moduleKey, $data)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ $suffix = self::sectionSuffix($moduleKey);
|
|
|
+ $name = trim(strval($data['name'] ?? ''));
|
|
|
+ $type = intval($data['type'] ?? 0);
|
|
|
+ $value = trim(strval($data['value'] ?? ''));
|
|
|
+ $sort = intval($data['sort'] ?? self::SORT_PRODUCT);
|
|
|
+ $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
|
|
|
+ if ($name === '') {
|
|
|
+ util::fail('请输入首页展示名称');
|
|
|
+ }
|
|
|
+ if (mb_strlen($name) > 20) {
|
|
|
+ util::fail('展示名称不能超过20字');
|
|
|
+ }
|
|
|
+ if (!in_array($type, [self::LINK_GOODS, self::LINK_CATEGORY, self::LINK_USE_CASE], true)) {
|
|
|
+ util::fail('关联类型无效');
|
|
|
+ }
|
|
|
+ if ($value === '') {
|
|
|
+ util::fail('请选择关联内容');
|
|
|
+ }
|
|
|
+ if (!in_array($sort, [self::SORT_PRODUCT, self::SORT_SALES, self::SORT_NEW], true)) {
|
|
|
+ util::fail('排序规则无效');
|
|
|
+ }
|
|
|
+ if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
|
|
|
+ util::fail('排列规则无效');
|
|
|
+ }
|
|
|
+ self::setJson($mainId, $suffix, [
|
|
|
+ 'name' => $name,
|
|
|
+ 'type' => $type,
|
|
|
+ 'value' => $value,
|
|
|
+ 'sort' => $sort,
|
|
|
+ 'layoutCols' => $layoutCols,
|
|
|
+ ]);
|
|
|
+ HomePageConfigClass::updateModuleEnabled($mainId, $moduleKey, !empty($data['enabled']) ? 1 : 0);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 规范化排列规则取值,非法/缺省时回退默认值
|
|
|
+ */
|
|
|
+ public static function normalizeLayoutCols($cols)
|
|
|
+ {
|
|
|
+ $cols = intval($cols);
|
|
|
+ return in_array($cols, self::LAYOUT_COLS_OPTIONS, true) ? $cols : self::DEFAULT_LAYOUT_COLS;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将热门推荐/今日上新/下拉商品的关联配置(type+value)解析为真实商品列表,用于首页展示
|
|
|
+ * type=2 商品:value 为商品ID逗号拼接,按选择顺序展示
|
|
|
+ * type=3 分类:value 为分类ID逗号拼接,取分类下全部商品
|
|
|
+ * type=4 场景:value 为场景ID逗号拼接,取场景下全部商品
|
|
|
+ * sort=1 按上面解析出的原始顺序;sort=2 按销量(虚拟+实际)降序;sort=3 按上架时间降序
|
|
|
+ *
|
|
|
+ * @param int $mainId
|
|
|
+ * @param int $type
|
|
|
+ * @param string $value
|
|
|
+ * @param int $sort
|
|
|
+ * @param int $limit
|
|
|
+ * @return array [{id,name,price,stock,cover,coverUrl,sold}]
|
|
|
+ */
|
|
|
+ public static function resolveDisplayGoods($mainId, $type, $value, $sort, $limit = self::DISPLAY_GOODS_LIMIT)
|
|
|
+ {
|
|
|
+ $mainId = intval($mainId);
|
|
|
+ $type = intval($type);
|
|
|
+ $value = trim(strval($value));
|
|
|
+ if ($mainId <= 0 || $value === '') {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $ids = array_values(array_unique(array_filter(array_map('intval', explode(',', $value)))));
|
|
|
+ if (empty($ids)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $goodsIds = [];
|
|
|
+ if ($type === self::LINK_GOODS) {
|
|
|
+ // 商品类型:保留用户选择顺序
|
|
|
+ $goodsIds = $ids;
|
|
|
+ } elseif ($type === self::LINK_CATEGORY) {
|
|
|
+ $rows = GoodsCategoryClass::getAllByCondition(
|
|
|
+ ['cId' => ['in', $ids], 'delStatus' => 0],
|
|
|
+ null,
|
|
|
+ 'gId'
|
|
|
+ );
|
|
|
+ $goodsIds = array_values(array_unique(array_map('intval', array_column($rows, 'gId'))));
|
|
|
+ } elseif ($type === self::LINK_USE_CASE) {
|
|
|
+ $rows = GoodsUseCaseClass::getAllByCondition(
|
|
|
+ ['useCaseId' => ['in', $ids]],
|
|
|
+ null,
|
|
|
+ 'goodsId'
|
|
|
+ );
|
|
|
+ $goodsIds = array_values(array_unique(array_map('intval', array_column($rows, 'goodsId'))));
|
|
|
+ }
|
|
|
+ if (empty($goodsIds)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 注意:Base::getByIds 传入 $order 会走到未定义的 order() 方法而报错,这里统一取回后在 PHP 侧排序
|
|
|
+ $rows = GoodsClass::getByIds($goodsIds, null, null, 'id,name,price,stock,cover,sold,actualSold,status,delStatus,masterId,mainId,createTime');
|
|
|
+ // 仅取当前门店、未删除、上架中的主规格商品
|
|
|
+ $rows = array_values(array_filter($rows, function ($row) use ($mainId) {
|
|
|
+ return intval($row['mainId'] ?? 0) === $mainId
|
|
|
+ && intval($row['delStatus'] ?? 0) === 0
|
|
|
+ && intval($row['status'] ?? 0) === 1
|
|
|
+ && intval($row['masterId'] ?? 0) === 0;
|
|
|
+ }));
|
|
|
+
|
|
|
+ if ($sort == self::SORT_SALES) {
|
|
|
+ usort($rows, function ($a, $b) {
|
|
|
+ $soldA = floatval($a['actualSold'] ?? 0) + floatval($a['sold'] ?? 0);
|
|
|
+ $soldB = floatval($b['actualSold'] ?? 0) + floatval($b['sold'] ?? 0);
|
|
|
+ return $soldB <=> $soldA;
|
|
|
+ });
|
|
|
+ } elseif ($sort == self::SORT_NEW) {
|
|
|
+ usort($rows, function ($a, $b) {
|
|
|
+ return strtotime($b['createTime'] ?? '') <=> strtotime($a['createTime'] ?? '');
|
|
|
+ });
|
|
|
+ } elseif ($type === self::LINK_GOODS) {
|
|
|
+ // 商品类型按用户选择顺序重新排列;分类/场景按商品排序时无自定义顺序可依,保持数据库默认返回顺序
|
|
|
+ $indexBy = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $indexBy[intval($row['id'])] = $row;
|
|
|
+ }
|
|
|
+ $ordered = [];
|
|
|
+ foreach ($goodsIds as $gid) {
|
|
|
+ if (isset($indexBy[$gid])) {
|
|
|
+ $ordered[] = $indexBy[$gid];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $rows = $ordered;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($limit > 0 && count($rows) > $limit) {
|
|
|
+ $rows = array_slice($rows, 0, $limit);
|
|
|
+ }
|
|
|
+
|
|
|
+ $list = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $cover = strval($row['cover'] ?? '');
|
|
|
+ $coverUrl = $cover !== '' ? imgUtil::groupImg($cover) . '?x-oss-process=image/resize,m_fill,h_700,w_700' : '';
|
|
|
+ $list[] = [
|
|
|
+ 'id' => intval($row['id']),
|
|
|
+ 'name' => strval($row['name'] ?? ''),
|
|
|
+ 'price' => floatval($row['price'] ?? 0),
|
|
|
+ 'stock' => intval($row['stock'] ?? 0),
|
|
|
+ 'cover' => $cover,
|
|
|
+ 'coverUrl' => $coverUrl,
|
|
|
+ 'sold' => intval(bcadd($row['actualSold'] ?? 0, $row['sold'] ?? 0)),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return $list;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function sectionSuffix($moduleKey)
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ 'hot' => self::REDIS_HOT,
|
|
|
+ 'new' => self::REDIS_NEW,
|
|
|
+ 'pullGoods' => self::REDIS_PULL_GOODS,
|
|
|
+ ];
|
|
|
+ if (!isset($map[$moduleKey])) {
|
|
|
+ util::fail('无效模块');
|
|
|
+ }
|
|
|
+ return $map[$moduleKey];
|
|
|
+ }
|
|
|
+
|
|
|
+ // -------------------- 活动公共 --------------------
|
|
|
+
|
|
|
+ public static function normalizeActivityBase($saved)
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ 'title' => strval($saved['title'] ?? ''),
|
|
|
+ 'subtitle' => strval($saved['subtitle'] ?? ''),
|
|
|
+ 'showCountdown' => !empty($saved['showCountdown']) ? 1 : 0,
|
|
|
+ 'expandHome' => !empty($saved['expandHome']) ? 1 : 0,
|
|
|
+ 'startTime' => intval($saved['startTime'] ?? 0),
|
|
|
+ 'endTime' => intval($saved['endTime'] ?? 0),
|
|
|
+ 'desc' => strval($saved['desc'] ?? ''),
|
|
|
+ 'layoutCols' => self::normalizeLayoutCols($saved['layoutCols'] ?? null),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function validateActivityBase($data)
|
|
|
+ {
|
|
|
+ $title = trim(strval($data['title'] ?? ''));
|
|
|
+ $subtitle = trim(strval($data['subtitle'] ?? ''));
|
|
|
+ $desc = trim(strval($data['desc'] ?? ''));
|
|
|
+ $startTime = intval($data['startTime'] ?? 0);
|
|
|
+ $endTime = intval($data['endTime'] ?? 0);
|
|
|
+ $layoutCols = intval($data['layoutCols'] ?? self::DEFAULT_LAYOUT_COLS);
|
|
|
+ if ($title === '') {
|
|
|
+ util::fail('请输入活动标题');
|
|
|
+ }
|
|
|
+ if (mb_strlen($title) > 10) {
|
|
|
+ util::fail('活动标题不能超过10字');
|
|
|
+ }
|
|
|
+ if ($subtitle === '') {
|
|
|
+ util::fail('请输入活动副标题');
|
|
|
+ }
|
|
|
+ if (mb_strlen($subtitle) > 20) {
|
|
|
+ util::fail('活动副标题不能超过20字');
|
|
|
+ }
|
|
|
+ if ($startTime <= 0 || $endTime <= 0) {
|
|
|
+ util::fail('请选择活动时间');
|
|
|
+ }
|
|
|
+ if ($endTime <= $startTime) {
|
|
|
+ util::fail('结束时间必须大于开始时间');
|
|
|
+ }
|
|
|
+ if ($desc === '') {
|
|
|
+ util::fail('请输入活动说明');
|
|
|
+ }
|
|
|
+ if (mb_strlen($desc) > 500) {
|
|
|
+ util::fail('活动说明不能超过500字');
|
|
|
+ }
|
|
|
+ if (!in_array($layoutCols, self::LAYOUT_COLS_OPTIONS, true)) {
|
|
|
+ util::fail('排列规则无效');
|
|
|
+ }
|
|
|
+ return [
|
|
|
+ 'title' => $title,
|
|
|
+ 'subtitle' => $subtitle,
|
|
|
+ 'showCountdown' => !empty($data['showCountdown']) ? 1 : 0,
|
|
|
+ 'expandHome' => !empty($data['expandHome']) ? 1 : 0,
|
|
|
+ 'startTime' => $startTime,
|
|
|
+ 'endTime' => $endTime,
|
|
|
+ 'desc' => $desc,
|
|
|
+ 'layoutCols' => $layoutCols,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 活动状态:0未开始 1进行中 2已结束;模块关闭视为已结束展示用
|
|
|
+ */
|
|
|
+ public static function calcActivityStatus($startTime, $endTime, $enabled)
|
|
|
+ {
|
|
|
+ if (empty($enabled)) {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ $now = time();
|
|
|
+ $startTime = intval($startTime);
|
|
|
+ $endTime = intval($endTime);
|
|
|
+ if ($startTime <= 0 || $endTime <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if ($now < $startTime) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if ($now > $endTime) {
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+}
|