优化 cloudtentacles 多账号管理与任务展示

This commit is contained in:
yml
2026-05-25 16:48:23 +08:00
parent e3d318d7f3
commit 28c8575a11
17 changed files with 495 additions and 167 deletions
@@ -90,6 +90,10 @@ export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord |
binding: {
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
cloudSourceKey: String(binding.cloudSourceKey || '').trim(),
cloudSourceKeyFallbacks: Array.isArray(binding.cloudSourceKeyFallbacks)
? binding.cloudSourceKeyFallbacks.map((value) => String(value || '').trim()).filter(Boolean)
: [],
resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(),
skuId: Number(binding.skuId || 0) || 0,
skuName: String(binding.skuName || '').trim(),
vnKey: String(binding.vnKey || '').trim(),
@@ -8,10 +8,12 @@ import {
} from "../../platforms/cloudtentacles/source-config-service.js";
import {
clearCloudtentaclesSessionStateByKey,
deleteCloudtentaclesSessionStateByKey,
getCloudtentaclesSessionFilePath,
getCloudtentaclesSessionStateByKey,
saveCloudtentaclesSessionStateByKey,
getAllCloudtentaclesSessionStates,
pruneCloudtentaclesSessionStates,
} from "../../platforms/cloudtentacles/session-state-service.js";
import {
loginCloudtentaclesSession,
@@ -102,7 +104,9 @@ export function updateAdminCloudtentaclesSourceConfig(
enabled: payload.enabled !== false,
sources: normalizedList,
});
const sessions = getAllCloudtentaclesSessionStates();
const sessions = pruneCloudtentaclesSessionStates(
normalizedList.map((item) => item.key)
);
const sources = Array.isArray(savedList.sources)
? savedList.sources.map((s) => mapAdminCloudtentaclesSourceConfig(s))
@@ -151,7 +155,7 @@ export function deleteAdminCloudtentaclesSource(sourceKey: string) {
if (!key) {
throw new Error("sourceKey is required for deletion");
}
clearCloudtentaclesSessionStateByKey(key);
deleteCloudtentaclesSessionStateByKey(key);
deleteCloudtentaclesSourceByKey(key);
}
@@ -190,3 +190,23 @@ test('buildAdminCloudtentaclesValidateResult assembles validate response payload
},
)
})
test('buildAdminCloudtentaclesSessionSummary preserves sourceKey', () => {
assert.deepEqual(
buildAdminCloudtentaclesSessionSummary(
{
token: 'abcdef1234567890',
permissions: [],
},
{},
'account2',
),
{
sourceKey: 'account2',
tokenMasked: 'abcdef****567890',
permissionCount: 0,
permissions: [],
persisted: false,
},
)
})
@@ -104,6 +104,7 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', ()
externalSkuName: '',
resolvedSkuName: '',
cloudSourceKey: 'default',
cloudSourceKeyFallbacks: [],
cloudSkuId: 0,
cloudSkuName: '',
vnKey: '',
@@ -77,6 +77,11 @@ export function mapAdminKuaishouCloudFulfillmentItem(item: JsonObject = {}) {
resolvedSkuName: String(item.resolvedSkuName || "").trim(),
cloudSourceKey:
String(item.cloudSourceKey || "default").trim() || "default",
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
? item.cloudSourceKeyFallbacks
.map((value) => String(value || "").trim())
.filter(Boolean)
: [],
cloudSkuId: Number(item.cloudSkuId || 0) || 0,
cloudSkuName: String(item.cloudSkuName || "").trim(),
vnKey: String(item.vnKey || "").trim(),
@@ -61,6 +61,13 @@ export function resolvePersistedCloudtentaclesContextWithFallback(
const session = getCloudtentaclesSessionStateByKey(sourceKey);
if (!source) continue;
if (source.enabled === false) {
lastError = createHttpError(
`cloudtentacles 账号 ${sourceKey} 已停用`,
{ statusCode: 409, errorCode: "kuaishou_cloud_source_disabled" }
);
continue;
}
const token = String(session?.token || "").trim();
if (!token) {
@@ -65,6 +65,23 @@ export function saveCloudtentaclesSessionStateByKey(sourceKey: unknown, rawValue
return states.sessions[key]
}
/**
* Delete a session by sourceKey.
*/
export function deleteCloudtentaclesSessionStateByKey(sourceKey: unknown) {
const key = String(sourceKey || '').trim()
if (!key) {
throw new Error('deleteCloudtentaclesSessionStateByKey: sourceKey is required')
}
const states = loadCloudtentaclesSessionStatesFromFile()
delete states.sessions[key]
fs.mkdirSync(path.dirname(CLOUDTENTACLES_SESSION_FILE_PATH), { recursive: true })
fs.writeFileSync(CLOUDTENTACLES_SESSION_FILE_PATH, `${JSON.stringify(states, null, 2)}\n`, 'utf8')
return states
}
/**
* Backward-compatible clear: clears the 'default' session only.
*/
@@ -86,6 +103,26 @@ export function clearCloudtentaclesSessionStateByKey(sourceKey: unknown) {
return cleared
}
/**
* Keep only sessions whose sourceKey still exists in source config.
*/
export function pruneCloudtentaclesSessionStates(sourceKeys: unknown[] = []) {
const allowedKeys = new Set(
Array.isArray(sourceKeys)
? sourceKeys.map((value) => String(value || '').trim()).filter(Boolean)
: []
)
const states = loadCloudtentaclesSessionStatesFromFile()
states.sessions = Object.fromEntries(
Object.entries(states.sessions || {}).filter(([key]) => allowedKeys.has(String(key || '').trim()))
)
fs.mkdirSync(path.dirname(CLOUDTENTACLES_SESSION_FILE_PATH), { recursive: true })
fs.writeFileSync(CLOUDTENTACLES_SESSION_FILE_PATH, `${JSON.stringify(states, null, 2)}\n`, 'utf8')
return states
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
@@ -133,6 +133,7 @@ function normalizeCloudtentaclesSourceItem(rawValue: unknown) {
return {
key: String(source.key || 'default').trim() || 'default',
label: String(source.label || '').trim(),
enabled: source.enabled !== false,
baseUrl: String(source.baseUrl || 'https://123.207.217.176').trim() || 'https://123.207.217.176',
username: String(source.username || '').trim(),
password: String(source.password || '').trim(),
@@ -1,6 +1,7 @@
export interface AdminCloudtentaclesSourceItem {
key: string
label: string
enabled: boolean
baseUrl: string
username: string
password: string
+2
View File
@@ -174,6 +174,8 @@ export interface AdminTaskDetail {
binding: {
prepareStatus: string
cloudSourceKey: string
cloudSourceKeyFallbacks: string[]
resolvedSourceKey: string
skuId: number
skuName: string
vnKey: string
@@ -81,8 +81,8 @@ export function useKuaishouCloudConfig() {
vnKey: '1',
autoBuyEnabled: true,
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
autoConsumeAfterDispatch: false,
autoReturnNumberAfterDispatch: true,
autoConsumeAfterDispatch: true,
kuaishouConsumeShopId: defaultShop?.shopId || '',
kuaishouConsumeShopName: defaultShop?.kshopName || '',
notes: '',
@@ -135,7 +135,9 @@ const {
sources: cloudtentaclesSources,
sessions: cloudtentaclesSessions,
selectedSourceKey: selectedCloudtentaclesSourceKey,
sourceOptions: cloudtentaclesSourceOptions,
currentSourceEnabled: currentCloudtentaclesSourceEnabled,
currentSource: currentCloudtentaclesSource,
currentSession: currentCloudtentaclesSession,
hydrateCloudtentaclesConfig,
switchSource: switchCloudtentaclesSource,
addSource: addCloudtentaclesSource,
@@ -426,7 +428,9 @@ onMounted(loadConfigs)
:sources="cloudtentaclesSources"
:sessions="cloudtentaclesSessions"
:selected-source-key="selectedCloudtentaclesSourceKey"
:source-options="cloudtentaclesSourceOptions"
:current-source-enabled="currentCloudtentaclesSourceEnabled"
:current-source="currentCloudtentaclesSource"
:current-session="currentCloudtentaclesSession"
:switch-source="switchCloudtentaclesSource"
:add-source="addCloudtentaclesSource"
:remove-source="removeCloudtentaclesSource"
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type {
AdminCloudtentaclesLoginTestResult,
AdminCloudtentaclesPersistedSession,
AdminCloudtentaclesSendSmsResult,
AdminCloudtentaclesSourceItem,
AdminCloudtentaclesValidateSessionResult,
@@ -23,11 +24,6 @@ type CloudtentaclesForm = {
deviceType: number
}
type SourceOption = {
key: string
label: string
}
type Props = {
cloudtentaclesForm: CloudtentaclesForm
cloudtentaclesSaving: boolean
@@ -39,8 +35,10 @@ type Props = {
cloudtentaclesLoginResult: AdminCloudtentaclesLoginTestResult | null
cloudtentaclesValidateResult: AdminCloudtentaclesValidateSessionResult | null
selectedSourceKey: string
sourceOptions: SourceOption[]
currentSourceEnabled: boolean
sources: AdminCloudtentaclesSourceItem[]
currentSource: AdminCloudtentaclesSourceItem | null
currentSession: AdminCloudtentaclesPersistedSession | null
handleCloudtentaclesSaveSource: () => void | Promise<void>
handleCloudtentaclesSendSmsCode: () => void | Promise<void>
handleCloudtentaclesTestLogin: () => void | Promise<void>
@@ -60,156 +58,262 @@ defineProps<Props>()
<div>
<span class="card-title">cloudtentacles 履约平台配置</span>
<p class="card-desc">
保存账号密码手机号和设备头再发送短信验证码最后执行登录测试或手动校验 token
维护账号清单再编辑当前账号的核心信息短信登录token 校验和调试参数已经拆开避免常用 CRUD 被大表单干扰
</p>
</div>
<div class="card-actions">
<el-button :loading="cloudtentaclesSaving" @click="handleCloudtentaclesSaveSource"
>保存履约配置</el-button
>
<el-button :loading="cloudtentaclesSendingSms" @click="handleCloudtentaclesSendSmsCode"
>发送短信验证码</el-button
>
<el-button
:loading="cloudtentaclesTesting"
type="primary"
@click="handleCloudtentaclesTestLogin"
>测试登录</el-button
>
<el-button
:loading="cloudtentaclesValidating"
@click="handleCloudtentaclesValidateSession"
>校验 token</el-button
>
</div>
</div>
</template>
<!-- 账号选择器 -->
<div class="source-selector">
<el-select
:model-value="selectedSourceKey"
placeholder="选择账号"
@change="switchSource"
class="source-select"
>
<el-option
v-for="opt in sourceOptions"
:key="opt.key"
:label="opt.label"
:value="opt.key"
/>
</el-select>
<el-button type="primary" plain size="small" @click="addSource">新增账号</el-button>
<el-button
v-if="selectedSourceKey !== 'default'"
type="danger"
plain
size="small"
@click="removeSource(selectedSourceKey)"
>删除账号</el-button
>
</div>
<div class="crud-layout">
<div class="account-column">
<div class="account-column-head">
<div>
<strong>账号列表</strong>
<p>先选账号再编辑右侧信息</p>
</div>
<el-button type="primary" plain size="small" @click="addSource">新增账号</el-button>
</div>
<el-form label-width="auto" label-position="top">
<el-form-item label="Base URL">
<el-input v-model="cloudtentaclesForm.baseUrl" placeholder="https://123.207.217.176" />
</el-form-item>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="账号">
<el-input
v-model="cloudtentaclesForm.username"
placeholder="cloudtentacles 登录账号"
<div class="account-list">
<button
v-for="source in sources"
:key="source.key"
type="button"
class="account-item"
:class="{ 'account-item--active': source.key === selectedSourceKey }"
@click="switchSource(source.key)"
>
<div class="account-item-head">
<strong>{{ source.label || source.key }}</strong>
<el-tag :type="source.enabled !== false ? 'success' : 'info'" size="small">
{{ source.enabled !== false ? '启用' : '停用' }}
</el-tag>
</div>
<div class="account-item-meta">
<span>{{ source.username || '未填写账号' }}</span>
<span>{{ source.phone || '未填写手机号' }}</span>
</div>
<div class="account-item-foot">
<span>{{ source.key }}</span>
<span>{{ currentSession && source.key === selectedSourceKey ? (currentSession.hasToken ? '已登录' : '未登录') : '-' }}</span>
</div>
</button>
</div>
</div>
<div class="editor-column">
<div class="editor-head">
<div>
<strong>{{ currentSource?.label || currentSource?.key || '未选择账号' }}</strong>
<p>
{{
currentSession?.hasToken
? `最近登录:${formatAdminDateTime(currentSession.loggedInAt)}`
: '当前还没有可用 token'
}}
</p>
</div>
<div class="editor-head-actions">
<el-tag :type="currentSourceEnabled ? 'success' : 'info'" effect="plain">
{{ currentSourceEnabled ? '已启用' : '已停用' }}
</el-tag>
<el-switch
:model-value="currentSourceEnabled"
inline-prompt
active-text="启用"
inactive-text="停用"
@change="
(value) => {
if (currentSource) currentSource.enabled = Boolean(value)
}
"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="密码">
<el-input
v-model="cloudtentaclesForm.password"
type="password"
show-password
placeholder="cloudtentacles 登录密码"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="手机号">
<el-input v-model="cloudtentaclesForm.phone" placeholder="接收短信验证码的手机号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="短信验证码">
<el-input v-model="cloudtentaclesForm.code" placeholder="收到验证码后填写" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="VN Key">
<el-input v-model="cloudtentaclesForm.vnKey" placeholder="例如 1" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="VN ID">
<el-input-number
v-model="cloudtentaclesForm.vnId"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="SKU ID">
<el-input-number
v-model="cloudtentaclesForm.skuId"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="购买数量">
<el-input-number
v-model="cloudtentaclesForm.skuCount"
:min="1"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="deviceId">
<el-input v-model="cloudtentaclesForm.deviceId" placeholder="-" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="deviceType">
<el-input-number
v-model="cloudtentaclesForm.deviceType"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="token(可手动粘贴)">
<el-input
v-model="cloudtentaclesForm.token"
placeholder="登录成功后可自动回填,也可手动粘贴"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-button
v-if="selectedSourceKey !== 'default'"
type="danger"
plain
size="small"
@click="removeSource(selectedSourceKey)"
>
删除账号
</el-button>
</div>
</div>
<el-form label-width="auto" label-position="top" class="editor-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="账号名称">
<el-input
:model-value="currentSource?.label || ''"
placeholder="例如:主号 / 备用号 / 夜班号"
@update:model-value="
(value) => {
if (currentSource) currentSource.label = String(value)
}
"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="账号 Key">
<el-input :model-value="currentSource?.key || ''" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="登录账号">
<el-input
v-model="cloudtentaclesForm.username"
placeholder="cloudtentacles 登录账号"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号">
<el-input v-model="cloudtentaclesForm.phone" placeholder="接收短信验证码的手机号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="登录密码">
<el-input
v-model="cloudtentaclesForm.password"
type="password"
show-password
placeholder="cloudtentacles 登录密码"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Base URL">
<el-input
v-model="cloudtentaclesForm.baseUrl"
placeholder="https://123.207.217.176"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-card shadow="never" class="session-card">
<div class="session-card-head">
<div>
<strong>登录与会话</strong>
<p>短信验证码登录测试token 校验都放在这里和账号 CRUD 分开</p>
</div>
<div class="session-card-actions">
<el-button
:loading="cloudtentaclesSendingSms"
@click="handleCloudtentaclesSendSmsCode"
>
发送短信验证码
</el-button>
<el-button
:loading="cloudtentaclesTesting"
type="primary"
@click="handleCloudtentaclesTestLogin"
>
测试登录
</el-button>
<el-button
:loading="cloudtentaclesValidating"
@click="handleCloudtentaclesValidateSession"
>
校验 token
</el-button>
</div>
</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="短信验证码">
<el-input v-model="cloudtentaclesForm.code" placeholder="收到验证码后填写" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手动 token">
<el-input
v-model="cloudtentaclesForm.token"
placeholder="仅在需要手动校验时填写"
/>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-collapse class="advanced-collapse">
<el-collapse-item name="advanced" title="高级参数与调试字段">
<el-form label-width="auto" label-position="top">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="deviceId">
<el-input v-model="cloudtentaclesForm.deviceId" placeholder="默认 -" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="deviceType">
<el-input-number
v-model="cloudtentaclesForm.deviceType"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="VN Key">
<el-input v-model="cloudtentaclesForm.vnKey" placeholder="例如 1" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="VN ID">
<el-input-number
v-model="cloudtentaclesForm.vnId"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="SKU ID">
<el-input-number
v-model="cloudtentaclesForm.skuId"
:min="0"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="购买数量">
<el-input-number
v-model="cloudtentaclesForm.skuCount"
:min="1"
controls-position="right"
class="w-full"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-collapse-item>
</el-collapse>
</div>
</div>
<el-alert
v-if="cloudtentaclesResultError"
@@ -260,15 +364,95 @@ defineProps<Props>()
flex-wrap: wrap;
}
.source-selector {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-4);
.crud-layout {
display: grid;
grid-template-columns: minmax(240px, 280px) minmax(0, 1fr);
gap: var(--space-4);
}
.source-select {
width: 180px;
.account-column,
.editor-column {
display: grid;
gap: var(--space-3);
}
.account-column-head,
.editor-head,
.session-card-head {
display: flex;
justify-content: space-between;
gap: var(--space-2);
align-items: flex-start;
}
.account-column-head p,
.editor-head p,
.session-card-head p {
margin: 6px 0 0;
color: var(--text-secondary);
font-size: var(--text-sm);
}
.account-list {
display: grid;
gap: var(--space-2);
}
.account-item {
width: 100%;
text-align: left;
border: 1px solid var(--el-border-color);
border-radius: var(--radius-lg);
background: var(--el-bg-color);
padding: var(--space-3);
display: grid;
gap: 10px;
cursor: pointer;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.account-item:hover {
border-color: var(--el-color-primary-light-5);
transform: translateY(-1px);
}
.account-item--active {
border-color: var(--el-color-primary);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--el-color-primary) 35%, transparent);
}
.account-item-head,
.account-item-meta,
.account-item-foot,
.editor-head-actions,
.session-card-actions {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
align-items: center;
}
.account-item-head {
justify-content: space-between;
}
.account-item-meta,
.account-item-foot {
color: var(--text-secondary);
font-size: var(--text-sm);
}
.account-item-foot {
font-size: var(--text-xs);
}
.editor-form,
.session-card,
.advanced-collapse {
margin-top: 0;
}
.result-grid {
@@ -276,4 +460,10 @@ defineProps<Props>()
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-3);
}
@media (max-width: 960px) {
.crud-layout {
grid-template-columns: 1fr;
}
}
</style>
@@ -16,6 +16,13 @@ type Props = {
}
defineProps<Props>()
function resolveOrphanSessionEntries(
sources: AdminCloudtentaclesSourceItem[],
sessions: AdminCloudtentaclesSessionsMap,
) {
return Object.entries(sessions).filter(([key]) => !sources.some((source) => source.key === key))
}
</script>
<template>
@@ -47,10 +54,31 @@ defineProps<Props>()
: '未保存'
}}
</el-descriptions-item>
<el-descriptions-item v-if="Object.keys(sessions).length > 0" label="各账号会话状态">
<el-descriptions-item
v-if="sources.length > 0 || Object.keys(sessions).length > 0"
label="各账号会话状态"
>
<div class="session-list">
<div v-for="(session, key) in sessions" :key="key" class="session-item">
<div v-for="source in sources" :key="source.key" class="session-item">
<el-tag :type="source.enabled !== false ? 'success' : 'info'" size="small">
{{ source.key }}
</el-tag>
<span>{{ source.label || source.key }}</span>
<span>
{{
sessions[source.key]?.hasToken
? sessions[source.key]?.tokenMasked
: '未登录'
}}
</span>
</div>
<div
v-for="[key, session] in resolveOrphanSessionEntries(sources, sessions)"
:key="`orphan-${key}`"
class="session-item"
>
<el-tag :type="session.hasToken ? 'success' : 'info'" size="small">{{ key }}</el-tag>
<span>孤立会话</span>
<span>{{ session.hasToken ? session.tokenMasked : '未登录' }}</span>
</div>
</div>
@@ -27,11 +27,6 @@ type CloudtentaclesForm = {
deviceType: number
}
type SourceOption = {
key: string
label: string
}
type CloudtentaclesStats = {
hasCredential: boolean
smsSent: boolean
@@ -58,7 +53,9 @@ type Props = {
sources: AdminCloudtentaclesSourceItem[]
sessions: AdminCloudtentaclesSessionsMap
selectedSourceKey: string
sourceOptions: SourceOption[]
currentSourceEnabled: boolean
currentSource: AdminCloudtentaclesSourceItem | null
currentSession: AdminCloudtentaclesPersistedSession | null
handleCloudtentaclesSaveSource: () => void | Promise<void>
handleCloudtentaclesSendSmsCode: () => void | Promise<void>
handleCloudtentaclesTestLogin: () => void | Promise<void>
@@ -107,8 +104,10 @@ defineProps<Props>()
:cloudtentacles-login-result="cloudtentaclesLoginResult"
:cloudtentacles-validate-result="cloudtentaclesValidateResult"
:selected-source-key="selectedSourceKey"
:source-options="sourceOptions"
:current-source-enabled="currentSourceEnabled"
:sources="sources"
:current-source="currentSource"
:current-session="currentSession"
:handle-cloudtentacles-save-source="handleCloudtentaclesSaveSource"
:handle-cloudtentacles-send-sms-code="handleCloudtentaclesSendSmsCode"
:handle-cloudtentacles-test-login="handleCloudtentaclesTestLogin"
@@ -67,6 +67,15 @@ export function useAdminCloudtentaclesPlatform() {
)
const currentSession = computed(() => sessions.value[selectedSourceKey.value] || null)
const currentSourceEnabled = computed({
get: () => currentSource.value?.enabled !== false,
set: (value: boolean) => {
const source = sources.value.find((item) => item.key === selectedSourceKey.value)
if (source) {
source.enabled = value
}
},
})
const cloudtentaclesStats = computed(() => ({
hasCredential: Boolean(
@@ -90,6 +99,7 @@ export function useAdminCloudtentaclesPlatform() {
sources.value.push({
key: 'default',
label: '默认账号',
enabled: true,
baseUrl: 'https://123.207.217.176',
username: '',
password: '',
@@ -165,6 +175,7 @@ export function useAdminCloudtentaclesPlatform() {
const newSource: AdminCloudtentaclesSourceItem = {
key: newKey,
label: trimmed,
enabled: true,
baseUrl: 'https://123.207.217.176',
username: '',
password: '',
@@ -220,6 +231,7 @@ export function useAdminCloudtentaclesPlatform() {
sources: sources.value.map((s) => ({
key: s.key,
label: s.label,
enabled: s.enabled !== false,
baseUrl: s.baseUrl,
username: s.username,
password: s.password,
@@ -421,6 +433,7 @@ export function useAdminCloudtentaclesPlatform() {
sourceOptions,
currentSource,
currentSession,
currentSourceEnabled,
hydrateCloudtentaclesConfig,
switchSource,
addSource,
@@ -221,7 +221,19 @@ const emit = defineEmits<{
<el-collapse-item name="cloud" title="Cloud 资源与发货">
<div class="flow-meta-list flow-meta-list--two-column">
<div>
<span>cloudSourceKey</span><strong>{{ flow.binding.cloudSourceKey || '-' }}</strong>
<span>规则主账号</span><strong>{{ flow.binding.cloudSourceKey || '-' }}</strong>
</div>
<div>
<span>实际使用账号</span>
<strong>{{ flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || '-' }}</strong>
</div>
<div>
<span>备选账号</span>
<strong>{{
flow.binding.cloudSourceKeyFallbacks?.length
? flow.binding.cloudSourceKeyFallbacks.join(' / ')
: '-'
}}</strong>
</div>
<div>
<span>cloud SKU</span>