diff --git a/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue b/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue
index 6201c722..389d115e 100644
--- a/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue
+++ b/apps/frontend/src/views/admin/manual-redeem/AdminManualRedeemView.vue
@@ -5,6 +5,9 @@ import TencentAuthCard from '@/components/tencent/TencentAuthCard.vue'
import TencentRedeemPanel from '@/components/tencent/TencentRedeemPanel.vue'
import TencentResultPanel from '@/components/tencent/TencentResultPanel.vue'
import { useAdminManualRedeemPage } from '@/composables/useAdminManualRedeemPage'
+import AdminManualRedeemCreateCard from './components/AdminManualRedeemCreateCard.vue'
+import AdminManualRedeemSummaryCard from './components/AdminManualRedeemSummaryCard.vue'
+import AdminManualRedeemReviewCard from './components/AdminManualRedeemReviewCard.vue'
const {
form,
@@ -104,82 +107,21 @@ const taskDetailLink = computed(() =>
-
-
-
-
1. 创建人工兑换任务
-
先录入唯一凭据并锁定库存,后续二维码、角色确认和兑换都基于这条任务继续。
-
-
+
-
-
-
-
- 创建任务并预占库存
-
-
-
-
-
-
- 唯一凭据
- {{ manualRequest?.proofValue || order?.platformOrderId || '-' }}
-
-
- 任务号
- {{ task?.taskNo || '-' }}
-
-
- 商品
- {{ orderItem?.skuName || '-' }}
-
-
- 当前状态
- {{ statusLabel }}
-
-
+
人工兑换详情加载中
@@ -237,43 +179,18 @@ const taskDetailLink = computed(() =>
-
-
-
-
2. {{ reviewCardTitle }}
-
- 客户登录后,系统会把当前角色截图回传到这里。客服复制后可直接发给客户做二次确认。
-
-
最终兑换完成后,客服可以把结果截图直接发给客户确认。
-
- {{ screenshotEmptyMessage }}
-
-
-
-
-
-
-
-
-
-
-
{{ screenshotEmptyTitle }}
-
{{ screenshotEmptyMessage }}
-
-
+
gap: 18px;
}
-.header-actions,
-.card-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--space-4);
-}
-
-.header-actions {
- flex-wrap: wrap;
-}
-
.task-link {
color: var(--color-primary);
text-decoration: none;
font-weight: 600;
}
-.create-card,
-.summary-card,
-.review-card {
- padding: 22px var(--space-6);
- border-radius: var(--radius-xl);
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- box-shadow: var(--shadow-lg);
-}
-
-.card-head h2 {
- margin: 0;
- font-size: var(--text-lg);
- color: var(--text-primary);
-}
-
-.card-head p {
- margin: 6px 0 0;
- color: var(--text-secondary);
- line-height: 1.7;
-}
-
-.form-grid {
- margin-top: 18px;
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: var(--space-4);
-}
-
-.field {
- display: grid;
- gap: var(--space-2);
-}
-
-.field span {
- color: var(--text-secondary);
- font-size: var(--text-sm);
- font-weight: 600;
-}
-
-.field-remark {
- grid-column: 1 / -1;
-}
-
-.action-row {
- margin-top: 18px;
- display: flex;
- gap: var(--space-3);
-}
-
-.summary-card {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: var(--space-3);
-}
-
-.summary-card article {
- padding: 14px;
- border-radius: var(--radius-md);
- background: var(--bg-page);
-}
-
-.summary-card span {
- display: block;
- font-size: var(--text-xs);
- color: var(--text-muted);
-}
-
-.summary-card strong {
- display: block;
- margin-top: var(--space-2);
- color: var(--text-primary);
- word-break: break-word;
-}
-
.layout-grid {
display: grid;
grid-template-columns: minmax(340px, 392px) minmax(0, 1fr);
@@ -426,30 +256,6 @@ const taskDetailLink = computed(() =>
display: grid;
}
-.review-stage {
- margin-top: 18px;
- border-radius: var(--radius-lg);
- overflow: hidden;
- border: 1px solid var(--border-default);
- background: var(--bg-page);
-}
-
-.screenshot-button {
- display: block;
- width: 100%;
- padding: 0;
- border: 0;
- background: transparent;
- cursor: zoom-in;
-}
-
-.screenshot-button img,
-.preview-image {
- display: block;
- width: 100%;
- height: auto;
-}
-
.empty-block {
min-height: 180px;
padding: var(--space-6);
@@ -472,10 +278,15 @@ const taskDetailLink = computed(() =>
line-height: 1.7;
}
+.preview-image {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
@media (max-width: 900px) {
.summary-card,
- .layout-grid,
- .form-grid {
+ .layout-grid {
grid-template-columns: 1fr;
}
}
diff --git a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemCreateCard.vue b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemCreateCard.vue
new file mode 100644
index 00000000..ac3e9a2f
--- /dev/null
+++ b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemCreateCard.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
1. 创建人工兑换任务
+
先录入唯一凭据并锁定库存,后续二维码、角色确认和兑换都基于这条任务继续。
+
+
+
+
+
+
+
+ 创建任务并预占库存
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue
new file mode 100644
index 00000000..20ba2dfe
--- /dev/null
+++ b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemReviewCard.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
2. {{ reviewCardTitle }}
+
+ 客户登录后,系统会把当前角色截图回传到这里。客服复制后可直接发给客户做二次确认。
+
+
最终兑换完成后,客服可以把结果截图直接发给客户确认。
+
+ {{ screenshotEmptyMessage }}
+
+
+
+
+
+
+
+
+
+
+
{{ screenshotEmptyTitle || '' }}
+
{{ screenshotEmptyMessage }}
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue
new file mode 100644
index 00000000..5f2e11ca
--- /dev/null
+++ b/apps/frontend/src/views/admin/manual-redeem/components/AdminManualRedeemSummaryCard.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+ 唯一凭据
+ {{ manualRequest?.proofValue || order?.platformOrderId || '-' }}
+
+
+ 任务号
+ {{ task.taskNo || '-' }}
+
+
+ 商品
+ {{ orderItem?.skuName || '-' }}
+
+
+ 当前状态
+ {{ statusLabel }}
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/AdminPlatformShopsView.vue b/apps/frontend/src/views/admin/platform-shops/AdminPlatformShopsView.vue
index e919b58e..7024fed9 100644
--- a/apps/frontend/src/views/admin/platform-shops/AdminPlatformShopsView.vue
+++ b/apps/frontend/src/views/admin/platform-shops/AdminPlatformShopsView.vue
@@ -12,10 +12,10 @@ import {
} from '@/services/admin'
import { hasAdminRole } from '@/utils/admin-auth'
import AdminPlatformAgisoSection from './components/AdminPlatformAgisoSection.vue'
-import AdminPlatformCloudtentaclesSection from './components/AdminPlatformCloudtentaclesSection.vue'
+import AdminPlatformCloudtentaclesSection from './components/cloudtentacles/AdminPlatformCloudtentaclesSection.vue'
import AdminPlatformNinetyoneSection from './components/AdminPlatformNinetyoneSection.vue'
-import AdminPlatformNotificationSection from './components/AdminPlatformNotificationSection.vue'
-import AdminPlatformKuaishouEticketSection from './components/AdminPlatformKuaishouEticketSection.vue'
+import AdminPlatformNotificationSection from './components/notification/AdminPlatformNotificationSection.vue'
+import AdminPlatformKuaishouEticketSection from './components/kuaishou-eticket/AdminPlatformKuaishouEticketSection.vue'
import { useAdminAgisoPlatform } from './composables/useAdminAgisoPlatform'
import { useAdminCloudtentaclesPlatform } from './composables/useAdminCloudtentaclesPlatform'
import { useAdminNinetyonePlatform } from './composables/useAdminNinetyonePlatform'
@@ -273,7 +273,9 @@ onMounted(loadConfigs)
tag: 'Cookie / 券核销',
value: kuaishouEticketStats.configuredShopCount,
unit: '已配置店铺',
- meta: `Cookie 就绪 ${kuaishouEticketStats.cookieReadyCount} 家 · 当前店铺 ${kuaishouEticketStats.selectedShopReady ? '可调试' : '待补全'}`,
+ meta: `Cookie 就绪 ${kuaishouEticketStats.cookieReadyCount} 家 · 当前店铺 ${
+ kuaishouEticketStats.selectedShopReady ? '可调试' : '待补全'
+ }`,
},
...(isDevMode
? [
@@ -283,7 +285,9 @@ onMounted(loadConfigs)
tag: '履约 / 登录',
value: cloudtentaclesStats.hasCredential ? '已配置' : '未配置',
unit: '履约凭据',
- meta: `短信 ${cloudtentaclesStats.smsSent ? '已发送' : '未发送'} · 会话 ${cloudtentaclesStats.validated ? '已验证' : '未验证'}`,
+ meta: `短信 ${cloudtentaclesStats.smsSent ? '已发送' : '未发送'} · 会话 ${
+ cloudtentaclesStats.validated ? '已验证' : '未验证'
+ }`,
},
]
: []),
@@ -455,9 +459,7 @@ onMounted(loadConfigs)
}
.overview-card {
- transition:
- border-color 0.2s ease,
- box-shadow 0.2s ease;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.overview-card.is-active {
diff --git a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformCloudtentaclesSection.vue b/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformCloudtentaclesSection.vue
deleted file mode 100644
index ff64296c..00000000
--- a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformCloudtentaclesSection.vue
+++ /dev/null
@@ -1,455 +0,0 @@
-
-
-
-
-
-
-
diff --git a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformKuaishouEticketSection.vue b/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformKuaishouEticketSection.vue
deleted file mode 100644
index 4c02f218..00000000
--- a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformKuaishouEticketSection.vue
+++ /dev/null
@@ -1,654 +0,0 @@
-
-
-
-
-
-
-
diff --git a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformNotificationSection.vue b/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformNotificationSection.vue
deleted file mode 100644
index 244235c9..00000000
--- a/apps/frontend/src/views/admin/platform-shops/components/AdminPlatformNotificationSection.vue
+++ /dev/null
@@ -1,548 +0,0 @@
-
-
-
-
-
-
-
diff --git a/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesConfigCard.vue b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesConfigCard.vue
new file mode 100644
index 00000000..30377dd0
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesConfigCard.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ cloudtentaclesSmsResult.phoneMasked }},{{
+ formatAdminDateTime(cloudtentaclesSmsResult.sentAt)
+ }}
+
+ 先发送短信验证码,确认手机号可用。
+
+
+
+ {{ cloudtentaclesLoginResult.phoneMasked }},{{
+ formatAdminDateTime(cloudtentaclesLoginResult.loggedInAt)
+ }}
+
+ 填写短信验证码后可执行测试登录。
+
+
+
+ 权限 {{ cloudtentaclesValidateResult.session.permissionCount }} 项,余额
+ {{ cloudtentaclesValidateResult.asset }}
+
+ 登录成功后可继续校验 token 会话。
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesDebugCard.vue b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesDebugCard.vue
new file mode 100644
index 00000000..187f1299
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesDebugCard.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
cloudtentacles 开发调试工具
+
+ 仅开发模式展示。用于调试分类、SKU、购买、发货、虚拟号、兑换链接和结束退还链路。
+
+
+
+
+
+ 余额
+ 分类
+ SKU
+ 购买
+ 发货
+ 背包
+ 虚拟号列表
+ 申请虚拟号
+ 生成登录码
+ 获取验证码
+ 校验验证码
+ 兑换链接
+ 退还号码
+ 完整流程
+
+
+ {{
+ cloudtentaclesDebugResult
+ }}
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesInfoCard.vue b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesInfoCard.vue
new file mode 100644
index 00000000..6d688929
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesInfoCard.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+ 配置信息
+
+
+
+ {{ cloudtentaclesFilePath || '-' }}
+
+
+ {{ cloudtentaclesSessionFilePath || '-' }}
+
+
+ 管理 cloudtentacles 外部履约平台的账号、短信验证码登录与 token
+ 探活。该区域不处理订单来源同步。
+
+
+ {{
+ cloudtentaclesPersistedSession?.hasToken
+ ? `${cloudtentaclesPersistedSession.tokenMasked} / ${formatAdminDateTime(cloudtentaclesPersistedSession.loggedInAt)}`
+ : '未保存'
+ }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesResultCard.vue b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesResultCard.vue
new file mode 100644
index 00000000..d9c4ac57
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminCloudtentaclesResultCard.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
cloudtentacles 调试结果
+
+ 用于确认当前账号会话、权限和用户信息,后续发货执行器会复用这里的登录能力。
+
+
+
+
+
+
+ 原始 token 已写入上方输入框,可继续用于校验或后续调试。
+
+
+
+ {{
+ (
+ cloudtentaclesValidateResult?.session.permissions ||
+ cloudtentaclesLoginResult?.session.permissions ||
+ []
+ ).join('、') || '暂无'
+ }}
+
+
+
+ 可用于确认当前履约账号状态。
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminPlatformCloudtentaclesSection.vue b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminPlatformCloudtentaclesSection.vue
new file mode 100644
index 00000000..84206a2b
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/cloudtentacles/AdminPlatformCloudtentaclesSection.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketDebug.vue b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketDebug.vue
new file mode 100644
index 00000000..95788c72
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketDebug.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 自动回填参数
+ 查询后生成
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ kuaishouEticketDetailResult.goods?.itemTitle ||
+ kuaishouEticketDetailResult.goods?.skuDesc ||
+ '-'
+ }}, 剩余 {{ kuaishouEticketDetailResult.detail.leftCount }} /
+ {{ kuaishouEticketDetailResult.detail.totalCount }}
+
+ 先通过查询券码获取核销详情并自动带出核销参数。
+
+
+
+ {{
+ kuaishouEticketConsumeResult.errorMessage ||
+ (kuaishouEticketConsumeResult.ok ? '核销成功' : '接口已返回结果')
+ }}
+
+ 查询详情后再执行核销会更稳妥。
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketInfoCard.vue b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketInfoCard.vue
new file mode 100644
index 00000000..91a2f9d9
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketInfoCard.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+ 说明信息
+
+
+ {{
+ kuaishouEticketFilePath || '-'
+ }}
+
+ 这是独立的新流程,只负责快手小店券核销调试与配置,不接入当前已完成的订单来源和履约主链路。
+
+
+ 先为每个店铺录入 kshopName + Cookie,可用 info 接口自动识别店铺名与店铺
+ ID;保存后选择目标店铺,再查询券码并执行核销。
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketResults.vue b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketResults.vue
new file mode 100644
index 00000000..66e64a73
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketResults.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
核销详情返回
+
成功查询后会自动回填 eTicketId、oid、formToken 和建议核销数量。
+
+
+ {{ JSON.stringify(kuaishouEticketDetailResult.raw, null, 2) }}
+
+
+
+
+
+
核销执行返回
+
这里保留接口原始响应,方便核对 result、error_msg 和 requestId。
+
+
+ {{ JSON.stringify(kuaishouEticketConsumeResult.raw, null, 2) }}
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketShopConfig.vue b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketShopConfig.vue
new file mode 100644
index 00000000..e401e822
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminKuaishouEticketShopConfig.vue
@@ -0,0 +1,405 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
{{ getKuaishouEticketShopInitial(shop) }}
+
+
+
+ {{ shop.kshopName || shop.shopId || '未命名店铺' }}
+ {{
+ shop.enabled ? '已启用' : '已停用'
+ }}
+ 当前调试
+
+
+ 店铺 ID:{{ shop.shopId || '等待 info 自动识别' }}
+
+ {{ shop.cookie.trim() ? 'Cookie 已粘贴' : '待补 Cookie' }}
+
+
+ {{ shop.userAvatar || shop.shopId.trim() ? '信息已识别' : '未读取信息' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 其中 Cookie 就绪 {{ kuaishouEticketStats.cookieReadyCount }} 家。
+
+
+
+ {{
+ kuaishouEticketStats.selectedShopReady
+ ? '已具备调试条件,可直接查询核销信息。'
+ : '请选择店铺并补全 Cookie。'
+ }}
+
+
+
+
+ 结算状态 {{ kuaishouEticketShopInfoResult.shop.settleStatus }}
+
+ 可用"读取店铺信息"校验 Cookie 是否属于预期店铺。
+
+
+ 1. 新增店铺并粘贴 Cookie
+ 2. 读取店铺信息确认 kshopName / shopId
+ 3. 设为调试店铺后查询券码并执行核销
+
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminPlatformKuaishouEticketSection.vue b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminPlatformKuaishouEticketSection.vue
new file mode 100644
index 00000000..061c2ce3
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/kuaishou-eticket/AdminPlatformKuaishouEticketSection.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationBarkCard.vue b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationBarkCard.vue
new file mode 100644
index 00000000..d1d7492e
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationBarkCard.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationInfoCard.vue b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationInfoCard.vue
new file mode 100644
index 00000000..efe2ae22
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationInfoCard.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+ 配置信息
+
+
+ 内部运营通知,仅发送给后台管理员、客服或值班人员。
+ {{
+ notificationFilePath || '-'
+ }}
+ {{
+ scheduledJobsFilePath || '-'
+ }}
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationScheduledJobsCard.vue b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationScheduledJobsCard.vue
new file mode 100644
index 00000000..37858cc1
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationScheduledJobsCard.vue
@@ -0,0 +1,317 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ formatJobType(job.type) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getScheduledJobRuntime(job.id)?.lastStatus || 'pending' }}
+
+ {{
+ getScheduledJobRuntime(job.id)?.lastMessage || '-'
+ }}
+ 余额:{{ getScheduledJobRuntime(job.id)?.lastAsset ?? '-' }}
+ 上次:{{
+ formatAdminDateTime(getScheduledJobRuntime(job.id)?.lastRunAt || '')
+ }}
+ 下次:{{
+ formatAdminDateTime(getScheduledJobRuntime(job.id)?.nextRunAt || '')
+ }}
+
+
+
+
+
+
+ 立即检查
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationTestCard.vue b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationTestCard.vue
new file mode 100644
index 00000000..b049c2b0
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminNotificationTestCard.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 成功
+ {{ notificationTestResult.successCount }}
+
+
+ 失败
+ {{ notificationTestResult.failedCount }}
+
+
+ 通道
+ {{ notificationTestResult.channel || '-' }}
+
+
+
+
+
+ {{
+ row.recipientName || row.recipientKeyMasked || '-'
+ }}
+
+
+
+ {{
+ row.ok ? '成功' : '失败'
+ }}
+
+
+
+
+ {{ row.errorMessage || '-' }}
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/components/notification/AdminPlatformNotificationSection.vue b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminPlatformNotificationSection.vue
new file mode 100644
index 00000000..92bfec76
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/components/notification/AdminPlatformNotificationSection.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts b/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts
index c31c23eb..f90ff6d5 100644
--- a/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts
+++ b/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts
@@ -2,24 +2,10 @@ import { computed, ref } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
- appointAdminCloudtentaclesVn,
- backAdminCloudtentaclesVn,
- buyAdminCloudtentaclesSku,
- fetchAdminCloudtentaclesAsset,
- fetchAdminCloudtentaclesBindUrl,
- fetchAdminCloudtentaclesCategories,
- fetchAdminCloudtentaclesKnapsack,
- fetchAdminCloudtentaclesSkuList,
- fetchAdminCloudtentaclesVnCode,
- fetchAdminCloudtentaclesVnList,
- generateAdminCloudtentaclesVnLoginCode,
- runAdminCloudtentaclesFullFlow,
saveAdminCloudtentaclesSourceConfig,
sendAdminCloudtentaclesSmsCode,
testAdminCloudtentaclesLogin,
- useAdminCloudtentaclesSku,
validateAdminCloudtentaclesSession,
- verifyAdminCloudtentaclesVnCode,
} from '@/services/admin'
import type {
AdminCloudtentaclesLoginTestResult,
@@ -29,10 +15,13 @@ import type {
AdminCloudtentaclesValidateSessionResult,
} from '@/types/admin'
+import type { CloudtentaclesForm } from './useCloudtentaclesDebugActions'
+import { useCloudtentaclesDebugActions } from './useCloudtentaclesDebugActions'
+
export function useAdminCloudtentaclesPlatform() {
const cloudtentaclesFilePath = ref('')
const cloudtentaclesSessionFilePath = ref('')
- const cloudtentaclesForm = ref({
+ const cloudtentaclesForm = ref({
baseUrl: 'https://123.207.217.176',
username: '',
password: '',
@@ -54,10 +43,10 @@ export function useAdminCloudtentaclesPlatform() {
const cloudtentaclesSmsResult = ref(null)
const cloudtentaclesLoginResult = ref(null)
const cloudtentaclesValidateResult = ref(null)
- const cloudtentaclesDebugLoading = ref(false)
- const cloudtentaclesDebugResult = ref('')
const cloudtentaclesPersistedSession = ref(null)
+ const debugActions = useCloudtentaclesDebugActions(cloudtentaclesForm, cloudtentaclesResultError)
+
const cloudtentaclesStats = computed(() => ({
hasCredential: Boolean(
cloudtentaclesForm.value.username.trim() &&
@@ -260,198 +249,6 @@ export function useAdminCloudtentaclesPlatform() {
}
}
- function buildCloudtentaclesTokenPayload() {
- return {
- baseUrl: cloudtentaclesForm.value.baseUrl.trim() || 'https://123.207.217.176',
- token: cloudtentaclesForm.value.token.trim(),
- deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
- deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
- }
- }
-
- function ensureCloudtentaclesToken() {
- if (!cloudtentaclesForm.value.token.trim()) {
- cloudtentaclesResultError.value = '请先登录或手动填写 token'
- return false
- }
- return true
- }
-
- function renderCloudtentaclesDebugResult(label: string, data: unknown) {
- cloudtentaclesDebugResult.value = `${label}\n${JSON.stringify(data, null, 2)}`
- }
-
- async function runCloudtentaclesDebugAction(
- label: string,
- runner: () => Promise<{ data: unknown }>,
- ) {
- cloudtentaclesDebugLoading.value = true
- cloudtentaclesResultError.value = ''
-
- try {
- const response = await runner()
- renderCloudtentaclesDebugResult(label, response.data)
- showSuccess(`${label}成功`)
- } catch (error) {
- cloudtentaclesResultError.value = error instanceof Error ? error.message : `${label}失败`
- showError(cloudtentaclesResultError.value)
- } finally {
- cloudtentaclesDebugLoading.value = false
- }
- }
-
- async function handleCloudtentaclesFetchAsset() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('查询余额', () =>
- fetchAdminCloudtentaclesAsset(buildCloudtentaclesTokenPayload()),
- )
- }
-
- async function handleCloudtentaclesFetchCategories() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('查询分类', () =>
- fetchAdminCloudtentaclesCategories(buildCloudtentaclesTokenPayload()),
- )
- }
-
- async function handleCloudtentaclesFetchSkuList() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('查询SKU列表', () =>
- fetchAdminCloudtentaclesSkuList(buildCloudtentaclesTokenPayload()),
- )
- }
-
- async function handleCloudtentaclesBuySku() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('购买SKU', () =>
- buyAdminCloudtentaclesSku({
- ...buildCloudtentaclesTokenPayload(),
- id: Number(cloudtentaclesForm.value.skuId || 0),
- count: Number(cloudtentaclesForm.value.skuCount || 1),
- }),
- )
- }
-
- async function handleCloudtentaclesUseSku() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('执行发货', () =>
- useAdminCloudtentaclesSku({
- ...buildCloudtentaclesTokenPayload(),
- id: Number(cloudtentaclesForm.value.skuId || 0),
- virtualNumberId: Number(cloudtentaclesForm.value.vnId || 0),
- phone: cloudtentaclesForm.value.phone.trim(),
- }),
- )
- }
-
- async function handleCloudtentaclesFetchKnapsack() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('查询背包', () =>
- fetchAdminCloudtentaclesKnapsack(buildCloudtentaclesTokenPayload()),
- )
- }
-
- async function handleCloudtentaclesFetchVnList() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('查询虚拟号列表', () =>
- fetchAdminCloudtentaclesVnList({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- }),
- )
- }
-
- async function handleCloudtentaclesAppointVn() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('申请虚拟号', async () => {
- const response = await appointAdminCloudtentaclesVn({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- })
- const item = (response.data as { item?: { id?: number; phone?: string } }).item
- if (item?.id) {
- cloudtentaclesForm.value.vnId = Number(item.id)
- }
- if (item?.phone) {
- cloudtentaclesForm.value.phone = String(item.phone)
- }
- return response
- })
- }
-
- async function handleCloudtentaclesGenerateVnLoginCode() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('生成登录码', () =>
- generateAdminCloudtentaclesVnLoginCode({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- id: Number(cloudtentaclesForm.value.vnId || 0),
- }),
- )
- }
-
- async function handleCloudtentaclesFetchVnCode() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('获取验证码', async () => {
- const response = await fetchAdminCloudtentaclesVnCode({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- phone: cloudtentaclesForm.value.phone.trim(),
- })
- const code = (response.data as { code?: string }).code
- if (code) {
- cloudtentaclesForm.value.code = String(code)
- }
- return response
- })
- }
-
- async function handleCloudtentaclesVerifyVnCode() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('校验验证码', () =>
- verifyAdminCloudtentaclesVnCode({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- id: Number(cloudtentaclesForm.value.vnId || 0),
- code: cloudtentaclesForm.value.code.trim(),
- }),
- )
- }
-
- async function handleCloudtentaclesFetchBindUrl() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('获取兑换链接', () =>
- fetchAdminCloudtentaclesBindUrl({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- id: Number(cloudtentaclesForm.value.vnId || 0),
- }),
- )
- }
-
- async function handleCloudtentaclesBackVn() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('退还号码', () =>
- backAdminCloudtentaclesVn({
- ...buildCloudtentaclesTokenPayload(),
- key: cloudtentaclesForm.value.vnKey.trim(),
- id: Number(cloudtentaclesForm.value.vnId || 0),
- }),
- )
- }
-
- async function handleCloudtentaclesRunFullFlow() {
- if (!ensureCloudtentaclesToken()) return
- await runCloudtentaclesDebugAction('完整测试流程', () =>
- runAdminCloudtentaclesFullFlow({
- ...buildCloudtentaclesTokenPayload(),
- skuId: Number(cloudtentaclesForm.value.skuId || 0),
- skuCount: Number(cloudtentaclesForm.value.skuCount || 1),
- vnKey: cloudtentaclesForm.value.vnKey.trim(),
- }),
- )
- }
-
return {
cloudtentaclesFilePath,
cloudtentaclesSessionFilePath,
@@ -464,8 +261,8 @@ export function useAdminCloudtentaclesPlatform() {
cloudtentaclesSmsResult,
cloudtentaclesLoginResult,
cloudtentaclesValidateResult,
- cloudtentaclesDebugLoading,
- cloudtentaclesDebugResult,
+ cloudtentaclesDebugLoading: debugActions.cloudtentaclesDebugLoading,
+ cloudtentaclesDebugResult: debugActions.cloudtentaclesDebugResult,
cloudtentaclesPersistedSession,
cloudtentaclesStats,
hydrateCloudtentaclesConfig,
@@ -473,19 +270,19 @@ export function useAdminCloudtentaclesPlatform() {
handleCloudtentaclesSendSmsCode,
handleCloudtentaclesTestLogin,
handleCloudtentaclesValidateSession,
- handleCloudtentaclesFetchAsset,
- handleCloudtentaclesFetchCategories,
- handleCloudtentaclesFetchSkuList,
- handleCloudtentaclesBuySku,
- handleCloudtentaclesUseSku,
- handleCloudtentaclesFetchKnapsack,
- handleCloudtentaclesFetchVnList,
- handleCloudtentaclesAppointVn,
- handleCloudtentaclesGenerateVnLoginCode,
- handleCloudtentaclesFetchVnCode,
- handleCloudtentaclesVerifyVnCode,
- handleCloudtentaclesFetchBindUrl,
- handleCloudtentaclesBackVn,
- handleCloudtentaclesRunFullFlow,
+ handleCloudtentaclesFetchAsset: debugActions.handleCloudtentaclesFetchAsset,
+ handleCloudtentaclesFetchCategories: debugActions.handleCloudtentaclesFetchCategories,
+ handleCloudtentaclesFetchSkuList: debugActions.handleCloudtentaclesFetchSkuList,
+ handleCloudtentaclesBuySku: debugActions.handleCloudtentaclesBuySku,
+ handleCloudtentaclesUseSku: debugActions.handleCloudtentaclesUseSku,
+ handleCloudtentaclesFetchKnapsack: debugActions.handleCloudtentaclesFetchKnapsack,
+ handleCloudtentaclesFetchVnList: debugActions.handleCloudtentaclesFetchVnList,
+ handleCloudtentaclesAppointVn: debugActions.handleCloudtentaclesAppointVn,
+ handleCloudtentaclesGenerateVnLoginCode: debugActions.handleCloudtentaclesGenerateVnLoginCode,
+ handleCloudtentaclesFetchVnCode: debugActions.handleCloudtentaclesFetchVnCode,
+ handleCloudtentaclesVerifyVnCode: debugActions.handleCloudtentaclesVerifyVnCode,
+ handleCloudtentaclesFetchBindUrl: debugActions.handleCloudtentaclesFetchBindUrl,
+ handleCloudtentaclesBackVn: debugActions.handleCloudtentaclesBackVn,
+ handleCloudtentaclesRunFullFlow: debugActions.handleCloudtentaclesRunFullFlow,
}
}
diff --git a/apps/frontend/src/views/admin/platform-shops/composables/useAdminKuaishouEticketPlatform.ts b/apps/frontend/src/views/admin/platform-shops/composables/useAdminKuaishouEticketPlatform.ts
index a4d481aa..217420ce 100644
--- a/apps/frontend/src/views/admin/platform-shops/composables/useAdminKuaishouEticketPlatform.ts
+++ b/apps/frontend/src/views/admin/platform-shops/composables/useAdminKuaishouEticketPlatform.ts
@@ -1,5 +1,4 @@
-import { computed, ref } from 'vue'
-
+import { ref, computed, type Ref } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
consumeAdminKuaishouEticket,
@@ -10,18 +9,28 @@ import {
import type {
AdminKuaishouEticketConsumeResult,
AdminKuaishouEticketDetailResult,
- AdminKuaishouEticketShopConfigItem,
AdminKuaishouEticketShopInfoResult,
AdminKuaishouEticketSourceConfig,
} from '@/types/admin'
-
import type { EditableKuaishouEticketShop } from './types'
+import { useKuaishouEticketShopManager } from './useKuaishouEticketShopManager'
+
+type KuaishouEticketForm = {
+ baseUrl: string
+ selectedShopId: string
+ queryTicketCode: string
+ eTicketId: string
+ oid: string
+ formToken: string
+ num: number
+ storeId: string
+}
export function useAdminKuaishouEticketPlatform() {
+ const shopManager = useKuaishouEticketShopManager()
+
const kuaishouEticketFilePath = ref('')
- const kuaishouEticketShops = ref([])
- const expandedKuaishouEticketShopIds = ref([])
- const kuaishouEticketForm = ref({
+ const kuaishouEticketForm = ref({
baseUrl: 'https://s.kwaixiaodian.com',
selectedShopId: '',
queryTicketCode: '',
@@ -42,14 +51,14 @@ export function useAdminKuaishouEticketPlatform() {
const activeKuaishouEticketShop = computed(
() =>
- kuaishouEticketShops.value.find(
+ shopManager.kuaishouEticketShops.value.find(
(item) => item.id === kuaishouEticketForm.value.selectedShopId,
) || null,
)
const kuaishouEticketStats = computed(() => ({
- configuredShopCount: kuaishouEticketShops.value.length,
- cookieReadyCount: kuaishouEticketShops.value.filter((item) => item.cookie.trim()).length,
+ configuredShopCount: shopManager.kuaishouEticketShops.value.length,
+ cookieReadyCount: shopManager.kuaishouEticketShops.value.filter((item) => item.cookie.trim()).length,
selectedShopReady: Boolean(activeKuaishouEticketShop.value?.cookie.trim()),
detailReady: Boolean(kuaishouEticketDetailResult.value?.detail),
consumed: Boolean(kuaishouEticketConsumeResult.value?.consumed),
@@ -59,11 +68,11 @@ export function useAdminKuaishouEticketPlatform() {
filePath: string
source: {
baseUrl?: string
- shops: AdminKuaishouEticketShopConfigItem[]
+ shops: AdminKuaishouEticketShopInfoResult['shop'][]
}
}) {
kuaishouEticketFilePath.value = data.filePath
- kuaishouEticketShops.value = data.source.shops.map(mapEditableKuaishouEticketShop)
+ shopManager.setShopsFromConfig(data.source.shops as any)
kuaishouEticketForm.value = {
baseUrl: data.source.baseUrl || 'https://s.kwaixiaodian.com',
selectedShopId: '',
@@ -74,9 +83,9 @@ export function useAdminKuaishouEticketPlatform() {
num: 1,
storeId: '0',
}
- kuaishouEticketForm.value.selectedShopId = kuaishouEticketShops.value[0]?.id || ''
- expandedKuaishouEticketShopIds.value = kuaishouEticketShops.value[0]?.id
- ? [kuaishouEticketShops.value[0].id]
+ kuaishouEticketForm.value.selectedShopId = shopManager.kuaishouEticketShops.value[0]?.id || ''
+ shopManager.expandedKuaishouEticketShopIds.value = shopManager.kuaishouEticketShops.value[0]?.id
+ ? [shopManager.kuaishouEticketShops.value[0].id]
: []
}
@@ -84,7 +93,7 @@ export function useAdminKuaishouEticketPlatform() {
return {
enabled: true,
baseUrl: kuaishouEticketForm.value.baseUrl.trim() || 'https://s.kwaixiaodian.com',
- shops: kuaishouEticketShops.value.map((item) => ({
+ shops: shopManager.kuaishouEticketShops.value.map((item) => ({
shopId: item.shopId.trim(),
kshopName: item.kshopName.trim(),
cookie: item.cookie.trim(),
@@ -97,66 +106,20 @@ export function useAdminKuaishouEticketPlatform() {
}
function addKuaishouEticketShop() {
- const shop = createEmptyKuaishouEticketShop()
- kuaishouEticketShops.value.unshift(shop)
- setKuaishouEticketDebugShop(shop.id)
+ const { newSelectedId } = shopManager.addKuaishouEticketShop(kuaishouEticketForm.value.selectedShopId)
+ setKuaishouEticketDebugShop(newSelectedId)
}
function removeKuaishouEticketShop(id: string) {
- kuaishouEticketShops.value = kuaishouEticketShops.value.filter((item) => item.id !== id)
- collapseKuaishouEticketShop(id)
- if (kuaishouEticketForm.value.selectedShopId === id) {
- kuaishouEticketForm.value.selectedShopId = kuaishouEticketShops.value[0]?.id || ''
- }
- }
-
- function isKuaishouEticketShopExpanded(id: string) {
- return expandedKuaishouEticketShopIds.value.includes(id)
- }
-
- function expandKuaishouEticketShop(id: string) {
- if (expandedKuaishouEticketShopIds.value.includes(id)) {
- return
- }
- expandedKuaishouEticketShopIds.value = [id, ...expandedKuaishouEticketShopIds.value]
- }
-
- function collapseKuaishouEticketShop(id: string) {
- expandedKuaishouEticketShopIds.value = expandedKuaishouEticketShopIds.value.filter(
- (item) => item !== id,
+ kuaishouEticketForm.value.selectedShopId = shopManager.removeKuaishouEticketShop(
+ id,
+ kuaishouEticketForm.value.selectedShopId,
)
}
- function toggleKuaishouEticketShop(id: string) {
- if (isKuaishouEticketShopExpanded(id)) {
- collapseKuaishouEticketShop(id)
- return
- }
- expandKuaishouEticketShop(id)
- }
-
- function expandAllKuaishouEticketShops() {
- expandedKuaishouEticketShopIds.value = kuaishouEticketShops.value.map((item) => item.id)
- }
-
- function collapseAllKuaishouEticketShops() {
- expandedKuaishouEticketShopIds.value = []
- }
-
function setKuaishouEticketDebugShop(id: string) {
kuaishouEticketForm.value.selectedShopId = id
- expandKuaishouEticketShop(id)
- }
-
- function describeKuaishouEticketShop(shop: EditableKuaishouEticketShop) {
- const status = shop.cookie.trim() ? 'Cookie 已就绪' : '待补 Cookie'
- const identity = shop.shopId.trim() ? '已识别店铺' : '待读取信息'
- return `${status} · ${identity}`
- }
-
- function getKuaishouEticketShopInitial(shop: EditableKuaishouEticketShop) {
- const label = shop.kshopName.trim() || shop.shopId.trim() || '店'
- return label.slice(0, 1).toUpperCase()
+ shopManager.expandKuaishouEticketShop(id)
}
function ensureSelectedKuaishouEticketShop() {
@@ -264,14 +227,12 @@ export function useAdminKuaishouEticketPlatform() {
kuaishouEticketForm.value.baseUrl =
response.data.source.baseUrl || 'https://s.kwaixiaodian.com'
const previousSelectedShopId = activeKuaishouEticketShop.value?.shopId || ''
- kuaishouEticketShops.value = response.data.source.shops.map(mapEditableKuaishouEticketShop)
+ shopManager.setShopsFromConfig(response.data.source.shops)
kuaishouEticketForm.value.selectedShopId =
- kuaishouEticketShops.value.find(
- (item) => item.shopId && item.shopId === previousSelectedShopId,
- )?.id ||
- kuaishouEticketShops.value[0]?.id ||
+ shopManager.findShopByShopId(previousSelectedShopId)?.id ||
+ shopManager.kuaishouEticketShops.value[0]?.id ||
''
- expandedKuaishouEticketShopIds.value = kuaishouEticketForm.value.selectedShopId
+ shopManager.expandedKuaishouEticketShopIds.value = kuaishouEticketForm.value.selectedShopId
? [kuaishouEticketForm.value.selectedShopId]
: []
showSuccess('快手小店核销配置已保存')
@@ -351,8 +312,8 @@ export function useAdminKuaishouEticketPlatform() {
return {
kuaishouEticketFilePath,
- kuaishouEticketShops,
- expandedKuaishouEticketShopIds,
+ kuaishouEticketShops: shopManager.kuaishouEticketShops,
+ expandedKuaishouEticketShopIds: shopManager.expandedKuaishouEticketShopIds,
kuaishouEticketForm,
kuaishouEticketSaving,
kuaishouEticketResolvingShopId,
@@ -367,42 +328,18 @@ export function useAdminKuaishouEticketPlatform() {
hydrateKuaishouEticketConfig,
addKuaishouEticketShop,
removeKuaishouEticketShop,
- isKuaishouEticketShopExpanded,
- expandKuaishouEticketShop,
- collapseKuaishouEticketShop,
- toggleKuaishouEticketShop,
- expandAllKuaishouEticketShops,
- collapseAllKuaishouEticketShops,
+ isKuaishouEticketShopExpanded: shopManager.isKuaishouEticketShopExpanded,
+ expandKuaishouEticketShop: shopManager.expandKuaishouEticketShop,
+ collapseKuaishouEticketShop: shopManager.collapseKuaishouEticketShop,
+ toggleKuaishouEticketShop: shopManager.toggleKuaishouEticketShop,
+ expandAllKuaishouEticketShops: shopManager.expandAllKuaishouEticketShops,
+ collapseAllKuaishouEticketShops: shopManager.collapseAllKuaishouEticketShops,
setKuaishouEticketDebugShop,
- describeKuaishouEticketShop,
- getKuaishouEticketShopInitial,
+ describeKuaishouEticketShop: shopManager.describeKuaishouEticketShop,
+ getKuaishouEticketShopInitial: shopManager.getKuaishouEticketShopInitial,
handleKuaishouEticketResolveShopInfo,
handleKuaishouEticketSaveSource,
handleKuaishouEticketQueryDetail,
handleKuaishouEticketConsume,
}
}
-
-function createEmptyKuaishouEticketShop(): EditableKuaishouEticketShop {
- return {
- id: crypto.randomUUID(),
- shopId: '',
- kshopName: '',
- cookie: '',
- userAvatar: '',
- enabled: true,
- }
-}
-
-function mapEditableKuaishouEticketShop(
- item: AdminKuaishouEticketShopConfigItem,
-): EditableKuaishouEticketShop {
- return {
- id: crypto.randomUUID(),
- shopId: item.shopId,
- kshopName: item.kshopName,
- cookie: item.cookie,
- userAvatar: item.userAvatar,
- enabled: item.enabled !== false,
- }
-}
diff --git a/apps/frontend/src/views/admin/platform-shops/composables/useCloudtentaclesDebugActions.ts b/apps/frontend/src/views/admin/platform-shops/composables/useCloudtentaclesDebugActions.ts
new file mode 100644
index 00000000..d616e63a
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/composables/useCloudtentaclesDebugActions.ts
@@ -0,0 +1,254 @@
+import { ref } from 'vue'
+import { showError, showSuccess } from '@/lib/feedback'
+import {
+ appointAdminCloudtentaclesVn,
+ backAdminCloudtentaclesVn,
+ buyAdminCloudtentaclesSku,
+ fetchAdminCloudtentaclesAsset,
+ fetchAdminCloudtentaclesBindUrl,
+ fetchAdminCloudtentaclesCategories,
+ fetchAdminCloudtentaclesKnapsack,
+ fetchAdminCloudtentaclesSkuList,
+ fetchAdminCloudtentaclesVnCode,
+ fetchAdminCloudtentaclesVnList,
+ generateAdminCloudtentaclesVnLoginCode,
+ runAdminCloudtentaclesFullFlow,
+ useAdminCloudtentaclesSku,
+ verifyAdminCloudtentaclesVnCode,
+} from '@/services/admin'
+
+export type CloudtentaclesForm = {
+ baseUrl: string
+ username: string
+ password: string
+ phone: string
+ code: string
+ token: string
+ vnKey: string
+ vnId: number
+ skuId: number
+ skuCount: number
+ deviceId: string
+ deviceType: number
+}
+
+export function useCloudtentaclesDebugActions(
+ cloudtentaclesForm: { value: CloudtentaclesForm },
+ cloudtentaclesResultError: { value: string },
+) {
+ const cloudtentaclesDebugLoading = ref(false)
+ const cloudtentaclesDebugResult = ref('')
+
+ function buildCloudtentaclesTokenPayload() {
+ return {
+ baseUrl: cloudtentaclesForm.value.baseUrl.trim() || 'https://123.207.217.176',
+ token: cloudtentaclesForm.value.token.trim(),
+ deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
+ deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
+ }
+ }
+
+ function ensureCloudtentaclesToken() {
+ if (!cloudtentaclesForm.value.token.trim()) {
+ cloudtentaclesResultError.value = '请先登录或手动填写 token'
+ return false
+ }
+ return true
+ }
+
+ function renderCloudtentaclesDebugResult(label: string, data: unknown) {
+ cloudtentaclesDebugResult.value = `${label}\n${JSON.stringify(data, null, 2)}`
+ }
+
+ async function runCloudtentaclesDebugAction(
+ label: string,
+ runner: () => Promise<{ data: unknown }>,
+ ) {
+ cloudtentaclesDebugLoading.value = true
+ cloudtentaclesResultError.value = ''
+
+ try {
+ const response = await runner()
+ renderCloudtentaclesDebugResult(label, response.data)
+ showSuccess(`${label}成功`)
+ } catch (error) {
+ cloudtentaclesResultError.value = error instanceof Error ? error.message : `${label}失败`
+ showError(cloudtentaclesResultError.value)
+ } finally {
+ cloudtentaclesDebugLoading.value = false
+ }
+ }
+
+ async function handleCloudtentaclesFetchAsset() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('查询余额', () =>
+ fetchAdminCloudtentaclesAsset(buildCloudtentaclesTokenPayload()),
+ )
+ }
+
+ async function handleCloudtentaclesFetchCategories() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('查询分类', () =>
+ fetchAdminCloudtentaclesCategories(buildCloudtentaclesTokenPayload()),
+ )
+ }
+
+ async function handleCloudtentaclesFetchSkuList() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('查询SKU列表', () =>
+ fetchAdminCloudtentaclesSkuList(buildCloudtentaclesTokenPayload()),
+ )
+ }
+
+ async function handleCloudtentaclesBuySku() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('购买SKU', () =>
+ buyAdminCloudtentaclesSku({
+ ...buildCloudtentaclesTokenPayload(),
+ id: Number(cloudtentaclesForm.value.skuId || 0),
+ count: Number(cloudtentaclesForm.value.skuCount || 1),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesUseSku() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('执行发货', () =>
+ useAdminCloudtentaclesSku({
+ ...buildCloudtentaclesTokenPayload(),
+ id: Number(cloudtentaclesForm.value.skuId || 0),
+ virtualNumberId: Number(cloudtentaclesForm.value.vnId || 0),
+ phone: cloudtentaclesForm.value.phone.trim(),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesFetchKnapsack() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('查询背包', () =>
+ fetchAdminCloudtentaclesKnapsack(buildCloudtentaclesTokenPayload()),
+ )
+ }
+
+ async function handleCloudtentaclesFetchVnList() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('查询虚拟号列表', () =>
+ fetchAdminCloudtentaclesVnList({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesAppointVn() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('申请虚拟号', async () => {
+ const response = await appointAdminCloudtentaclesVn({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ })
+ const item = (response.data as { item?: { id?: number; phone?: string } }).item
+ if (item?.id) {
+ cloudtentaclesForm.value.vnId = Number(item.id)
+ }
+ if (item?.phone) {
+ cloudtentaclesForm.value.phone = String(item.phone)
+ }
+ return response
+ })
+ }
+
+ async function handleCloudtentaclesGenerateVnLoginCode() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('生成登录码', () =>
+ generateAdminCloudtentaclesVnLoginCode({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ id: Number(cloudtentaclesForm.value.vnId || 0),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesFetchVnCode() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('获取验证码', async () => {
+ const response = await fetchAdminCloudtentaclesVnCode({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ phone: cloudtentaclesForm.value.phone.trim(),
+ })
+ const code = (response.data as { code?: string }).code
+ if (code) {
+ cloudtentaclesForm.value.code = String(code)
+ }
+ return response
+ })
+ }
+
+ async function handleCloudtentaclesVerifyVnCode() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('校验验证码', () =>
+ verifyAdminCloudtentaclesVnCode({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ id: Number(cloudtentaclesForm.value.vnId || 0),
+ code: cloudtentaclesForm.value.code.trim(),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesFetchBindUrl() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('获取兑换链接', () =>
+ fetchAdminCloudtentaclesBindUrl({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ id: Number(cloudtentaclesForm.value.vnId || 0),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesBackVn() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('退还号码', () =>
+ backAdminCloudtentaclesVn({
+ ...buildCloudtentaclesTokenPayload(),
+ key: cloudtentaclesForm.value.vnKey.trim(),
+ id: Number(cloudtentaclesForm.value.vnId || 0),
+ }),
+ )
+ }
+
+ async function handleCloudtentaclesRunFullFlow() {
+ if (!ensureCloudtentaclesToken()) return
+ await runCloudtentaclesDebugAction('完整测试流程', () =>
+ runAdminCloudtentaclesFullFlow({
+ ...buildCloudtentaclesTokenPayload(),
+ skuId: Number(cloudtentaclesForm.value.skuId || 0),
+ skuCount: Number(cloudtentaclesForm.value.skuCount || 1),
+ vnKey: cloudtentaclesForm.value.vnKey.trim(),
+ }),
+ )
+ }
+
+ return {
+ cloudtentaclesDebugLoading,
+ cloudtentaclesDebugResult,
+ buildCloudtentaclesTokenPayload,
+ ensureCloudtentaclesToken,
+ handleCloudtentaclesFetchAsset,
+ handleCloudtentaclesFetchCategories,
+ handleCloudtentaclesFetchSkuList,
+ handleCloudtentaclesBuySku,
+ handleCloudtentaclesUseSku,
+ handleCloudtentaclesFetchKnapsack,
+ handleCloudtentaclesFetchVnList,
+ handleCloudtentaclesAppointVn,
+ handleCloudtentaclesGenerateVnLoginCode,
+ handleCloudtentaclesFetchVnCode,
+ handleCloudtentaclesVerifyVnCode,
+ handleCloudtentaclesFetchBindUrl,
+ handleCloudtentaclesBackVn,
+ handleCloudtentaclesRunFullFlow,
+ }
+}
diff --git a/apps/frontend/src/views/admin/platform-shops/composables/useKuaishouEticketShopManager.ts b/apps/frontend/src/views/admin/platform-shops/composables/useKuaishouEticketShopManager.ts
new file mode 100644
index 00000000..1dfb8617
--- /dev/null
+++ b/apps/frontend/src/views/admin/platform-shops/composables/useKuaishouEticketShopManager.ts
@@ -0,0 +1,117 @@
+import { ref } from 'vue'
+import type { EditableKuaishouEticketShop } from '../types'
+import type { AdminKuaishouEticketShopConfigItem } from '@/types/admin'
+
+export function useKuaishouEticketShopManager() {
+ const kuaishouEticketShops = ref([])
+ const expandedKuaishouEticketShopIds = ref([])
+
+ function addKuaishouEticketShop(selectedShopId: string) {
+ const shop = createEmptyKuaishouEticketShop()
+ kuaishouEticketShops.value.unshift(shop)
+ return { shop, newSelectedId: shop.id }
+ }
+
+ function removeKuaishouEticketShop(id: string, currentSelectedId: string) {
+ kuaishouEticketShops.value = kuaishouEticketShops.value.filter((item) => item.id !== id)
+ collapseKuaishouEticketShop(id)
+ if (currentSelectedId === id) {
+ return kuaishouEticketShops.value[0]?.id || ''
+ }
+ return currentSelectedId
+ }
+
+ function isKuaishouEticketShopExpanded(id: string) {
+ return expandedKuaishouEticketShopIds.value.includes(id)
+ }
+
+ function expandKuaishouEticketShop(id: string) {
+ if (expandedKuaishouEticketShopIds.value.includes(id)) {
+ return
+ }
+ expandedKuaishouEticketShopIds.value = [id, ...expandedKuaishouEticketShopIds.value]
+ }
+
+ function collapseKuaishouEticketShop(id: string) {
+ expandedKuaishouEticketShopIds.value = expandedKuaishouEticketShopIds.value.filter(
+ (item) => item !== id,
+ )
+ }
+
+ function toggleKuaishouEticketShop(id: string) {
+ if (isKuaishouEticketShopExpanded(id)) {
+ collapseKuaishouEticketShop(id)
+ return
+ }
+ expandKuaishouEticketShop(id)
+ }
+
+ function expandAllKuaishouEticketShops() {
+ expandedKuaishouEticketShopIds.value = kuaishouEticketShops.value.map((item) => item.id)
+ }
+
+ function collapseAllKuaishouEticketShops() {
+ expandedKuaishouEticketShopIds.value = []
+ }
+
+ function describeKuaishouEticketShop(shop: EditableKuaishouEticketShop) {
+ const status = shop.cookie.trim() ? 'Cookie 已就绪' : '待补 Cookie'
+ const identity = shop.shopId.trim() ? '已识别店铺' : '待读取信息'
+ return `${status} · ${identity}`
+ }
+
+ function getKuaishouEticketShopInitial(shop: EditableKuaishouEticketShop) {
+ const label = shop.kshopName.trim() || shop.shopId.trim() || '店'
+ return label.slice(0, 1).toUpperCase()
+ }
+
+ function setShopsFromConfig(shops: AdminKuaishouEticketShopConfigItem[]) {
+ kuaishouEticketShops.value = shops.map(mapEditableKuaishouEticketShop)
+ return kuaishouEticketShops.value
+ }
+
+ function findShopByShopId(shopId: string) {
+ return kuaishouEticketShops.value.find((item) => item.shopId && item.shopId === shopId)
+ }
+
+ return {
+ kuaishouEticketShops,
+ expandedKuaishouEticketShopIds,
+ addKuaishouEticketShop,
+ removeKuaishouEticketShop,
+ isKuaishouEticketShopExpanded,
+ expandKuaishouEticketShop,
+ collapseKuaishouEticketShop,
+ toggleKuaishouEticketShop,
+ expandAllKuaishouEticketShops,
+ collapseAllKuaishouEticketShops,
+ describeKuaishouEticketShop,
+ getKuaishouEticketShopInitial,
+ setShopsFromConfig,
+ findShopByShopId,
+ }
+}
+
+function createEmptyKuaishouEticketShop(): EditableKuaishouEticketShop {
+ return {
+ id: crypto.randomUUID(),
+ shopId: '',
+ kshopName: '',
+ cookie: '',
+ userAvatar: '',
+ enabled: true,
+ }
+}
+
+function mapEditableKuaishouEticketShop(
+ item: AdminKuaishouEticketShopConfigItem,
+): EditableKuaishouEticketShop {
+ return {
+ id: crypto.randomUUID(),
+ shopId: item.shopId,
+ kshopName: item.kshopName,
+ cookie: item.cookie,
+ userAvatar: item.userAvatar,
+ enabled: item.enabled !== false,
+ }
+}