半自动+人工”链路已经落下第一版

This commit is contained in:
yml
2026-04-12 19:07:51 +08:00
parent 4c98b78267
commit 75647e9eed
24 changed files with 711 additions and 93 deletions
@@ -1,12 +1,14 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
import {
closeAdminTask,
confirmAdminTaskAssistedRole,
completeAdminTaskManualDispatch,
redeemAdminTaskAssisted,
fetchAdminTaskScreenshot,
fetchAdminTaskDetail,
markAdminTaskManualReview,
@@ -31,6 +33,16 @@ const manualDispatchForm = reactive({
deliveredCredential: '',
resultMessage: '',
})
const canOperateTasks = computed(() => hasAdminRole('operator'))
const canViewSensitiveTaskData = computed(() => Boolean(detail.value?.operations.canViewSensitiveTaskData))
const claimUrl = computed(() => lastClaimUrl.value || detail.value?.claimToken?.claimUrl || '')
const screenshotSectionTitle = computed(() => {
if (detail.value?.task.status === 'redeemed') {
return '结果截图'
}
return detail.value?.review.required ? '客服复核截图' : '结果截图'
})
async function loadDetail() {
loading.value = true
@@ -168,7 +180,10 @@ onBeforeUnmount(clearScreenshotPreview)
<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="lastClaimUrl">新领取链接{{ lastClaimUrl }}</p>
<p v-if="claimUrl">
领取链接
<a class="claim-link" :href="claimUrl" target="_blank" rel="noreferrer">{{ claimUrl }}</a>
</p>
</section>
<section class="action-row">
@@ -182,7 +197,7 @@ onBeforeUnmount(clearScreenshotPreview)
重试任务
</el-button>
<el-button
v-if="hasAdminRole('admin')"
v-if="detail.operations.canRegenerateClaimLink"
:disabled="!detail.operations.canRegenerateClaimLink"
:loading="actionLoading"
round
@@ -191,7 +206,33 @@ onBeforeUnmount(clearScreenshotPreview)
重发链接
</el-button>
<el-button
v-if="hasAdminRole('admin')"
v-if="detail.operations.canSupportConfirmRole"
:loading="actionLoading"
round
type="warning"
@click="runAction(
() => confirmAdminTaskAssistedRole(detail!.task.taskId),
'角色已确认',
`确认以客服身份锁定任务 ${detail!.task.taskNo} 当前识别到的角色信息吗?`
)"
>
客服确认角色
</el-button>
<el-button
v-if="detail.operations.canSupportRedeem"
:loading="actionLoading"
round
type="success"
@click="runAction(
() => redeemAdminTaskAssisted(detail!.task.taskId),
'兑换任务已启动',
`确认开始执行任务 ${detail!.task.taskNo} 的自动兑换吗?`
)"
>
客服开始兑换
</el-button>
<el-button
v-if="canOperateTasks"
:disabled="!detail.operations.canMarkManualReview"
:loading="actionLoading"
round
@@ -200,7 +241,7 @@ onBeforeUnmount(clearScreenshotPreview)
转人工
</el-button>
<el-button
v-if="hasAdminRole('admin') && detail.operations.canCompleteManualDispatch"
v-if="canOperateTasks && detail.operations.canCompleteManualDispatch"
:loading="actionLoading"
round
type="success"
@@ -209,7 +250,7 @@ onBeforeUnmount(clearScreenshotPreview)
人工完成
</el-button>
<el-button
v-if="hasAdminRole('admin') && detail.operations.canCompleteManualDispatch"
v-if="canOperateTasks && detail.operations.canCompleteManualDispatch"
:loading="actionLoading"
round
type="danger"
@@ -219,7 +260,7 @@ onBeforeUnmount(clearScreenshotPreview)
履约失败
</el-button>
<el-button
v-if="hasAdminRole('admin')"
v-if="canOperateTasks"
:disabled="!detail.operations.canClose"
:loading="actionLoading"
round
@@ -240,12 +281,20 @@ onBeforeUnmount(clearScreenshotPreview)
<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><th>库存凭据</th><td>{{ detail.inventory?.displayValue || '-' }}</td></tr>
<tr><th>凭据类型</th><td>{{ detail.inventory?.credentialType || '-' }}</td></tr>
<tr><th>库存状态</th><td><AdminStatusTag :status="detail.inventory?.status || ''" /></td></tr>
<tr><th>Claim Token</th><td>{{ detail.claimToken?.token || '-' }}</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>-</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>
@@ -295,7 +344,7 @@ onBeforeUnmount(clearScreenshotPreview)
</div>
<div v-if="binding.invalidReason" class="binding-meta">作废原因{{ binding.invalidReason }}</div>
</td>
<td>{{ binding.displayValue || '-' }}</td>
<td>{{ canViewSensitiveTaskData ? (binding.displayValue || '-') : '客服不可见' }}</td>
<td>
<div>绑定 {{ formatAdminDateTime(binding.createdAt) }}</div>
<div class="binding-meta">消费 {{ formatAdminDateTime(binding.consumedAt) }}</div>
@@ -328,6 +377,7 @@ onBeforeUnmount(clearScreenshotPreview)
<span>外部流水 / 单号</span>
<el-input
v-model="manualDispatchForm.deliveryReference"
:disabled="!canOperateTasks"
maxlength="120"
placeholder="例如快递单号、平台消息回执号"
/>
@@ -336,6 +386,7 @@ onBeforeUnmount(clearScreenshotPreview)
<span>已交付内容</span>
<el-input
v-model="manualDispatchForm.deliveredCredential"
:disabled="!canOperateTasks"
maxlength="500"
placeholder="可填写人工发送的卡密、链接或关键信息"
type="textarea"
@@ -346,6 +397,7 @@ onBeforeUnmount(clearScreenshotPreview)
<span>处理备注</span>
<el-input
v-model="manualDispatchForm.resultMessage"
:disabled="!canOperateTasks"
maxlength="500"
placeholder="说明实际履约结果,失败时建议写清原因"
type="textarea"
@@ -379,10 +431,10 @@ onBeforeUnmount(clearScreenshotPreview)
</section>
<section v-if="detail.screenshotUrl" class="table-card">
<h3>结果截图</h3>
<h3>{{ screenshotSectionTitle }}</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="任务兑换截图" />
<img v-if="screenshotPreviewUrl" class="screenshot-preview" :src="screenshotPreviewUrl" :alt="screenshotSectionTitle" />
</section>
</template>
</section>
@@ -392,6 +444,7 @@ onBeforeUnmount(clearScreenshotPreview)
.admin-panel { display: grid; gap: 16px; }
.panel-header h1 { margin: 0; color: #1d3555; }
.panel-header p { margin: 8px 0 0; color: #64748b; }
.claim-link { color: #175cd3; text-decoration: none; word-break: break-all; }
.info-card,.table-card,.empty-block,.error-copy {
padding: 18px; border-radius: 20px; background: rgba(255,255,255,.94); border: 1px solid rgba(86,108,138,.1);
}