From 6f12afcd109b63e6822e9b2ffb3fd3e6c2398ad0 Mon Sep 17 00:00:00 2001
From: zhenhui <1276357500@qq.com>
Date: Fri, 3 Apr 2026 10:59:56 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2=E7=BF=BB?=
=?UTF-8?q?=E8=AF=91=EF=BC=8C=E4=BC=98=E5=8C=96=E7=BB=9F=E4=B8=80=E8=AE=A2?=
=?UTF-8?q?=E5=8D=95=E9=A1=B5=E9=9D=A2=E5=AE=A1=E6=A0=B8=E6=93=8D=E4=BD=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/admin/controller/mall/Order.php | 42 ++++--
app/admin/lang/en.php | 5 +
app/admin/lang/zh-cn.php | 5 +
app/common/library/MallBonusGrantPush.php | 85 +++++++++++++
app/process/PlayxJobs.php | 81 ++++--------
web/src/lang/backend/en.ts | 3 +
web/src/lang/backend/en/mall/order.ts | 5 +-
web/src/lang/backend/en/menu.ts | 119 +++++++++++++++++
web/src/lang/backend/zh-cn.ts | 3 +
web/src/lang/backend/zh-cn/mall/order.ts | 3 +
web/src/lang/backend/zh-cn/menu.ts | 120 ++++++++++++++++++
.../backend/components/menus/menuTree.vue | 5 +-
.../backend/components/navBar/tabs.vue | 3 +-
web/src/utils/menuI18n.ts | 33 +++++
web/src/views/backend/mall/order/index.vue | 26 ++--
.../views/backend/mall/playxOrder/index.vue | 7 +-
16 files changed, 462 insertions(+), 83 deletions(-)
create mode 100644 app/common/library/MallBonusGrantPush.php
create mode 100644 web/src/lang/backend/en/menu.ts
create mode 100644 web/src/lang/backend/zh-cn/menu.ts
create mode 100644 web/src/utils/menuI18n.ts
diff --git a/app/admin/controller/mall/Order.php b/app/admin/controller/mall/Order.php
index c926c4a..5791bc7 100644
--- a/app/admin/controller/mall/Order.php
+++ b/app/admin/controller/mall/Order.php
@@ -4,6 +4,7 @@ namespace app\admin\controller\mall;
use Throwable;
use app\common\controller\Backend;
+use app\common\library\MallBonusGrantPush;
use app\common\model\MallOrder;
use app\common\model\MallUserAsset;
use support\think\Db;
@@ -251,7 +252,7 @@ class Order extends Backend
}
/**
- * 手动重试(仅红利推送失败可重试)
+ * 手动推送红利(同步调用 PlayX,不限制自动重试次数;成功则 ACCEPTED,失败写入 fail_reason)
*/
public function retry(Request $request): Response
{
@@ -276,17 +277,42 @@ class Order extends Backend
if ($order->type !== MallOrder::TYPE_BONUS) {
return $this->error(__('Only BONUS can retry'));
}
- if ($order->grant_status !== MallOrder::GRANT_FAILED_RETRYABLE) {
- return $this->error(__('Only FAILED_RETRYABLE can retry'));
- }
- if (($order->retry_count ?? 0) >= 3) {
- return $this->error(__('Retry count exceeded'));
+ if ($order->status !== MallOrder::STATUS_PENDING) {
+ return $this->error(__('Order status must be PENDING'));
}
- $order->grant_status = MallOrder::GRANT_NOT_SENT;
+ $allowedStatuses = [
+ MallOrder::GRANT_NOT_SENT,
+ MallOrder::GRANT_SENT_PENDING,
+ MallOrder::GRANT_FAILED_RETRYABLE,
+ MallOrder::GRANT_FAILED_FINAL,
+ ];
+ if (!in_array($order->grant_status, $allowedStatuses, true)) {
+ return $this->error(__('Current grant status cannot be manually pushed'));
+ }
+
+ if (strval(config('playx.api.base_url', '')) === '') {
+ return $this->error(__('PlayX API not configured'));
+ }
+
+ $result = MallBonusGrantPush::push($order);
+ if ($result['ok']) {
+ $order->grant_status = MallOrder::GRANT_ACCEPTED;
+ $order->playx_transaction_id = $result['playx_transaction_id'];
+ $order->fail_reason = null;
+ $order->update_time = time();
+ $order->save();
+
+ return $this->success(__('Push succeeded'));
+ }
+
+ $failReason = __('Manual push failed') . ': ' . $result['message'];
+ $order->fail_reason = $failReason;
+ $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
+ $order->update_time = time();
$order->save();
- return $this->success(__('Retry queued'));
+ return $this->error($failReason);
}
}
diff --git a/app/admin/lang/en.php b/app/admin/lang/en.php
index 087e8a8..8e6917c 100644
--- a/app/admin/lang/en.php
+++ b/app/admin/lang/en.php
@@ -95,4 +95,9 @@ return [
'%d records and files have been deleted' => '%d records and files have been deleted',
'Please input correct username' => 'Please enter the correct username',
'Group Name Arr' => 'Group Name Arr',
+ 'Push succeeded' => 'Push succeeded',
+ 'Manual push failed' => 'Manual push failed',
+ 'PlayX API not configured' => 'PlayX API not configured',
+ 'Current grant status cannot be manually pushed' => 'Current grant status cannot be manually pushed',
+ 'Order status must be PENDING' => 'Order status must be PENDING',
];
\ No newline at end of file
diff --git a/app/admin/lang/zh-cn.php b/app/admin/lang/zh-cn.php
index 1445196..ec5ee58 100644
--- a/app/admin/lang/zh-cn.php
+++ b/app/admin/lang/zh-cn.php
@@ -114,4 +114,9 @@ return [
'%d records and files have been deleted' => '已删除%d条记录和文件',
'Please input correct username' => '请输入正确的用户名',
'Group Name Arr' => '分组名称数组',
+ 'Push succeeded' => '推送成功',
+ 'Manual push failed' => '手动推送失败',
+ 'PlayX API not configured' => 'PlayX 接口未配置',
+ 'Current grant status cannot be manually pushed' => '当前发放状态不可手动推送',
+ 'Order status must be PENDING' => '订单状态须为处理中',
];
\ No newline at end of file
diff --git a/app/common/library/MallBonusGrantPush.php b/app/common/library/MallBonusGrantPush.php
new file mode 100644
index 0000000..b32cfe4
--- /dev/null
+++ b/app/common/library/MallBonusGrantPush.php
@@ -0,0 +1,85 @@
+ false,
+ 'message' => 'PlayX base_url not configured',
+ 'playx_transaction_id' => '',
+ ];
+ }
+
+ $path = strval(config('playx.api.bonus_grant_url', '/api/v1/bonus/grant'));
+ $url = $baseUrl . $path;
+
+ $item = MallItem::where('id', $order->mall_item_id)->find();
+ $rewardName = $item ? strval($item->title) : '';
+ $category = $item ? strval($item->category) : 'daily';
+ $categoryTitle = $item ? strval($item->category_title) : '';
+ $multiplier = intval($order->multiplier ?? 0);
+ if ($multiplier <= 0) {
+ $multiplier = 1;
+ }
+
+ $client = new Client([
+ 'timeout' => 20,
+ 'http_errors' => false,
+ ]);
+
+ $requestId = 'mall_bonus_' . uniqid();
+
+ try {
+ $res = $client->post($url, [
+ 'json' => [
+ 'request_id' => $requestId,
+ 'externalTransactionId' => $order->external_transaction_id,
+ 'user_id' => $order->user_id,
+ 'amount' => $order->amount,
+ 'rewardName' => $rewardName,
+ 'category' => $category,
+ 'categoryTitle' => $categoryTitle,
+ 'multiplier' => $multiplier,
+ ],
+ ]);
+
+ $data = json_decode(strval($res->getBody()), true) ?? [];
+ if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
+ return [
+ 'ok' => true,
+ 'message' => '',
+ 'playx_transaction_id' => strval($data['playx_transaction_id'] ?? ''),
+ ];
+ }
+
+ return [
+ 'ok' => false,
+ 'message' => strval($data['message'] ?? 'PlayX bonus grant not accepted'),
+ 'playx_transaction_id' => '',
+ ];
+ } catch (\Throwable $e) {
+ return [
+ 'ok' => false,
+ 'message' => $e->getMessage(),
+ 'playx_transaction_id' => '',
+ ];
+ }
+ }
+}
diff --git a/app/process/PlayxJobs.php b/app/process/PlayxJobs.php
index db4fcb2..ebbb7f8 100644
--- a/app/process/PlayxJobs.php
+++ b/app/process/PlayxJobs.php
@@ -2,7 +2,7 @@
namespace app\process;
-use app\common\model\MallItem;
+use app\common\library\MallBonusGrantPush;
use app\common\model\MallOrder;
use app\common\model\MallUserAsset;
use GuzzleHttp\Client;
@@ -99,9 +99,6 @@ class PlayxJobs
return;
}
- $bonusPath = strval(config('playx.api.bonus_grant_url', '/api/v1/bonus/grant'));
- $bonusUrl = rtrim($baseUrl, '/') . $bonusPath;
-
$maxRetry = 3;
$list = MallOrder::where('type', MallOrder::TYPE_BONUS)
->whereIn('grant_status', [
@@ -124,14 +121,12 @@ class PlayxJobs
$order->retry_count = intval($order->retry_count ?? 0) + 1;
try {
- $this->sendGrantByOrder($order, $bonusUrl, $maxRetry);
+ $this->sendGrantByOrder($order, $maxRetry);
} catch (\Throwable $e) {
$order->fail_reason = $e->getMessage();
if (intval($order->retry_count) >= $maxRetry) {
$order->grant_status = MallOrder::GRANT_FAILED_FINAL;
- $order->status = MallOrder::STATUS_REJECTED;
$order->save();
- $this->refundPoints($order);
} else {
$order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
$order->save();
@@ -167,61 +162,31 @@ class PlayxJobs
return false;
}
- private function sendGrantByOrder(MallOrder $order, string $bonusUrl, int $maxRetry): void
+ private function sendGrantByOrder(MallOrder $order, int $maxRetry): void
{
- $item = null;
- if ($order->mallItem) {
- $item = $order->mallItem;
- } else {
- $item = MallItem::where('id', $order->mall_item_id)->find();
- }
-
- if ($order->type === MallOrder::TYPE_BONUS) {
- $rewardName = $item ? strval($item->title) : '';
- $category = $item ? strval($item->category) : 'daily';
- $categoryTitle = $item ? strval($item->category_title) : '';
- $multiplier = intval($order->multiplier ?? 0);
- if ($multiplier <= 0) {
- $multiplier = 1;
- }
-
- $requestId = 'mall_retry_bonus_' . uniqid();
- $res = $this->http->post($bonusUrl, [
- 'json' => [
- 'request_id' => $requestId,
- 'externalTransactionId' => $order->external_transaction_id,
- 'user_id' => $order->user_id,
- 'amount' => $order->amount,
- 'rewardName' => $rewardName,
- 'category' => $category,
- 'categoryTitle' => $categoryTitle,
- 'multiplier' => $multiplier,
- ],
- ]);
-
- $data = json_decode(strval($res->getBody()), true) ?? [];
- if ($res->getStatusCode() === 200 && ($data['status'] ?? '') === 'accepted') {
- $order->grant_status = MallOrder::GRANT_ACCEPTED;
- $order->playx_transaction_id = strval($data['playx_transaction_id'] ?? '');
- $order->save();
- return;
- }
-
- $order->fail_reason = strval($data['message'] ?? 'PlayX bonus grant not accepted');
- if (intval($order->retry_count) >= $maxRetry) {
- $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
- $order->status = MallOrder::STATUS_REJECTED;
- $order->save();
- $this->refundPoints($order);
- return;
- }
-
- $order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
- $order->save();
+ if ($order->type !== MallOrder::TYPE_BONUS) {
return;
}
- // 非 BONUS 订单不参与 PlayX 发放重试(提现/实物由后台流程处理)
+ $result = MallBonusGrantPush::push($order);
+ if ($result['ok']) {
+ $order->grant_status = MallOrder::GRANT_ACCEPTED;
+ $order->playx_transaction_id = $result['playx_transaction_id'];
+ $order->save();
+
+ return;
+ }
+
+ $order->fail_reason = $result['message'];
+ if (intval($order->retry_count) >= $maxRetry) {
+ $order->grant_status = MallOrder::GRANT_FAILED_FINAL;
+ $order->save();
+
+ return;
+ }
+
+ $order->grant_status = MallOrder::GRANT_FAILED_RETRYABLE;
+ $order->save();
}
private function refundPoints(MallOrder $order): void
diff --git a/web/src/lang/backend/en.ts b/web/src/lang/backend/en.ts
index 293704a..248e5a4 100644
--- a/web/src/lang/backend/en.ts
+++ b/web/src/lang/backend/en.ts
@@ -1,7 +1,10 @@
/**
* backend common language package
*/
+import menu from './en/menu'
+
export default {
+ menu,
Balance: 'Balance',
Integral: 'Integral',
Connection: 'connection',
diff --git a/web/src/lang/backend/en/mall/order.ts b/web/src/lang/backend/en/mall/order.ts
index 18845ef..330aaa4 100644
--- a/web/src/lang/backend/en/mall/order.ts
+++ b/web/src/lang/backend/en/mall/order.ts
@@ -1,5 +1,8 @@
export default {
- id: 'id',
+ approve: 'Review',
+ manual_retry: 'Retry grant',
+ retry_confirm: 'Queue this order for grant retry?',
+ id: 'ID',
user_id: 'user_id',
type: 'type',
'type BONUS': 'Bonus(BONUS)',
diff --git a/web/src/lang/backend/en/menu.ts b/web/src/lang/backend/en/menu.ts
new file mode 100644
index 0000000..cf4815b
--- /dev/null
+++ b/web/src/lang/backend/en/menu.ts
@@ -0,0 +1,119 @@
+/**
+ * Admin menu titles (admin_rule.name → menu.names.{name with / as _})
+ */
+export default {
+ names: {
+ dashboard: 'Dashboard',
+ dashboard_index: 'Browse',
+ dashboard_dashboard: 'Dashboard',
+ auth: 'Access control',
+ auth_group: 'Admin groups',
+ auth_group_index: 'Browse',
+ auth_group_add: 'Add',
+ auth_group_edit: 'Edit',
+ auth_group_del: 'Delete',
+ auth_admin: 'Administrators',
+ auth_admin_index: 'Browse',
+ auth_admin_add: 'Add',
+ auth_admin_edit: 'Edit',
+ auth_admin_del: 'Delete',
+ auth_rule: 'Menu rules',
+ auth_rule_index: 'Browse',
+ auth_rule_add: 'Add',
+ auth_rule_edit: 'Edit',
+ auth_rule_del: 'Delete',
+ auth_rule_sortable: 'Sort',
+ auth_adminLog: 'Admin logs',
+ auth_adminLog_index: 'Browse',
+ user: 'Members',
+ user_user: 'Members',
+ user_user_index: 'Browse',
+ user_user_add: 'Add',
+ user_user_edit: 'Edit',
+ user_user_del: 'Delete',
+ user_group: 'Member groups',
+ user_group_index: 'Browse',
+ user_group_add: 'Add',
+ user_group_edit: 'Edit',
+ user_group_del: 'Delete',
+ user_rule: 'Member rules',
+ user_rule_index: 'Browse',
+ user_rule_add: 'Add',
+ user_rule_edit: 'Edit',
+ user_rule_del: 'Delete',
+ user_rule_sortable: 'Sort',
+ user_moneyLog: 'Balance logs',
+ user_moneyLog_index: 'Browse',
+ user_moneyLog_add: 'Add',
+ user_scoreLog: 'Points logs',
+ user_scoreLog_index: 'Browse',
+ user_scoreLog_add: 'Add',
+ routine: 'General',
+ routine_config: 'System config',
+ routine_config_index: 'Browse',
+ routine_config_edit: 'Edit',
+ routine_config_add: 'Add',
+ routine_config_del: 'Delete',
+ routine_attachment: 'Attachments',
+ routine_attachment_index: 'Browse',
+ routine_attachment_edit: 'Edit',
+ routine_attachment_del: 'Delete',
+ routine_adminInfo: 'Profile',
+ routine_adminInfo_index: 'Browse',
+ routine_adminInfo_edit: 'Edit',
+ security: 'Data security',
+ security_dataRecycleLog: 'Recycle bin',
+ security_dataRecycleLog_index: 'Browse',
+ security_dataRecycleLog_del: 'Delete',
+ security_dataRecycleLog_restore: 'Restore',
+ security_dataRecycleLog_info: 'Details',
+ security_sensitiveDataLog: 'Sensitive data logs',
+ security_sensitiveDataLog_index: 'Browse',
+ security_sensitiveDataLog_del: 'Delete',
+ security_sensitiveDataLog_rollback: 'Rollback',
+ security_sensitiveDataLog_info: 'Details',
+ security_dataRecycle: 'Recycle rules',
+ security_dataRecycle_index: 'Browse',
+ security_dataRecycle_add: 'Add',
+ security_dataRecycle_edit: 'Edit',
+ security_dataRecycle_del: 'Delete',
+ security_sensitiveData: 'Sensitive field rules',
+ security_sensitiveData_index: 'Browse',
+ security_sensitiveData_add: 'Add',
+ security_sensitiveData_edit: 'Edit',
+ security_sensitiveData_del: 'Delete',
+ buildadmin: 'BuildAdmin',
+ buildadmin_buildadmin: 'BuildAdmin',
+ moduleStore_moduleStore: 'Module store',
+ moduleStore_moduleStore_index: 'Browse',
+ moduleStore_moduleStore_install: 'Install',
+ moduleStore_moduleStore_changeState: 'Change state',
+ moduleStore_moduleStore_uninstall: 'Uninstall',
+ moduleStore_moduleStore_update: 'Update',
+ crud_crud: 'CRUD generator',
+ crud_crud_index: 'Browse',
+ crud_crud_generate: 'Generate',
+ crud_crud_delete: 'Delete',
+ mall: 'Points mall',
+ mall_userAsset: 'User assets',
+ mall_userAsset_index: 'Browse',
+ mall_userAsset_edit: 'Edit',
+ mall_userAsset_del: 'Delete',
+ mall_address: 'Shipping addresses',
+ mall_order: 'Orders',
+ mall_order_add: 'Add',
+ mall_order_edit: 'Edit',
+ mall_order_del: 'Delete',
+ mall_order_approve: 'Approve',
+ mall_dailyPush: 'Daily push',
+ mall_claimLog: 'Claim log',
+ mall_item: 'Products',
+ mall_playxOrder: 'PlayX orders',
+ mall_playxCenter: 'PlayX center',
+ mall_playxClaimLog: 'PlayX claim log',
+ mall_playxDailyPush: 'PlayX daily push',
+ mall_playxUserAsset: 'PlayX user assets',
+ mall_pintsOrder: 'Points orders',
+ mall_redemptionOrder: 'Redemption orders',
+ },
+}
diff --git a/web/src/lang/backend/zh-cn.ts b/web/src/lang/backend/zh-cn.ts
index 031c5f3..f7af161 100644
--- a/web/src/lang/backend/zh-cn.ts
+++ b/web/src/lang/backend/zh-cn.ts
@@ -2,7 +2,10 @@
* 后台公共语言包
* 覆盖风险:请避免使用页面语言包的目录名、文件名作为翻译 key
*/
+import menu from './zh-cn/menu'
+
export default {
+ menu,
Balance: '余额',
Integral: '积分',
Connection: '连接标识',
diff --git a/web/src/lang/backend/zh-cn/mall/order.ts b/web/src/lang/backend/zh-cn/mall/order.ts
index badffef..b7f76d8 100644
--- a/web/src/lang/backend/zh-cn/mall/order.ts
+++ b/web/src/lang/backend/zh-cn/mall/order.ts
@@ -1,4 +1,7 @@
export default {
+ approve: '审核',
+ manual_retry: '手动重试',
+ retry_confirm: '确认将该订单加入重试队列?',
id: 'ID',
user_id: '用户ID',
type: '类型',
diff --git a/web/src/lang/backend/zh-cn/menu.ts b/web/src/lang/backend/zh-cn/menu.ts
new file mode 100644
index 0000000..97fc9e7
--- /dev/null
+++ b/web/src/lang/backend/zh-cn/menu.ts
@@ -0,0 +1,120 @@
+/**
+ * 后台菜单标题(与 admin_rule.name 对应:menu.names.{name 中 / 改为 _})
+ */
+export default {
+ names: {
+ /** version202 后菜单 name 为 dashboard,不再使用 dashboard/dashboard */
+ dashboard: '控制台',
+ dashboard_index: '查看',
+ dashboard_dashboard: '控制台',
+ auth: '权限管理',
+ auth_group: '角色组管理',
+ auth_group_index: '查看',
+ auth_group_add: '添加',
+ auth_group_edit: '编辑',
+ auth_group_del: '删除',
+ auth_admin: '管理员管理',
+ auth_admin_index: '查看',
+ auth_admin_add: '添加',
+ auth_admin_edit: '编辑',
+ auth_admin_del: '删除',
+ auth_rule: '菜单规则管理',
+ auth_rule_index: '查看',
+ auth_rule_add: '添加',
+ auth_rule_edit: '编辑',
+ auth_rule_del: '删除',
+ auth_rule_sortable: '快速排序',
+ auth_adminLog: '管理员日志管理',
+ auth_adminLog_index: '查看',
+ user: '会员管理',
+ user_user: '会员管理',
+ user_user_index: '查看',
+ user_user_add: '添加',
+ user_user_edit: '编辑',
+ user_user_del: '删除',
+ user_group: '会员分组管理',
+ user_group_index: '查看',
+ user_group_add: '添加',
+ user_group_edit: '编辑',
+ user_group_del: '删除',
+ user_rule: '会员规则管理',
+ user_rule_index: '查看',
+ user_rule_add: '添加',
+ user_rule_edit: '编辑',
+ user_rule_del: '删除',
+ user_rule_sortable: '快速排序',
+ user_moneyLog: '会员余额管理',
+ user_moneyLog_index: '查看',
+ user_moneyLog_add: '添加',
+ user_scoreLog: '会员积分管理',
+ user_scoreLog_index: '查看',
+ user_scoreLog_add: '添加',
+ routine: '常规管理',
+ routine_config: '系统配置',
+ routine_config_index: '查看',
+ routine_config_edit: '编辑',
+ routine_config_add: '添加',
+ routine_config_del: '删除',
+ routine_attachment: '附件管理',
+ routine_attachment_index: '查看',
+ routine_attachment_edit: '编辑',
+ routine_attachment_del: '删除',
+ routine_adminInfo: '个人资料',
+ routine_adminInfo_index: '查看',
+ routine_adminInfo_edit: '编辑',
+ security: '数据安全管理',
+ security_dataRecycleLog: '数据回收站',
+ security_dataRecycleLog_index: '查看',
+ security_dataRecycleLog_del: '删除',
+ security_dataRecycleLog_restore: '还原',
+ security_dataRecycleLog_info: '查看详情',
+ security_sensitiveDataLog: '敏感数据修改记录',
+ security_sensitiveDataLog_index: '查看',
+ security_sensitiveDataLog_del: '删除',
+ security_sensitiveDataLog_rollback: '回滚',
+ security_sensitiveDataLog_info: '查看详情',
+ security_dataRecycle: '数据回收规则管理',
+ security_dataRecycle_index: '查看',
+ security_dataRecycle_add: '添加',
+ security_dataRecycle_edit: '编辑',
+ security_dataRecycle_del: '删除',
+ security_sensitiveData: '敏感字段规则管理',
+ security_sensitiveData_index: '查看',
+ security_sensitiveData_add: '添加',
+ security_sensitiveData_edit: '编辑',
+ security_sensitiveData_del: '删除',
+ buildadmin: 'BuildAdmin',
+ buildadmin_buildadmin: 'BuildAdmin',
+ moduleStore_moduleStore: '模块市场',
+ moduleStore_moduleStore_index: '查看',
+ moduleStore_moduleStore_install: '安装',
+ moduleStore_moduleStore_changeState: '调整状态',
+ moduleStore_moduleStore_uninstall: '卸载',
+ moduleStore_moduleStore_update: '更新',
+ crud_crud: 'CRUD代码生成',
+ crud_crud_index: '查看',
+ crud_crud_generate: '生成',
+ crud_crud_delete: '删除',
+ mall: '积分商城',
+ mall_userAsset: '用户资产',
+ mall_userAsset_index: '查看',
+ mall_userAsset_edit: '编辑',
+ mall_userAsset_del: '删除',
+ mall_address: '收货地址管理',
+ mall_order: '统一订单',
+ mall_order_add: '新增',
+ mall_order_edit: '编辑',
+ mall_order_del: '删除',
+ mall_order_approve: '审核通过',
+ mall_dailyPush: '每日推送',
+ mall_claimLog: '领取记录',
+ mall_item: '商品管理',
+ mall_playxOrder: 'PlayX订单',
+ mall_playxCenter: 'PlayX中心',
+ mall_playxClaimLog: 'PlayX领取记录',
+ mall_playxDailyPush: 'PlayX每日推送',
+ mall_playxUserAsset: 'PlayX用户资产',
+ mall_pintsOrder: '积分订单',
+ mall_redemptionOrder: '兑换订单',
+ },
+}
diff --git a/web/src/layouts/backend/components/menus/menuTree.vue b/web/src/layouts/backend/components/menus/menuTree.vue
index 8d3c136..65c0729 100644
--- a/web/src/layouts/backend/components/menus/menuTree.vue
+++ b/web/src/layouts/backend/components/menus/menuTree.vue
@@ -4,7 +4,7 @@
- {{ menu.meta?.title ? menu.meta?.title : $t('noTitle') }}
+ {{ menuTitleFromRoute(menu) }}
@@ -12,7 +12,7 @@
- {{ menu.meta?.title ? menu.meta?.title : $t('noTitle') }}
+ {{ menuTitleFromRoute(menu) }}
@@ -23,6 +23,7 @@ import { ElNotification } from 'element-plus'
import { useI18n } from 'vue-i18n'
import type { RouteRecordRaw } from 'vue-router'
import { useConfig } from '/@/stores/config'
+import { menuTitleFromRoute } from '/@/utils/menuI18n'
import { getFirstRoute, getMenuKey, onClickMenu } from '/@/utils/router'
const { t } = useI18n()
diff --git a/web/src/layouts/backend/components/navBar/tabs.vue b/web/src/layouts/backend/components/navBar/tabs.vue
index 1a7344f..c3358f7 100644
--- a/web/src/layouts/backend/components/navBar/tabs.vue
+++ b/web/src/layouts/backend/components/navBar/tabs.vue
@@ -9,7 +9,7 @@
:ref="tabsRefs.set"
:key="idx"
>
- {{ item.meta.title }}
+ {{ menuTitleFromRoute(item) }}
@@ -29,6 +29,7 @@ import type { ContextMenuItem, ContextMenuItemClickEmitArg } from '/@/components
import useCurrentInstance from '/@/utils/useCurrentInstance'
import Contextmenu from '/@/components/contextmenu/index.vue'
import horizontalScroll from '/@/utils/horizontalScroll'
+import { menuTitleFromRoute } from '/@/utils/menuI18n'
import { getFirstRoute, routePush } from '/@/utils/router'
import { adminBaseRoutePath } from '/@/router/static/adminBase'
diff --git a/web/src/utils/menuI18n.ts b/web/src/utils/menuI18n.ts
new file mode 100644
index 0000000..2a25f21
--- /dev/null
+++ b/web/src/utils/menuI18n.ts
@@ -0,0 +1,33 @@
+import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
+import { i18n } from '/@/lang/index'
+
+/**
+ * 后台菜单/标签标题:优先按路由 name(对应 admin_rule.name)匹配 menu.names.*
+ */
+export function menuI18nKeyFromName(name: string | symbol | null | undefined): string {
+ if (name == null || name === '') {
+ return ''
+ }
+ const n = String(name).trim()
+ if (!n) {
+ return ''
+ }
+ return `menu.names.${n.replace(/\//g, '_')}`
+}
+
+export function menuTitleFromName(name: string | symbol | null | undefined, fallback?: string): string {
+ const key = menuI18nKeyFromName(name)
+ if (key && i18n.global.te(key)) {
+ return String(i18n.global.t(key))
+ }
+ if (fallback && i18n.global.te(fallback)) {
+ return String(i18n.global.t(fallback))
+ }
+ return fallback || ''
+}
+
+export function menuTitleFromRoute(route: RouteRecordRaw | RouteLocationNormalized): string {
+ const name = route.name
+ const metaTitle = route.meta && typeof route.meta.title === 'string' ? route.meta.title : ''
+ return menuTitleFromName(name, metaTitle) || metaTitle || String(i18n.global.t('noTitle'))
+}
diff --git a/web/src/views/backend/mall/order/index.vue b/web/src/views/backend/mall/order/index.vue
index dc58ea6..a2ad912 100644
--- a/web/src/views/backend/mall/order/index.vue
+++ b/web/src/views/backend/mall/order/index.vue
@@ -34,7 +34,7 @@ const optButtons: OptButton[] = defaultOptButtons(['edit', 'delete']).map((btn)
btn.name === 'edit'
? {
...btn,
- title: '审核',
+ title: t('mall.order.approve'),
type: 'primary',
class: 'table-row-edit',
icon: 'fa fa-check',
@@ -235,7 +235,7 @@ const baTable = new baTableClass(
{
label: t('Operate'),
align: 'center',
- width: 80,
+ width: 120,
fixed: 'right',
render: 'buttons',
buttons: [
@@ -243,16 +243,20 @@ const baTable = new baTableClass(
{
render: 'confirmButton',
name: 'retry',
- title: 'Retry',
- text: '手动重试',
- type: 'warning',
- icon: '',
- display: (row: TableRow) => row.type === 'BONUS' && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
+ title: t('mall.order.manual_retry'),
+ text: '',
+ type: 'primary',
+ class: 'table-row-edit',
+ icon: 'fa fa-refresh',
+ display: (row: TableRow) =>
+ row.type === 'BONUS' &&
+ row.status === 'PENDING' &&
+ ['NOT_SENT', 'SENT_PENDING', 'FAILED_RETRYABLE', 'FAILED_FINAL'].includes(String(row.grant_status)),
popconfirm: {
- title: '确认将该订单加入重试队列?',
- confirmButtonText: '确认',
- cancelButtonText: '取消',
- confirmButtonType: 'warning',
+ title: t('mall.order.retry_confirm'),
+ confirmButtonText: t('Confirm'),
+ cancelButtonText: t('Cancel'),
+ confirmButtonType: 'primary',
},
click: async (row: TableRow) => {
await createAxios(
diff --git a/web/src/views/backend/mall/playxOrder/index.vue b/web/src/views/backend/mall/playxOrder/index.vue
index 8336b53..2512cde 100644
--- a/web/src/views/backend/mall/playxOrder/index.vue
+++ b/web/src/views/backend/mall/playxOrder/index.vue
@@ -116,7 +116,10 @@ const baTable = new baTableClass(
text: '手动重试',
type: 'warning',
icon: '',
- display: (row: TableRow) => row.type === 'BONUS' && row.grant_status === 'FAILED_RETRYABLE' && row.status === 'PENDING',
+ display: (row: TableRow) =>
+ row.type === 'BONUS' &&
+ row.status === 'PENDING' &&
+ ['NOT_SENT', 'SENT_PENDING', 'FAILED_RETRYABLE', 'FAILED_FINAL'].includes(String(row.grant_status)),
popconfirm: {
title: '确认将该订单加入重试队列?',
confirmButtonText: '确认',
@@ -126,7 +129,7 @@ const baTable = new baTableClass(
click: async (row: TableRow) => {
await createAxios(
{
- url: '/admin/mall.PlayxOrder/retry',
+ url: '/admin/mall.Order/retry',
method: 'post',
data: {
id: row.id,