fix: some
This commit is contained in:
750
src/views/asset-management/phone-asset-association/index.vue
Normal file
750
src/views/asset-management/phone-asset-association/index.vue
Normal file
@@ -0,0 +1,750 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="phone-asset-association-page" id="table-full-screen">
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
label-width="110"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #actions>
|
||||
<ElButton
|
||||
type="danger"
|
||||
plain
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="openBatchUnbindDialog"
|
||||
v-if="canBatchUnbind"
|
||||
>
|
||||
批量解绑
|
||||
</ElButton>
|
||||
<ElButton type="primary" plain @click="openImportDialog" v-if="canImportCreate">
|
||||
CSV解绑导入
|
||||
</ElButton>
|
||||
<ElButton @click="goUnbindImportTasks" v-if="canImportPage">解绑导入任务</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<ArtTable
|
||||
:loading="loading"
|
||||
:data="associationList"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:actions="getActions"
|
||||
:actionsWidth="100"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<!-- 单条解绑 -->
|
||||
<ElDialog
|
||||
v-model="unbindDialogVisible"
|
||||
title="解除手机号资产关联"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<ElForm ref="unbindFormRef" :model="unbindForm" :rules="unbindRules" label-width="90px">
|
||||
<ElFormItem label="资产标识">
|
||||
<span>{{ unbindForm.asset_identifier || '-' }}</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="手机号">
|
||||
<span>{{ unbindForm.phone || '-' }}</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="解除原因" prop="reason">
|
||||
<ElInput
|
||||
v-model="unbindForm.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请填写解除原因(1~500 字符)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="二次确认" prop="confirmed">
|
||||
<ElCheckbox v-model="unbindForm.confirmed">我已确认解除该关联关系</ElCheckbox>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="unbindDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="danger" :loading="unbindLoading" @click="confirmUnbind">确认解绑</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 批量解绑 -->
|
||||
<ElDialog
|
||||
v-model="batchUnbindDialogVisible"
|
||||
title="批量解除手机号资产关联"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<ElAlert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
:title="`已选择 ${selectedRows.length} 项资产(按资产类型与ID去重后执行,上限 200)`"
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
<ElForm ref="batchFormRef" :model="batchForm" :rules="batchRules" label-width="90px">
|
||||
<ElFormItem label="解除原因" prop="reason">
|
||||
<ElInput
|
||||
v-model="batchForm.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请填写解除原因(1~500 字符)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="二次确认" prop="confirmed">
|
||||
<ElCheckbox v-model="batchForm.confirmed">我已确认解除上述全部关联关系</ElCheckbox>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="batchUnbindDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="danger" :loading="batchUnbindLoading" @click="confirmBatchUnbind">
|
||||
确认批量解绑
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 批量解绑结果 -->
|
||||
<ElDialog v-model="batchResultVisible" title="批量解绑结果" width="560px">
|
||||
<div class="batch-result-summary">
|
||||
<span>成功 {{ batchResult?.success_count ?? 0 }} 项</span>
|
||||
<span class="text-danger">失败 {{ batchResult?.fail_count ?? 0 }} 项</span>
|
||||
</div>
|
||||
<ElTable :data="batchResult?.items || []" max-height="320" size="small" border>
|
||||
<ElTableColumn prop="asset_type" label="资产类型" width="110" />
|
||||
<ElTableColumn prop="asset_id" label="资产ID" width="90" />
|
||||
<ElTableColumn prop="success" label="结果" width="80" />
|
||||
<ElTableColumn prop="unbound_count" label="解除关系数" width="100" />
|
||||
<ElTableColumn prop="reason" label="失败原因" show-overflow-tooltip />
|
||||
</ElTable>
|
||||
<template #footer>
|
||||
<ElButton type="primary" @click="batchResultVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- CSV 解绑导入 -->
|
||||
<ElDialog
|
||||
v-model="importDialogVisible"
|
||||
title="CSV 解绑导入"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleImportDialogClosed"
|
||||
>
|
||||
<div class="import-tips">
|
||||
<span>模板表头:</span>
|
||||
<ElButton link type="primary" @click="downloadTemplate">下载模板</ElButton>
|
||||
<div class="import-tips-detail">
|
||||
资产标识,备注(备注可选);支持 UTF-8(可带 BOM),非 UTF-8 按 GBK 解码;无行数上限。
|
||||
</div>
|
||||
</div>
|
||||
<ElForm ref="importFormRef" :model="importForm" :rules="importRules" label-width="90px">
|
||||
<ElFormItem label="CSV 文件" prop="file">
|
||||
<ElUpload
|
||||
ref="uploadRef"
|
||||
drag
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
accept=".csv"
|
||||
:on-change="handleFileChange"
|
||||
:on-exceed="handleFileExceed"
|
||||
>
|
||||
<ElIcon class="el-icon--upload"><UploadFilled /></ElIcon>
|
||||
<div class="el-upload__text">拖拽文件到此处,或<em>点击选择 .csv 文件</em></div>
|
||||
</ElUpload>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="解除原因" prop="reason">
|
||||
<ElInput
|
||||
v-model="importForm.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请填写任务级解除原因(1~500 字符)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="二次确认" prop="confirmed">
|
||||
<ElCheckbox v-model="importForm.confirmed">我已确认按模板批量解除关联关系</ElCheckbox>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="importDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" :loading="importLoading" @click="confirmImport"
|
||||
>创建导入任务</ElButton
|
||||
>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
ElMessage,
|
||||
ElTag,
|
||||
FormInstance,
|
||||
FormRules,
|
||||
UploadFile,
|
||||
UploadInstance
|
||||
} from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import { PhoneAssetAssociationService, StorageService } from '@/api/modules'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { normalizeApiError } from '@/utils/business/apiError'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { AUGUST_PERMISSIONS } from '@/config/constants'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type {
|
||||
BatchUnbindAssetItem,
|
||||
BatchUnbindPhoneAssetAssociationResponse,
|
||||
PhoneAssetAssociation,
|
||||
PhoneAssetAssociationStatus
|
||||
} from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'PhoneAssetAssociation' })
|
||||
|
||||
const router = useRouter()
|
||||
const { hasAuth } = useAuth()
|
||||
const perms = AUGUST_PERMISSIONS.phoneAssetAssociation
|
||||
|
||||
const canBatchUnbind = computed(() => hasAuth(perms.unbindBatch))
|
||||
const canImportCreate = computed(() => hasAuth(perms.unbindImportCreate))
|
||||
const canImportPage = computed(() => hasAuth(perms.unbindImportPage))
|
||||
const canUnbind = computed(() => hasAuth(perms.unbind))
|
||||
|
||||
const loading = ref(false)
|
||||
const associationList = ref<PhoneAssetAssociation[]>([])
|
||||
const selectedRows = ref<PhoneAssetAssociation[]>([])
|
||||
const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
|
||||
|
||||
const initialSearchState = {
|
||||
asset_identifier: '',
|
||||
phone: '',
|
||||
status: undefined as PhoneAssetAssociationStatus | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
const statusOptions = [
|
||||
{ label: '有效', value: 1 as const },
|
||||
{ label: '已失效', value: 0 as const }
|
||||
]
|
||||
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '资产标识',
|
||||
prop: 'asset_identifier',
|
||||
type: 'input',
|
||||
config: { clearable: true, placeholder: 'ICCID / 虚拟号 / IMEI / SN / 接入号(精确匹配)' }
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
prop: 'phone',
|
||||
type: 'input',
|
||||
config: { clearable: true, placeholder: '完整手机号(精确匹配)' }
|
||||
},
|
||||
{
|
||||
label: '关联状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: { clearable: true, placeholder: '全部' },
|
||||
options: () => statusOptions
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'dateRange',
|
||||
type: 'datetimerange',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const columnOptions = [
|
||||
{ label: '资产类型', prop: 'asset_type' },
|
||||
{ label: '资产标识', prop: 'asset_identifier' },
|
||||
{ label: '手机号', prop: 'phone' },
|
||||
{ label: '建立时间', prop: 'established_at' },
|
||||
{ label: '来源', prop: 'source' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '失效时间', prop: 'invalidated_at' },
|
||||
{ label: '失效方式', prop: 'invalidation_method_name' },
|
||||
{ label: '失效原因', prop: 'invalidation_reason' }
|
||||
]
|
||||
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'asset_type',
|
||||
label: '资产类型',
|
||||
width: 100,
|
||||
formatter: (row: PhoneAssetAssociation) =>
|
||||
row.asset_type === 'iot_card' ? '物联网卡' : '设备'
|
||||
},
|
||||
{
|
||||
prop: 'asset_identifier',
|
||||
label: '资产标识',
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: PhoneAssetAssociation) =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: () => goToAsset(row)
|
||||
},
|
||||
row.asset_identifier
|
||||
)
|
||||
},
|
||||
{ prop: 'phone', label: '手机号', width: 140 },
|
||||
{
|
||||
prop: 'established_at',
|
||||
label: '建立时间',
|
||||
minWidth: 170,
|
||||
formatter: (row: PhoneAssetAssociation) => formatDateTime(row.established_at) || '-'
|
||||
},
|
||||
{
|
||||
prop: 'source',
|
||||
label: '来源',
|
||||
width: 120,
|
||||
formatter: () => 'H5 短信验证'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 90,
|
||||
formatter: (row: PhoneAssetAssociation) =>
|
||||
h(
|
||||
ElTag,
|
||||
{ type: row.status === 1 ? 'success' : 'info' },
|
||||
() => row.status_name || (row.status === 1 ? '有效' : '已失效')
|
||||
)
|
||||
},
|
||||
{
|
||||
prop: 'invalidated_at',
|
||||
label: '失效时间',
|
||||
minWidth: 170,
|
||||
formatter: (row: PhoneAssetAssociation) => formatDateTime(row.invalidated_at) || '-'
|
||||
},
|
||||
{
|
||||
prop: 'invalidation_method_name',
|
||||
label: '失效方式',
|
||||
width: 130,
|
||||
formatter: (row: PhoneAssetAssociation) => row.invalidation_method_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'invalidation_reason',
|
||||
label: '失效原因',
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: PhoneAssetAssociation) => row.invalidation_reason || '-'
|
||||
}
|
||||
])
|
||||
|
||||
// ========== 列表数据 ==========
|
||||
|
||||
const syncDateRange = () => {
|
||||
if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) {
|
||||
searchForm.created_at_start = searchForm.dateRange[0] || ''
|
||||
searchForm.created_at_end = searchForm.dateRange[1] || ''
|
||||
} else {
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
}
|
||||
}
|
||||
|
||||
const toRfc3339 = (date: string, endOfDay = false) => {
|
||||
if (!date) return undefined
|
||||
if (date.includes('T')) return date
|
||||
return `${date}T${endOfDay ? '23:59:59' : '00:00:00'}+08:00`
|
||||
}
|
||||
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
syncDateRange()
|
||||
const res = await PhoneAssetAssociationService.getAssociations({
|
||||
page: pagination.page,
|
||||
page_size: pagination.pageSize,
|
||||
asset_identifier: searchForm.asset_identifier || undefined,
|
||||
phone: searchForm.phone || undefined,
|
||||
status: searchForm.status,
|
||||
created_at_start: toRfc3339(searchForm.created_at_start),
|
||||
created_at_end: toRfc3339(searchForm.created_at_end, true)
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
associationList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取手机号资产关联列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取手机号资产关联列表失败:', error)
|
||||
ElMessage.error(normalizeApiError(error).message || '获取手机号资产关联列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (pageSize: number) => {
|
||||
pagination.pageSize = pageSize
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page: number) => {
|
||||
pagination.page = page
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleSelectionChange = (rows: PhoneAssetAssociation[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
const goToAsset = (row: PhoneAssetAssociation) => {
|
||||
router.push({
|
||||
path: RoutesAlias.AssetInformation,
|
||||
query:
|
||||
row.asset_type === 'iot_card'
|
||||
? { iccid: row.asset_identifier }
|
||||
: { virtual_no: row.asset_identifier }
|
||||
})
|
||||
}
|
||||
|
||||
const getActions = (row: PhoneAssetAssociation) => {
|
||||
const actions: any[] = []
|
||||
if (row.status === 1 && canUnbind.value) {
|
||||
actions.push({ label: '解绑', handler: () => openUnbindDialog(row), type: 'danger' })
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
// ========== 单条解绑 ==========
|
||||
|
||||
const unbindDialogVisible = ref(false)
|
||||
const unbindLoading = ref(false)
|
||||
const unbindFormRef = ref<FormInstance>()
|
||||
const unbindForm = reactive<{
|
||||
id: number
|
||||
asset_identifier: string
|
||||
phone: string
|
||||
reason: string
|
||||
confirmed: boolean
|
||||
}>({
|
||||
id: 0,
|
||||
asset_identifier: '',
|
||||
phone: '',
|
||||
reason: '',
|
||||
confirmed: false
|
||||
})
|
||||
|
||||
const unbindRules: FormRules = {
|
||||
reason: [
|
||||
{ required: true, message: '请填写解除原因', trigger: 'blur' },
|
||||
{ min: 1, max: 500, message: '解除原因长度为 1~500 字符', trigger: 'blur' }
|
||||
],
|
||||
confirmed: [
|
||||
{
|
||||
validator: (_rule, value: boolean, callback) => {
|
||||
if (!value) callback(new Error('请先勾选二次确认'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const openUnbindDialog = (row: PhoneAssetAssociation) => {
|
||||
unbindForm.id = row.id
|
||||
unbindForm.asset_identifier = row.asset_identifier
|
||||
unbindForm.phone = row.phone
|
||||
unbindForm.reason = ''
|
||||
unbindForm.confirmed = false
|
||||
unbindDialogVisible.value = true
|
||||
}
|
||||
|
||||
const confirmUnbind = async () => {
|
||||
if (!(await unbindFormRef.value?.validate().catch(() => false))) return
|
||||
unbindLoading.value = true
|
||||
try {
|
||||
const res = await PhoneAssetAssociationService.unbindAssociation(
|
||||
unbindForm.id,
|
||||
unbindForm.reason.trim(),
|
||||
unbindForm.confirmed
|
||||
)
|
||||
if (res.code !== 0) {
|
||||
ElMessage.error(res.msg || '解除关联失败')
|
||||
return
|
||||
}
|
||||
ElMessage.success('解除关联成功')
|
||||
unbindDialogVisible.value = false
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error('解除关联失败:', error)
|
||||
ElMessage.error(normalizeApiError(error).message || '解除关联失败')
|
||||
} finally {
|
||||
unbindLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 批量解绑 ==========
|
||||
|
||||
const batchUnbindDialogVisible = ref(false)
|
||||
const batchUnbindLoading = ref(false)
|
||||
const batchFormRef = ref<FormInstance>()
|
||||
const batchForm = reactive({ reason: '', confirmed: false, assets: [] as BatchUnbindAssetItem[] })
|
||||
const batchResult = ref<BatchUnbindPhoneAssetAssociationResponse | null>(null)
|
||||
const batchResultVisible = ref(false)
|
||||
|
||||
const batchRules: FormRules = {
|
||||
reason: [
|
||||
{ required: true, message: '请填写解除原因', trigger: 'blur' },
|
||||
{ min: 1, max: 500, message: '解除原因长度为 1~500 字符', trigger: 'blur' }
|
||||
],
|
||||
confirmed: [
|
||||
{
|
||||
validator: (_rule, value: boolean, callback) => {
|
||||
if (!value) callback(new Error('请先勾选二次确认'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const buildUniqueAssets = (rows: PhoneAssetAssociation[]): BatchUnbindAssetItem[] => {
|
||||
const map = new Map<string, BatchUnbindAssetItem>()
|
||||
rows.forEach((row) => {
|
||||
if (row.status !== 1) return
|
||||
const key = `${row.asset_type}:${row.asset_id}`
|
||||
if (!map.has(key)) map.set(key, { asset_type: row.asset_type, asset_id: row.asset_id })
|
||||
})
|
||||
return Array.from(map.values()).slice(0, 200)
|
||||
}
|
||||
|
||||
const openBatchUnbindDialog = () => {
|
||||
batchForm.assets = buildUniqueAssets(selectedRows.value)
|
||||
if (batchForm.assets.length === 0) {
|
||||
ElMessage.warning('请选择有效关联的资产')
|
||||
return
|
||||
}
|
||||
batchForm.reason = ''
|
||||
batchForm.confirmed = false
|
||||
batchResult.value = null
|
||||
batchUnbindDialogVisible.value = true
|
||||
}
|
||||
|
||||
const confirmBatchUnbind = async () => {
|
||||
if (!(await batchFormRef.value?.validate().catch(() => false))) return
|
||||
batchUnbindLoading.value = true
|
||||
try {
|
||||
const res = await PhoneAssetAssociationService.batchUnbind({
|
||||
assets: batchForm.assets,
|
||||
reason: batchForm.reason.trim(),
|
||||
confirmed: batchForm.confirmed
|
||||
})
|
||||
if (res.code !== 0 || !res.data) {
|
||||
ElMessage.error(res.msg || '批量解绑失败')
|
||||
return
|
||||
}
|
||||
batchResult.value = res.data
|
||||
batchUnbindDialogVisible.value = false
|
||||
batchResultVisible.value = true
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error('批量解绑失败:', error)
|
||||
ElMessage.error(normalizeApiError(error).message || '批量解绑失败')
|
||||
} finally {
|
||||
batchUnbindLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ========== CSV 解绑导入 ==========
|
||||
|
||||
const importDialogVisible = ref(false)
|
||||
const importLoading = ref(false)
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const importFormRef = ref<FormInstance>()
|
||||
const importForm = reactive({
|
||||
file: null as File | null,
|
||||
reason: '',
|
||||
confirmed: false
|
||||
})
|
||||
const importRules: FormRules = {
|
||||
file: [
|
||||
{
|
||||
validator: (_rule, _value, callback) => {
|
||||
if (!importForm.file) callback(new Error('请选择 CSV 文件'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
reason: [
|
||||
{ required: true, message: '请填写任务级解除原因', trigger: 'blur' },
|
||||
{ min: 1, max: 500, message: '解除原因长度为 1~500 字符', trigger: 'blur' }
|
||||
],
|
||||
confirmed: [
|
||||
{
|
||||
validator: (_rule, value: boolean, callback) => {
|
||||
if (!value) callback(new Error('请先勾选二次确认'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const openImportDialog = () => {
|
||||
importForm.file = null
|
||||
importForm.reason = ''
|
||||
importForm.confirmed = false
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleImportDialogClosed = () => {
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
const handleFileChange = (file: UploadFile) => {
|
||||
const rawFile = file.raw
|
||||
if (!rawFile) return
|
||||
if (!rawFile.name.toLowerCase().endsWith('.csv')) {
|
||||
ElMessage.error('解绑导入仅支持 .csv 文件')
|
||||
uploadRef.value?.clearFiles()
|
||||
importForm.file = null
|
||||
return
|
||||
}
|
||||
importForm.file = rawFile
|
||||
}
|
||||
|
||||
const handleFileExceed = () => {
|
||||
ElMessage.warning('仅支持上传一个 CSV 文件')
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
const content = '\uFEFF资产标识,备注\n'
|
||||
const blob = new Blob([content], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = '手机号资产解绑导入模板.csv'
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const goUnbindImportTasks = () => {
|
||||
router.push(RoutesAlias.PhoneAssetUnbindImportTasks)
|
||||
}
|
||||
|
||||
const confirmImport = async () => {
|
||||
if (!(await importFormRef.value?.validate().catch(() => false))) return
|
||||
const file = importForm.file
|
||||
if (!file) return
|
||||
importLoading.value = true
|
||||
try {
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: 'text/csv',
|
||||
purpose: 'phone_unbind_import'
|
||||
})
|
||||
if (uploadUrlRes.code !== 0 || !uploadUrlRes.data) {
|
||||
ElMessage.error(uploadUrlRes.msg || '获取上传地址失败')
|
||||
return
|
||||
}
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
await StorageService.uploadFile(upload_url, file, 'text/csv')
|
||||
|
||||
const res = await PhoneAssetAssociationService.createUnbindImport({
|
||||
file_key,
|
||||
reason: importForm.reason.trim(),
|
||||
confirmed: importForm.confirmed
|
||||
})
|
||||
if (res.code !== 0 || !res.data) {
|
||||
ElMessage.error(res.msg || '创建解绑导入任务失败')
|
||||
return
|
||||
}
|
||||
ElMessage.success(`解绑导入任务已创建!任务编号:${res.data.task_no}`)
|
||||
importDialogVisible.value = false
|
||||
goUnbindImportTasks()
|
||||
} catch (error) {
|
||||
console.error('创建解绑导入任务失败:', error)
|
||||
ElMessage.error(normalizeApiError(error).message || '创建解绑导入任务失败')
|
||||
} finally {
|
||||
importLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.phone-asset-association-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.batch-result-summary {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
|
||||
.text-danger {
|
||||
color: var(--el-color-danger);
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-tips {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
.import-tips-detail {
|
||||
margin-top: 4px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user