admin后台 第一阶段目录优化
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
export type PlatformTab = 'agiso' | 'notifications' | 'ninetyone' | '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
|
||||
}
|
||||
|
||||
export type EditableNotificationRecipient = {
|
||||
id: string
|
||||
name: string
|
||||
deviceKey: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export type EditableScheduledJob = {
|
||||
id: string
|
||||
type: string
|
||||
enabled: boolean
|
||||
intervalSeconds: number
|
||||
intervalSecondsAmount: number
|
||||
intervalSecondsUnit: number
|
||||
cooldownSeconds: number
|
||||
cooldownSecondsAmount: number
|
||||
cooldownSecondsUnit: number
|
||||
assetThreshold: number
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,458 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
-382
@@ -1,382 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
failAdminNinetyoneOrder,
|
||||
fetchAdminNinetyoneOrders,
|
||||
retryAdminNinetyoneOrder,
|
||||
} from '@/services/admin'
|
||||
import type { AdminNinetyoneOrderItem, AdminNinetyoneOrderListResult } from '@/types/admin'
|
||||
|
||||
export function useAdminNinetyonePlatform() {
|
||||
const ninetyoneLoading = ref(false)
|
||||
const ninetyoneActionLoadingId = ref<number | null>(null)
|
||||
const ninetyoneResultError = ref('')
|
||||
const ninetyoneStatus = ref<'pending_config' | 'all' | 'manual_failed'>('pending_config')
|
||||
const ninetyoneOrders = ref<AdminNinetyoneOrderListResult>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
items: [],
|
||||
})
|
||||
|
||||
const ninetyoneStats = computed(() => {
|
||||
const pendingCount = ninetyoneOrders.value.items.filter((item) => item.orderStatus === 'pending_config').length
|
||||
const failedCount = ninetyoneOrders.value.items.filter((item) => item.orderStatus === 'manual_failed').length
|
||||
const taskReadyCount = ninetyoneOrders.value.items.filter((item) => item.taskCount > 0).length
|
||||
|
||||
return {
|
||||
total: ninetyoneOrders.value.total,
|
||||
currentCount: ninetyoneOrders.value.items.length,
|
||||
pendingCount,
|
||||
failedCount,
|
||||
taskReadyCount,
|
||||
}
|
||||
})
|
||||
|
||||
async function loadNinetyoneOrders(page = 1) {
|
||||
ninetyoneLoading.value = true
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetchAdminNinetyoneOrders({
|
||||
page,
|
||||
pageSize: ninetyoneOrders.value.pageSize || 20,
|
||||
status: ninetyoneStatus.value,
|
||||
})
|
||||
ninetyoneOrders.value = response.data
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单读取失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNinetyoneStatusChange(status: 'pending_config' | 'all' | 'manual_failed') {
|
||||
ninetyoneStatus.value = status
|
||||
await loadNinetyoneOrders(1)
|
||||
}
|
||||
|
||||
async function handleNinetyoneRetryOrder(item: AdminNinetyoneOrderItem) {
|
||||
ninetyoneActionLoadingId.value = item.orderId
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await retryAdminNinetyoneOrder(item.orderId)
|
||||
showSuccess(`91卡券订单已重试,生成任务 ${response.data.taskCount} 个`)
|
||||
await loadNinetyoneOrders(ninetyoneOrders.value.page || 1)
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单重试失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneActionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNinetyoneFailOrder(item: AdminNinetyoneOrderItem) {
|
||||
try {
|
||||
await showConfirm(`确认把 91卡券订单 ${item.orderNo} 标记为无法履约吗?查询接口会返回失败状态。`, '确认标记失败', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '标记失败',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
ninetyoneActionLoadingId.value = item.orderId
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
await failAdminNinetyoneOrder(item.orderId, {
|
||||
reason: '商家后台手动标记无法履约',
|
||||
})
|
||||
showSuccess('91卡券订单已标记失败')
|
||||
await loadNinetyoneOrders(ninetyoneOrders.value.page || 1)
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单标记失败失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneActionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ninetyoneLoading,
|
||||
ninetyoneActionLoadingId,
|
||||
ninetyoneResultError,
|
||||
ninetyoneStatus,
|
||||
ninetyoneOrders,
|
||||
ninetyoneStats,
|
||||
loadNinetyoneOrders,
|
||||
handleNinetyoneStatusChange,
|
||||
handleNinetyoneRetryOrder,
|
||||
handleNinetyoneFailOrder,
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
runAdminScheduledJob,
|
||||
saveAdminNotificationConfig,
|
||||
saveAdminScheduledJobsConfig,
|
||||
testAdminNotification,
|
||||
} from '@/services/admin'
|
||||
import type {
|
||||
AdminNotificationConfig,
|
||||
AdminNotificationTestResult,
|
||||
AdminScheduledJobRuntimeState,
|
||||
AdminScheduledJobsConfig,
|
||||
AdminScheduledJobsResponse,
|
||||
} from '@/types/admin'
|
||||
|
||||
import type { EditableNotificationRecipient, EditableScheduledJob } from './types'
|
||||
|
||||
export function useAdminNotificationPlatform() {
|
||||
const notificationFilePath = ref('')
|
||||
const notificationForm = ref({
|
||||
enabled: true,
|
||||
barkEnabled: true,
|
||||
barkServerUrl: 'https://api.day.app',
|
||||
testTitle: '订单系统测试通知',
|
||||
testBody: '这是一条 Bark 内部通知测试。',
|
||||
testUrl: '',
|
||||
})
|
||||
const notificationRecipients = ref<EditableNotificationRecipient[]>([])
|
||||
const notificationSaving = ref(false)
|
||||
const notificationTesting = ref(false)
|
||||
const notificationResultError = ref('')
|
||||
const notificationTestResult = ref<AdminNotificationTestResult | null>(null)
|
||||
const scheduledJobsFilePath = ref('')
|
||||
const scheduledJobsForm = ref({
|
||||
enabled: true,
|
||||
})
|
||||
const scheduledJobs = ref<EditableScheduledJob[]>([])
|
||||
const scheduledJobRuntime = ref<AdminScheduledJobRuntimeState[]>([])
|
||||
const scheduledJobsSaving = ref(false)
|
||||
const scheduledJobRunningId = ref('')
|
||||
|
||||
const notificationStats = computed(() => {
|
||||
const enabledRecipients = notificationRecipients.value.filter((item) => item.enabled && item.deviceKey.trim())
|
||||
|
||||
return {
|
||||
configuredRecipientCount: notificationRecipients.value.length,
|
||||
enabledRecipientCount: enabledRecipients.length,
|
||||
barkReady: notificationForm.value.enabled && notificationForm.value.barkEnabled && enabledRecipients.length > 0,
|
||||
lastSuccessCount: notificationTestResult.value?.successCount || 0,
|
||||
lastFailedCount: notificationTestResult.value?.failedCount || 0,
|
||||
scheduledJobEnabledCount: scheduledJobs.value.filter((item) => item.enabled).length,
|
||||
}
|
||||
})
|
||||
|
||||
function hydrateNotificationConfig(data: {
|
||||
filePath: string
|
||||
source: AdminNotificationConfig
|
||||
}) {
|
||||
notificationFilePath.value = data.filePath
|
||||
notificationForm.value.enabled = data.source.enabled !== false
|
||||
notificationForm.value.barkEnabled = data.source.channels.bark.enabled !== false
|
||||
notificationForm.value.barkServerUrl = data.source.channels.bark.serverUrl || 'https://api.day.app'
|
||||
notificationRecipients.value = data.source.channels.bark.recipients.map((item) => ({
|
||||
id: item.id || crypto.randomUUID(),
|
||||
name: item.name,
|
||||
deviceKey: item.deviceKey,
|
||||
enabled: item.enabled !== false,
|
||||
}))
|
||||
}
|
||||
|
||||
function hydrateScheduledJobsConfig(data: AdminScheduledJobsResponse) {
|
||||
scheduledJobsFilePath.value = data.filePath
|
||||
scheduledJobsForm.value.enabled = data.source.enabled !== false
|
||||
scheduledJobs.value = data.source.jobs.map((item) => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
enabled: item.enabled === true,
|
||||
intervalSeconds: Number(item.intervalSeconds || 300),
|
||||
intervalSecondsAmount: resolveTimeValue(Number(item.intervalSeconds || 300)).amount,
|
||||
intervalSecondsUnit: resolveTimeValue(Number(item.intervalSeconds || 300)).unit,
|
||||
cooldownSeconds: Number(item.cooldownSeconds || 1800),
|
||||
cooldownSecondsAmount: resolveTimeValue(Number(item.cooldownSeconds || 1800)).amount,
|
||||
cooldownSecondsUnit: resolveTimeValue(Number(item.cooldownSeconds || 1800)).unit,
|
||||
assetThreshold: Number(item.config?.assetThreshold || 500),
|
||||
}))
|
||||
scheduledJobRuntime.value = data.runtime || []
|
||||
}
|
||||
|
||||
function addNotificationRecipient() {
|
||||
notificationRecipients.value.unshift({
|
||||
id: crypto.randomUUID(),
|
||||
name: '',
|
||||
deviceKey: '',
|
||||
enabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
function removeNotificationRecipient(id: string) {
|
||||
notificationRecipients.value = notificationRecipients.value.filter((item) => item.id !== id)
|
||||
}
|
||||
|
||||
function buildNotificationConfigPayload(): AdminNotificationConfig {
|
||||
return {
|
||||
enabled: notificationForm.value.enabled,
|
||||
channels: {
|
||||
bark: {
|
||||
enabled: notificationForm.value.barkEnabled,
|
||||
serverUrl: notificationForm.value.barkServerUrl.trim() || 'https://api.day.app',
|
||||
recipients: notificationRecipients.value.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name.trim(),
|
||||
deviceKey: item.deviceKey.trim(),
|
||||
deviceKeyMasked: '',
|
||||
enabled: item.enabled,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function buildScheduledJobsConfigPayload(): AdminScheduledJobsConfig {
|
||||
return {
|
||||
enabled: scheduledJobsForm.value.enabled,
|
||||
jobs: scheduledJobs.value.map((item) => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
enabled: item.enabled,
|
||||
intervalSeconds: Number(item.intervalSeconds || 300),
|
||||
cooldownSeconds: Number(item.cooldownSeconds || 1800),
|
||||
config: {
|
||||
assetThreshold: Number(item.assetThreshold || 500),
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNotificationSaveConfig() {
|
||||
notificationSaving.value = true
|
||||
notificationResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await saveAdminNotificationConfig(buildNotificationConfigPayload())
|
||||
notificationFilePath.value = response.data.filePath
|
||||
hydrateNotificationConfig(response.data)
|
||||
showSuccess('内部通知配置已保存')
|
||||
} catch (error) {
|
||||
notificationResultError.value = error instanceof Error ? error.message : '内部通知配置保存失败'
|
||||
showError(notificationResultError.value)
|
||||
} finally {
|
||||
notificationSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNotificationTest() {
|
||||
notificationTesting.value = true
|
||||
notificationResultError.value = ''
|
||||
notificationTestResult.value = null
|
||||
|
||||
try {
|
||||
const saved = await saveAdminNotificationConfig(buildNotificationConfigPayload())
|
||||
notificationFilePath.value = saved.data.filePath
|
||||
hydrateNotificationConfig(saved.data)
|
||||
|
||||
const response = await testAdminNotification({
|
||||
title: notificationForm.value.testTitle.trim(),
|
||||
body: notificationForm.value.testBody.trim(),
|
||||
url: notificationForm.value.testUrl.trim(),
|
||||
})
|
||||
notificationTestResult.value = response.data
|
||||
if (response.data.successCount > 0) {
|
||||
showSuccess(`内部通知测试完成,成功 ${response.data.successCount} 个`)
|
||||
} else {
|
||||
showError('内部通知测试未成功发送,请检查 Bark 配置')
|
||||
}
|
||||
} catch (error) {
|
||||
notificationResultError.value = error instanceof Error ? error.message : '内部通知测试失败'
|
||||
showError(notificationResultError.value)
|
||||
} finally {
|
||||
notificationTesting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleScheduledJobsSaveConfig() {
|
||||
scheduledJobsSaving.value = true
|
||||
notificationResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await saveAdminScheduledJobsConfig(buildScheduledJobsConfigPayload())
|
||||
hydrateScheduledJobsConfig(response.data)
|
||||
showSuccess('监控任务配置已保存')
|
||||
} catch (error) {
|
||||
notificationResultError.value = error instanceof Error ? error.message : '监控任务配置保存失败'
|
||||
showError(notificationResultError.value)
|
||||
} finally {
|
||||
scheduledJobsSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleScheduledJobRunNow(jobId: string) {
|
||||
scheduledJobRunningId.value = jobId
|
||||
notificationResultError.value = ''
|
||||
|
||||
try {
|
||||
const saved = await saveAdminScheduledJobsConfig(buildScheduledJobsConfigPayload())
|
||||
hydrateScheduledJobsConfig(saved.data)
|
||||
const response = await runAdminScheduledJob(jobId)
|
||||
scheduledJobRuntime.value = response.data.runtime || []
|
||||
const message = String(response.data.result?.message || '监控任务已执行')
|
||||
showSuccess(message)
|
||||
} catch (error) {
|
||||
notificationResultError.value = error instanceof Error ? error.message : '监控任务执行失败'
|
||||
showError(notificationResultError.value)
|
||||
} finally {
|
||||
scheduledJobRunningId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function getScheduledJobRuntime(jobId: string) {
|
||||
return scheduledJobRuntime.value.find((item) => item.id === jobId) || null
|
||||
}
|
||||
|
||||
function resolveTimeValue(seconds: number) {
|
||||
const normalized = Math.max(0, Number(seconds || 0))
|
||||
if (normalized >= 3600 && normalized % 3600 === 0) {
|
||||
return { amount: normalized / 3600, unit: 3600 }
|
||||
}
|
||||
|
||||
if (normalized >= 60 && normalized % 60 === 0) {
|
||||
return { amount: normalized / 60, unit: 60 }
|
||||
}
|
||||
|
||||
return { amount: normalized || 1, unit: 1 }
|
||||
}
|
||||
|
||||
return {
|
||||
notificationFilePath,
|
||||
notificationForm,
|
||||
notificationRecipients,
|
||||
notificationSaving,
|
||||
notificationTesting,
|
||||
notificationResultError,
|
||||
notificationTestResult,
|
||||
scheduledJobsFilePath,
|
||||
scheduledJobsForm,
|
||||
scheduledJobs,
|
||||
scheduledJobRuntime,
|
||||
scheduledJobsSaving,
|
||||
scheduledJobRunningId,
|
||||
notificationStats,
|
||||
hydrateNotificationConfig,
|
||||
hydrateScheduledJobsConfig,
|
||||
addNotificationRecipient,
|
||||
removeNotificationRecipient,
|
||||
handleNotificationSaveConfig,
|
||||
handleNotificationTest,
|
||||
handleScheduledJobsSaveConfig,
|
||||
handleScheduledJobRunNow,
|
||||
getScheduledJobRuntime,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user