2ed5dfa6a1
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
741 lines
26 KiB
Vue
741 lines
26 KiB
Vue
<script lang="ts" setup>
|
||
import type { AllocateBatchRes, AllocateTarget, AppCard } from '@/api/card'
|
||
import {
|
||
allocateBatchPrepare,
|
||
allocateBatchSubmit,
|
||
getAllocateTargets,
|
||
getCardList,
|
||
} from '@/api/card'
|
||
import {
|
||
displayCardValue,
|
||
displayPackageName,
|
||
formatDateTime,
|
||
formatFlowPair,
|
||
} from '@/pages/card/data'
|
||
import { useUserStore } from '@/store/user'
|
||
import { hasUserPermission } from '@/utils/permission'
|
||
import { getAccountWorkspace } from '@/utils/userIdentity'
|
||
|
||
definePage({
|
||
style: {
|
||
navigationBarTitleText: '批量卡片划拨',
|
||
},
|
||
})
|
||
|
||
const userStore = useUserStore()
|
||
const keyword = ref('')
|
||
const targetKeyword = ref('')
|
||
const cards = ref<AppCard[]>([])
|
||
const targets = ref<AllocateTarget[]>([])
|
||
const selectedCardValues = ref<string[]>([])
|
||
const selectedTargetId = ref<number>()
|
||
const loadingCards = ref(false)
|
||
const loadingTargets = ref(false)
|
||
const preparing = ref(false)
|
||
const submitting = ref(false)
|
||
const noPermission = ref(false)
|
||
const errorMessage = ref('')
|
||
const targetPopupVisible = ref(false)
|
||
const prepare = ref<AllocateBatchRes>()
|
||
const result = ref<AllocateBatchRes>()
|
||
const initialCardValue = ref('')
|
||
let targetLoadSeq = 0
|
||
|
||
const workspace = computed(() => getAccountWorkspace(userStore.userInfo))
|
||
const isAgentUser = computed(() => workspace.value === 'enterprise' || workspace.value === 'agent' || workspace.value === 'admin')
|
||
const canUseAllocate = computed(() => isAgentUser.value && hasUserPermission(userStore.userInfo, 'allocate'))
|
||
const selectedTarget = computed(() =>
|
||
targets.value.find(item => Number(item.deptId) === Number(selectedTargetId.value)),
|
||
)
|
||
const allocatableCards = computed(() => cards.value.filter(item => item.canAllocate !== false))
|
||
const visibleCards = computed(() => {
|
||
const value = keyword.value.trim()
|
||
if (!value)
|
||
return allocatableCards.value
|
||
return allocatableCards.value.filter((item) => {
|
||
return (
|
||
allocateCardValue(item).includes(value)
|
||
|| (item.msisdn || '').includes(value)
|
||
|| (item.imsi || '').includes(value)
|
||
|| displayPackageName(item).includes(value)
|
||
|| (item.remark || '').includes(value)
|
||
|| (item.terminalName || '').includes(value)
|
||
|| ownerDeptText(item).includes(value)
|
||
)
|
||
})
|
||
})
|
||
const canPrepare = computed(
|
||
() => canUseAllocate.value && !!selectedTarget.value && !!selectedCardValues.value.length,
|
||
)
|
||
const canSubmit = computed(() => {
|
||
return !!prepare.value?.batchNo
|
||
&& !submitting.value
|
||
&& !result.value
|
||
&& (prepare.value.failCount || 0) === 0
|
||
&& prepare.value.status !== 'failed'
|
||
})
|
||
const isFixedCardMode = computed(() => !!initialCardValue.value)
|
||
|
||
async function openTargetPopup() {
|
||
if (noPermission.value)
|
||
return
|
||
targetPopupVisible.value = true
|
||
await refreshTargetPopup()
|
||
}
|
||
|
||
async function refreshTargetPopup() {
|
||
targetKeyword.value = ''
|
||
targets.value = []
|
||
await loadTargets('')
|
||
}
|
||
|
||
async function searchTargetPopup() {
|
||
await loadTargets(targetKeyword.value)
|
||
}
|
||
|
||
async function bootstrap() {
|
||
await userStore.fetchUserInfo().catch(() => undefined)
|
||
if (!canUseAllocate.value) {
|
||
noPermission.value = true
|
||
uni.showToast({ title: '当前账号不能使用卡片划拨', icon: 'none' })
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 800)
|
||
return
|
||
}
|
||
await Promise.all([loadTargets(), loadCards()])
|
||
}
|
||
|
||
async function loadTargets(keyword?: string) {
|
||
const seq = ++targetLoadSeq
|
||
loadingTargets.value = true
|
||
try {
|
||
const actualKeyword = typeof keyword === 'string' ? keyword : targetKeyword.value
|
||
const res = await getAllocateTargets({
|
||
page: 1,
|
||
pageSize: 1000,
|
||
keyword: actualKeyword.trim() || undefined,
|
||
})
|
||
if (seq !== targetLoadSeq)
|
||
return
|
||
targets.value = (res.items || []).map(item => ({
|
||
...item,
|
||
deptId: Number(item.deptId),
|
||
parentId: item.parentId == null ? undefined : Number(item.parentId),
|
||
})).filter(isSelectableTarget)
|
||
if (
|
||
selectedTargetId.value
|
||
&& !targets.value.some(item => Number(item.deptId) === Number(selectedTargetId.value))
|
||
) {
|
||
selectedTargetId.value = undefined
|
||
}
|
||
}
|
||
catch (error) {
|
||
if (seq !== targetLoadSeq)
|
||
return
|
||
targets.value = []
|
||
uni.showToast({ title: getErrorMessage(error, '目标客户加载失败'), icon: 'none' })
|
||
}
|
||
finally {
|
||
if (seq === targetLoadSeq)
|
||
loadingTargets.value = false
|
||
}
|
||
}
|
||
|
||
async function loadCards() {
|
||
loadingCards.value = true
|
||
errorMessage.value = ''
|
||
try {
|
||
const res = await getCardList({
|
||
page: 1,
|
||
pageSize: 100,
|
||
cardValue: keyword.value.trim() || undefined,
|
||
})
|
||
cards.value = res.items || []
|
||
applyInitialCardSelection()
|
||
const nextKeys = new Set(allocatableCards.value.map(allocateCardValue))
|
||
selectedCardValues.value = selectedCardValues.value.filter(value => nextKeys.has(value))
|
||
}
|
||
catch (error) {
|
||
cards.value = []
|
||
selectedCardValues.value = []
|
||
errorMessage.value = getErrorMessage(error, '卡片列表加载失败')
|
||
}
|
||
finally {
|
||
loadingCards.value = false
|
||
}
|
||
}
|
||
|
||
function allocateCardValue(item: AppCard) {
|
||
return item.iccid || item.msisdn || item.imsi || item.imei || ''
|
||
}
|
||
|
||
function ownerDeptText(item: AppCard) {
|
||
return item.deptName || '-'
|
||
}
|
||
|
||
function cardMatchesValue(item: AppCard, value: string) {
|
||
return [item.iccid, item.msisdn, item.imsi, item.imei].filter(Boolean).includes(value)
|
||
}
|
||
|
||
function applyInitialCardSelection() {
|
||
const value = initialCardValue.value.trim()
|
||
if (!value)
|
||
return
|
||
const item = allocatableCards.value.find(cardItem => cardMatchesValue(cardItem, value))
|
||
if (!item)
|
||
return
|
||
const selectedValue = allocateCardValue(item)
|
||
if (selectedValue) {
|
||
selectedCardValues.value = [selectedValue]
|
||
}
|
||
}
|
||
|
||
function isSelected(item: AppCard) {
|
||
return selectedCardValues.value.includes(allocateCardValue(item))
|
||
}
|
||
|
||
function selectVisibleCards() {
|
||
const values = visibleCards.value.map(allocateCardValue).filter(Boolean)
|
||
selectedCardValues.value = Array.from(new Set([...selectedCardValues.value, ...values]))
|
||
}
|
||
|
||
function clearSelection() {
|
||
selectedCardValues.value = []
|
||
}
|
||
|
||
function selectTarget(item: AllocateTarget) {
|
||
selectedTargetId.value = Number(item.deptId)
|
||
targetPopupVisible.value = false
|
||
}
|
||
|
||
function targetLabel(item?: AllocateTarget) {
|
||
if (!item)
|
||
return '请选择划拨目标客户'
|
||
return item.label || item.deptName || '未命名客户'
|
||
}
|
||
|
||
function targetDescription(item?: AllocateTarget) {
|
||
if (!item)
|
||
return ''
|
||
return item.deptKey ? `客户编码:${item.deptKey}` : targetScopeName(item)
|
||
}
|
||
|
||
function targetScopeName(item?: AllocateTarget) {
|
||
return item?.targetScopeName || (item?.targetScope === 'direct_child' ? '直属下级' : '当前机构')
|
||
}
|
||
|
||
function targetScopeTagType(item?: AllocateTarget) {
|
||
return item?.targetScope === 'direct_child' ? 'primary' : 'success'
|
||
}
|
||
|
||
function isSelectableTarget(item: AllocateTarget) {
|
||
const currentDeptId = Number(userStore.userInfo?.deptId || userStore.userInfo?.enterpriseProfile?.deptId || 0)
|
||
if (currentDeptId && Number(item.deptId) === currentDeptId)
|
||
return false
|
||
if (item.targetScope === 'current')
|
||
return false
|
||
if (item.targetScope === 'direct_child')
|
||
return true
|
||
if (currentDeptId && Number(item.parentId) === currentDeptId)
|
||
return true
|
||
return String(item.targetScopeName || '').includes('下级')
|
||
}
|
||
|
||
function prepareStatusText(item?: AllocateBatchRes) {
|
||
if (!item)
|
||
return '-'
|
||
if (result.value)
|
||
return '已划拨'
|
||
if (item.status === 'failed' || (item.failCount || 0) > 0)
|
||
return '校验失败'
|
||
return '已校验'
|
||
}
|
||
|
||
function failedAllocateDetails(item?: AllocateBatchRes) {
|
||
return item?.details?.filter(detail => detail.status === 'failed') || []
|
||
}
|
||
|
||
function showPrepareFailedModal(item: AllocateBatchRes) {
|
||
const failedDetails = failedAllocateDetails(item)
|
||
const failCount = item.failCount || failedDetails.length
|
||
const lines = failedDetails.slice(0, 6).map((detail) => {
|
||
const cardNo = detail.itemId || `第${detail.rowNo}行`
|
||
return `${cardNo}:${detail.errorReason || '校验失败'}`
|
||
})
|
||
if (failCount > lines.length)
|
||
lines.push(`还有 ${failCount - lines.length} 条失败明细,请在页面下方查看。`)
|
||
uni.showModal({
|
||
title: `校验失败 ${failCount} 张`,
|
||
content: lines.join('\n') || item.failReason || '存在校验失败卡,请调整后重新校验。',
|
||
confirmText: '知道了',
|
||
showCancel: false,
|
||
})
|
||
}
|
||
|
||
async function handlePrepare() {
|
||
if (!canPrepare.value || preparing.value) {
|
||
uni.showToast({ title: '请先选择目标客户和需要划拨的卡', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
preparing.value = true
|
||
try {
|
||
const res = await allocateBatchPrepare({
|
||
cardValues: selectedCardValues.value,
|
||
targetDeptId: selectedTargetId.value as number,
|
||
})
|
||
prepare.value = res
|
||
result.value = undefined
|
||
if ((res.failCount || 0) > 0 || res.status === 'failed') {
|
||
showPrepareFailedModal(res)
|
||
}
|
||
else {
|
||
uni.showToast({ title: '校验通过', icon: 'success' })
|
||
}
|
||
}
|
||
catch (error) {
|
||
prepare.value = undefined
|
||
uni.showToast({ title: getErrorMessage(error, '划拨校验失败'), icon: 'none' })
|
||
}
|
||
finally {
|
||
preparing.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (!prepare.value?.batchNo || !selectedTarget.value) {
|
||
uni.showToast({ title: '请先校验划拨批次', icon: 'none' })
|
||
return
|
||
}
|
||
if (submitting.value)
|
||
return
|
||
const confirmed = await confirmAllocateSubmit()
|
||
if (!confirmed)
|
||
return
|
||
|
||
submitting.value = true
|
||
try {
|
||
const res = await allocateBatchSubmit({
|
||
batchNo: prepare.value.batchNo,
|
||
cardValues: selectedCardValues.value,
|
||
targetDeptId: selectedTarget.value.deptId,
|
||
})
|
||
result.value = res
|
||
prepare.value = res
|
||
uni.showModal({
|
||
title: '划拨成功',
|
||
content: `划拨批次:${res.batchNo || '-'}\n目标客户:${targetLabel(res.target)}\n划拨卡数:${res.quantity || selectedCardValues.value.length} 张\n当前企业保留查看和其他办理权限,划拨权交由目标客户。`,
|
||
showCancel: false,
|
||
})
|
||
initialCardValue.value = ''
|
||
clearSelection()
|
||
await loadCards()
|
||
}
|
||
catch (error) {
|
||
uni.showToast({ title: getErrorMessage(error, '划拨提交失败'), icon: 'none' })
|
||
}
|
||
finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
function confirmAllocateSubmit() {
|
||
return new Promise<boolean>((resolve) => {
|
||
uni.showModal({
|
||
title: '确认划拨',
|
||
content: `目标客户:${targetLabel(selectedTarget.value)}\n划拨卡数:${prepare.value?.quantity || selectedCardValues.value.length} 张\n确认后更新卡片归属,当前企业保留查看和其他办理权限,划拨权交由目标客户。`,
|
||
confirmText: '确认划拨',
|
||
cancelText: '再看一下',
|
||
success: res => resolve(!!res.confirm),
|
||
fail: () => resolve(false),
|
||
})
|
||
})
|
||
}
|
||
|
||
function getErrorMessage(error: unknown, fallback: string) {
|
||
if (error && typeof error === 'object') {
|
||
const data = error as {
|
||
msg?: unknown
|
||
message?: unknown
|
||
errMsg?: unknown
|
||
statusCode?: unknown
|
||
data?: { msg?: unknown, message?: unknown }
|
||
}
|
||
if (typeof data.msg === 'string' && data.msg)
|
||
return data.msg
|
||
if (typeof data.message === 'string' && data.message)
|
||
return data.message
|
||
if (typeof data.data?.msg === 'string' && data.data.msg)
|
||
return data.data.msg
|
||
if (typeof data.data?.message === 'string' && data.data.message)
|
||
return data.data.message
|
||
if (typeof data.statusCode === 'number')
|
||
return fallback
|
||
if (typeof data.errMsg === 'string' && data.errMsg)
|
||
return fallback
|
||
}
|
||
return fallback
|
||
}
|
||
|
||
watch([selectedCardValues, selectedTargetId], () => {
|
||
prepare.value = undefined
|
||
result.value = undefined
|
||
})
|
||
|
||
onMounted(() => {
|
||
void bootstrap()
|
||
})
|
||
|
||
onLoad((query) => {
|
||
const value = normalizeQueryValue(query?.cardValue || query?.cardNo || query?.iccid)
|
||
if (!value)
|
||
return
|
||
initialCardValue.value = value
|
||
keyword.value = value
|
||
})
|
||
|
||
function normalizeQueryValue(value: string | string[] | undefined) {
|
||
return Array.isArray(value) ? value[0] : String(value || '')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<scroll-view scroll-y class="h-screen">
|
||
<view class="biz-page">
|
||
<view class="biz-stack">
|
||
<view v-if="noPermission" class="biz-card">
|
||
<view class="biz-card__body text-26rpx text-#64748b leading-38rpx">
|
||
当前账号不能使用卡片划拨。
|
||
</view>
|
||
</view>
|
||
|
||
<template v-else>
|
||
<view class="biz-hero">
|
||
<view class="biz-hero__top">
|
||
<view class="min-w-0 flex items-center gap-16rpx">
|
||
<view class="biz-hero__icon">
|
||
<view class="i-carbon-user-multiple" />
|
||
</view>
|
||
<view class="min-w-0">
|
||
<text class="biz-hero__title">卡片划拨</text>
|
||
<text class="biz-hero__sub">选择可划拨卡片,校验后转给目标下级客户</text>
|
||
</view>
|
||
</view>
|
||
<view class="app-tag app-tag--info shrink-0 whitespace-nowrap">
|
||
代理商
|
||
</view>
|
||
</view>
|
||
<view class="biz-stat-grid">
|
||
<view class="biz-stat">
|
||
<text class="biz-stat__label">目标客户</text>
|
||
<text class="biz-stat__value">{{ targetLabel(selectedTarget) }}</text>
|
||
</view>
|
||
<view class="biz-stat">
|
||
<text class="biz-stat__label">已选卡片</text>
|
||
<text class="biz-stat__value">{{ selectedCardValues.length }} 张</text>
|
||
</view>
|
||
<view class="biz-stat">
|
||
<text class="biz-stat__label">校验状态</text>
|
||
<text class="biz-stat__value">{{ prepareStatusText(prepare) }}</text>
|
||
</view>
|
||
<view class="biz-stat">
|
||
<text class="biz-stat__label">失败卡数</text>
|
||
<text class="biz-stat__value">{{ prepare?.failCount || 0 }} 张</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="biz-card">
|
||
<wd-cell
|
||
title="目标客户"
|
||
:value="targetLabel(selectedTarget)"
|
||
clickable
|
||
is-link
|
||
center
|
||
@click="openTargetPopup"
|
||
/>
|
||
</view>
|
||
|
||
<view class="biz-card">
|
||
<view class="biz-card__body">
|
||
<wd-input
|
||
v-model="keyword"
|
||
placeholder="搜索卡号、套餐、备注或客户"
|
||
:clearable="!isFixedCardMode"
|
||
@confirm="loadCards"
|
||
/>
|
||
<view class="biz-mini-action-grid mt-16rpx">
|
||
<wd-button size="small" type="primary" plain block custom-class="biz-mini-action-btn" :loading="loadingCards" @click="loadCards">
|
||
<view class="biz-mini-action-btn__inner">
|
||
<view class="i-carbon-renew biz-mini-action-btn__icon biz-mini-action-btn__icon--blue" />
|
||
<text class="biz-mini-action-btn__text">刷新</text>
|
||
</view>
|
||
</wd-button>
|
||
<wd-button
|
||
size="small"
|
||
type="primary"
|
||
plain
|
||
block
|
||
custom-class="biz-mini-action-btn"
|
||
:disabled="!visibleCards.length"
|
||
@click="selectVisibleCards"
|
||
>
|
||
<view class="biz-mini-action-btn__inner">
|
||
<view class="i-carbon-checkmark-outline biz-mini-action-btn__icon biz-mini-action-btn__icon--green" />
|
||
<text class="biz-mini-action-btn__text">全选</text>
|
||
</view>
|
||
</wd-button>
|
||
<wd-button
|
||
size="small"
|
||
type="primary"
|
||
plain
|
||
block
|
||
custom-class="biz-mini-action-btn"
|
||
:disabled="!selectedCardValues.length"
|
||
@click="clearSelection"
|
||
>
|
||
<view class="biz-mini-action-btn__inner">
|
||
<view class="i-carbon-close biz-mini-action-btn__icon biz-mini-action-btn__icon--gray" />
|
||
<text class="biz-mini-action-btn__text">清空</text>
|
||
</view>
|
||
</wd-button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="biz-section-head">
|
||
<view>
|
||
<text class="biz-section-title">当前可划拨卡</text>
|
||
<text class="biz-section-sub">划拨前会生成 ALC 批次进行归属和状态校验</text>
|
||
</view>
|
||
<view class="app-tag app-tag--info shrink-0 whitespace-nowrap">
|
||
已选 {{ selectedCardValues.length }}
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loadingCards" class="biz-card">
|
||
<view class="biz-card__body">
|
||
<wd-skeleton theme="paragraph" />
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else-if="errorMessage" class="biz-card">
|
||
<view class="biz-card__body grid gap-16rpx">
|
||
<view class="text-26rpx text-#b91c1c leading-38rpx">
|
||
{{ errorMessage }}
|
||
</view>
|
||
<wd-button size="small" type="primary" @click="loadCards">
|
||
重试
|
||
</wd-button>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="biz-card">
|
||
<view v-if="visibleCards.length" class="biz-card__body">
|
||
<wd-checkbox-group v-model="selectedCardValues" type="square" checked-color="#2563eb">
|
||
<view class="grid gap-12rpx">
|
||
<view
|
||
v-for="item in visibleCards"
|
||
:key="allocateCardValue(item)"
|
||
class="biz-check-row"
|
||
:class="{ 'is-active': isSelected(item) }"
|
||
>
|
||
<wd-checkbox
|
||
:name="allocateCardValue(item)"
|
||
custom-label-class="!block !w-full"
|
||
>
|
||
<view class="min-w-0 pr-16rpx">
|
||
<view class="truncate text-30rpx text-#111827 font-900">
|
||
{{ displayCardValue(item) }}
|
||
</view>
|
||
<view class="mt-8rpx truncate text-24rpx text-#64748b">
|
||
{{ displayPackageName(item) }}
|
||
</view>
|
||
<view class="mt-8rpx text-22rpx text-#64748b leading-32rpx">
|
||
{{ formatFlowPair(item) }} · 到期 {{ formatDateTime(item.flowPkgEndAt) }}
|
||
</view>
|
||
<view class="mt-8rpx text-22rpx text-#64748b leading-32rpx">
|
||
归属客户:{{ ownerDeptText(item) }}
|
||
</view>
|
||
<view v-if="item.terminalName" class="mt-8rpx text-22rpx text-#64748b leading-32rpx">
|
||
终端绑定:{{ item.terminalName }}
|
||
</view>
|
||
</view>
|
||
</wd-checkbox>
|
||
</view>
|
||
</view>
|
||
</wd-checkbox-group>
|
||
</view>
|
||
<view v-else class="biz-card__body text-center text-26rpx text-#64748b">
|
||
暂无可划拨卡片
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="prepare" class="biz-card">
|
||
<view class="biz-card__body">
|
||
<view class="biz-section-head mb-8rpx">
|
||
<text class="biz-section-title">校验结果</text>
|
||
<wd-tag :type="prepare.failCount ? 'warning' : 'success'" variant="plain">
|
||
{{ prepareStatusText(prepare) }}
|
||
</wd-tag>
|
||
</view>
|
||
<view class="biz-row">
|
||
<text class="biz-row__label">划拨批次</text>
|
||
<text class="biz-row__value mono">{{ prepare.batchNo || '-' }}</text>
|
||
</view>
|
||
<view class="biz-row">
|
||
<text class="biz-row__label">目标客户</text>
|
||
<text class="biz-row__value">{{ targetLabel(prepare.target) }}</text>
|
||
</view>
|
||
<view class="biz-row">
|
||
<text class="biz-row__label">校验卡数</text>
|
||
<text class="biz-row__value">
|
||
{{ prepare.validCount ?? prepare.quantity ?? 0 }}/{{ prepare.totalCount ?? selectedCardValues.length }} 张
|
||
</text>
|
||
</view>
|
||
<view class="biz-row">
|
||
<text class="biz-row__label">失败卡数</text>
|
||
<text class="biz-row__value">{{ prepare.failCount || 0 }} 张</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
v-if="prepare.failCount"
|
||
class="border-t border-#eef2f7 border-solid px-24rpx py-20rpx"
|
||
>
|
||
<view class="mb-14rpx text-26rpx text-#991b1b font-800">
|
||
失败明细
|
||
</view>
|
||
<view class="grid gap-12rpx">
|
||
<view
|
||
v-for="item in failedAllocateDetails(prepare)"
|
||
:key="`${item.rowNo}-${item.itemId}`"
|
||
class="rounded-8rpx bg-#fef2f2 px-18rpx py-16rpx"
|
||
>
|
||
<view class="text-24rpx text-#7f1d1d font-700">
|
||
第 {{ item.rowNo }} 行:{{ item.itemId || '-' }}
|
||
</view>
|
||
<view class="mt-6rpx text-23rpx text-#991b1b leading-32rpx">
|
||
{{ item.errorReason || '校验失败' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<wd-popup v-model="targetPopupVisible" position="bottom" round safe-area-inset-bottom>
|
||
<view class="max-h-84vh flex flex-col p-24rpx">
|
||
<view class="text-32rpx text-#111827 font-900">
|
||
选择目标客户
|
||
</view>
|
||
<view class="mt-20rpx">
|
||
<wd-input
|
||
v-model="targetKeyword"
|
||
label="搜索"
|
||
label-width="72px"
|
||
placeholder="客户名称或编码"
|
||
clearable
|
||
@confirm="searchTargetPopup"
|
||
/>
|
||
<view class="mt-16rpx">
|
||
<wd-button
|
||
size="small"
|
||
type="primary"
|
||
plain
|
||
block
|
||
custom-class="biz-mini-action-btn"
|
||
:loading="loadingTargets"
|
||
@click="refreshTargetPopup"
|
||
>
|
||
<view class="biz-mini-action-btn__inner">
|
||
<view class="i-carbon-renew biz-mini-action-btn__icon biz-mini-action-btn__icon--blue" />
|
||
<text class="biz-mini-action-btn__text">刷新目标客户</text>
|
||
</view>
|
||
</wd-button>
|
||
</view>
|
||
</view>
|
||
<view class="mt-16rpx text-24rpx text-#64748b">
|
||
{{ loadingTargets ? '正在加载下级代理...' : `已加载 ${targets.length} 个下级代理` }}
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="mt-20rpx h-620rpx min-h-260rpx">
|
||
<view v-if="targets.length" class="grid gap-12rpx">
|
||
<view
|
||
v-for="item in targets"
|
||
:key="item.deptId"
|
||
class="border rounded-8rpx border-solid p-20rpx"
|
||
:class="
|
||
Number(item.deptId) === Number(selectedTargetId)
|
||
? 'border-#2563eb bg-#eff6ff'
|
||
: 'border-#e5e7eb bg-#f8fafc'
|
||
"
|
||
@click="selectTarget(item)"
|
||
>
|
||
<view class="flex items-center justify-between gap-16rpx">
|
||
<view class="min-w-0">
|
||
<view class="truncate text-28rpx text-#111827 font-900">
|
||
{{ item.deptName }}
|
||
</view>
|
||
<view class="mt-8rpx text-23rpx text-#64748b">
|
||
{{ targetDescription(item) }}
|
||
</view>
|
||
</view>
|
||
<view class="flex shrink-0 items-center gap-8rpx">
|
||
<wd-tag :type="targetScopeTagType(item)" variant="plain">
|
||
{{ targetScopeName(item) }}
|
||
</wd-tag>
|
||
<wd-tag v-if="Number(item.deptId) === Number(selectedTargetId)" type="primary" variant="plain">
|
||
已选择
|
||
</wd-tag>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="p-32rpx text-center text-26rpx text-#64748b">
|
||
暂无可划拨下级代理
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</wd-popup>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<view
|
||
v-if="!noPermission && !targetPopupVisible"
|
||
class="biz-paybar z-1100"
|
||
>
|
||
<view class="biz-paybar__inner">
|
||
<view class="biz-paybar__summary">
|
||
<text class="biz-paybar__label">已选 {{ selectedCardValues.length }} 张</text>
|
||
<text class="biz-paybar__value">
|
||
{{ selectedTarget ? `${targetScopeName(selectedTarget)} · ${targetLabel(selectedTarget)}` : '请选择目标客户' }}
|
||
</text>
|
||
</view>
|
||
<view class="biz-paybar-actions">
|
||
<wd-button
|
||
type="primary"
|
||
plain
|
||
block
|
||
custom-class="biz-paybar-btn"
|
||
:loading="preparing"
|
||
:disabled="!canPrepare || preparing"
|
||
@click="handlePrepare"
|
||
>
|
||
校验划拨
|
||
</wd-button>
|
||
<wd-button
|
||
type="primary"
|
||
block
|
||
custom-class="biz-paybar-btn"
|
||
:loading="submitting"
|
||
:disabled="!canSubmit"
|
||
@click="handleSubmit"
|
||
>
|
||
确认划拨
|
||
</wd-button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|