优化履约账号优先级配置

This commit is contained in:
yml2213
2026-05-26 12:44:01 +08:00
parent 2ff320c6ac
commit 6e85002724
26 changed files with 161 additions and 181 deletions
@@ -14,8 +14,7 @@
"externalItemId": "", "externalItemId": "",
"externalSkuName": "示例外部商品", "externalSkuName": "示例外部商品",
"resolvedSkuName": "示例快手云商品", "resolvedSkuName": "示例快手云商品",
"cloudSourceKey": "default", "cloudSourceKeys": ["account_example"],
"cloudSourceKeyFallbacks": [],
"cloudSkuId": 1, "cloudSkuId": 1,
"cloudSkuName": "示例云商品", "cloudSkuName": "示例云商品",
"autoBuyEnabled": true, "autoBuyEnabled": true,
@@ -84,9 +84,8 @@ export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord |
}, },
binding: { binding: {
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending', prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
cloudSourceKey: String(binding.cloudSourceKey || '').trim(), cloudSourceKeys: Array.isArray(binding.cloudSourceKeys)
cloudSourceKeyFallbacks: Array.isArray(binding.cloudSourceKeyFallbacks) ? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean)
? binding.cloudSourceKeyFallbacks.map((value: unknown) => String(value || '').trim()).filter(Boolean)
: [], : [],
resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(), resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(),
skuId: Number(binding.skuId || 0) || 0, skuId: Number(binding.skuId || 0) || 0,
@@ -79,7 +79,7 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', ()
id: ' item-1 ', id: ' item-1 ',
provider: '', provider: '',
platform: '', platform: '',
cloudSourceKey: '', cloudSourceKeys: [' account_a ', 'account_b'],
cloudSkuId: '0', cloudSkuId: '0',
autoConsumeAfterDispatch: true, autoConsumeAfterDispatch: true,
kuaishouConsumeShopId: ' ks1 ', kuaishouConsumeShopId: ' ks1 ',
@@ -103,8 +103,7 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', ()
externalItemId: '', externalItemId: '',
externalSkuName: '', externalSkuName: '',
resolvedSkuName: '', resolvedSkuName: '',
cloudSourceKey: 'default', cloudSourceKeys: ['account_a', 'account_b'],
cloudSourceKeyFallbacks: [],
cloudSkuId: 0, cloudSkuId: 0,
cloudSkuName: '', cloudSkuName: '',
vnKey: '', vnKey: '',
@@ -75,10 +75,8 @@ export function mapAdminKuaishouCloudFulfillmentItem(item: JsonObject = {}) {
externalItemId: String(item.externalItemId || "").trim(), externalItemId: String(item.externalItemId || "").trim(),
externalSkuName: String(item.externalSkuName || "").trim(), externalSkuName: String(item.externalSkuName || "").trim(),
resolvedSkuName: String(item.resolvedSkuName || "").trim(), resolvedSkuName: String(item.resolvedSkuName || "").trim(),
cloudSourceKey: cloudSourceKeys: Array.isArray(item.cloudSourceKeys)
String(item.cloudSourceKey || "default").trim() || "default", ? item.cloudSourceKeys
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
? item.cloudSourceKeyFallbacks
.map((value) => String(value || "").trim()) .map((value) => String(value || "").trim())
.filter(Boolean) .filter(Boolean)
: [], : [],
@@ -75,7 +75,7 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(
const taskContext = parseTaskContext(task) const taskContext = parseTaskContext(task)
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext(flow.binding.cloudSourceKeys)
const [knapsack, skuList] = await Promise.all([ const [knapsack, skuList] = await Promise.all([
getCloudtentaclesKnapsack(cloudContext), getCloudtentaclesKnapsack(cloudContext),
listCloudtentaclesSku(cloudContext), listCloudtentaclesSku(cloudContext),
@@ -164,6 +164,7 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(
...flowWithResolvedBinding, ...flowWithResolvedBinding,
binding: { binding: {
...flowWithResolvedBinding.binding, ...flowWithResolvedBinding.binding,
resolvedSourceKey: cloudContext.resolvedSourceKey,
vnKey: preparedBinding.vnKey, vnKey: preparedBinding.vnKey,
prepareStatus: 'ready', prepareStatus: 'ready',
vnId, vnId,
@@ -250,7 +251,10 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment(
const taskContext = parseTaskContext(task) const taskContext = parseTaskContext(task)
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext([
flow.binding.resolvedSourceKey,
...flow.binding.cloudSourceKeys,
])
if (!flow.binding.skuId || !flow.binding.vnId || !flow.binding.vnPhone) { if (!flow.binding.skuId || !flow.binding.vnId || !flow.binding.vnPhone) {
throw createHttpError('当前任务还没有准备好绑定资源,请先准备绑定资源', { throw createHttpError('当前任务还没有准备好绑定资源,请先准备绑定资源', {
@@ -364,7 +368,10 @@ export async function refreshAdminTaskKuaishouCloudRoleInfo(
}) })
} }
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext([
flow.binding.resolvedSourceKey,
...flow.binding.cloudSourceKeys,
])
const bindInfoResult = await getCloudtentaclesBindInfo({ const bindInfoResult = await getCloudtentaclesBindInfo({
...cloudContext, ...cloudContext,
key: flow.binding.vnKey, key: flow.binding.vnKey,
@@ -441,7 +448,10 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(
const taskContext = parseTaskContext(task) const taskContext = parseTaskContext(task)
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext([
flow.binding.resolvedSourceKey,
...flow.binding.cloudSourceKeys,
])
if (!flow.binding.vnId || !flow.binding.vnKey) { if (!flow.binding.vnId || !flow.binding.vnKey) {
throw createHttpError('当前任务缺少可退还的虚拟号信息', { throw createHttpError('当前任务缺少可退还的虚拟号信息', {
@@ -1,7 +1,7 @@
import { createHttpError } from '../../../utils/http.js' import { createHttpError } from '../../../utils/http.js'
import type { HttpErrorLike } from '../../../utils/http.js' import type { HttpErrorLike } from '../../../utils/http.js'
import { import {
resolvePersistedCloudtentaclesContext as resolveFulfillmentPersistedCloudtentaclesContext, resolvePersistedCloudtentaclesContextBySourceKeys,
} from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js' } from '../../fulfillment/kuaishou-cloud/cloudtentacles-context.js'
export { export {
@@ -23,6 +23,7 @@ export type CloudtentaclesContext = {
token: string token: string
deviceId: string deviceId: string
deviceType: number deviceType: number
resolvedSourceKey: string
} }
export type CloudSkuLikeItem = { export type CloudSkuLikeItem = {
@@ -62,9 +63,9 @@ export type PreparedKuaishouCloudBindResource = {
bindUrl: string bindUrl: string
} }
export function resolvePersistedCloudtentaclesContext(): CloudtentaclesContext { export function resolvePersistedCloudtentaclesContext(sourceKeys: unknown[]): CloudtentaclesContext {
try { try {
return resolveFulfillmentPersistedCloudtentaclesContext() return resolvePersistedCloudtentaclesContextBySourceKeys(sourceKeys)
} catch (error) { } catch (error) {
if (isMissingCloudtentaclesTokenError(error)) { if (isMissingCloudtentaclesTokenError(error)) {
throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', { throw createHttpError('当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验', {
@@ -172,7 +172,10 @@ export function mapClaimKuaishouCloudFulfillment(task: TaskRow, order: OrderRow)
}, },
binding: { binding: {
prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending', prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending',
cloudSourceKey: String(binding.cloudSourceKey || '').trim(), cloudSourceKeys: Array.isArray(binding.cloudSourceKeys)
? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean)
: [],
resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(),
skuId: Number(binding.skuId || 0) || 0, skuId: Number(binding.skuId || 0) || 0,
skuName: String(binding.skuName || '').trim(), skuName: String(binding.skuName || '').trim(),
vnKey: String(binding.vnKey || '').trim(), vnKey: String(binding.vnKey || '').trim(),
@@ -1,56 +1,18 @@
import { createHttpError } from "../../../utils/http.js"; import { createHttpError } from "../../../utils/http.js";
import { import { getCloudtentaclesSourceByKey } from "../../platforms/cloudtentacles/source-config-service.js";
getCloudtentaclesSourceConfig, import { getCloudtentaclesSessionStateByKey } from "../../platforms/cloudtentacles/session-state-service.js";
getCloudtentaclesSourceByKey,
} from "../../platforms/cloudtentacles/source-config-service.js";
import {
getCloudtentaclesSessionState,
getCloudtentaclesSessionStateByKey,
} from "../../platforms/cloudtentacles/session-state-service.js";
import { normalizeStringArray } from "./domain.js"; import { normalizeStringArray } from "./domain.js";
export function resolvePersistedCloudtentaclesContext(sourceKey = "default") {
const source =
getCloudtentaclesSourceByKey(sourceKey) || getCloudtentaclesSourceConfig();
const session =
getCloudtentaclesSessionStateByKey(sourceKey) ||
getCloudtentaclesSessionState();
const token = String(session.token || "").trim();
if (!token) {
throw createHttpError(
"当前 cloudtentacles 没有可用 token,请先到平台配置完成登录校验",
{
statusCode: 409,
errorCode: "kuaishou_cloud_missing_cloud_token",
}
);
}
return {
baseUrl:
String(session.baseUrl || source.baseUrl || "").trim() ||
"https://123.207.217.176",
token,
deviceId: String(session.deviceId || source.deviceId || "-").trim() || "-",
deviceType: Number(session.deviceType ?? source.deviceType ?? 0),
};
}
/** /**
* 按优先级依次尝试 cloudSourceKey 和 fallbacks 列表中的账号, * 按优先级依次尝试 cloudSourceKeys 列表中的账号,
* 找到第一个有可用 token 的账号返回其 context。 * 找到第一个有可用 token 的账号返回其 context。
* 将实际使用的 resolvedSourceKey 也返回,确保后续操作(退号、发货等) * 将实际使用的 resolvedSourceKey 也返回,确保后续操作(退号、发货等)
* 使用同一个 sourceKey,防止跨账号操作导致数据不一致。 * 使用同一个 sourceKey,防止跨账号操作导致数据不一致。
*/ */
export function resolvePersistedCloudtentaclesContextWithFallback( export function resolvePersistedCloudtentaclesContextBySourceKeys(
primarySourceKey = "default", sourceKeys: unknown[] = []
fallbacks: unknown[] = []
) { ) {
const candidates = [ const candidates = normalizeStringArray(sourceKeys);
String(primarySourceKey || "default").trim() || "default",
...normalizeStringArray(fallbacks),
];
const uniqueCandidates = [...new Set(candidates)]; const uniqueCandidates = [...new Set(candidates)];
@@ -101,7 +63,7 @@ export function resolvePersistedCloudtentaclesContextWithFallback(
throw ( throw (
lastError || lastError ||
createHttpError( createHttpError(
"所有 cloudtentacles 备选账号均不可用,请先到平台配置完成登录校验", "所有 cloudtentacles 账号均不可用,请先到平台配置完成登录校验",
{ statusCode: 409, errorCode: "kuaishou_cloud_all_source_keys_exhausted" } { statusCode: 409, errorCode: "kuaishou_cloud_all_source_keys_exhausted" }
) )
); );
@@ -62,11 +62,7 @@ export function normalizeKuaishouCloudFlow(value: unknown) {
binding: { binding: {
prepareStatus: prepareStatus:
String(binding.prepareStatus || "pending").trim() || "pending", String(binding.prepareStatus || "pending").trim() || "pending",
cloudSourceKey: cloudSourceKeys: normalizeStringArray(binding.cloudSourceKeys),
String(binding.cloudSourceKey || "default").trim() || "default",
cloudSourceKeyFallbacks: normalizeStringArray(
binding.cloudSourceKeyFallbacks
),
resolvedSourceKey: String(binding.resolvedSourceKey || "").trim(), resolvedSourceKey: String(binding.resolvedSourceKey || "").trim(),
skuId: Number(binding.skuId || 0) || 0, skuId: Number(binding.skuId || 0) || 0,
skuName: String(binding.skuName || "").trim(), skuName: String(binding.skuName || "").trim(),
@@ -35,7 +35,7 @@ import {
resolveKuaishouCloudVnKeyCandidates, resolveKuaishouCloudVnKeyCandidates,
} from "./binding-resources.js"; } from "./binding-resources.js";
import { import {
resolvePersistedCloudtentaclesContextWithFallback, resolvePersistedCloudtentaclesContextBySourceKeys,
} from "./cloudtentacles-context.js"; } from "./cloudtentacles-context.js";
import { import {
getTaskClaimExpiresAt, getTaskClaimExpiresAt,
@@ -55,8 +55,7 @@ export {
resolveKuaishouCloudBindUrlExpiresAt, resolveKuaishouCloudBindUrlExpiresAt,
} from "./domain.js"; } from "./domain.js";
export { export {
resolvePersistedCloudtentaclesContext, resolvePersistedCloudtentaclesContextBySourceKeys,
resolvePersistedCloudtentaclesContextWithFallback,
} from "./cloudtentacles-context.js"; } from "./cloudtentacles-context.js";
export async function ensureTaskClaimLink(task: TaskRow) { export async function ensureTaskClaimLink(task: TaskRow) {
@@ -135,9 +134,8 @@ export async function prepareKuaishouCloudFulfillmentTask(
}; };
} }
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback( const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys(
flow.binding.cloudSourceKey || "default", flow.binding.cloudSourceKeys
flow.binding.cloudSourceKeyFallbacks || []
); );
const [knapsack, skuList] = await Promise.all([ const [knapsack, skuList] = await Promise.all([
getCloudtentaclesKnapsack(cloudContext), getCloudtentaclesKnapsack(cloudContext),
@@ -335,12 +333,10 @@ export async function refreshKuaishouCloudTaskBindUrl(
}); });
} }
const effectiveSourceKey = const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys([
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default"; flow.binding.resolvedSourceKey,
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback( ...flow.binding.cloudSourceKeys,
effectiveSourceKey, ]);
flow.binding.cloudSourceKeyFallbacks || []
);
const oldVnKey = flow.binding.vnKey; const oldVnKey = flow.binding.vnKey;
const oldVnId = flow.binding.vnId; const oldVnId = flow.binding.vnId;
const oldVnPhone = flow.binding.vnPhone; const oldVnPhone = flow.binding.vnPhone;
@@ -707,12 +703,10 @@ export async function refreshKuaishouCloudTaskRoleInfo(
} }
} }
const effectiveSourceKey = const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys([
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default"; flow.binding.resolvedSourceKey,
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback( ...flow.binding.cloudSourceKeys,
effectiveSourceKey, ]);
flow.binding.cloudSourceKeyFallbacks || []
);
const bindInfoResult = await getCloudtentaclesBindInfo({ const bindInfoResult = await getCloudtentaclesBindInfo({
...cloudContext, ...cloudContext,
key: flow.binding.vnKey, key: flow.binding.vnKey,
@@ -18,7 +18,7 @@ import {
normalizeKuaishouCloudFlow, normalizeKuaishouCloudFlow,
type JsonObject, type JsonObject,
} from "./domain.js"; } from "./domain.js";
import { resolvePersistedCloudtentaclesContextWithFallback } from "./cloudtentacles-context.js"; import { resolvePersistedCloudtentaclesContextBySourceKeys } from "./cloudtentacles-context.js";
import { normalizeActor, parseTaskContext } from "./task-context.js"; import { normalizeActor, parseTaskContext } from "./task-context.js";
import type { TaskRow } from "../../../types/repository/rows.js"; import type { TaskRow } from "../../../types/repository/rows.js";
@@ -37,12 +37,10 @@ export async function dispatchKuaishouCloudFulfillmentTask(
const now = nowIso(); const now = nowIso();
const taskContext = parseTaskContext(task); const taskContext = parseTaskContext(task);
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment); const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
const effectiveSourceKey = const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys([
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default"; flow.binding.resolvedSourceKey,
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback( ...flow.binding.cloudSourceKeys,
effectiveSourceKey, ]);
flow.binding.cloudSourceKeyFallbacks || []
);
if (!flow.binding.skuId || !flow.binding.vnId || !flow.binding.vnPhone) { if (!flow.binding.skuId || !flow.binding.vnId || !flow.binding.vnPhone) {
throw createHttpError( throw createHttpError(
@@ -178,12 +176,10 @@ export async function returnKuaishouCloudFulfillmentTask(
const now = nowIso(); const now = nowIso();
const taskContext = parseTaskContext(task); const taskContext = parseTaskContext(task);
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment); const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment);
const effectiveSourceKey = const cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys([
flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || "default"; flow.binding.resolvedSourceKey,
const cloudContext = resolvePersistedCloudtentaclesContextWithFallback( ...flow.binding.cloudSourceKeys,
effectiveSourceKey, ]);
flow.binding.cloudSourceKeyFallbacks || []
);
if (!flow.binding.vnId || !flow.binding.vnKey) { if (!flow.binding.vnId || !flow.binding.vnKey) {
throw createHttpError("当前任务缺少可退还的虚拟号信息", { throw createHttpError("当前任务缺少可退还的虚拟号信息", {
@@ -185,7 +185,7 @@ export async function syncDeliveryTasksForOrderWithDeps(
}, },
binding: { binding: {
prepareStatus: 'pending', prepareStatus: 'pending',
cloudSourceKey: String(cloudtentaclesConfig.cloudSourceKey || 'default').trim() || 'default', cloudSourceKeys: normalizeStringArray(cloudtentaclesConfig.cloudSourceKeys),
skuId: Number(cloudtentaclesConfig.skuId || 0) || 0, skuId: Number(cloudtentaclesConfig.skuId || 0) || 0,
skuName: String(cloudtentaclesConfig.skuName || '').trim(), skuName: String(cloudtentaclesConfig.skuName || '').trim(),
vnKey: '1', vnKey: '1',
@@ -384,6 +384,16 @@ function parseJsonObject(value: unknown): JsonObject {
} }
} }
function normalizeStringArray(value: unknown): string[] {
if (!Array.isArray(value)) {
return []
}
return Array.from(
new Set(value.map((item) => String(item || '').trim()).filter(Boolean)),
)
}
function isTaskRow(task: TaskRow | DeliveryTaskRow | null | undefined): task is TaskRow { function isTaskRow(task: TaskRow | DeliveryTaskRow | null | undefined): task is TaskRow {
return Boolean(task && Number(task.id || 0) > 0) return Boolean(task && Number(task.id || 0) > 0)
} }
@@ -17,6 +17,7 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
externalSkuCode: '952', externalSkuCode: '952',
externalItemId: '952', externalItemId: '952',
externalSkuName: '测试2', externalSkuName: '测试2',
cloudSourceKeys: ['account_a', 'account_b'],
cloudSkuId: 28, cloudSkuId: 28,
}, },
], ],
@@ -35,8 +36,7 @@ test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => {
flowType: 'kuaishou_cloud_fulfillment', flowType: 'kuaishou_cloud_fulfillment',
configId: '', configId: '',
cloudtentacles: { cloudtentacles: {
cloudSourceKey: 'default', cloudSourceKeys: ['account_a', 'account_b'],
cloudSourceKeyFallbacks: [],
skuId: 28, skuId: 28,
skuName: '', skuName: '',
vnKey: '1', vnKey: '1',
@@ -46,11 +46,7 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config: JsonObject =
flowType: "kuaishou_cloud_fulfillment", flowType: "kuaishou_cloud_fulfillment",
configId: String(item.id || "").trim(), configId: String(item.id || "").trim(),
cloudtentacles: { cloudtentacles: {
cloudSourceKey: cloudSourceKeys: normalizeStringArray(item.cloudSourceKeys),
String(item.cloudSourceKey || "default").trim() || "default",
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
? item.cloudSourceKeyFallbacks
: [],
skuId: normalizePositiveInteger(item.cloudSkuId), skuId: normalizePositiveInteger(item.cloudSkuId),
skuName: String(item.cloudSkuName || "").trim(), skuName: String(item.cloudSkuName || "").trim(),
vnKey: "1", vnKey: "1",
@@ -113,6 +109,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
const internalSkuCode = String(rawValue.internalSkuCode || "").trim(); const internalSkuCode = String(rawValue.internalSkuCode || "").trim();
const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId); const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId);
const cloudSourceKeys = normalizeStringArray(rawValue.cloudSourceKeys);
const externalSkuCode = String(rawValue.externalSkuCode || "").trim(); const externalSkuCode = String(rawValue.externalSkuCode || "").trim();
const externalItemId = String(rawValue.externalItemId || "").trim(); const externalItemId = String(rawValue.externalItemId || "").trim();
const externalSkuName = String(rawValue.externalSkuName || "").trim(); const externalSkuName = String(rawValue.externalSkuName || "").trim();
@@ -135,6 +132,10 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
return null; return null;
} }
if (cloudSourceKeys.length === 0) {
return null;
}
if (!externalSkuCode && !externalItemId && !externalSkuName) { if (!externalSkuCode && !externalItemId && !externalSkuName) {
return null; return null;
} }
@@ -155,11 +156,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue: unknown) {
resolvedSkuName: String( resolvedSkuName: String(
rawValue.resolvedSkuName || rawValue.internalSkuName || "" rawValue.resolvedSkuName || rawValue.internalSkuName || ""
).trim(), ).trim(),
cloudSourceKey: cloudSourceKeys,
String(rawValue.cloudSourceKey || "default").trim() || "default",
cloudSourceKeyFallbacks: normalizeStringArray(
rawValue.cloudSourceKeyFallbacks
),
cloudSkuId, cloudSkuId,
cloudSkuName: String(rawValue.cloudSkuName || "").trim(), cloudSkuName: String(rawValue.cloudSkuName || "").trim(),
vnKey: "1", vnKey: "1",
@@ -204,12 +201,5 @@ function normalizeStringArray(value: unknown) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map((v: unknown) => String(v || "").trim()).filter(Boolean); return value.map((v: unknown) => String(v || "").trim()).filter(Boolean);
} }
// 兼容旧格式:字符串用逗号分隔
if (typeof value === "string") {
return value
.split(",")
.map((v: string) => v.trim())
.filter(Boolean);
}
return []; return [];
} }
+1 -2
View File
@@ -11,8 +11,7 @@ export type AdminKuaishouCloudFulfillmentItemInput = {
externalItemId?: string externalItemId?: string
externalSkuName?: string externalSkuName?: string
resolvedSkuName?: string resolvedSkuName?: string
cloudSourceKey?: string cloudSourceKeys?: string[]
cloudSourceKeyFallbacks?: string[]
cloudSkuId?: number | string cloudSkuId?: number | string
cloudSkuName?: string cloudSkuName?: string
vnKey?: string vnKey?: string
@@ -11,7 +11,7 @@ export interface AdminKuaishouCloudFulfillmentItem {
externalItemId: string externalItemId: string
externalSkuName: string externalSkuName: string
resolvedSkuName: string resolvedSkuName: string
cloudSourceKey: string cloudSourceKeys: string[]
cloudSkuId: number cloudSkuId: number
cloudSkuName: string cloudSkuName: string
vnKey: string vnKey: string
@@ -22,7 +22,6 @@ export interface AdminKuaishouCloudFulfillmentItem {
kuaishouConsumeShopId: string kuaishouConsumeShopId: string
kuaishouConsumeShopName: string kuaishouConsumeShopName: string
notes: string notes: string
cloudSourceKeyFallbacks?: string[]
} }
export interface AdminKuaishouCloudFulfillmentConfig { export interface AdminKuaishouCloudFulfillmentConfig {
+1 -2
View File
@@ -126,8 +126,7 @@ export interface AdminTaskDetail {
} }
binding: { binding: {
prepareStatus: string prepareStatus: string
cloudSourceKey: string cloudSourceKeys: string[]
cloudSourceKeyFallbacks: string[]
resolvedSourceKey: string resolvedSourceKey: string
skuId: number skuId: number
skuName: string skuName: string
+2 -1
View File
@@ -74,7 +74,8 @@ export interface ClaimKuaishouCloudFlowInfo {
} }
binding: { binding: {
prepareStatus: string prepareStatus: string
cloudSourceKey: string cloudSourceKeys: string[]
resolvedSourceKey: string
skuId: number skuId: number
skuName: string skuName: string
vnKey: string vnKey: string
@@ -58,6 +58,7 @@ onMounted(config.loadConfigs)
:get-card-summary="config.getCardSummary" :get-card-summary="config.getCardSummary"
:get-rule-state="config.getRuleState" :get-rule-state="config.getRuleState"
:get-external-match-summary="config.getExternalMatchSummary" :get-external-match-summary="config.getExternalMatchSummary"
:get-cloud-source-summary="config.getCloudSourceSummary"
:get-cloud-sku-summary="config.getCloudSkuSummary" :get-cloud-sku-summary="config.getCloudSkuSummary"
:get-consume-shop-summary="config.getConsumeShopSummary" :get-consume-shop-summary="config.getConsumeShopSummary"
:get-cloud-sku-options="cloudSku.getCloudSkuOptions" :get-cloud-sku-options="cloudSku.getCloudSkuOptions"
@@ -78,6 +79,7 @@ onMounted(config.loadConfigs)
(item, visible) => cloudSku.handleCloudSkuDropdownVisible(item, visible) (item, visible) => cloudSku.handleCloudSkuDropdownVisible(item, visible)
" "
@handle-kuaishou-consume-shop-selected="config.handleKuaishouConsumeShopSelected($event)" @handle-kuaishou-consume-shop-selected="config.handleKuaishouConsumeShopSelected($event)"
@handle-cloud-source-keys-changed="config.handleCloudSourceKeysChanged($event)"
@remove-item="config.removeItem($event)" @remove-item="config.removeItem($event)"
/> />
</template> </template>
@@ -16,6 +16,7 @@ defineProps<{
getCardSummary: (item: EditableItem) => string getCardSummary: (item: EditableItem) => string
getRuleState: (item: EditableItem) => { label: string; tone: string } getRuleState: (item: EditableItem) => { label: string; tone: string }
getExternalMatchSummary: (item: EditableItem) => string getExternalMatchSummary: (item: EditableItem) => string
getCloudSourceSummary: (item: EditableItem) => string
getCloudSkuSummary: (item: EditableItem) => string getCloudSkuSummary: (item: EditableItem) => string
getConsumeShopSummary: (item: EditableItem) => string getConsumeShopSummary: (item: EditableItem) => string
getCloudSkuOptions: (item: EditableItem) => AdminCloudtentaclesSkuItem[] getCloudSkuOptions: (item: EditableItem) => AdminCloudtentaclesSkuItem[]
@@ -29,6 +30,7 @@ const emit = defineEmits<{
handleCloudSkuSelected: [value: number | string | undefined] handleCloudSkuSelected: [value: number | string | undefined]
handleCloudSkuDropdownVisible: [visible: boolean] handleCloudSkuDropdownVisible: [visible: boolean]
handleKuaishouConsumeShopSelected: [] handleKuaishouConsumeShopSelected: []
handleCloudSourceKeysChanged: []
remove: [] remove: []
}>() }>()
</script> </script>
@@ -66,6 +68,9 @@ const emit = defineEmits<{
<el-descriptions-item label="内部 SKU">{{ <el-descriptions-item label="内部 SKU">{{
item.internalSkuCode || '-' item.internalSkuCode || '-'
}}</el-descriptions-item> }}</el-descriptions-item>
<el-descriptions-item label="cloud 账号">{{
getCloudSourceSummary(item)
}}</el-descriptions-item>
<el-descriptions-item label="cloud 资源">{{ getCloudSkuSummary(item) }}</el-descriptions-item> <el-descriptions-item label="cloud 资源">{{ getCloudSkuSummary(item) }}</el-descriptions-item>
<el-descriptions-item label="核销店铺">{{ <el-descriptions-item label="核销店铺">{{
getConsumeShopSummary(item) getConsumeShopSummary(item)
@@ -178,11 +183,15 @@ const emit = defineEmits<{
placeholder="展示用,可选但建议填" placeholder="展示用,可选但建议填"
/> />
</el-form-item> </el-form-item>
<el-form-item label="cloud 账号"> <el-form-item label="cloud 账号优先级" class="field-wide">
<el-select <el-select
v-model="item.cloudSourceKey" v-model="item.cloudSourceKeys"
class="text-input" class="text-input"
placeholder="选择 cloud 账号" multiple
collapse-tags
collapse-tags-tooltip
placeholder="按顺序选择可用于履约的 cloud 账号"
@change="emit('handleCloudSourceKeysChanged')"
> >
<el-option <el-option
v-for="opt in cloudtentaclesSourceOptions" v-for="opt in cloudtentaclesSourceOptions"
@@ -191,23 +200,7 @@ const emit = defineEmits<{
:value="opt.key" :value="opt.key"
/> />
</el-select> </el-select>
</el-form-item> <small class="field-help">执行时会从左到右尝试SKU 列表使用第一个账号加载</small>
<el-form-item label="备选 cloud 账号" class="field-wide">
<el-select
v-model="item.cloudSourceKeyFallbacks"
class="text-input"
multiple
placeholder="主账号不可用时自动尝试备选"
>
<el-option
v-for="opt in cloudtentaclesSourceOptions.filter(
(o) => o.key !== item.cloudSourceKey,
)"
:key="opt.key"
:label="opt.label || opt.key"
:value="opt.key"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="cloud SKU ID"> <el-form-item label="cloud SKU ID">
<el-select <el-select
@@ -21,6 +21,7 @@ defineProps<{
getCardSummary: (item: EditableItem) => string getCardSummary: (item: EditableItem) => string
getRuleState: (item: EditableItem) => { label: string; tone: string } getRuleState: (item: EditableItem) => { label: string; tone: string }
getExternalMatchSummary: (item: EditableItem) => string getExternalMatchSummary: (item: EditableItem) => string
getCloudSourceSummary: (item: EditableItem) => string
getCloudSkuSummary: (item: EditableItem) => string getCloudSkuSummary: (item: EditableItem) => string
getConsumeShopSummary: (item: EditableItem) => string getConsumeShopSummary: (item: EditableItem) => string
getCloudSkuOptions: (item: EditableItem) => AdminCloudtentaclesSkuItem[] getCloudSkuOptions: (item: EditableItem) => AdminCloudtentaclesSkuItem[]
@@ -42,6 +43,7 @@ const emit = defineEmits<{
handleCloudSkuSelected: [item: EditableItem, value: number | string | undefined] handleCloudSkuSelected: [item: EditableItem, value: number | string | undefined]
handleCloudSkuDropdownVisible: [item: EditableItem, visible: boolean] handleCloudSkuDropdownVisible: [item: EditableItem, visible: boolean]
handleKuaishouConsumeShopSelected: [item: EditableItem] handleKuaishouConsumeShopSelected: [item: EditableItem]
handleCloudSourceKeysChanged: [item: EditableItem]
removeItem: [localId: string] removeItem: [localId: string]
}>() }>()
</script> </script>
@@ -125,6 +127,7 @@ const emit = defineEmits<{
:get-card-summary="getCardSummary" :get-card-summary="getCardSummary"
:get-rule-state="getRuleState" :get-rule-state="getRuleState"
:get-external-match-summary="getExternalMatchSummary" :get-external-match-summary="getExternalMatchSummary"
:get-cloud-source-summary="getCloudSourceSummary"
:get-cloud-sku-summary="getCloudSkuSummary" :get-cloud-sku-summary="getCloudSkuSummary"
:get-consume-shop-summary="getConsumeShopSummary" :get-consume-shop-summary="getConsumeShopSummary"
:get-cloud-sku-options="getCloudSkuOptions" :get-cloud-sku-options="getCloudSkuOptions"
@@ -135,6 +138,7 @@ const emit = defineEmits<{
@handle-cloud-sku-selected="emit('handleCloudSkuSelected', item, $event)" @handle-cloud-sku-selected="emit('handleCloudSkuSelected', item, $event)"
@handle-cloud-sku-dropdown-visible="emit('handleCloudSkuDropdownVisible', item, $event)" @handle-cloud-sku-dropdown-visible="emit('handleCloudSkuDropdownVisible', item, $event)"
@handle-kuaishou-consume-shop-selected="emit('handleKuaishouConsumeShopSelected', item)" @handle-kuaishou-consume-shop-selected="emit('handleKuaishouConsumeShopSelected', item)"
@handle-cloud-source-keys-changed="emit('handleCloudSourceKeysChanged', item)"
@remove="emit('removeItem', item.localId)" @remove="emit('removeItem', item.localId)"
/> />
</el-card> </el-card>
@@ -2,6 +2,7 @@ import type { AdminKuaishouCloudFulfillmentItem } from '@/types/admin'
export type EditableItem = AdminKuaishouCloudFulfillmentItem & { export type EditableItem = AdminKuaishouCloudFulfillmentItem & {
localId: string localId: string
cloudSourceKeys: string[]
} }
export type ValidationState = { export type ValidationState = {
@@ -61,6 +61,7 @@ export function useKuaishouCloudConfig() {
function createEmptyItem(): EditableItem { function createEmptyItem(): EditableItem {
const defaultShop = getDefaultKuaishouConsumeShop() const defaultShop = getDefaultKuaishouConsumeShop()
const sourceKeys = getDefaultCloudSourceKeys()
return { return {
localId: crypto.randomUUID(), localId: crypto.randomUUID(),
id: '', id: '',
@@ -75,7 +76,7 @@ export function useKuaishouCloudConfig() {
externalItemId: '', externalItemId: '',
externalSkuName: '', externalSkuName: '',
resolvedSkuName: '', resolvedSkuName: '',
cloudSourceKey: 'default', cloudSourceKeys: sourceKeys,
cloudSkuId: 0, cloudSkuId: 0,
cloudSkuName: '', cloudSkuName: '',
vnKey: '1', vnKey: '1',
@@ -86,7 +87,6 @@ export function useKuaishouCloudConfig() {
kuaishouConsumeShopId: defaultShop?.shopId || '', kuaishouConsumeShopId: defaultShop?.shopId || '',
kuaishouConsumeShopName: defaultShop?.kshopName || '', kuaishouConsumeShopName: defaultShop?.kshopName || '',
notes: '', notes: '',
cloudSourceKeyFallbacks: [],
} }
} }
@@ -95,6 +95,7 @@ export function useKuaishouCloudConfig() {
item.kuaishouConsumeShopId, item.kuaishouConsumeShopId,
item.kuaishouConsumeShopName, item.kuaishouConsumeShopName,
) )
const sourceKeys = normalizeCloudSourceKeys(item.cloudSourceKeys)
return { return {
localId: crypto.randomUUID(), localId: crypto.randomUUID(),
id: item.id || crypto.randomUUID(), id: item.id || crypto.randomUUID(),
@@ -109,7 +110,7 @@ export function useKuaishouCloudConfig() {
externalItemId: item.externalItemId || '', externalItemId: item.externalItemId || '',
externalSkuName: item.externalSkuName || '', externalSkuName: item.externalSkuName || '',
resolvedSkuName: item.resolvedSkuName || '', resolvedSkuName: item.resolvedSkuName || '',
cloudSourceKey: item.cloudSourceKey || 'default', cloudSourceKeys: sourceKeys,
cloudSkuId: Number(item.cloudSkuId || 0) || 0, cloudSkuId: Number(item.cloudSkuId || 0) || 0,
cloudSkuName: item.cloudSkuName || '', cloudSkuName: item.cloudSkuName || '',
vnKey: item.vnKey || '', vnKey: item.vnKey || '',
@@ -120,9 +121,6 @@ export function useKuaishouCloudConfig() {
kuaishouConsumeShopId: matchedShop?.shopId || item.kuaishouConsumeShopId || '', kuaishouConsumeShopId: matchedShop?.shopId || item.kuaishouConsumeShopId || '',
kuaishouConsumeShopName: matchedShop?.kshopName || item.kuaishouConsumeShopName || '', kuaishouConsumeShopName: matchedShop?.kshopName || item.kuaishouConsumeShopName || '',
notes: item.notes || '', notes: item.notes || '',
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
? item.cloudSourceKeyFallbacks
: [],
} }
} }
@@ -131,6 +129,7 @@ export function useKuaishouCloudConfig() {
item.kuaishouConsumeShopId, item.kuaishouConsumeShopId,
item.kuaishouConsumeShopName, item.kuaishouConsumeShopName,
) )
const sourceKeys = normalizeCloudSourceKeys(item.cloudSourceKeys)
return { return {
id: item.id.trim() || item.internalSkuCode.trim() || item.localId, id: item.id.trim() || item.internalSkuCode.trim() || item.localId,
enabled: item.enabled, enabled: item.enabled,
@@ -144,7 +143,7 @@ export function useKuaishouCloudConfig() {
externalItemId: item.externalItemId.trim(), externalItemId: item.externalItemId.trim(),
externalSkuName: item.externalSkuName.trim(), externalSkuName: item.externalSkuName.trim(),
resolvedSkuName: item.resolvedSkuName.trim(), resolvedSkuName: item.resolvedSkuName.trim(),
cloudSourceKey: item.cloudSourceKey.trim() || 'default', cloudSourceKeys: sourceKeys,
cloudSkuId: Number(item.cloudSkuId || 0) || 0, cloudSkuId: Number(item.cloudSkuId || 0) || 0,
cloudSkuName: item.cloudSkuName.trim(), cloudSkuName: item.cloudSkuName.trim(),
vnKey: item.vnKey.trim(), vnKey: item.vnKey.trim(),
@@ -157,12 +156,20 @@ export function useKuaishouCloudConfig() {
matchedShop?.kshopName || item.kuaishouConsumeShopName, matchedShop?.kshopName || item.kuaishouConsumeShopName,
).trim(), ).trim(),
notes: item.notes.trim(), notes: item.notes.trim(),
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
? item.cloudSourceKeyFallbacks.filter(Boolean)
: [],
} }
} }
function normalizeCloudSourceKeys(values: unknown[] = []) {
const keys = values
.map((value) => String(value || '').trim())
.filter(Boolean)
return Array.from(new Set(keys))
}
function getDefaultCloudSourceKeys() {
return normalizeCloudSourceKeys([cloudtentaclesSourceOptions.value[0]?.key])
}
// ── validation helpers ──────────────────────────────── // ── validation helpers ────────────────────────────────
function hasExternalMatch( function hasExternalMatch(
@@ -181,7 +188,7 @@ export function useKuaishouCloudConfig() {
item.externalItemId.trim() || item.externalItemId.trim() ||
item.externalSkuName.trim() || item.externalSkuName.trim() ||
item.resolvedSkuName.trim() || item.resolvedSkuName.trim() ||
item.cloudSourceKey.trim() !== 'default' || item.cloudSourceKeys.length > 0 ||
Number(item.cloudSkuId || 0) > 0 || Number(item.cloudSkuId || 0) > 0 ||
item.cloudSkuName.trim() || item.cloudSkuName.trim() ||
item.vnKey.trim() || item.vnKey.trim() ||
@@ -203,6 +210,7 @@ export function useKuaishouCloudConfig() {
function isItemComplete(item: EditableItem) { function isItemComplete(item: EditableItem) {
return Boolean( return Boolean(
item.internalSkuCode.trim() && item.internalSkuCode.trim() &&
item.cloudSourceKeys.length > 0 &&
Number(item.cloudSkuId || 0) > 0 && Number(item.cloudSkuId || 0) > 0 &&
(!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim())) && (!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim())) &&
hasExternalMatch(item), hasExternalMatch(item),
@@ -220,6 +228,10 @@ export function useKuaishouCloudConfig() {
return { localId: item.localId, message: `${title} 缺少内部 SKU 编码` } return { localId: item.localId, message: `${title} 缺少内部 SKU 编码` }
} }
if (item.cloudSourceKeys.length === 0) {
return { localId: item.localId, message: `${title} 需要选择至少一个 cloud 账号` }
}
if (Number(item.cloudSkuId || 0) <= 0) { if (Number(item.cloudSkuId || 0) <= 0) {
return { localId: item.localId, message: `${title} 需要填写 cloud SKU ID` } return { localId: item.localId, message: `${title} 需要填写 cloud SKU ID` }
} }
@@ -340,6 +352,20 @@ export function useKuaishouCloudConfig() {
return name || id || '未绑定核销店铺' return name || id || '未绑定核销店铺'
} }
function getCloudSourceSummary(item: EditableItem) {
const keys = normalizeCloudSourceKeys(item.cloudSourceKeys)
if (keys.length === 0) {
return '未选择 cloud 账号'
}
return keys
.map((key, index) => {
const option = cloudtentaclesSourceOptions.value.find((source) => source.key === key)
return `${index + 1}. ${option?.label || key}`
})
.join(' / ')
}
function getCloudSkuSummary(item: EditableItem) { function getCloudSkuSummary(item: EditableItem) {
if (item.cloudSkuId > 0 && item.cloudSkuName.trim()) { if (item.cloudSkuId > 0 && item.cloudSkuName.trim()) {
return `${item.cloudSkuName.trim()} · #${item.cloudSkuId}` return `${item.cloudSkuName.trim()} · #${item.cloudSkuId}`
@@ -441,6 +467,13 @@ export function useKuaishouCloudConfig() {
setCollapsed(localId, false) setCollapsed(localId, false)
} }
function handleCloudSourceKeysChanged(item: EditableItem) {
const sourceKeys = normalizeCloudSourceKeys(item.cloudSourceKeys)
item.cloudSourceKeys = sourceKeys
item.cloudSkuId = 0
item.cloudSkuName = ''
}
async function focusValidationTarget(localId: string) { async function focusValidationTarget(localId: string) {
if (!localId) { if (!localId) {
return return
@@ -524,6 +557,7 @@ export function useKuaishouCloudConfig() {
getRuleState, getRuleState,
getExternalMatchSummary, getExternalMatchSummary,
getCloudSkuSummary, getCloudSkuSummary,
getCloudSourceSummary,
getConsumeShopSummary, getConsumeShopSummary,
// consume shops // consume shops
getKuaishouConsumeShopOptions, getKuaishouConsumeShopOptions,
@@ -531,6 +565,7 @@ export function useKuaishouCloudConfig() {
getDefaultKuaishouConsumeShop, getDefaultKuaishouConsumeShop,
formatKuaishouConsumeShopOption, formatKuaishouConsumeShopOption,
handleKuaishouConsumeShopSelected, handleKuaishouConsumeShopSelected,
handleCloudSourceKeysChanged,
// collapse // collapse
isCollapsed, isCollapsed,
setCollapsed, setCollapsed,
@@ -42,6 +42,9 @@ export function useKuaishouCloudNinetyone(
const defaultShop = config.getDefaultKuaishouConsumeShop() const defaultShop = config.getDefaultKuaishouConsumeShop()
const shopOpts = config.getKuaishouConsumeShopOptions() const shopOpts = config.getKuaishouConsumeShopOptions()
const shop = defaultShop || (shopOpts.length > 0 ? shopOpts[0] : null) const shop = defaultShop || (shopOpts.length > 0 ? shopOpts[0] : null)
const sourceKeys = config.cloudtentaclesSourceOptions.value[0]?.key
? [config.cloudtentaclesSourceOptions.value[0].key]
: []
return { return {
localId: crypto.randomUUID(), localId: crypto.randomUUID(),
id: '', id: '',
@@ -56,7 +59,7 @@ export function useKuaishouCloudNinetyone(
externalItemId: productNo, externalItemId: productNo,
externalSkuName: productName, externalSkuName: productName,
resolvedSkuName: productName, resolvedSkuName: productName,
cloudSourceKey: 'default', cloudSourceKeys: sourceKeys,
cloudSkuId: 0, cloudSkuId: 0,
cloudSkuName: '', cloudSkuName: '',
vnKey: '1', vnKey: '1',
@@ -12,7 +12,7 @@ export function useKuaishouCloudSku(cloudtentaclesSourceOptions?: { value: { key
const cloudSkuCatalogBySourceKey = ref<Record<string, AdminCloudtentaclesSkuItem[]>>({}) const cloudSkuCatalogBySourceKey = ref<Record<string, AdminCloudtentaclesSkuItem[]>>({})
function getSourceKey(item: EditableItem) { function getSourceKey(item: EditableItem) {
return String(item.cloudSourceKey || 'default').trim() return String(item.cloudSourceKeys[0] || '').trim()
} }
async function ensureCloudSkuCatalogLoaded(item: EditableItem, force = false) { async function ensureCloudSkuCatalogLoaded(item: EditableItem, force = false) {
@@ -149,19 +149,13 @@ export function useKuaishouCloudSku(cloudtentaclesSourceOptions?: { value: { key
const count = cloudSkuCatalogBySourceKey.value[sourceKey]?.length ?? 0 const count = cloudSkuCatalogBySourceKey.value[sourceKey]?.length ?? 0
showSuccess(`cloud SKU 列表(${sourceKey})已刷新,共 ${count}`) showSuccess(`cloud SKU 列表(${sourceKey})已刷新,共 ${count}`)
} else { } else {
// No item provided — refresh all configured sourceKeys, or at least 'default'
const cachedKeys = Object.keys(cloudSkuCatalogBySourceKey.value) const cachedKeys = Object.keys(cloudSkuCatalogBySourceKey.value)
const configuredKeys = cloudtentaclesSourceOptions?.value?.map((s) => s.key) || [] const configuredKeys = cloudtentaclesSourceOptions?.value?.map((s) => s.key) || []
const keysToRefresh = const keysToRefresh = cachedKeys.length > 0 ? cachedKeys : configuredKeys
cachedKeys.length > 0
? cachedKeys
: configuredKeys.length > 0
? configuredKeys
: ['default']
for (const key of keysToRefresh) { for (const key of keysToRefresh) {
await ensureCloudSkuCatalogLoaded( await ensureCloudSkuCatalogLoaded(
{ cloudSourceKey: key, localId: '' } as EditableItem, { cloudSourceKeys: [key], localId: '' } as EditableItem,
true, true,
) )
} }
@@ -177,8 +171,6 @@ export function useKuaishouCloudSku(cloudtentaclesSourceOptions?: { value: { key
} }
const cloudSkuCatalogLoading = ref(false) const cloudSkuCatalogLoading = ref(false)
// Maintain backward-compat: derive a combined loading state
// The template uses cloudSkuCatalogLoading for the refresh button spinner
function syncCloudSkuCatalogLoading() { function syncCloudSkuCatalogLoading() {
cloudSkuCatalogLoading.value = Object.values(cloudSkuCatalogLoadingBySourceKey.value).some( cloudSkuCatalogLoading.value = Object.values(cloudSkuCatalogLoadingBySourceKey.value).some(
Boolean, Boolean,
@@ -221,19 +221,14 @@ const emit = defineEmits<{
<el-collapse-item name="cloud" title="Cloud 资源与发货"> <el-collapse-item name="cloud" title="Cloud 资源与发货">
<div class="flow-meta-list flow-meta-list--two-column"> <div class="flow-meta-list flow-meta-list--two-column">
<div> <div>
<span>规则主账号</span><strong>{{ flow.binding.cloudSourceKey || '-' }}</strong> <span>账号优先级</span>
<strong>{{
flow.binding.cloudSourceKeys?.length ? flow.binding.cloudSourceKeys.join(' / ') : '-'
}}</strong>
</div> </div>
<div> <div>
<span>实际使用账号</span> <span>实际使用账号</span>
<strong>{{ flow.binding.resolvedSourceKey || flow.binding.cloudSourceKey || '-' }}</strong> <strong>{{ flow.binding.resolvedSourceKey || '-' }}</strong>
</div>
<div>
<span>备选账号</span>
<strong>{{
flow.binding.cloudSourceKeyFallbacks?.length
? flow.binding.cloudSourceKeyFallbacks.join(' / ')
: '-'
}}</strong>
</div> </div>
<div> <div>
<span>cloud SKU</span> <span>cloud SKU</span>