重构:前端架构优化 - 消灭巨石文件,统一代码规范
Phase 1 - 消灭巨石页面: - 拆分 AdminKuaishouCloudFulfillmentView (1,224行→kuaishou-cloud/子目录) - 拆分 AdminFulfillmentBindingsView (989行→bindings/子目录) - 拆分 AdminTaskDetailView (1,007行→9个子组件+2个composable) - 合并去重 useClaimPage + useAdminManualRedeemPage (1,554行→共享模块+差异化薄层) - 拆分 services/admin/platform-config.ts (477行→8个领域子模块) Phase 2 - 架构收口: - 拆分 types/admin.ts (925行→13个子文件+platform-config/子目录) - 统一 API code 检查(http.ts拦截器统一处理业务错误) - 修改 apiPost 签名消除 as unknown as 类型断言(6处) - 新增 BusinessError 类型便于错误分类处理 所有改动通过 vue-tsc --noEmit 零错误和 vite build 验证
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
actionLoading: boolean
|
||||
canManageTaskLifecycle: boolean
|
||||
canCloseTasks: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
retry: []
|
||||
regenerateClaimLink: []
|
||||
confirmRole: []
|
||||
supportRedeem: []
|
||||
prepareKuaishouCloud: []
|
||||
dispatchKuaishouCloud: []
|
||||
returnKuaishouCloud: []
|
||||
markManualReview: []
|
||||
submitManualDispatch: [outcome: 'delivered' | 'failed']
|
||||
closeTask: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="action-row">
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle"
|
||||
:disabled="!detail.operations.canRetry"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="primary"
|
||||
@click="emit('retry')"
|
||||
>
|
||||
重试任务
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="detail.operations.canRegenerateClaimLink"
|
||||
:disabled="!detail.operations.canRegenerateClaimLink"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
@click="emit('regenerateClaimLink')"
|
||||
>
|
||||
重发链接
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="detail.operations.canSupportConfirmRole"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="warning"
|
||||
@click="emit('confirmRole')"
|
||||
>
|
||||
客服确认角色
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="detail.operations.canSupportRedeem"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="success"
|
||||
@click="emit('supportRedeem')"
|
||||
>
|
||||
客服开始兑换
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle && detail.operations.canPrepareKuaishouCloudFulfillment"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="primary"
|
||||
@click="emit('prepareKuaishouCloud')"
|
||||
>
|
||||
准备绑定资源
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle && detail.operations.canDispatchKuaishouCloudFulfillment"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="success"
|
||||
@click="emit('dispatchKuaishouCloud')"
|
||||
>
|
||||
确认绑定完成并发货
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle && detail.operations.canReturnKuaishouCloudFulfillment"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="warning"
|
||||
@click="emit('returnKuaishouCloud')"
|
||||
>
|
||||
退还号码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle"
|
||||
:disabled="!detail.operations.canMarkManualReview"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
@click="emit('markManualReview')"
|
||||
>
|
||||
转人工
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle && detail.operations.canCompleteManualDispatch"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="success"
|
||||
@click="emit('submitManualDispatch', 'delivered')"
|
||||
>
|
||||
人工完成
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canManageTaskLifecycle && detail.operations.canCompleteManualDispatch"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="danger"
|
||||
plain
|
||||
@click="emit('submitManualDispatch', 'failed')"
|
||||
>
|
||||
履约失败
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canCloseTasks"
|
||||
:disabled="!detail.operations.canClose"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="danger"
|
||||
@click="emit('closeTask')"
|
||||
>
|
||||
关闭任务
|
||||
</el-button>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatAutoDeliveryReason, formatAutoDeliveryTrigger } from '@/utils/admin-display'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
autoDeliveryStatus: string
|
||||
autoDeliverySummary: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>咸鱼自动发货</h3>
|
||||
<p class="section-copy">
|
||||
这里展示这条任务最近一次触发咸鱼自动发货的状态、原因和接口回执摘要。
|
||||
</p>
|
||||
|
||||
<div class="redeem-summary-grid">
|
||||
<article class="redeem-summary-card">
|
||||
<span>当前状态</span>
|
||||
<div class="auto-delivery-tag-line">
|
||||
<AdminStatusTag :status="autoDeliveryStatus" />
|
||||
</div>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>触发来源</span>
|
||||
<strong>{{
|
||||
formatAutoDeliveryTrigger(detail.task.agisoAutoDelivery?.trigger || '')
|
||||
}}</strong>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>最近执行</span>
|
||||
<strong>{{ formatAdminDateTime(detail.task.agisoAutoDelivery?.updatedAt) }}</strong>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>接口回执</span>
|
||||
<strong>
|
||||
{{
|
||||
detail.task.agisoAutoDelivery?.requestId ||
|
||||
(detail.task.agisoAutoDelivery?.responseStatus
|
||||
? `HTTP ${detail.task.agisoAutoDelivery.responseStatus}`
|
||||
: '-')
|
||||
}}
|
||||
</strong>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="auto-delivery-note">
|
||||
<p>{{ autoDeliverySummary }}</p>
|
||||
<p v-if="detail.task.agisoAutoDelivery?.reason">
|
||||
原因:{{ formatAutoDeliveryReason(detail.task.agisoAutoDelivery.reason) }}
|
||||
</p>
|
||||
<p v-if="detail.task.agisoAutoDelivery?.responseStatus">
|
||||
HTTP:{{ detail.task.agisoAutoDelivery.responseStatus }}
|
||||
</p>
|
||||
<p v-if="detail.task.agisoAutoDelivery?.requestId">
|
||||
请求 ID:{{ detail.task.agisoAutoDelivery.requestId }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatTaskEventPayload, formatTaskEventType } from '@/utils/admin-display'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>任务事件</h3>
|
||||
<table class="data-table">
|
||||
<tbody>
|
||||
<tr v-for="event in detail.events" :key="event.eventId">
|
||||
<th>{{ formatTaskEventType(event.eventType) }}</th>
|
||||
<td>
|
||||
<div class="event-copy">
|
||||
{{ formatTaskEventPayload(event.payload) }}
|
||||
</div>
|
||||
<div class="event-time">
|
||||
{{ formatAdminDateTime(event.createdAt) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
|
||||
import type { SummaryCard } from '../composables/types'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
isKuaishouCloudTask: boolean
|
||||
summaryCards: SummaryCard[]
|
||||
claimUrl: string
|
||||
claimLinkInvalid: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Kuaishou Cloud hero card -->
|
||||
<section v-if="isKuaishouCloudTask && detail.kuaishouCloudFulfillment" class="hero-card">
|
||||
<div class="hero-copy">
|
||||
<span class="hero-eyebrow">{{ detail.task.executorKey || 'task' }}</span>
|
||||
<h2>{{ detail.task.taskNo }}</h2>
|
||||
<p>
|
||||
订单 {{ detail.order?.platformOrderId || '-' }} · 商品
|
||||
{{ detail.orderItem?.skuName || detail.orderItem?.skuCode || '-' }}
|
||||
· 店铺
|
||||
{{ detail.order?.shopName || detail.order?.shopId || '-' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-meta-grid">
|
||||
<article
|
||||
v-for="card in summaryCards"
|
||||
:key="card.label"
|
||||
:class="['hero-metric', `hero-metric--${card.tone}`]"
|
||||
>
|
||||
<span>{{ card.label }}</span>
|
||||
<strong>{{ card.value }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
<div class="hero-inline">
|
||||
<span>系统绑定 <AdminStatusTag :status="detail.task.systemBindingStatus" /></span>
|
||||
<span>完整绑定 <AdminStatusTag :status="detail.task.userBindingStatus" /></span>
|
||||
<span v-if="detail.task.lastError">最近异常 {{ detail.task.lastError }}</span>
|
||||
</div>
|
||||
<div v-if="claimUrl" class="hero-link-row">
|
||||
<span>客户领取页</span>
|
||||
<a class="claim-link" :href="claimUrl" target="_blank" rel="noreferrer">{{ claimUrl }}</a>
|
||||
</div>
|
||||
<div v-else-if="claimLinkInvalid" class="hero-link-row">
|
||||
<span>客户领取页</span>
|
||||
<strong>已失效</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Simple info card for other task types -->
|
||||
<section v-else class="info-card">
|
||||
<h2>{{ detail.task.taskNo }}</h2>
|
||||
<p>
|
||||
状态:<AdminStatusTag :status="detail.task.status" /> · 订单:{{
|
||||
detail.order?.platformOrderId || '-'
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
执行器:{{ detail.task.executorKey || '-' }} · 履约:<AdminStatusTag
|
||||
:status="detail.task.deliveryStatus"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
系统绑定:<AdminStatusTag :status="detail.task.systemBindingStatus" /> ·
|
||||
完整绑定:<AdminStatusTag :status="detail.task.userBindingStatus" />
|
||||
</p>
|
||||
<p>
|
||||
角色:{{ detail.task.roleName || '-' }} /
|
||||
{{ detail.task.roleId || '-' }}
|
||||
</p>
|
||||
<p>最后错误:{{ detail.task.lastError || '-' }}</p>
|
||||
<p v-if="claimUrl">
|
||||
领取链接:
|
||||
<a class="claim-link" :href="claimUrl" target="_blank" rel="noreferrer">{{ claimUrl }}</a>
|
||||
</p>
|
||||
<p v-else-if="claimLinkInvalid">领取链接:已失效</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
canViewSensitiveTaskData: boolean
|
||||
claimUrl: string
|
||||
claimLinkInvalid: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>任务信息</h3>
|
||||
<table class="data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>商品</th>
|
||||
<td>{{ detail.orderItem?.skuName || '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<td>{{ detail.orderItem?.skuCode || '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>任务执行器</th>
|
||||
<td>{{ detail.task.executorKey || '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>履约状态</th>
|
||||
<td>
|
||||
<AdminStatusTag :status="detail.task.deliveryStatus || ''" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>结果代码</th>
|
||||
<td>{{ detail.task.resultCode || '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>结果说明</th>
|
||||
<td>{{ detail.task.resultMessage || '-' }}</td>
|
||||
</tr>
|
||||
<tr v-if="canViewSensitiveTaskData">
|
||||
<th>库存凭据</th>
|
||||
<td>{{ detail.inventory?.displayValue || '-' }}</td>
|
||||
</tr>
|
||||
<tr v-if="canViewSensitiveTaskData">
|
||||
<th>凭据类型</th>
|
||||
<td>{{ detail.inventory?.credentialType || '-' }}</td>
|
||||
</tr>
|
||||
<tr v-if="canViewSensitiveTaskData">
|
||||
<th>库存状态</th>
|
||||
<td>
|
||||
<AdminStatusTag :status="detail.inventory?.status || ''" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="canViewSensitiveTaskData">
|
||||
<th>Claim Token</th>
|
||||
<td>{{ detail.claimToken?.token || '-' }}</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<th>库存 / Token</th>
|
||||
<td>客服账号不可查看库存凭据与原始领取 Token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Token 状态</th>
|
||||
<td>
|
||||
<AdminStatusTag :status="detail.claimToken?.status || ''" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Token 过期</th>
|
||||
<td>{{ formatAdminDateTime(detail.claimToken?.expiredAt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>领取链接</th>
|
||||
<td>
|
||||
<a
|
||||
v-if="claimUrl"
|
||||
class="claim-link"
|
||||
:href="claimUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ claimUrl }}</a
|
||||
>
|
||||
<span v-else-if="claimLinkInvalid">已失效</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>浏览器会话</th>
|
||||
<td>{{ detail.task.browserSessionId || '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>用户打开链接</th>
|
||||
<td>{{ formatAdminDateTime(detail.task.claimedAt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>用户提交绑定</th>
|
||||
<td>{{ formatAdminDateTime(detail.task.roleConfirmedAt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>完整绑定成功</th>
|
||||
<td>{{ formatAdminDateTime(detail.task.redeemedAt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>截图路径</th>
|
||||
<td>{{ detail.task.screenshotPath || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
canViewSensitiveTaskData: boolean
|
||||
actionLoading: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
releaseBinding: [bindingId: number]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>库存绑定</h3>
|
||||
<p class="section-copy">
|
||||
这里展示任务当前和历史绑定过的库存项,便于核对多库存任务的真实履约轨迹。
|
||||
</p>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>绑定</th>
|
||||
<th>角色</th>
|
||||
<th>库存项</th>
|
||||
<th>状态</th>
|
||||
<th>凭据内容</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="detail.inventoryBindings.length === 0">
|
||||
<td colspan="7" class="table-empty">当前任务还没有库存绑定记录</td>
|
||||
</tr>
|
||||
<tr v-for="binding in detail.inventoryBindings" :key="binding.bindingId">
|
||||
<td>
|
||||
<div>#{{ binding.bindingId }}</div>
|
||||
<div v-if="binding.isPrimary" class="binding-primary">主库存项</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ binding.roleKey || '-' }}</div>
|
||||
<div class="binding-meta">数量 {{ binding.quantity }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>ID {{ binding.inventoryItemId }}</div>
|
||||
<div>{{ binding.skuCode || '-' }}</div>
|
||||
<div class="binding-meta">
|
||||
{{ binding.credentialType || '-' }} ·
|
||||
{{ binding.batchNo || '无批次' }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="status-stack">
|
||||
<AdminStatusTag :status="binding.bindingStatus" />
|
||||
<AdminStatusTag :status="binding.inventoryStatus" />
|
||||
</div>
|
||||
<div v-if="binding.invalidReason" class="binding-meta">
|
||||
{{
|
||||
binding.inventoryStatus === 'invalid'
|
||||
? '作废原因'
|
||||
: binding.inventoryStatus === 'consumed'
|
||||
? '消耗原因'
|
||||
: '处理原因'
|
||||
}}:{{ binding.invalidReason }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ canViewSensitiveTaskData ? binding.displayValue || '-' : '客服不可见' }}
|
||||
</td>
|
||||
<td>
|
||||
<div>绑定 {{ formatAdminDateTime(binding.createdAt) }}</div>
|
||||
<div class="binding-meta">消费 {{ formatAdminDateTime(binding.consumedAt) }}</div>
|
||||
<div class="binding-meta">释放 {{ formatAdminDateTime(binding.releasedAt) }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<el-button
|
||||
v-if="hasAdminRole('admin') && binding.canRelease"
|
||||
:loading="actionLoading"
|
||||
link
|
||||
@click="emit('releaseBinding', binding.bindingId)"
|
||||
>
|
||||
释放绑定
|
||||
</el-button>
|
||||
<span v-else class="binding-meta">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
import type { ManualDispatchForm } from '../composables/types'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
form: ManualDispatchForm
|
||||
canManageTaskLifecycle: boolean
|
||||
actionLoading: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:form': [form: ManualDispatchForm]
|
||||
submit: [outcome: 'delivered' | 'failed']
|
||||
}>()
|
||||
|
||||
function updateField(field: keyof ManualDispatchForm, value: string) {
|
||||
emit('update:form', { ...props.form, [field]: value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>人工履约回写</h3>
|
||||
<div class="manual-grid">
|
||||
<label class="field-block">
|
||||
<span>外部流水 / 单号</span>
|
||||
<el-input
|
||||
:model-value="form.deliveryReference"
|
||||
:disabled="!canManageTaskLifecycle"
|
||||
maxlength="120"
|
||||
placeholder="例如快递单号、平台消息回执号"
|
||||
@update:model-value="updateField('deliveryReference', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>已交付内容</span>
|
||||
<el-input
|
||||
:model-value="form.deliveredCredential"
|
||||
:disabled="!canManageTaskLifecycle"
|
||||
maxlength="500"
|
||||
placeholder="可填写人工发送的卡密、链接或关键信息"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
@update:model-value="updateField('deliveredCredential', $event)"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>处理备注</span>
|
||||
<el-input
|
||||
:model-value="form.resultMessage"
|
||||
:disabled="!canManageTaskLifecycle"
|
||||
maxlength="500"
|
||||
placeholder="说明实际履约结果,失败时建议写清原因"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
@update:model-value="updateField('resultMessage', $event)"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<p class="manual-hint">回写后会直接更新任务终态,不再走旧式领取链接流程。</p>
|
||||
<div v-if="detail.manualDispatch" class="manual-summary">
|
||||
<p>
|
||||
最近回写:<AdminStatusTag
|
||||
:status="detail.manualDispatch.outcome || detail.task.deliveryStatus"
|
||||
/>
|
||||
· {{ formatAdminDateTime(detail.manualDispatch.completedAt) }}
|
||||
</p>
|
||||
<p>
|
||||
处理人:{{ detail.manualDispatch.completedBy?.username || '-' }} /
|
||||
{{ detail.manualDispatch.completedBy?.role || '-' }}
|
||||
</p>
|
||||
<p>流水号:{{ detail.manualDispatch.deliveryReference || '-' }}</p>
|
||||
<p>交付内容:{{ detail.manualDispatch.deliveredCredential || '-' }}</p>
|
||||
<p>备注:{{ detail.manualDispatch.resultMessage || '-' }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatRedeemOutcomeLabel, formatRedeemResolutionStatus } from '@/utils/admin-display'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>兑换重试链路</h3>
|
||||
<p class="section-copy">
|
||||
这里记录自动兑换时每次使用过的 CDK、判定结果,以及是否切换到了新的同类型凭据。
|
||||
</p>
|
||||
|
||||
<div class="redeem-summary-grid">
|
||||
<article class="redeem-summary-card">
|
||||
<span>处理结果</span>
|
||||
<strong>{{ formatRedeemResolutionStatus(detail.redeemResolution!.status) }}</strong>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>任务收口状态</span>
|
||||
<strong>{{ detail.redeemResolution!.taskStatus || detail.task.status || '-' }}</strong>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>更换新码次数</span>
|
||||
<strong>{{ detail.redeemResolution!.replacementCount }}</strong>
|
||||
</article>
|
||||
<article class="redeem-summary-card">
|
||||
<span>完成时间</span>
|
||||
<strong>{{ formatAdminDateTime(detail.redeemResolution!.finishedAt) }}</strong>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-if="detail.redeemResolution!.attempts.length > 0" class="redeem-attempts">
|
||||
<article
|
||||
v-for="attempt in detail.redeemResolution!.attempts"
|
||||
:key="`${attempt.attempt}-${attempt.inventoryItemId || 'na'}`"
|
||||
class="redeem-attempt-card"
|
||||
>
|
||||
<div class="redeem-attempt-head">
|
||||
<strong>第 {{ attempt.attempt }} 次尝试</strong>
|
||||
<span class="redeem-outcome">{{ formatRedeemOutcomeLabel(attempt.outcome) }}</span>
|
||||
</div>
|
||||
<div class="redeem-attempt-grid">
|
||||
<div>
|
||||
<span>库存项</span>
|
||||
<strong>{{ attempt.inventoryItemId ? `#${attempt.inventoryItemId}` : '-' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>凭据</span>
|
||||
<strong>{{ attempt.codeMasked || '-' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>类型</span>
|
||||
<strong>{{ attempt.credentialType || '-' }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>结果码</span>
|
||||
<strong>{{ attempt.resultCode || '-' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<p class="redeem-attempt-copy">
|
||||
{{ attempt.resultMessage || '-' }}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
screenshotPreviewUrl: string
|
||||
sectionTitle: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="info-card">
|
||||
<h3>{{ sectionTitle }}</h3>
|
||||
<p v-if="!screenshotPreviewUrl" class="screenshot-empty">截图加载中或当前不可读取。</p>
|
||||
<a
|
||||
v-else
|
||||
class="screenshot-link"
|
||||
:href="screenshotPreviewUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>打开原图</a
|
||||
>
|
||||
<img
|
||||
v-if="screenshotPreviewUrl"
|
||||
class="screenshot-preview"
|
||||
:src="screenshotPreviewUrl"
|
||||
:alt="sectionTitle"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
|
||||
export type KuaishouCloudFlow = NonNullable<AdminTaskDetail['kuaishouCloudFulfillment']>
|
||||
|
||||
export type SummaryCard = {
|
||||
label: string
|
||||
value: string
|
||||
tone: 'blue' | 'green' | 'amber' | 'slate'
|
||||
}
|
||||
|
||||
export type RoleInfoEntry = {
|
||||
key: string
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type ChecklistItem = {
|
||||
key: string
|
||||
label: string
|
||||
status: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
export type ManualDispatchForm = {
|
||||
deliveryReference: string
|
||||
deliveredCredential: string
|
||||
resultMessage: string
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
closeAdminTask,
|
||||
completeAdminTaskManualDispatch,
|
||||
fetchAdminTaskScreenshot,
|
||||
fetchAdminTaskDetail,
|
||||
markAdminTaskManualReview,
|
||||
redeemAdminTaskAssisted,
|
||||
regenerateAdminTaskClaimLink,
|
||||
releaseAdminTaskInventoryBinding,
|
||||
retryAdminTask,
|
||||
confirmAdminTaskAssistedRole,
|
||||
} from '@/services/admin'
|
||||
import type { AdminTaskActionResponse, AdminTaskDetail } from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
|
||||
import type { ManualDispatchForm } from './types'
|
||||
|
||||
export function useAdminTaskDetail() {
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
const actionLoading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const detail = ref<AdminTaskDetail | null>(null)
|
||||
const lastClaimUrl = ref('')
|
||||
const screenshotPreviewUrl = ref('')
|
||||
const manualDispatchForm = ref<ManualDispatchForm>({
|
||||
deliveryReference: '',
|
||||
deliveredCredential: '',
|
||||
resultMessage: '',
|
||||
})
|
||||
|
||||
const canManageTaskLifecycle = computed(() => hasAdminRole('operator'))
|
||||
const canCloseTasks = computed(() => hasAdminRole('support'))
|
||||
const canViewSensitiveTaskData = computed(() =>
|
||||
Boolean(detail.value?.operations.canViewSensitiveTaskData),
|
||||
)
|
||||
|
||||
const claimUrl = computed(() => {
|
||||
const tokenStatus = String(detail.value?.claimToken?.status || '').trim()
|
||||
const taskStatus = String(detail.value?.task.status || '').trim()
|
||||
|
||||
if (tokenStatus !== 'active' || ['closed', 'expired'].includes(taskStatus)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return lastClaimUrl.value || detail.value?.claimToken?.claimUrl || ''
|
||||
})
|
||||
|
||||
const claimLinkInvalid = computed(() => {
|
||||
const tokenStatus = String(detail.value?.claimToken?.status || '').trim()
|
||||
return Boolean(detail.value?.claimToken?.claimUrl) && tokenStatus && tokenStatus !== 'active'
|
||||
})
|
||||
|
||||
const screenshotSectionTitle = computed(() => {
|
||||
if (detail.value?.task.status === 'redeemed') {
|
||||
return '结果截图'
|
||||
}
|
||||
|
||||
return detail.value?.review.required ? '客服复核截图' : '结果截图'
|
||||
})
|
||||
|
||||
async function loadDetail() {
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetchAdminTaskDetail(String(route.params.taskId || ''))
|
||||
detail.value = response.data
|
||||
syncManualDispatchForm(response.data)
|
||||
await loadScreenshotPreview(response.data)
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '读取任务详情失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runAction(
|
||||
action: () => Promise<{ data: AdminTaskActionResponse }>,
|
||||
successMessage: string,
|
||||
confirmText: string,
|
||||
) {
|
||||
try {
|
||||
await showConfirm(confirmText, '确认操作', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '继续执行',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
actionLoading.value = true
|
||||
|
||||
try {
|
||||
const response = await action()
|
||||
if (response.data.claimUrl) {
|
||||
lastClaimUrl.value = response.data.claimUrl
|
||||
}
|
||||
showSuccess(successMessage)
|
||||
await loadDetail()
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : '操作失败')
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadScreenshotPreview(taskDetail: AdminTaskDetail) {
|
||||
clearScreenshotPreview()
|
||||
|
||||
if (!taskDetail.screenshotUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = await fetchAdminTaskScreenshot(taskDetail.task.taskId)
|
||||
screenshotPreviewUrl.value = URL.createObjectURL(blob)
|
||||
} catch {
|
||||
screenshotPreviewUrl.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function clearScreenshotPreview() {
|
||||
if (screenshotPreviewUrl.value) {
|
||||
URL.revokeObjectURL(screenshotPreviewUrl.value)
|
||||
screenshotPreviewUrl.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function syncManualDispatchForm(taskDetail: AdminTaskDetail) {
|
||||
manualDispatchForm.value = {
|
||||
deliveryReference: taskDetail.manualDispatch?.deliveryReference || '',
|
||||
deliveredCredential: taskDetail.manualDispatch?.deliveredCredential || '',
|
||||
resultMessage:
|
||||
taskDetail.manualDispatch?.resultMessage || taskDetail.task.resultMessage || '',
|
||||
}
|
||||
}
|
||||
|
||||
async function submitManualDispatch(outcome: 'delivered' | 'failed') {
|
||||
if (!detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const actionLabel = outcome === 'failed' ? '标记履约失败' : '标记已完成履约'
|
||||
const confirmText =
|
||||
outcome === 'failed'
|
||||
? `确认把任务 ${detail.value.task.taskNo} 回写为人工履约失败吗?这会把任务直接收口并保留失败原因。`
|
||||
: `确认把任务 ${detail.value.task.taskNo} 回写为人工履约完成吗?这会把任务直接标记为已发放。`
|
||||
|
||||
await runAction(
|
||||
() =>
|
||||
completeAdminTaskManualDispatch(detail.value!.task.taskId, {
|
||||
outcome,
|
||||
resultMessage: manualDispatchForm.value.resultMessage,
|
||||
deliveryReference: manualDispatchForm.value.deliveryReference,
|
||||
deliveredCredential: manualDispatchForm.value.deliveredCredential,
|
||||
}),
|
||||
actionLabel,
|
||||
confirmText,
|
||||
)
|
||||
}
|
||||
|
||||
async function copyText(value: string, successMessage: string) {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
showError('当前没有可复制的内容')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
showSuccess(successMessage)
|
||||
} catch {
|
||||
showError('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
|
||||
function openExternalLink(value: string) {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
return
|
||||
}
|
||||
|
||||
window.open(text, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
function resolveTaskAutoDeliveryStatus(taskDetail: AdminTaskDetail) {
|
||||
if (taskDetail.task.agisoAutoDelivery?.status) {
|
||||
return taskDetail.task.agisoAutoDelivery.status
|
||||
}
|
||||
|
||||
return taskDetail.task.deliveryStatus === 'delivered' ? 'pending' : 'waiting'
|
||||
}
|
||||
|
||||
function resolveTaskAutoDeliverySummary(taskDetail: AdminTaskDetail) {
|
||||
const autoDelivery = taskDetail.task.agisoAutoDelivery
|
||||
|
||||
if (autoDelivery?.errorMessage) {
|
||||
return autoDelivery.errorMessage
|
||||
}
|
||||
|
||||
if (autoDelivery?.reason) {
|
||||
return autoDelivery.reason
|
||||
}
|
||||
|
||||
return taskDetail.task.deliveryStatus === 'delivered'
|
||||
? '当前任务已交付,等待订单下其他任务完成后统一自动发货'
|
||||
: '当前任务尚未完成交付,暂不会触发自动发货'
|
||||
}
|
||||
|
||||
onBeforeUnmount(clearScreenshotPreview)
|
||||
|
||||
return {
|
||||
loading,
|
||||
actionLoading,
|
||||
errorMessage,
|
||||
detail,
|
||||
screenshotPreviewUrl,
|
||||
manualDispatchForm,
|
||||
canManageTaskLifecycle,
|
||||
canCloseTasks,
|
||||
canViewSensitiveTaskData,
|
||||
claimUrl,
|
||||
claimLinkInvalid,
|
||||
screenshotSectionTitle,
|
||||
loadDetail,
|
||||
runAction,
|
||||
submitManualDispatch,
|
||||
copyText,
|
||||
openExternalLink,
|
||||
resolveTaskAutoDeliveryStatus,
|
||||
resolveTaskAutoDeliverySummary,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { computed, type Ref } from 'vue'
|
||||
|
||||
import {
|
||||
dispatchAdminTaskKuaishouCloudFulfillment,
|
||||
prepareAdminTaskKuaishouCloudFulfillment,
|
||||
refreshAdminTaskKuaishouCloudRoleInfo,
|
||||
returnNumberAdminTaskKuaishouCloudFulfillment,
|
||||
} from '@/services/admin'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatKuaishouRoleInfoLabel } from '@/utils/admin-display'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
import type { ChecklistItem, RoleInfoEntry, SummaryCard } from './types'
|
||||
|
||||
export function useAdminTaskKuaishouCloud(
|
||||
detail: Ref<AdminTaskDetail | null>,
|
||||
runAction: (
|
||||
action: () => Promise<{ data: any }>,
|
||||
successMessage: string,
|
||||
confirmText: string,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
const kuaishouCloudFlow = computed(() => detail.value?.kuaishouCloudFulfillment || null)
|
||||
const kuaishouCloudRole = computed(() => kuaishouCloudFlow.value?.role || null)
|
||||
const isKuaishouCloudTask = computed(
|
||||
() => detail.value?.task.executorKey === 'kuaishou_ct_assisted',
|
||||
)
|
||||
|
||||
const summaryCards = computed<SummaryCard[]>(() => {
|
||||
if (!detail.value) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: '任务状态',
|
||||
value: detail.value.task.status || '-',
|
||||
tone: 'blue',
|
||||
},
|
||||
{
|
||||
label: '履约状态',
|
||||
value: detail.value.task.deliveryStatus || '-',
|
||||
tone: 'green',
|
||||
},
|
||||
{
|
||||
label: '当前角色',
|
||||
value: kuaishouCloudRole.value?.name || detail.value.task.roleName || '-',
|
||||
tone: 'amber',
|
||||
},
|
||||
{
|
||||
label: '角色 ID',
|
||||
value: kuaishouCloudRole.value?.rid || detail.value.task.roleId || '-',
|
||||
tone: 'slate',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const kuaishouRoleInfoEntries = computed<RoleInfoEntry[]>(() => {
|
||||
const rawInfo = kuaishouCloudRole.value?.rawInfo
|
||||
if (!rawInfo) {
|
||||
return []
|
||||
}
|
||||
|
||||
return Object.entries(rawInfo)
|
||||
.filter(([, value]) => value !== null && value !== undefined && String(value).trim() !== '')
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
label: formatKuaishouRoleInfoLabel(key),
|
||||
value: typeof value === 'object' ? JSON.stringify(value) : String(value),
|
||||
}))
|
||||
})
|
||||
|
||||
const kuaishouCloudChecklist = computed<ChecklistItem[]>(() => {
|
||||
const flow = kuaishouCloudFlow.value
|
||||
const role = kuaishouCloudRole.value
|
||||
if (!flow) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'ticket',
|
||||
label: '核销码校验',
|
||||
status: flow.ticket.status || 'pending',
|
||||
detail: flow.ticket.verifiedAt
|
||||
? `已于 ${formatAdminDateTime(flow.ticket.verifiedAt)} 校验`
|
||||
: '等待客户提交并校验核销码',
|
||||
},
|
||||
{
|
||||
key: 'bind',
|
||||
label: '绑定资源',
|
||||
status: flow.binding.prepareStatus || 'pending',
|
||||
detail: flow.binding.bindPreparedAt
|
||||
? `绑定资源已准备,VN ${flow.binding.vnId || '-'}`
|
||||
: '等待准备 Cloud 绑定资源',
|
||||
},
|
||||
{
|
||||
key: 'role',
|
||||
label: '角色识别',
|
||||
status: role?.status || 'pending',
|
||||
detail:
|
||||
role?.name || role?.rid
|
||||
? `${role?.name || '-'} / ${role?.rid || '-'}`
|
||||
: '客户绑定后刷新角色信息',
|
||||
},
|
||||
{
|
||||
key: 'dispatch',
|
||||
label: '发货执行',
|
||||
status: flow.dispatch.status || 'pending',
|
||||
detail: flow.dispatch.dispatchAt
|
||||
? `已于 ${formatAdminDateTime(flow.dispatch.dispatchAt)} 发货`
|
||||
: '等待客服确认绑定并发货',
|
||||
},
|
||||
{
|
||||
key: 'return',
|
||||
label: '退还号码',
|
||||
status: flow.returnNumber.status || 'pending',
|
||||
detail: flow.returnNumber.returnedAt
|
||||
? `已于 ${formatAdminDateTime(flow.returnNumber.returnedAt)} 退号`
|
||||
: '发货后执行退还号码',
|
||||
},
|
||||
{
|
||||
key: 'consume',
|
||||
label: '快手核销',
|
||||
status: flow.consume.status || 'pending',
|
||||
detail: flow.consume.consumedAt
|
||||
? `已于 ${formatAdminDateTime(flow.consume.consumedAt)} 完成核销`
|
||||
: flow.consume.errorMessage || '等待退号后核销收口',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
async function submitKuaishouCloudPrepare() {
|
||||
if (!detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await runAction(
|
||||
() => prepareAdminTaskKuaishouCloudFulfillment(detail.value!.task.taskId),
|
||||
'绑定资源已准备完成',
|
||||
`确认开始为任务 ${detail.value.task.taskNo} 准备绑定资源吗?系统会自动检查背包、余额并申请虚拟号。`,
|
||||
)
|
||||
}
|
||||
|
||||
async function submitKuaishouCloudDispatch() {
|
||||
if (!detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await runAction(
|
||||
() => dispatchAdminTaskKuaishouCloudFulfillment(detail.value!.task.taskId),
|
||||
'已完成绑定确认并发货',
|
||||
`确认客户已经完成绑定,并立即为任务 ${detail.value.task.taskNo} 执行发货吗?这个动作会把"确认绑定完成"和"发货"合并为一步。`,
|
||||
)
|
||||
}
|
||||
|
||||
async function submitKuaishouCloudRefreshRoleInfo() {
|
||||
if (!detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await runAction(
|
||||
() => refreshAdminTaskKuaishouCloudRoleInfo(detail.value!.task.taskId),
|
||||
'角色信息已刷新',
|
||||
`确认刷新任务 ${detail.value.task.taskNo} 当前云绑定角色信息吗?系统会重新查询 cloudtentacles 的绑定结果。`,
|
||||
)
|
||||
}
|
||||
|
||||
async function submitKuaishouCloudReturnNumber() {
|
||||
if (!detail.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await runAction(
|
||||
() => returnNumberAdminTaskKuaishouCloudFulfillment(detail.value!.task.taskId),
|
||||
'号码已退还',
|
||||
`确认退还任务 ${detail.value.task.taskNo} 当前使用的虚拟号吗?退号后该流程会正式收口。`,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
kuaishouCloudFlow,
|
||||
kuaishouCloudRole,
|
||||
isKuaishouCloudTask,
|
||||
summaryCards,
|
||||
kuaishouRoleInfoEntries,
|
||||
kuaishouCloudChecklist,
|
||||
submitKuaishouCloudPrepare,
|
||||
submitKuaishouCloudDispatch,
|
||||
submitKuaishouCloudRefreshRoleInfo,
|
||||
submitKuaishouCloudReturnNumber,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user