From 2686c54781b07976364b10325e63d464eeb178de Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Mon, 30 Mar 2026 17:37:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A6=96=E9=A1=B5=E5=92=8C?= =?UTF-8?q?=E6=94=B6=E8=B4=A7=E5=9C=B0=E5=9D=80=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/Dashboard.php | 76 +++- app/api/controller/v1/Playx.php | 192 ++++++++ config/route.php | 4 + docs/PlayX-接口文档.md | 37 ++ web/src/lang/backend/en/dashboard.ts | 19 + web/src/lang/backend/en/mall/address.ts | 2 +- web/src/lang/backend/zh-cn/dashboard.ts | 19 + web/src/lang/backend/zh-cn/mall/address.ts | 2 +- web/src/views/backend/dashboard.vue | 506 +++------------------ 9 files changed, 422 insertions(+), 435 deletions(-) diff --git a/app/admin/controller/Dashboard.php b/app/admin/controller/Dashboard.php index 0c128d7..d659f16 100644 --- a/app/admin/controller/Dashboard.php +++ b/app/admin/controller/Dashboard.php @@ -5,6 +5,10 @@ declare(strict_types=1); namespace app\admin\controller; use app\common\controller\Backend; +use app\common\model\MallPlayxClaimLog; +use app\common\model\MallPlayxOrder; +use app\common\model\MallPlayxUserAsset; +use support\think\Db; use Webman\Http\Request; use support\Response; @@ -15,8 +19,78 @@ class Dashboard extends Backend $response = $this->initializeBackend($request); if ($response !== null) return $response; + $now = time(); + $todayStart = strtotime(date('Y-m-d', $now) . ' 00:00:00'); + $yesterdayStart = $todayStart - 86400; + + $newPlayersToday = MallPlayxUserAsset::where('create_time', '>=', $todayStart) + ->where('create_time', '<=', $now) + ->count(); + + $yesterdayPointsClaimed = MallPlayxClaimLog::where('create_time', '>=', $yesterdayStart) + ->where('create_time', '<', $todayStart) + ->sum('claimed_amount'); + + $yesterdayRedeemQuery = MallPlayxOrder::where('create_time', '>=', $yesterdayStart) + ->where('create_time', '<', $todayStart); + + $yesterdayRedeemCount = (clone $yesterdayRedeemQuery)->count(); + $yesterdayRedeemPointsCostSum = (clone $yesterdayRedeemQuery)->sum('points_cost'); + $yesterdayRedeemAmountSum = (clone $yesterdayRedeemQuery)->sum('amount'); + $yesterdayRedeemCompletedCount = (clone $yesterdayRedeemQuery) + ->where('status', MallPlayxOrder::STATUS_COMPLETED) + ->count(); + $yesterdayRedeemRejectedCount = (clone $yesterdayRedeemQuery) + ->where('status', MallPlayxOrder::STATUS_REJECTED) + ->count(); + + $yesterdayRedeemByItem = Db::name('mall_playx_order') + ->alias('o') + ->leftJoin('mall_item i', 'i.id = o.mall_item_id') + ->where('o.create_time', '>=', $yesterdayStart) + ->where('o.create_time', '<', $todayStart) + ->group('o.mall_item_id, i.title') + ->field([ + 'o.mall_item_id', + 'i.title', + Db::raw('COUNT(*) as order_count'), + Db::raw('SUM(o.points_cost) as points_cost_sum'), + Db::raw('SUM(o.amount) as amount_sum'), + Db::raw('SUM(CASE WHEN o.status = "COMPLETED" THEN 1 ELSE 0 END) as completed_count'), + Db::raw('SUM(CASE WHEN o.status = "REJECTED" THEN 1 ELSE 0 END) as rejected_count'), + ]) + ->orderRaw('order_count DESC') + ->select() + ->toArray(); + + $pendingPhysicalToShip = MallPlayxOrder::where('type', MallPlayxOrder::TYPE_PHYSICAL) + ->where('status', MallPlayxOrder::STATUS_PENDING) + ->count(); + $grantFailedRetryableCount = MallPlayxOrder::whereIn('type', [MallPlayxOrder::TYPE_BONUS, MallPlayxOrder::TYPE_WITHDRAW]) + ->where('grant_status', MallPlayxOrder::GRANT_FAILED_RETRYABLE) + ->count(); + return $this->success('', [ - 'remark' => get_route_remark() + 'remark' => get_route_remark(), + 'playx' => [ + 'time_range' => [ + 'today_start' => $todayStart, + 'yesterday_start' => $yesterdayStart, + 'now' => $now, + ], + 'new_players_today' => $newPlayersToday, + 'yesterday_points_claimed' => $yesterdayPointsClaimed, + 'yesterday_redeem' => [ + 'order_count' => $yesterdayRedeemCount, + 'points_cost_sum' => $yesterdayRedeemPointsCostSum, + 'amount_sum' => $yesterdayRedeemAmountSum, + 'completed_count' => $yesterdayRedeemCompletedCount, + 'rejected_count' => $yesterdayRedeemRejectedCount, + 'by_item' => $yesterdayRedeemByItem, + ], + 'pending_physical_to_ship' => $pendingPhysicalToShip, + 'grant_failed_retryable' => $grantFailedRetryableCount, + ], ]); } } diff --git a/app/api/controller/v1/Playx.php b/app/api/controller/v1/Playx.php index 86fc050..de13853 100644 --- a/app/api/controller/v1/Playx.php +++ b/app/api/controller/v1/Playx.php @@ -14,6 +14,7 @@ use app\common\model\MallPlayxDailyPush; use app\common\model\MallPlayxSession; use app\common\model\MallPlayxOrder; use app\common\model\MallPlayxUserAsset; +use app\common\model\MallAddress; use support\think\Db; use Webman\Http\Request; use support\Response; @@ -675,6 +676,197 @@ class Playx extends Api return $this->success('', ['list' => $list->toArray()]); } + /** + * 收货地址列表 + * GET /api/v1/playx/address/list?session_id=xxx + */ + public function addressList(Request $request): Response + { + $response = $this->initializeApi($request); + if ($response !== null) { + return $response; + } + + $assetId = $this->resolvePlayxAssetIdFromRequest($request); + if ($assetId === null) { + return $this->error(__('Invalid token'), null, 0, ['statusCode' => 401]); + } + + $list = MallAddress::where('playx_user_asset_id', $assetId) + ->order('default_setting', 'desc') + ->order('id', 'desc') + ->select(); + + return $this->success('', ['list' => $list->toArray()]); + } + + /** + * 添加收货地址(可设置默认) + * POST /api/v1/playx/address/add + */ + public function addressAdd(Request $request): Response + { + $response = $this->initializeApi($request); + if ($response !== null) { + return $response; + } + + $assetId = $this->resolvePlayxAssetIdFromRequest($request); + if ($assetId === null) { + return $this->error(__('Invalid token'), null, 0, ['statusCode' => 401]); + } + + $phone = trim(strval($request->post('phone', ''))); + $region = $request->post('region', ''); + $detailAddress = trim(strval($request->post('detail_address', ''))); + $address = trim(strval($request->post('address', ''))); + $defaultSetting = strval($request->post('default_setting', '0')) === '1' ? 1 : 0; + + if ($phone === '' || $detailAddress === '' || $address === '' || $region === '' || $region === null) { + return $this->error(__('Missing required fields')); + } + + Db::startTrans(); + try { + if ($defaultSetting === 1) { + MallAddress::where('playx_user_asset_id', $assetId)->update(['default_setting' => 0]); + } + + $created = MallAddress::create([ + 'playx_user_asset_id' => $assetId, + 'phone' => $phone, + 'region' => $region, + 'detail_address' => $detailAddress, + 'address' => $address, + 'default_setting' => $defaultSetting, + 'create_time' => time(), + 'update_time' => time(), + ]); + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success('', [ + 'id' => $created ? $created->id : null, + ]); + } + + /** + * 修改收货地址(包含设置默认地址) + * POST /api/v1/playx/address/edit + */ + public function addressEdit(Request $request): Response + { + $response = $this->initializeApi($request); + if ($response !== null) { + return $response; + } + + $assetId = $this->resolvePlayxAssetIdFromRequest($request); + if ($assetId === null) { + return $this->error(__('Invalid token'), null, 0, ['statusCode' => 401]); + } + + $id = intval($request->post('id', 0)); + if ($id <= 0) { + return $this->error(__('Missing required fields')); + } + + $row = MallAddress::where('id', $id)->where('playx_user_asset_id', $assetId)->find(); + if (!$row) { + return $this->error(__('Record not found')); + } + + $updates = []; + if ($request->post('phone', null) !== null) { + $updates['phone'] = trim(strval($request->post('phone', ''))); + } + if ($request->post('region', null) !== null) { + $updates['region'] = $request->post('region', ''); + } + if ($request->post('detail_address', null) !== null) { + $updates['detail_address'] = trim(strval($request->post('detail_address', ''))); + } + if ($request->post('address', null) !== null) { + $updates['address'] = trim(strval($request->post('address', ''))); + } + if ($request->post('default_setting', null) !== null) { + $updates['default_setting'] = strval($request->post('default_setting', '0')) === '1' ? 1 : 0; + } + + if (empty($updates)) { + return $this->success('', ['updated' => false]); + } + + Db::startTrans(); + try { + if (isset($updates['default_setting']) && $updates['default_setting'] === 1) { + MallAddress::where('playx_user_asset_id', $assetId)->update(['default_setting' => 0]); + } + $updates['update_time'] = time(); + MallAddress::where('id', $id)->where('playx_user_asset_id', $assetId)->update($updates); + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success('', ['updated' => true]); + } + + /** + * 删除收货地址 + * POST /api/v1/playx/address/delete + */ + public function addressDelete(Request $request): Response + { + $response = $this->initializeApi($request); + if ($response !== null) { + return $response; + } + + $assetId = $this->resolvePlayxAssetIdFromRequest($request); + if ($assetId === null) { + return $this->error(__('Invalid token'), null, 0, ['statusCode' => 401]); + } + + $id = intval($request->post('id', 0)); + if ($id <= 0) { + return $this->error(__('Missing required fields')); + } + + $row = MallAddress::where('id', $id)->where('playx_user_asset_id', $assetId)->find(); + if (!$row) { + return $this->error(__('Record not found')); + } + + $wasDefault = intval($row->default_setting ?? 0) === 1; + + Db::startTrans(); + try { + MallAddress::where('id', $id)->where('playx_user_asset_id', $assetId)->delete(); + + if ($wasDefault) { + $fallback = MallAddress::where('playx_user_asset_id', $assetId)->order('id', 'desc')->find(); + if ($fallback) { + $fallback->default_setting = 1; + $fallback->update_time = time(); + $fallback->save(); + } + } + + Db::commit(); + } catch (\Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success('', ['deleted' => true]); + } + private function formatAsset(?MallPlayxUserAsset $asset): array { if (!$asset) { diff --git a/config/route.php b/config/route.php index a2f7931..8c77845 100644 --- a/config/route.php +++ b/config/route.php @@ -122,6 +122,10 @@ Route::post('/api/v1/playx/bonus/redeem', [\app\api\controller\v1\Playx::class, Route::post('/api/v1/playx/physical/redeem', [\app\api\controller\v1\Playx::class, 'physicalRedeem']); Route::post('/api/v1/playx/withdraw/apply', [\app\api\controller\v1\Playx::class, 'withdrawApply']); Route::get('/api/v1/playx/orders', [\app\api\controller\v1\Playx::class, 'orders']); +Route::get('/api/v1/playx/address/list', [\app\api\controller\v1\Playx::class, 'addressList']); +Route::post('/api/v1/playx/address/add', [\app\api\controller\v1\Playx::class, 'addressAdd']); +Route::post('/api/v1/playx/address/edit', [\app\api\controller\v1\Playx::class, 'addressEdit']); +Route::post('/api/v1/playx/address/delete', [\app\api\controller\v1\Playx::class, 'addressDelete']); // ==================== Admin 路由 ==================== // Admin 多为 JSON API,前端可能用 GET 传参查列表、POST 提交表单,使用 any 确保兼容 diff --git a/docs/PlayX-接口文档.md b/docs/PlayX-接口文档.md index 4fcc601..2b09c0e 100644 --- a/docs/PlayX-接口文档.md +++ b/docs/PlayX-接口文档.md @@ -447,6 +447,43 @@ curl -X POST 'http://localhost:1818/api/v1/playx/verify-token' \ --data-urlencode 'token=上一步TemLogin返回的token' ``` +--- + +### 3.x 收货地址(`mall_address`) + +> 下面接口用于 H5 维护收货地址。鉴权同本章其他接口:携带 `session_id` 或 `token` 或 `user_id`。 + +#### 3.x.1 地址列表 +* 方法:`GET` +* 路径:`/api/v1/playx/address/list` + +返回:`data.list` 为地址数组。 + +#### 3.x.2 添加地址 +* 方法:`POST` +* 路径:`/api/v1/playx/address/add` + +Body: +| 字段 | 必填 | 说明 | +|------|------|------| +| `phone` | 是 | 电话 | +| `region` | 是 | 地区(数组或逗号分隔字符串) | +| `detail_address` | 是 | 详细地址 | +| `address` | 是 | 地址补充 | +| `default_setting` | 否 | `1` 设为默认地址 | + +#### 3.x.3 修改地址(含设为默认) +* 方法:`POST` +* 路径:`/api/v1/playx/address/edit` + +Body:`id` 必填,其余字段按需传入更新。 + +#### 3.x.4 删除地址 +* 方法:`POST` +* 路径:`/api/v1/playx/address/delete` + +Body:`id` 必填。若删除默认地址,服务端会自动挑选一条剩余地址设为默认(如存在)。 + #### 远程模式(`verify_token_local_only=false` + 已配置 `base_url`) 商城侧请求 URL:`${playx.api.base_url}${playx.api.token_verify_url}`(默认路径 `/api/v1/auth/verify-token`)。 diff --git a/web/src/lang/backend/en/dashboard.ts b/web/src/lang/backend/en/dashboard.ts index 3927977..1bf160b 100644 --- a/web/src/lang/backend/en/dashboard.ts +++ b/web/src/lang/backend/en/dashboard.ts @@ -36,4 +36,23 @@ export default { second: 'Second', day: 'Day', 'Number of attachments Uploaded': 'Number of attachments upload', + Today: 'Today', + Yesterday: 'Yesterday', + Orders: 'Orders', + Pending: 'Pending', + 'Daily new players': 'Daily new players', + 'Yesterday points': 'Yesterday points (claimed)', + 'Yesterday redeem': 'Yesterday redeem', + 'Pending physical to ship': 'Pending physical to ship', + 'Yesterday item redeem stat': 'Yesterday item redeem stat', + 'Yesterday redeem points sum': 'Yesterday redeem points sum', + 'Yesterday redeem amount sum': 'Yesterday redeem amount sum', + 'Grant failed retryable': 'Grant failed retryable', + 'Item ID': 'Item ID', + 'Item title': 'Item title', + 'Order count': 'Order count', + Completed: 'Completed', + Rejected: 'Rejected', + 'Points sum': 'Points sum', + 'Amount sum': 'Amount sum', } diff --git a/web/src/lang/backend/en/mall/address.ts b/web/src/lang/backend/en/mall/address.ts index 4c3bc6a..d143ee4 100644 --- a/web/src/lang/backend/en/mall/address.ts +++ b/web/src/lang/backend/en/mall/address.ts @@ -7,7 +7,7 @@ export default { detail_address: 'detail_address', address: 'address', default_setting: 'Default address', - 'default_setting 0': 'NO', + 'default_setting 0': '--', 'default_setting 1': 'YES', create_time: 'create_time', update_time: 'update_time', diff --git a/web/src/lang/backend/zh-cn/dashboard.ts b/web/src/lang/backend/zh-cn/dashboard.ts index 009eaa8..19234e4 100644 --- a/web/src/lang/backend/zh-cn/dashboard.ts +++ b/web/src/lang/backend/zh-cn/dashboard.ts @@ -36,4 +36,23 @@ export default { second: '秒', day: '天', 'Number of attachments Uploaded': '附件上传量', + Today: '今日', + Yesterday: '昨日', + Orders: '订单', + Pending: '待处理', + 'Daily new players': '每日新增玩家', + 'Yesterday points': '昨日积分(领取)', + 'Yesterday redeem': '昨日兑换', + 'Pending physical to ship': '待发货实物单', + 'Yesterday item redeem stat': '昨日商品兑换统计', + 'Yesterday redeem points sum': '昨日兑换消耗积分合计', + 'Yesterday redeem amount sum': '昨日兑换现金面值合计', + 'Grant failed retryable': '发放失败待重试', + 'Item ID': '商品ID', + 'Item title': '商品名称', + 'Order count': '兑换次数', + Completed: '已完成', + Rejected: '已驳回', + 'Points sum': '消耗积分合计', + 'Amount sum': '现金面值合计', } diff --git a/web/src/lang/backend/zh-cn/mall/address.ts b/web/src/lang/backend/zh-cn/mall/address.ts index eef609c..bf0e4a2 100644 --- a/web/src/lang/backend/zh-cn/mall/address.ts +++ b/web/src/lang/backend/zh-cn/mall/address.ts @@ -7,7 +7,7 @@ export default { detail_address: '详细地址', address: '地址', default_setting: '默认地址', - 'default_setting 0': '', + 'default_setting 0': '--', 'default_setting 1': '是', create_time: '创建时间', update_time: '修改时间', diff --git a/web/src/views/backend/dashboard.vue b/web/src/views/backend/dashboard.vue index 0921c94..dea07e9 100644 --- a/web/src/views/backend/dashboard.vue +++ b/web/src/views/backend/dashboard.vue @@ -28,119 +28,88 @@
-
{{ t('dashboard.Member registration') }}
+
{{ t('dashboard.Daily new players') }}
-
+14%
+
{{ t('dashboard.Today') }}
-
{{ t('dashboard.Number of attachments Uploaded') }}
+
{{ t('dashboard.Yesterday points') }}
-
+50%
+
{{ t('dashboard.Yesterday') }}
-
{{ t('dashboard.Total number of members') }}
+
{{ t('dashboard.Yesterday redeem') }}
-
+28%
+
{{ t('dashboard.Orders') }}
-
{{ t('dashboard.Number of installed plug-ins') }}
+
{{ t('dashboard.Pending physical to ship') }}
-
+88%
+
{{ t('dashboard.Pending') }}
-
- - - -
-
-
- - -
-
-
- - -
- -
- -
-
妙码生花
-
12分钟前{{ t('dashboard.Joined us') }}
-
- -
-
- -
-
码上生花
-
12分钟前{{ t('dashboard.Joined us') }}
-
- -
-
- -
-
Admin
-
12分钟前{{ t('dashboard.Joined us') }}
-
- -
-
- -
-
纯属虚构
-
12分钟前{{ t('dashboard.Joined us') }}
-
- -
-
-
-
-
-
-
- - -
-
-
- - -
+ + +
+
+
{{ t('dashboard.Yesterday redeem points sum') }}
+
{{ state.playx?.yesterday_redeem?.points_cost_sum ?? 0 }}
+
+
+
{{ t('dashboard.Yesterday redeem amount sum') }}
+
{{ state.playx?.yesterday_redeem?.amount_sum ?? 0 }}
+
+
+
{{ t('dashboard.Grant failed retryable') }}
+
{{ state.playx?.grant_failed_retryable ?? 0 }}
+
+
+ + + + + + + + + +
@@ -149,17 +118,15 @@