拆分平台配置页脚本逻辑
This commit is contained in:
Vendored
-3
@@ -23,9 +23,6 @@ declare module 'vue' {
|
|||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
|
||||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export type PlatformTab = 'agiso' | 'khhao' | 'kuaishouEticket' | 'cloudtentacles'
|
||||||
|
|
||||||
|
export type EditableDefaults = {
|
||||||
|
messageTemplate: string
|
||||||
|
autoDeliveryMessageTemplate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditableShop = {
|
||||||
|
id: string
|
||||||
|
shopId: string
|
||||||
|
shopName: string
|
||||||
|
accessToken: string
|
||||||
|
enabled: boolean
|
||||||
|
messageTemplate: string
|
||||||
|
autoDeliveryMessageTemplate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditableKuaishouEticketShop = {
|
||||||
|
id: string
|
||||||
|
shopId: string
|
||||||
|
kshopName: string
|
||||||
|
cookie: string
|
||||||
|
userAvatar: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import { showError, showSuccess } from '@/lib/feedback'
|
||||||
|
import { saveAdminAgisoShopConfigs } from '@/services/admin'
|
||||||
|
import type { AdminAgisoMessagingDefaults, AdminAgisoObservedShopItem, AdminAgisoShopConfigItem } from '@/types/admin'
|
||||||
|
|
||||||
|
import type { EditableDefaults, EditableShop } from './types'
|
||||||
|
|
||||||
|
export function useAdminAgisoPlatform() {
|
||||||
|
const saving = ref(false)
|
||||||
|
const agisoFilePath = ref('')
|
||||||
|
const defaults = ref<EditableDefaults>(createEmptyDefaults())
|
||||||
|
const shops = ref<EditableShop[]>([])
|
||||||
|
const observedShops = ref<AdminAgisoObservedShopItem[]>([])
|
||||||
|
const expandedShopIds = ref<string[]>([])
|
||||||
|
|
||||||
|
const agisoStats = computed(() => ({
|
||||||
|
configuredShopCount: shops.value.length,
|
||||||
|
observedShopCount: observedShops.value.length,
|
||||||
|
pendingObservedCount: observedShops.value.filter((item) => !item.configured).length,
|
||||||
|
}))
|
||||||
|
|
||||||
|
function hydrateAgisoConfig(data: {
|
||||||
|
filePath: string
|
||||||
|
defaults: AdminAgisoMessagingDefaults
|
||||||
|
shops: AdminAgisoShopConfigItem[]
|
||||||
|
observedShops: AdminAgisoObservedShopItem[]
|
||||||
|
}) {
|
||||||
|
agisoFilePath.value = data.filePath
|
||||||
|
defaults.value = mapEditableDefaults(data.defaults)
|
||||||
|
shops.value = data.shops.map(mapEditableShop)
|
||||||
|
observedShops.value = data.observedShops
|
||||||
|
expandedShopIds.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function addShop() {
|
||||||
|
const shop = createEmptyShop()
|
||||||
|
shops.value.unshift(shop)
|
||||||
|
expandShop(shop.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeShop(id: string) {
|
||||||
|
shops.value = shops.value.filter((item) => item.id !== id)
|
||||||
|
collapseShop(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function importObservedShop(item: AdminAgisoObservedShopItem) {
|
||||||
|
const shop: EditableShop = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
shopId: item.shopId,
|
||||||
|
shopName: item.detectedShopName || item.displayShopName || '',
|
||||||
|
accessToken: '',
|
||||||
|
enabled: true,
|
||||||
|
messageTemplate: '',
|
||||||
|
autoDeliveryMessageTemplate: '',
|
||||||
|
}
|
||||||
|
shops.value.unshift(shop)
|
||||||
|
expandShop(shop.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isShopExpanded(id: string) {
|
||||||
|
return expandedShopIds.value.includes(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandShop(id: string) {
|
||||||
|
if (expandedShopIds.value.includes(id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expandedShopIds.value = [id, ...expandedShopIds.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseShop(id: string) {
|
||||||
|
expandedShopIds.value = expandedShopIds.value.filter((item) => item !== id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleShop(id: string) {
|
||||||
|
if (isShopExpanded(id)) {
|
||||||
|
collapseShop(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expandShop(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandAllShops() {
|
||||||
|
expandedShopIds.value = shops.value.map((item) => item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseAllShops() {
|
||||||
|
expandedShopIds.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeShop(shop: EditableShop) {
|
||||||
|
const overrides: string[] = []
|
||||||
|
|
||||||
|
if (shop.messageTemplate.trim()) {
|
||||||
|
overrides.push('领取模板')
|
||||||
|
}
|
||||||
|
if (shop.autoDeliveryMessageTemplate.trim()) {
|
||||||
|
overrides.push('发货模板')
|
||||||
|
}
|
||||||
|
|
||||||
|
return overrides.length === 0 ? '使用默认模板' : `已覆盖 ${overrides.join('、')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveAgisoConfigs(reloadConfigs: () => Promise<void>) {
|
||||||
|
const payloadDefaults = {
|
||||||
|
messageTemplate: defaults.value.messageTemplate.trim(),
|
||||||
|
autoDeliveryMessageTemplate: defaults.value.autoDeliveryMessageTemplate.trim(),
|
||||||
|
}
|
||||||
|
const payload = shops.value
|
||||||
|
.map((item) => ({
|
||||||
|
shopId: item.shopId.trim(),
|
||||||
|
shopName: item.shopName.trim(),
|
||||||
|
accessToken: item.accessToken.trim(),
|
||||||
|
enabled: item.enabled,
|
||||||
|
messageTemplate: item.messageTemplate.trim(),
|
||||||
|
autoDeliveryMessageTemplate: item.autoDeliveryMessageTemplate.trim(),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.shopId && item.accessToken)
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await saveAdminAgisoShopConfigs({
|
||||||
|
defaults: payloadDefaults,
|
||||||
|
shops: payload,
|
||||||
|
})
|
||||||
|
agisoFilePath.value = response.data.filePath
|
||||||
|
defaults.value = mapEditableDefaults(response.data.defaults)
|
||||||
|
shops.value = response.data.shops.map(mapEditableShop)
|
||||||
|
showSuccess('Agiso 配置已保存')
|
||||||
|
await reloadConfigs()
|
||||||
|
} catch (error) {
|
||||||
|
showError(error instanceof Error ? error.message : '保存 Agiso 配置失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
saving,
|
||||||
|
agisoFilePath,
|
||||||
|
defaults,
|
||||||
|
shops,
|
||||||
|
observedShops,
|
||||||
|
expandedShopIds,
|
||||||
|
agisoStats,
|
||||||
|
hydrateAgisoConfig,
|
||||||
|
addShop,
|
||||||
|
removeShop,
|
||||||
|
importObservedShop,
|
||||||
|
isShopExpanded,
|
||||||
|
expandShop,
|
||||||
|
collapseShop,
|
||||||
|
toggleShop,
|
||||||
|
expandAllShops,
|
||||||
|
collapseAllShops,
|
||||||
|
describeShop,
|
||||||
|
saveAgisoConfigs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyDefaults(): EditableDefaults {
|
||||||
|
return {
|
||||||
|
messageTemplate: '',
|
||||||
|
autoDeliveryMessageTemplate: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyShop(): EditableShop {
|
||||||
|
return {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
shopId: '',
|
||||||
|
shopName: '',
|
||||||
|
accessToken: '',
|
||||||
|
enabled: true,
|
||||||
|
messageTemplate: '',
|
||||||
|
autoDeliveryMessageTemplate: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapEditableDefaults(item?: AdminAgisoMessagingDefaults): EditableDefaults {
|
||||||
|
return {
|
||||||
|
messageTemplate: item?.messageTemplate ?? '',
|
||||||
|
autoDeliveryMessageTemplate: item?.autoDeliveryMessageTemplate ?? '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapEditableShop(item: AdminAgisoShopConfigItem): EditableShop {
|
||||||
|
return {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
shopId: item.shopId,
|
||||||
|
shopName: item.shopName,
|
||||||
|
accessToken: item.accessToken,
|
||||||
|
enabled: item.enabled !== false,
|
||||||
|
messageTemplate: item.messageTemplate,
|
||||||
|
autoDeliveryMessageTemplate: item.autoDeliveryMessageTemplate,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
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,
|
||||||
|
AdminCloudtentaclesPersistedSession,
|
||||||
|
AdminCloudtentaclesSendSmsResult,
|
||||||
|
AdminCloudtentaclesSourceConfig,
|
||||||
|
AdminCloudtentaclesValidateSessionResult,
|
||||||
|
} from '@/types/admin'
|
||||||
|
|
||||||
|
export function useAdminCloudtentaclesPlatform() {
|
||||||
|
const cloudtentaclesFilePath = ref('')
|
||||||
|
const cloudtentaclesSessionFilePath = ref('')
|
||||||
|
const cloudtentaclesForm = ref({
|
||||||
|
baseUrl: 'https://123.207.217.176',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
token: '',
|
||||||
|
vnKey: '1',
|
||||||
|
vnId: 0,
|
||||||
|
skuId: 5,
|
||||||
|
skuCount: 1,
|
||||||
|
deviceId: '-',
|
||||||
|
deviceType: 0,
|
||||||
|
})
|
||||||
|
const cloudtentaclesSaving = ref(false)
|
||||||
|
const cloudtentaclesSendingSms = ref(false)
|
||||||
|
const cloudtentaclesTesting = ref(false)
|
||||||
|
const cloudtentaclesValidating = ref(false)
|
||||||
|
const cloudtentaclesResultError = ref('')
|
||||||
|
const cloudtentaclesSmsResult = ref<AdminCloudtentaclesSendSmsResult | null>(null)
|
||||||
|
const cloudtentaclesLoginResult = ref<AdminCloudtentaclesLoginTestResult | null>(null)
|
||||||
|
const cloudtentaclesValidateResult = ref<AdminCloudtentaclesValidateSessionResult | null>(null)
|
||||||
|
const cloudtentaclesDebugLoading = ref(false)
|
||||||
|
const cloudtentaclesDebugResult = ref('')
|
||||||
|
const cloudtentaclesPersistedSession = ref<AdminCloudtentaclesPersistedSession | null>(null)
|
||||||
|
|
||||||
|
const cloudtentaclesStats = computed(() => ({
|
||||||
|
hasCredential: Boolean(
|
||||||
|
cloudtentaclesForm.value.username.trim()
|
||||||
|
&& cloudtentaclesForm.value.password.trim()
|
||||||
|
&& cloudtentaclesForm.value.phone.trim(),
|
||||||
|
),
|
||||||
|
smsSent: Boolean(cloudtentaclesSmsResult.value),
|
||||||
|
validated: Boolean(cloudtentaclesValidateResult.value || cloudtentaclesLoginResult.value),
|
||||||
|
}))
|
||||||
|
|
||||||
|
function hydrateCloudtentaclesConfig(data: {
|
||||||
|
filePath: string
|
||||||
|
sessionFilePath: string
|
||||||
|
session: AdminCloudtentaclesPersistedSession
|
||||||
|
source: {
|
||||||
|
baseUrl?: string
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
phone?: string
|
||||||
|
deviceId?: string
|
||||||
|
deviceType?: number
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
cloudtentaclesFilePath.value = data.filePath
|
||||||
|
cloudtentaclesSessionFilePath.value = data.sessionFilePath
|
||||||
|
cloudtentaclesPersistedSession.value = data.session
|
||||||
|
cloudtentaclesForm.value = {
|
||||||
|
baseUrl: data.source.baseUrl || 'https://123.207.217.176',
|
||||||
|
username: data.source.username || '',
|
||||||
|
password: data.source.password || '',
|
||||||
|
phone: data.source.phone || '',
|
||||||
|
code: '',
|
||||||
|
token: data.session?.token || '',
|
||||||
|
vnKey: '1',
|
||||||
|
vnId: 0,
|
||||||
|
skuId: 5,
|
||||||
|
skuCount: 1,
|
||||||
|
deviceId: data.source.deviceId || '-',
|
||||||
|
deviceType: data.source.deviceType || 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCloudtentaclesConfigPayload(): AdminCloudtentaclesSourceConfig {
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
baseUrl: cloudtentaclesForm.value.baseUrl.trim() || 'https://123.207.217.176',
|
||||||
|
username: cloudtentaclesForm.value.username.trim(),
|
||||||
|
password: cloudtentaclesForm.value.password.trim(),
|
||||||
|
phone: cloudtentaclesForm.value.phone.trim(),
|
||||||
|
deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
|
||||||
|
deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCloudtentaclesLoginPayload() {
|
||||||
|
return {
|
||||||
|
...buildCloudtentaclesConfigPayload(),
|
||||||
|
code: cloudtentaclesForm.value.code.trim(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCloudtentaclesBaseFields() {
|
||||||
|
if (!cloudtentaclesForm.value.username.trim() || !cloudtentaclesForm.value.phone.trim()) {
|
||||||
|
cloudtentaclesResultError.value = '请先填写 cloudtentacles 账号和手机号'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCloudtentaclesLoginFields() {
|
||||||
|
if (
|
||||||
|
!cloudtentaclesForm.value.username.trim()
|
||||||
|
|| !cloudtentaclesForm.value.password.trim()
|
||||||
|
|| !cloudtentaclesForm.value.phone.trim()
|
||||||
|
) {
|
||||||
|
cloudtentaclesResultError.value = '请先填写 cloudtentacles 账号、密码和手机号'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cloudtentaclesForm.value.code.trim()) {
|
||||||
|
cloudtentaclesResultError.value = '请先填写短信验证码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloudtentaclesSaveSource() {
|
||||||
|
cloudtentaclesSaving.value = true
|
||||||
|
cloudtentaclesResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await saveAdminCloudtentaclesSourceConfig(buildCloudtentaclesConfigPayload())
|
||||||
|
cloudtentaclesFilePath.value = response.data.filePath
|
||||||
|
cloudtentaclesSessionFilePath.value = response.data.sessionFilePath
|
||||||
|
cloudtentaclesPersistedSession.value = response.data.session
|
||||||
|
cloudtentaclesForm.value.token = response.data.session?.token || ''
|
||||||
|
showSuccess('cloudtentacles 履约平台配置已保存')
|
||||||
|
} catch (error) {
|
||||||
|
cloudtentaclesResultError.value = error instanceof Error ? error.message : 'cloudtentacles 配置保存失败'
|
||||||
|
showError(cloudtentaclesResultError.value)
|
||||||
|
} finally {
|
||||||
|
cloudtentaclesSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloudtentaclesSendSmsCode() {
|
||||||
|
if (!ensureCloudtentaclesBaseFields()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudtentaclesSendingSms.value = true
|
||||||
|
cloudtentaclesResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await sendAdminCloudtentaclesSmsCode(buildCloudtentaclesConfigPayload())
|
||||||
|
cloudtentaclesSmsResult.value = response.data
|
||||||
|
showSuccess('cloudtentacles 短信验证码已发送')
|
||||||
|
} catch (error) {
|
||||||
|
cloudtentaclesResultError.value = error instanceof Error ? error.message : 'cloudtentacles 发送短信验证码失败'
|
||||||
|
showError(cloudtentaclesResultError.value)
|
||||||
|
} finally {
|
||||||
|
cloudtentaclesSendingSms.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloudtentaclesTestLogin() {
|
||||||
|
if (!ensureCloudtentaclesLoginFields()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudtentaclesTesting.value = true
|
||||||
|
cloudtentaclesResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await testAdminCloudtentaclesLogin(buildCloudtentaclesLoginPayload())
|
||||||
|
cloudtentaclesLoginResult.value = response.data
|
||||||
|
cloudtentaclesForm.value.token = response.data.token || cloudtentaclesForm.value.token
|
||||||
|
cloudtentaclesPersistedSession.value = {
|
||||||
|
token: response.data.token || '',
|
||||||
|
tokenMasked: response.data.session.tokenMasked,
|
||||||
|
baseUrl: response.data.baseUrl,
|
||||||
|
username: response.data.username,
|
||||||
|
phoneMasked: response.data.phoneMasked,
|
||||||
|
loggedInAt: response.data.loggedInAt,
|
||||||
|
deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
|
||||||
|
deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
|
||||||
|
hasToken: Boolean(response.data.token),
|
||||||
|
}
|
||||||
|
showSuccess('cloudtentacles 登录测试成功')
|
||||||
|
} catch (error) {
|
||||||
|
cloudtentaclesResultError.value = error instanceof Error ? error.message : 'cloudtentacles 登录测试失败'
|
||||||
|
showError(cloudtentaclesResultError.value)
|
||||||
|
} finally {
|
||||||
|
cloudtentaclesTesting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloudtentaclesValidateSession() {
|
||||||
|
const token = cloudtentaclesForm.value.token.trim()
|
||||||
|
if (!token) {
|
||||||
|
cloudtentaclesResultError.value = '请先填写 token'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cloudtentaclesValidating.value = true
|
||||||
|
cloudtentaclesResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await validateAdminCloudtentaclesSession({
|
||||||
|
baseUrl: cloudtentaclesForm.value.baseUrl.trim() || 'https://123.207.217.176',
|
||||||
|
token,
|
||||||
|
deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
|
||||||
|
deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
|
||||||
|
})
|
||||||
|
cloudtentaclesValidateResult.value = response.data
|
||||||
|
cloudtentaclesPersistedSession.value = {
|
||||||
|
token,
|
||||||
|
tokenMasked: response.data.session.tokenMasked,
|
||||||
|
baseUrl: response.data.baseUrl,
|
||||||
|
username: cloudtentaclesPersistedSession.value?.username || cloudtentaclesForm.value.username.trim(),
|
||||||
|
phoneMasked: cloudtentaclesPersistedSession.value?.phoneMasked || '',
|
||||||
|
loggedInAt: response.data.loggedInAt,
|
||||||
|
deviceId: cloudtentaclesForm.value.deviceId.trim() || '-',
|
||||||
|
deviceType: Number(cloudtentaclesForm.value.deviceType || 0),
|
||||||
|
hasToken: Boolean(token),
|
||||||
|
}
|
||||||
|
showSuccess('cloudtentacles 会话校验成功')
|
||||||
|
} catch (error) {
|
||||||
|
cloudtentaclesResultError.value = error instanceof Error ? error.message : 'cloudtentacles 会话校验失败'
|
||||||
|
showError(cloudtentaclesResultError.value)
|
||||||
|
} finally {
|
||||||
|
cloudtentaclesValidating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
cloudtentaclesForm,
|
||||||
|
cloudtentaclesSaving,
|
||||||
|
cloudtentaclesSendingSms,
|
||||||
|
cloudtentaclesTesting,
|
||||||
|
cloudtentaclesValidating,
|
||||||
|
cloudtentaclesResultError,
|
||||||
|
cloudtentaclesSmsResult,
|
||||||
|
cloudtentaclesLoginResult,
|
||||||
|
cloudtentaclesValidateResult,
|
||||||
|
cloudtentaclesDebugLoading,
|
||||||
|
cloudtentaclesDebugResult,
|
||||||
|
cloudtentaclesPersistedSession,
|
||||||
|
cloudtentaclesStats,
|
||||||
|
hydrateCloudtentaclesConfig,
|
||||||
|
handleCloudtentaclesSaveSource,
|
||||||
|
handleCloudtentaclesSendSmsCode,
|
||||||
|
handleCloudtentaclesTestLogin,
|
||||||
|
handleCloudtentaclesValidateSession,
|
||||||
|
handleCloudtentaclesFetchAsset,
|
||||||
|
handleCloudtentaclesFetchCategories,
|
||||||
|
handleCloudtentaclesFetchSkuList,
|
||||||
|
handleCloudtentaclesBuySku,
|
||||||
|
handleCloudtentaclesUseSku,
|
||||||
|
handleCloudtentaclesFetchKnapsack,
|
||||||
|
handleCloudtentaclesFetchVnList,
|
||||||
|
handleCloudtentaclesAppointVn,
|
||||||
|
handleCloudtentaclesGenerateVnLoginCode,
|
||||||
|
handleCloudtentaclesFetchVnCode,
|
||||||
|
handleCloudtentaclesVerifyVnCode,
|
||||||
|
handleCloudtentaclesFetchBindUrl,
|
||||||
|
handleCloudtentaclesBackVn,
|
||||||
|
handleCloudtentaclesRunFullFlow,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import { showError, showSuccess } from '@/lib/feedback'
|
||||||
|
import {
|
||||||
|
queryAdminKhhaoOrders,
|
||||||
|
saveAdminKhhaoSourceConfig,
|
||||||
|
syncAdminKhhaoOrders,
|
||||||
|
testAdminKhhaoLogin,
|
||||||
|
} from '@/services/admin'
|
||||||
|
import type {
|
||||||
|
AdminKhhaoLoginTestResult,
|
||||||
|
AdminKhhaoOrderQueryResult,
|
||||||
|
AdminKhhaoOrderSyncResult,
|
||||||
|
AdminKhhaoSyncState,
|
||||||
|
} from '@/types/admin'
|
||||||
|
|
||||||
|
export function useAdminKhhaoPlatform() {
|
||||||
|
const khhaoFilePath = ref('')
|
||||||
|
const khhaoStateFilePath = ref('')
|
||||||
|
const khhaoForm = ref({
|
||||||
|
baseUrl: 'https://admin.khhao.com',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
maxCaptchaAttempts: 3,
|
||||||
|
autoSyncEnabled: false,
|
||||||
|
autoSyncIntervalMinutes: 3,
|
||||||
|
autoSyncPages: 2,
|
||||||
|
autoSyncPageSize: 20,
|
||||||
|
})
|
||||||
|
const khhaoSaving = ref(false)
|
||||||
|
const khhaoTesting = ref(false)
|
||||||
|
const khhaoQuerying = ref(false)
|
||||||
|
const khhaoSyncing = ref(false)
|
||||||
|
const khhaoResultError = ref('')
|
||||||
|
const khhaoLoginResult = ref<AdminKhhaoLoginTestResult | null>(null)
|
||||||
|
const khhaoQueryResult = ref<AdminKhhaoOrderQueryResult | null>(null)
|
||||||
|
const khhaoSyncResult = ref<AdminKhhaoOrderSyncResult | null>(null)
|
||||||
|
const khhaoAutoSyncState = ref<AdminKhhaoSyncState | null>(null)
|
||||||
|
|
||||||
|
const khhaoStats = computed(() => ({
|
||||||
|
hasCredential: Boolean(khhaoForm.value.username.trim() && khhaoForm.value.password.trim()),
|
||||||
|
queryCount: khhaoQueryResult.value?.itemCount || 0,
|
||||||
|
syncedCount: khhaoAutoSyncState.value?.syncedCount || khhaoSyncResult.value?.syncedCount || 0,
|
||||||
|
ignoredCount: khhaoAutoSyncState.value?.ignoredCount || khhaoSyncResult.value?.ignoredCount || 0,
|
||||||
|
}))
|
||||||
|
|
||||||
|
function hydrateKhhaoConfig(data: {
|
||||||
|
filePath: string
|
||||||
|
stateFilePath: string
|
||||||
|
syncState: AdminKhhaoSyncState
|
||||||
|
source: {
|
||||||
|
baseUrl?: string
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
maxCaptchaAttempts?: number
|
||||||
|
autoSync?: {
|
||||||
|
enabled?: boolean
|
||||||
|
intervalMinutes?: number
|
||||||
|
pages?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
khhaoFilePath.value = data.filePath
|
||||||
|
khhaoStateFilePath.value = data.stateFilePath
|
||||||
|
khhaoAutoSyncState.value = data.syncState
|
||||||
|
khhaoForm.value = {
|
||||||
|
baseUrl: data.source.baseUrl || 'https://admin.khhao.com',
|
||||||
|
username: data.source.username || '',
|
||||||
|
password: data.source.password || '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
maxCaptchaAttempts: data.source.maxCaptchaAttempts || 3,
|
||||||
|
autoSyncEnabled: data.source.autoSync?.enabled === true,
|
||||||
|
autoSyncIntervalMinutes: data.source.autoSync?.intervalMinutes || 3,
|
||||||
|
autoSyncPages: data.source.autoSync?.pages || 2,
|
||||||
|
autoSyncPageSize: data.source.autoSync?.pageSize || 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildKhhaoPayload() {
|
||||||
|
return {
|
||||||
|
baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com',
|
||||||
|
username: khhaoForm.value.username.trim(),
|
||||||
|
password: khhaoForm.value.password.trim(),
|
||||||
|
page: Number(khhaoForm.value.page || 1),
|
||||||
|
limit: Number(khhaoForm.value.limit || 10),
|
||||||
|
maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureKhhaoCredentials() {
|
||||||
|
if (!khhaoForm.value.username.trim() || !khhaoForm.value.password.trim()) {
|
||||||
|
khhaoResultError.value = '请先填写 khhao 账号和密码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKhhaoSaveSource() {
|
||||||
|
khhaoSaving.value = true
|
||||||
|
khhaoResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await saveAdminKhhaoSourceConfig({
|
||||||
|
enabled: true,
|
||||||
|
baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com',
|
||||||
|
username: khhaoForm.value.username.trim(),
|
||||||
|
password: khhaoForm.value.password.trim(),
|
||||||
|
maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3),
|
||||||
|
autoSync: {
|
||||||
|
enabled: Boolean(khhaoForm.value.autoSyncEnabled),
|
||||||
|
intervalMinutes: Number(khhaoForm.value.autoSyncIntervalMinutes || 3),
|
||||||
|
pages: Number(khhaoForm.value.autoSyncPages || 2),
|
||||||
|
pageSize: Number(khhaoForm.value.autoSyncPageSize || 20),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
khhaoFilePath.value = response.data.filePath
|
||||||
|
khhaoStateFilePath.value = response.data.stateFilePath
|
||||||
|
khhaoAutoSyncState.value = response.data.syncState
|
||||||
|
showSuccess('khhao 来源配置已保存')
|
||||||
|
} catch (error) {
|
||||||
|
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 来源配置保存失败'
|
||||||
|
showError(khhaoResultError.value)
|
||||||
|
} finally {
|
||||||
|
khhaoSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKhhaoTestLogin() {
|
||||||
|
if (!ensureKhhaoCredentials()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
khhaoTesting.value = true
|
||||||
|
khhaoResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await testAdminKhhaoLogin({
|
||||||
|
...buildKhhaoPayload(),
|
||||||
|
includeImageBase64: false,
|
||||||
|
})
|
||||||
|
khhaoLoginResult.value = response.data
|
||||||
|
showSuccess('khhao 登录测试成功')
|
||||||
|
} catch (error) {
|
||||||
|
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 登录测试失败'
|
||||||
|
showError(khhaoResultError.value)
|
||||||
|
} finally {
|
||||||
|
khhaoTesting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKhhaoQueryOrders() {
|
||||||
|
if (!ensureKhhaoCredentials()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
khhaoQuerying.value = true
|
||||||
|
khhaoResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await queryAdminKhhaoOrders(buildKhhaoPayload())
|
||||||
|
khhaoQueryResult.value = response.data
|
||||||
|
showSuccess(`khhao 订单查询成功,共返回 ${response.data.itemCount} 条`)
|
||||||
|
} catch (error) {
|
||||||
|
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单查询失败'
|
||||||
|
showError(khhaoResultError.value)
|
||||||
|
} finally {
|
||||||
|
khhaoQuerying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKhhaoSyncOrders() {
|
||||||
|
if (!ensureKhhaoCredentials()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
khhaoSyncing.value = true
|
||||||
|
khhaoResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await syncAdminKhhaoOrders(buildKhhaoPayload())
|
||||||
|
khhaoSyncResult.value = response.data
|
||||||
|
showSuccess(`khhao 同步完成:拉取 ${response.data.fetchedCount} 条,成功 ${response.data.syncedCount} 条`)
|
||||||
|
} catch (error) {
|
||||||
|
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单同步失败'
|
||||||
|
showError(khhaoResultError.value)
|
||||||
|
} finally {
|
||||||
|
khhaoSyncing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
khhaoFilePath,
|
||||||
|
khhaoStateFilePath,
|
||||||
|
khhaoForm,
|
||||||
|
khhaoSaving,
|
||||||
|
khhaoTesting,
|
||||||
|
khhaoQuerying,
|
||||||
|
khhaoSyncing,
|
||||||
|
khhaoResultError,
|
||||||
|
khhaoLoginResult,
|
||||||
|
khhaoQueryResult,
|
||||||
|
khhaoSyncResult,
|
||||||
|
khhaoAutoSyncState,
|
||||||
|
khhaoStats,
|
||||||
|
hydrateKhhaoConfig,
|
||||||
|
handleKhhaoSaveSource,
|
||||||
|
handleKhhaoTestLogin,
|
||||||
|
handleKhhaoQueryOrders,
|
||||||
|
handleKhhaoSyncOrders,
|
||||||
|
}
|
||||||
|
}
|
||||||
+382
@@ -0,0 +1,382 @@
|
|||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import { showError, showSuccess } from '@/lib/feedback'
|
||||||
|
import {
|
||||||
|
consumeAdminKuaishouEticket,
|
||||||
|
queryAdminKuaishouEticketDetail,
|
||||||
|
queryAdminKuaishouEticketShopInfo,
|
||||||
|
saveAdminKuaishouEticketSourceConfig,
|
||||||
|
} from '@/services/admin'
|
||||||
|
import type {
|
||||||
|
AdminKuaishouEticketConsumeResult,
|
||||||
|
AdminKuaishouEticketDetailResult,
|
||||||
|
AdminKuaishouEticketShopConfigItem,
|
||||||
|
AdminKuaishouEticketShopInfoResult,
|
||||||
|
AdminKuaishouEticketSourceConfig,
|
||||||
|
} from '@/types/admin'
|
||||||
|
|
||||||
|
import type { EditableKuaishouEticketShop } from './types'
|
||||||
|
|
||||||
|
export function useAdminKuaishouEticketPlatform() {
|
||||||
|
const kuaishouEticketFilePath = ref('')
|
||||||
|
const kuaishouEticketShops = ref<EditableKuaishouEticketShop[]>([])
|
||||||
|
const expandedKuaishouEticketShopIds = ref<string[]>([])
|
||||||
|
const kuaishouEticketForm = ref({
|
||||||
|
baseUrl: 'https://s.kwaixiaodian.com',
|
||||||
|
selectedShopId: '',
|
||||||
|
queryTicketCode: '',
|
||||||
|
eTicketId: '',
|
||||||
|
oid: '',
|
||||||
|
formToken: '',
|
||||||
|
num: 1,
|
||||||
|
storeId: '0',
|
||||||
|
})
|
||||||
|
const kuaishouEticketSaving = ref(false)
|
||||||
|
const kuaishouEticketResolvingShopId = ref('')
|
||||||
|
const kuaishouEticketDetailing = ref(false)
|
||||||
|
const kuaishouEticketConsuming = ref(false)
|
||||||
|
const kuaishouEticketResultError = ref('')
|
||||||
|
const kuaishouEticketShopInfoResult = ref<AdminKuaishouEticketShopInfoResult | null>(null)
|
||||||
|
const kuaishouEticketDetailResult = ref<AdminKuaishouEticketDetailResult | null>(null)
|
||||||
|
const kuaishouEticketConsumeResult = ref<AdminKuaishouEticketConsumeResult | null>(null)
|
||||||
|
|
||||||
|
const activeKuaishouEticketShop = computed(() => (
|
||||||
|
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,
|
||||||
|
selectedShopReady: Boolean(activeKuaishouEticketShop.value?.cookie.trim()),
|
||||||
|
detailReady: Boolean(kuaishouEticketDetailResult.value?.detail),
|
||||||
|
consumed: Boolean(kuaishouEticketConsumeResult.value?.consumed),
|
||||||
|
}))
|
||||||
|
|
||||||
|
function hydrateKuaishouEticketConfig(data: {
|
||||||
|
filePath: string
|
||||||
|
source: {
|
||||||
|
baseUrl?: string
|
||||||
|
shops: AdminKuaishouEticketShopConfigItem[]
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
kuaishouEticketFilePath.value = data.filePath
|
||||||
|
kuaishouEticketShops.value = data.source.shops.map(mapEditableKuaishouEticketShop)
|
||||||
|
kuaishouEticketForm.value = {
|
||||||
|
baseUrl: data.source.baseUrl || 'https://s.kwaixiaodian.com',
|
||||||
|
selectedShopId: '',
|
||||||
|
queryTicketCode: '',
|
||||||
|
eTicketId: '',
|
||||||
|
oid: '',
|
||||||
|
formToken: '',
|
||||||
|
num: 1,
|
||||||
|
storeId: '0',
|
||||||
|
}
|
||||||
|
kuaishouEticketForm.value.selectedShopId = kuaishouEticketShops.value[0]?.id || ''
|
||||||
|
expandedKuaishouEticketShopIds.value = kuaishouEticketShops.value[0]?.id ? [kuaishouEticketShops.value[0].id] : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildKuaishouEticketConfigPayload(): AdminKuaishouEticketSourceConfig {
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
baseUrl: kuaishouEticketForm.value.baseUrl.trim() || 'https://s.kwaixiaodian.com',
|
||||||
|
shops: kuaishouEticketShops.value.map((item) => ({
|
||||||
|
shopId: item.shopId.trim(),
|
||||||
|
kshopName: item.kshopName.trim(),
|
||||||
|
cookie: item.cookie.trim(),
|
||||||
|
cookieMasked: '',
|
||||||
|
hasCookie: Boolean(item.cookie.trim()),
|
||||||
|
userAvatar: item.userAvatar.trim(),
|
||||||
|
enabled: item.enabled,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKuaishouEticketShop() {
|
||||||
|
const shop = createEmptyKuaishouEticketShop()
|
||||||
|
kuaishouEticketShops.value.unshift(shop)
|
||||||
|
setKuaishouEticketDebugShop(shop.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSelectedKuaishouEticketShop() {
|
||||||
|
const selected = activeKuaishouEticketShop.value
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
kuaishouEticketResultError.value = '请先新增并选择一个店铺配置'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selected.cookie.trim()) {
|
||||||
|
kuaishouEticketResultError.value = '当前店铺缺少 Cookie,请先补全后再操作'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureKuaishouEticketQueryFields() {
|
||||||
|
const selected = ensureSelectedKuaishouEticketShop()
|
||||||
|
if (!selected) return false
|
||||||
|
|
||||||
|
if (!kuaishouEticketForm.value.queryTicketCode.trim()) {
|
||||||
|
kuaishouEticketResultError.value = '请先填写查询券码'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureKuaishouEticketConsumeFields() {
|
||||||
|
const selected = ensureSelectedKuaishouEticketShop()
|
||||||
|
if (!selected) return false
|
||||||
|
|
||||||
|
if (!kuaishouEticketDetailResult.value?.detail) {
|
||||||
|
kuaishouEticketResultError.value = '请先查询核销信息,系统会自动带出 eTicketId、oid 和 formToken'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyKuaishouEticketDetailToForm(result: AdminKuaishouEticketDetailResult | null) {
|
||||||
|
if (!result?.detail) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kuaishouEticketForm.value.eTicketId = result.detail.eTicketId || kuaishouEticketForm.value.eTicketId
|
||||||
|
kuaishouEticketForm.value.oid = result.detail.oid || kuaishouEticketForm.value.oid
|
||||||
|
kuaishouEticketForm.value.formToken = result.detail.formToken || kuaishouEticketForm.value.formToken
|
||||||
|
|
||||||
|
const leftCount = Number(result.detail.leftCount || 0)
|
||||||
|
if (leftCount > 0) {
|
||||||
|
kuaishouEticketForm.value.num = leftCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKuaishouEticketResolveShopInfo(shop: EditableKuaishouEticketShop) {
|
||||||
|
if (!shop.cookie.trim()) {
|
||||||
|
kuaishouEticketResultError.value = '请先为该店铺粘贴 Cookie'
|
||||||
|
showError(kuaishouEticketResultError.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kuaishouEticketResolvingShopId.value = shop.id
|
||||||
|
kuaishouEticketResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await queryAdminKuaishouEticketShopInfo({
|
||||||
|
baseUrl: kuaishouEticketForm.value.baseUrl.trim() || 'https://s.kwaixiaodian.com',
|
||||||
|
shopId: shop.shopId.trim(),
|
||||||
|
cookie: shop.cookie.trim(),
|
||||||
|
})
|
||||||
|
kuaishouEticketShopInfoResult.value = response.data
|
||||||
|
shop.shopId = response.data.shop.shopId || shop.shopId
|
||||||
|
shop.kshopName = response.data.shop.kshopName || shop.kshopName
|
||||||
|
shop.userAvatar = response.data.shop.userAvatar || shop.userAvatar
|
||||||
|
if (!kuaishouEticketForm.value.selectedShopId) {
|
||||||
|
setKuaishouEticketDebugShop(shop.id)
|
||||||
|
}
|
||||||
|
showSuccess(`已识别店铺:${response.data.shop.kshopName || response.data.shop.shopId || '未知店铺'}`)
|
||||||
|
} catch (error) {
|
||||||
|
kuaishouEticketResultError.value = error instanceof Error ? error.message : '读取快手小店店铺信息失败'
|
||||||
|
showError(kuaishouEticketResultError.value)
|
||||||
|
} finally {
|
||||||
|
kuaishouEticketResolvingShopId.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKuaishouEticketSaveSource() {
|
||||||
|
kuaishouEticketSaving.value = true
|
||||||
|
kuaishouEticketResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await saveAdminKuaishouEticketSourceConfig(buildKuaishouEticketConfigPayload())
|
||||||
|
kuaishouEticketFilePath.value = response.data.filePath
|
||||||
|
kuaishouEticketForm.value.baseUrl = response.data.source.baseUrl || 'https://s.kwaixiaodian.com'
|
||||||
|
const previousSelectedShopId = activeKuaishouEticketShop.value?.shopId || ''
|
||||||
|
kuaishouEticketShops.value = response.data.source.shops.map(mapEditableKuaishouEticketShop)
|
||||||
|
kuaishouEticketForm.value.selectedShopId = (
|
||||||
|
kuaishouEticketShops.value.find((item) => item.shopId && item.shopId === previousSelectedShopId)?.id
|
||||||
|
|| kuaishouEticketShops.value[0]?.id
|
||||||
|
|| ''
|
||||||
|
)
|
||||||
|
expandedKuaishouEticketShopIds.value = kuaishouEticketForm.value.selectedShopId
|
||||||
|
? [kuaishouEticketForm.value.selectedShopId]
|
||||||
|
: []
|
||||||
|
showSuccess('快手小店核销配置已保存')
|
||||||
|
} catch (error) {
|
||||||
|
kuaishouEticketResultError.value = error instanceof Error ? error.message : '快手小店核销配置保存失败'
|
||||||
|
showError(kuaishouEticketResultError.value)
|
||||||
|
} finally {
|
||||||
|
kuaishouEticketSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKuaishouEticketQueryDetail() {
|
||||||
|
if (!ensureKuaishouEticketQueryFields()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = activeKuaishouEticketShop.value
|
||||||
|
kuaishouEticketDetailing.value = true
|
||||||
|
kuaishouEticketResultError.value = ''
|
||||||
|
kuaishouEticketConsumeResult.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await queryAdminKuaishouEticketDetail({
|
||||||
|
baseUrl: kuaishouEticketForm.value.baseUrl.trim() || 'https://s.kwaixiaodian.com',
|
||||||
|
shopId: selected?.shopId.trim(),
|
||||||
|
cookie: selected?.cookie.trim(),
|
||||||
|
eTicketId: kuaishouEticketForm.value.queryTicketCode.trim(),
|
||||||
|
})
|
||||||
|
kuaishouEticketDetailResult.value = response.data
|
||||||
|
applyKuaishouEticketDetailToForm(response.data)
|
||||||
|
showSuccess(response.data.ok ? '核销信息查询成功' : (response.data.errorMessage || '核销信息已返回'))
|
||||||
|
} catch (error) {
|
||||||
|
kuaishouEticketResultError.value = error instanceof Error ? error.message : '查询快手小店核销信息失败'
|
||||||
|
showError(kuaishouEticketResultError.value)
|
||||||
|
} finally {
|
||||||
|
kuaishouEticketDetailing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleKuaishouEticketConsume() {
|
||||||
|
if (!ensureKuaishouEticketConsumeFields()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = activeKuaishouEticketShop.value
|
||||||
|
kuaishouEticketConsuming.value = true
|
||||||
|
kuaishouEticketResultError.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const detail = kuaishouEticketDetailResult.value?.detail
|
||||||
|
const response = await consumeAdminKuaishouEticket({
|
||||||
|
baseUrl: kuaishouEticketForm.value.baseUrl.trim() || 'https://s.kwaixiaodian.com',
|
||||||
|
shopId: selected?.shopId.trim(),
|
||||||
|
cookie: selected?.cookie.trim(),
|
||||||
|
eTicketId: String(detail?.eTicketId || kuaishouEticketForm.value.eTicketId || '').trim(),
|
||||||
|
oid: String(detail?.oid || kuaishouEticketForm.value.oid || '').trim(),
|
||||||
|
formToken: String(detail?.formToken || kuaishouEticketForm.value.formToken || '').trim(),
|
||||||
|
num: Number(kuaishouEticketForm.value.num || 1),
|
||||||
|
storeId: kuaishouEticketForm.value.storeId.trim() || '0',
|
||||||
|
})
|
||||||
|
kuaishouEticketConsumeResult.value = response.data
|
||||||
|
showSuccess(response.data.ok ? '快手小店核销成功' : (response.data.errorMessage || '核销请求已执行'))
|
||||||
|
} catch (error) {
|
||||||
|
kuaishouEticketResultError.value = error instanceof Error ? error.message : '执行快手小店核销失败'
|
||||||
|
showError(kuaishouEticketResultError.value)
|
||||||
|
} finally {
|
||||||
|
kuaishouEticketConsuming.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
kuaishouEticketFilePath,
|
||||||
|
kuaishouEticketShops,
|
||||||
|
expandedKuaishouEticketShopIds,
|
||||||
|
kuaishouEticketForm,
|
||||||
|
kuaishouEticketSaving,
|
||||||
|
kuaishouEticketResolvingShopId,
|
||||||
|
kuaishouEticketDetailing,
|
||||||
|
kuaishouEticketConsuming,
|
||||||
|
kuaishouEticketResultError,
|
||||||
|
kuaishouEticketShopInfoResult,
|
||||||
|
kuaishouEticketDetailResult,
|
||||||
|
kuaishouEticketConsumeResult,
|
||||||
|
activeKuaishouEticketShop,
|
||||||
|
kuaishouEticketStats,
|
||||||
|
hydrateKuaishouEticketConfig,
|
||||||
|
addKuaishouEticketShop,
|
||||||
|
removeKuaishouEticketShop,
|
||||||
|
isKuaishouEticketShopExpanded,
|
||||||
|
expandKuaishouEticketShop,
|
||||||
|
collapseKuaishouEticketShop,
|
||||||
|
toggleKuaishouEticketShop,
|
||||||
|
expandAllKuaishouEticketShops,
|
||||||
|
collapseAllKuaishouEticketShops,
|
||||||
|
setKuaishouEticketDebugShop,
|
||||||
|
describeKuaishouEticketShop,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user