diff --git a/apps/backend/data/cloudtentacles-sources.json b/apps/backend/data/cloudtentacles-sources.json index 1f1f9799..91c8f277 100644 --- a/apps/backend/data/cloudtentacles-sources.json +++ b/apps/backend/data/cloudtentacles-sources.json @@ -1,9 +1,45 @@ { "enabled": true, - "baseUrl": "https://123.207.217.176", - "username": "17665234375", - "password": "yaochao11", - "phone": "17665234375", - "deviceId": "-", - "deviceType": 0 + "sources": [ + { + "key": "default", + "label": "默认账号", + "baseUrl": "https://123.207.217.176", + "username": "17665234375", + "password": "yaochao11", + "phone": "17665234375", + "deviceId": "-", + "deviceType": 0 + }, + { + "key": "account2", + "label": "备用账号 1", + "baseUrl": "https://123.207.217.176", + "username": "", + "password": "", + "phone": "", + "deviceId": "-", + "deviceType": 0 + }, + { + "key": "account3", + "label": "备用账号 2", + "baseUrl": "https://123.207.217.176", + "username": "", + "password": "", + "phone": "", + "deviceId": "-", + "deviceType": 0 + }, + { + "key": "测试12", + "label": "测试12", + "baseUrl": "https://123.207.217.176", + "username": "", + "password": "", + "phone": "", + "deviceId": "-", + "deviceType": 0 + } + ] } diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue index 8f1a00aa..672f6ed2 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue @@ -12,7 +12,7 @@ import { useKuaishouCloudSku } from './composables/useKuaishouCloudSku' const config = useKuaishouCloudConfig() const ninetyone = useKuaishouCloudNinetyone(config) -const cloudSku = useKuaishouCloudSku() +const cloudSku = useKuaishouCloudSku(config.cloudtentaclesSourceOptions) onMounted(config.loadConfigs) @@ -73,9 +73,9 @@ onMounted(config.loadConfigs) @add-item="config.addItem()" @save-configs="config.saveConfigs()" @toggle-collapsed="config.toggleCollapsed($event)" - @handle-cloud-sku-selected="cloudSku.handleCloudSkuSelected($event.item, $event.value)" + @handle-cloud-sku-selected="(item, value) => cloudSku.handleCloudSkuSelected(item, value)" @handle-cloud-sku-dropdown-visible=" - cloudSku.handleCloudSkuDropdownVisible($event.item, $event.visible) + (item, visible) => cloudSku.handleCloudSkuDropdownVisible(item, visible) " @handle-kuaishou-consume-shop-selected="config.handleKuaishouConsumeShopSelected($event)" @remove-item="config.removeItem($event)" diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/composables/useKuaishouCloudSku.ts b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/composables/useKuaishouCloudSku.ts index 364f36c7..978bdf1b 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/composables/useKuaishouCloudSku.ts +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/composables/useKuaishouCloudSku.ts @@ -6,7 +6,7 @@ import type { AdminCloudtentaclesSkuItem } from '@/types/admin' import type { EditableItem } from './types' -export function useKuaishouCloudSku() { +export function useKuaishouCloudSku(cloudtentaclesSourceOptions?: { value: { key: string }[] }) { const cloudSkuCatalogLoadingBySourceKey = ref>({}) const cloudSkuCatalogErrorMessage = ref('') const cloudSkuCatalogBySourceKey = ref>({}) @@ -26,7 +26,9 @@ export function useKuaishouCloudSku() { try { const response = await fetchAdminCloudtentaclesSkuList({ sourceKey }) - cloudSkuCatalogBySourceKey.value[sourceKey] = Array.isArray(response.data.items) ? response.data.items : [] + cloudSkuCatalogBySourceKey.value[sourceKey] = Array.isArray(response.data.items) + ? response.data.items + : [] return cloudSkuCatalogBySourceKey.value[sourceKey] } catch (error) { cloudSkuCatalogBySourceKey.value[sourceKey] = [] @@ -147,12 +149,26 @@ export function useKuaishouCloudSku() { const count = cloudSkuCatalogBySourceKey.value[sourceKey]?.length ?? 0 showSuccess(`cloud SKU 列表(${sourceKey})已刷新,共 ${count} 条`) } else { - // No item provided — refresh all loaded caches - const keys = Object.keys(cloudSkuCatalogBySourceKey.value) - for (const key of keys) { - await ensureCloudSkuCatalogLoaded({ cloudSourceKey: key, localId: '' } as EditableItem, true) + // No item provided — refresh all configured sourceKeys, or at least 'default' + const cachedKeys = Object.keys(cloudSkuCatalogBySourceKey.value) + const configuredKeys = cloudtentaclesSourceOptions?.value?.map((s) => s.key) || [] + const keysToRefresh = + cachedKeys.length > 0 + ? cachedKeys + : configuredKeys.length > 0 + ? configuredKeys + : ['default'] + + for (const key of keysToRefresh) { + await ensureCloudSkuCatalogLoaded( + { cloudSourceKey: key, localId: '' } as EditableItem, + true, + ) } - const total = Object.values(cloudSkuCatalogBySourceKey.value).reduce((sum, list) => sum + list.length, 0) + const total = Object.values(cloudSkuCatalogBySourceKey.value).reduce( + (sum, list) => sum + list.length, + 0, + ) showSuccess(`cloud SKU 列表已全部刷新,共 ${total} 条`) } } catch (error) { @@ -164,7 +180,9 @@ export function useKuaishouCloudSku() { // Maintain backward-compat: derive a combined loading state // The template uses cloudSkuCatalogLoading for the refresh button spinner function syncCloudSkuCatalogLoading() { - cloudSkuCatalogLoading.value = Object.values(cloudSkuCatalogLoadingBySourceKey.value).some(Boolean) + cloudSkuCatalogLoading.value = Object.values(cloudSkuCatalogLoadingBySourceKey.value).some( + Boolean, + ) } return { 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 62fd7106..e36566c5 100644 --- a/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts +++ b/apps/frontend/src/views/admin/platform-shops/composables/useAdminCloudtentaclesPlatform.ts @@ -1,6 +1,6 @@ import { computed, ref } from 'vue' -import { showError, showSuccess } from '@/lib/feedback' +import { showError, showPrompt, showSuccess, isFeedbackDismissed } from '@/lib/feedback' import { deleteAdminCloudtentaclesSource, saveAdminCloudtentaclesSourceConfig, @@ -60,17 +60,13 @@ export function useAdminCloudtentaclesPlatform() { selectedSourceKey, ) - const sourceOptions = computed(() => - sources.value.map((s) => ({ key: s.key, label: s.label })), - ) + const sourceOptions = computed(() => sources.value.map((s) => ({ key: s.key, label: s.label }))) const currentSource = computed( () => sources.value.find((s) => s.key === selectedSourceKey.value) || sources.value[0], ) - const currentSession = computed( - () => sessions.value[selectedSourceKey.value] || null, - ) + const currentSession = computed(() => sessions.value[selectedSourceKey.value] || null) const cloudtentaclesStats = computed(() => ({ hasCredential: Boolean( @@ -144,21 +140,45 @@ export function useAdminCloudtentaclesPlatform() { hydrateFormFromSource(sourceKey) } - function addSource() { - const newKey = `account${sources.value.length + 1}` - const newSource: AdminCloudtentaclesSourceItem = { - key: newKey, - label: `备用账号 ${sources.value.length}`, - baseUrl: 'https://123.207.217.176', - username: '', - password: '', - phone: '', - deviceId: '-', - deviceType: 0, + async function addSource() { + try { + const result = await showPrompt( + '请输入新账号名称(例如:运营号、客服号)', + '新增 cloudtentacles 账号', + { + inputPattern: /\S/, + inputErrorMessage: '账号名称不能为空', + }, + ) + const trimmed = (result.value as string).trim() + if (!trimmed) { + showError('账号名称不能为空') + return + } + // Generate a URL-safe key from the label, ensure uniqueness + const baseKey = trimmed.replace(/[^a-zA-Z0-9\u4e00-\u9fff_]/g, '_').toLowerCase() + let newKey = baseKey + let counter = 2 + while (sources.value.some((s) => s.key === newKey)) { + newKey = `${baseKey}_${counter++}` + } + const newSource: AdminCloudtentaclesSourceItem = { + key: newKey, + label: trimmed, + baseUrl: 'https://123.207.217.176', + username: '', + password: '', + phone: '', + deviceId: '-', + deviceType: 0, + } + sources.value.push(newSource) + selectedSourceKey.value = newKey + hydrateFormFromSource(newKey) + } catch (error) { + if (isFeedbackDismissed(error)) return // user cancelled / closed + showError(error instanceof Error ? error.message : '新增账号失败') } - sources.value.push(newSource) - selectedSourceKey.value = newKey - hydrateFormFromSource(newKey) } async function removeSource(sourceKey: string) {