fetch(add): 分配记录,批量分配/回收, 单卡列表, 任务列表, 导入ICCID
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m21s

This commit is contained in:
sexygoat
2026-01-24 16:18:30 +08:00
parent c69124a819
commit 0eed8244e5
13 changed files with 2375 additions and 334 deletions

View File

@@ -1,352 +1,324 @@
<template>
<div class="page-content">
<!-- 分配模式选择 -->
<ElCard shadow="never" style="margin-bottom: 20px">
<ElRadioGroup v-model="assignMode" size="large">
<ElRadioButton value="sim">网卡批量分配</ElRadioButton>
<ElRadioButton value="device">设备批量分配</ElRadioButton>
</ElRadioGroup>
</ElCard>
<ArtTableFullScreen>
<div class="asset-allocation-records-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="formFilters"
:items="formItems"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<!-- 网卡分配 -->
<ElCard v-if="assignMode === 'sim'" shadow="never" style="margin-bottom: 20px">
<template #header>
<span style="font-weight: 500">选择网卡资产</span>
</template>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
/>
<ElRow :gutter="12" style="margin-bottom: 16px">
<ElCol :xs="24" :sm="12" :lg="6">
<ElInput v-model="simSearchQuery" placeholder="ICCID/IMSI" clearable />
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElSelect v-model="simStatusFilter" placeholder="状态筛选" clearable style="width: 100%">
<ElOption label="全部" value="" />
<ElOption label="激活" value="active" />
<ElOption label="未激活" value="inactive" />
<ElOption label="停机" value="suspended" />
</ElSelect>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElButton v-ripple @click="searchSims">搜索</ElButton>
</ElCol>
</ElRow>
<ArtTable :data="filteredSimData" index>
<template #default>
<ElTableColumn label="ICCID" prop="iccid" width="200" />
<ElTableColumn label="IMSI" prop="imsi" width="180" />
<ElTableColumn label="运营商" prop="operator" width="100">
<template #default="scope">
<ElTag size="small">{{ scope.row.operator }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="状态" prop="status" width="100">
<template #default="scope">
<ElTag v-if="scope.row.status === 'active'" type="success" size="small">激活</ElTag>
<ElTag v-else-if="scope.row.status === 'inactive'" type="info" size="small"
>未激活</ElTag
>
<ElTag v-else type="warning" size="small">停机</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="绑定设备" prop="deviceCode" width="150">
<template #default="scope">
<ElTag v-if="scope.row.deviceCode" type="primary" size="small">
{{ scope.row.deviceCode }}
</ElTag>
<span v-else style="color: var(--el-text-color-secondary)">未绑定</span>
</template>
</ElTableColumn>
<ElTableColumn label="剩余流量" prop="remainData" width="120" />
<ElTableColumn label="到期时间" prop="expireTime" width="180" />
</template>
</ArtTable>
</ElCard>
<!-- 设备分配 -->
<ElCard v-if="assignMode === 'device'" shadow="never">
<template #header>
<span style="font-weight: 500">选择设备资产</span>
</template>
<ElRow :gutter="12" style="margin-bottom: 16px">
<ElCol :xs="24" :sm="12" :lg="6">
<ElInput v-model="deviceSearchQuery" placeholder="设备编号/名称" clearable />
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElSelect v-model="deviceTypeFilter" placeholder="设备类型" clearable style="width: 100%">
<ElOption label="全部" value="" />
<ElOption label="GPS定位器" value="gps" />
<ElOption label="智能水表" value="water_meter" />
<ElOption label="智能电表" value="electric_meter" />
</ElSelect>
</ElCol>
<ElCol :xs="24" :sm="12" :lg="6">
<ElButton v-ripple @click="searchDevices">搜索</ElButton>
</ElCol>
</ElRow>
<ArtTable :data="filteredDeviceData" index>
<template #default>
<ElTableColumn label="设备编号" prop="deviceCode" width="180" />
<ElTableColumn label="设备名称" prop="deviceName" min-width="180" />
<ElTableColumn label="设备类型" prop="deviceType" width="120">
<template #default="scope">
<ElTag size="small">{{ getDeviceTypeText(scope.row.deviceType) }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="绑定ICCID" prop="iccid" width="200">
<template #default="scope">
<ElTag v-if="scope.row.iccid" type="success" size="small">
{{ scope.row.iccid }}
</ElTag>
<span v-else style="color: var(--el-text-color-secondary)">未绑定</span>
</template>
</ElTableColumn>
<ElTableColumn label="在线状态" prop="onlineStatus" width="100">
<template #default="scope">
<ElTag v-if="scope.row.onlineStatus === 'online'" type="success" size="small"
>在线</ElTag
>
<ElTag v-else type="info" size="small">离线</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="createTime" width="180" />
</template>
</ArtTable>
</ElCard>
<!-- 分配对话框 -->
<ElDialog v-model="assignDialogVisible" title="资产分配" width="600px" align-center>
<ElForm ref="formRef" :model="assignForm" :rules="assignRules" label-width="120px">
<ElFormItem label="分配类型">
<ElTag v-if="assignForm.type === 'sim'" type="primary">网卡资产</ElTag>
<ElTag v-else type="success">设备资产</ElTag>
</ElFormItem>
<ElFormItem label="分配数量">
<div>
<span
v-if="assignForm.type === 'sim'"
style="font-size: 18px; font-weight: 600; color: var(--el-color-primary)"
>
{{ selectedSims.length }} 张网卡
</span>
<span
v-else
style="font-size: 18px; font-weight: 600; color: var(--el-color-success)"
>
{{ selectedDevices.length }} 个设备
</span>
</div>
</ElFormItem>
<ElFormItem label="目标代理商" prop="targetAgentId">
<ElSelect
v-model="assignForm.targetAgentId"
placeholder="请选择目标代理商"
filterable
style="width: 100%"
>
<ElOption
v-for="agent in agentList"
:key="agent.id"
:label="`${agent.agentName} (等级${agent.level})`"
:value="agent.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="分配说明" prop="remark">
<ElInput
v-model="assignForm.remark"
type="textarea"
:rows="3"
placeholder="请输入分配说明"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="assignDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleAssignSubmit">确认分配</ElButton>
</div>
</template>
</ElDialog>
</div>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="recordList"
:currentPage="pagination.page"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
<ElTableColumn label="操作" width="120" fixed="right">
<template #default="scope">
<ElButton type="primary" link @click="viewDetail(scope.row)">查看详情</ElButton>
</template>
</ElTableColumn>
</template>
</ArtTable>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { h } from 'vue'
import { useRouter } from 'vue-router'
import { CardService } from '@/api/modules'
import { ElMessage, ElTag } from 'element-plus'
import type { SearchFormItem } from '@/types'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { formatDateTime } from '@/utils/business/format'
import type {
AssetAllocationRecord,
AllocationTypeEnum,
AssetTypeEnum
} from '@/types/api/card'
defineOptions({ name: 'AssetAssign' })
defineOptions({ name: 'AssetAllocationRecords' })
interface SimCard {
id: string
iccid: string
imsi: string
operator: string
status: string
deviceCode?: string
remainData: string
expireTime: string
const router = useRouter()
const loading = ref(false)
const tableRef = ref()
// 搜索表单初始值
const initialSearchState = {
allocation_type: undefined as AllocationTypeEnum | undefined,
asset_type: undefined as AssetTypeEnum | undefined,
asset_identifier: '',
allocation_no: '',
from_shop_id: undefined as number | undefined,
to_shop_id: undefined as number | undefined,
operator_id: undefined as number | undefined,
created_at_start: '',
created_at_end: ''
}
interface Device {
id: string
deviceCode: string
deviceName: string
deviceType: string
iccid?: string
onlineStatus: string
createTime: string
}
// 搜索表单
const formFilters = reactive({ ...initialSearchState })
const assignMode = ref('sim')
const simSearchQuery = ref('')
const simStatusFilter = ref('')
const deviceSearchQuery = ref('')
const deviceTypeFilter = ref('')
const assignDialogVisible = ref(false)
const formRef = ref<FormInstance>()
const selectedSims = ref<SimCard[]>([])
const selectedDevices = ref<Device[]>([])
const assignForm = reactive({
type: 'sim',
targetAgentId: '',
remark: ''
// 分页
const pagination = reactive({
page: 1,
pageSize: 20,
total: 0
})
const assignRules = reactive<FormRules>({
targetAgentId: [{ required: true, message: '请选择目标代理商', trigger: 'change' }]
})
const agentList = ref([
{ id: '1', agentName: '华东区总代理', level: 1 },
{ id: '2', agentName: '华南区代理', level: 2 },
{ id: '3', agentName: '华北区代理', level: 1 }
])
const simMockData = ref<SimCard[]>([
// 搜索表单配置
const formItems: SearchFormItem[] = [
{
id: '1',
iccid: '89860123456789012345',
imsi: '460012345678901',
operator: '中国移动',
status: 'active',
deviceCode: 'DEV001',
remainData: '50GB',
expireTime: '2026-12-31'
label: '分配类型',
prop: 'allocation_type',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '分配', value: 'allocate' },
{ label: '回收', value: 'recall' }
]
},
{
id: '2',
iccid: '89860123456789012346',
imsi: '460012345678902',
operator: '中国联通',
status: 'active',
remainData: '80GB',
expireTime: '2026-11-30'
}
])
const deviceMockData = ref<Device[]>([
{
id: '1',
deviceCode: 'DEV001',
deviceName: 'GPS定位器-001',
deviceType: 'gps',
iccid: '89860123456789012345',
onlineStatus: 'online',
createTime: '2026-01-01 10:00:00'
label: '资产类型',
prop: 'asset_type',
type: 'select',
config: {
clearable: true,
placeholder: '全部'
},
options: () => [
{ label: '物联网卡', value: 'iot_card' },
{ label: '设备', value: 'device' }
]
},
{
id: '2',
deviceCode: 'DEV002',
deviceName: '智能水表-002',
deviceType: 'water_meter',
iccid: '89860123456789012346',
onlineStatus: 'offline',
createTime: '2026-01-02 11:00:00'
}
])
const filteredSimData = computed(() => {
let data = simMockData.value
if (simSearchQuery.value) {
data = data.filter(
(item) =>
item.iccid.includes(simSearchQuery.value) || item.imsi.includes(simSearchQuery.value)
)
}
if (simStatusFilter.value) {
data = data.filter((item) => item.status === simStatusFilter.value)
}
return data
})
const filteredDeviceData = computed(() => {
let data = deviceMockData.value
if (deviceSearchQuery.value) {
data = data.filter(
(item) =>
item.deviceCode.includes(deviceSearchQuery.value) ||
item.deviceName.includes(deviceSearchQuery.value)
)
}
if (deviceTypeFilter.value) {
data = data.filter((item) => item.deviceType === deviceTypeFilter.value)
}
return data
})
const getDeviceTypeText = (type: string) => {
const map: Record<string, string> = {
gps: 'GPS定位器',
water_meter: '智能水表',
electric_meter: '智能电表'
}
return map[type] || type
}
const searchSims = () => {}
const searchDevices = () => {}
const handleSimSelectionChange = (rows: SimCard[]) => {
selectedSims.value = rows
}
const handleDeviceSelectionChange = (rows: Device[]) => {
selectedDevices.value = rows
}
const showAssignDialog = (type: string) => {
assignForm.type = type
assignDialogVisible.value = true
}
const handleAssignSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
assignDialogVisible.value = false
formRef.value.resetFields()
selectedSims.value = []
selectedDevices.value = []
ElMessage.success('资产分配成功')
label: '分配单号',
prop: 'allocation_no',
type: 'input',
config: {
clearable: true,
placeholder: '请输入分配单号'
}
},
{
label: '资产标识符',
prop: 'asset_identifier',
type: 'input',
config: {
clearable: true,
placeholder: 'ICCID或设备号'
}
},
{
label: '创建时间',
prop: 'created_at_range',
type: 'date',
config: {
type: 'datetimerange',
clearable: true,
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
]
// 列配置
const columnOptions = [
{ label: '分配单号', prop: 'allocation_no' },
{ label: '分配类型', prop: 'allocation_name' },
{ label: '资产类型', prop: 'asset_type_name' },
{ label: '资产标识符', prop: 'asset_identifier' },
{ label: '来源所有者', prop: 'from_owner_name' },
{ label: '目标所有者', prop: 'to_owner_name' },
{ label: '操作人', prop: 'operator_name' },
{ label: '关联卡数量', prop: 'related_card_count' },
{ label: '创建时间', prop: 'created_at' }
]
const recordList = ref<AssetAllocationRecord[]>([])
// 获取分配类型标签类型
const getAllocationTypeType = (type: AllocationTypeEnum) => {
return type === 'allocate' ? 'success' : 'warning'
}
// 获取资产类型标签类型
const getAssetTypeType = (type: AssetTypeEnum) => {
return type === 'iot_card' ? 'primary' : 'info'
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'allocation_no',
label: '分配单号',
minWidth: 180
},
{
prop: 'allocation_name',
label: '分配类型',
width: 100,
formatter: (row: AssetAllocationRecord) => {
return h(
ElTag,
{ type: getAllocationTypeType(row.allocation_type) },
() => row.allocation_name
)
}
},
{
prop: 'asset_type_name',
label: '资产类型',
width: 100,
formatter: (row: AssetAllocationRecord) => {
return h(ElTag, { type: getAssetTypeType(row.asset_type) }, () => row.asset_type_name)
}
},
{
prop: 'asset_identifier',
label: '资产标识符',
minWidth: 180
},
{
prop: 'from_owner_name',
label: '来源所有者',
width: 150
},
{
prop: 'to_owner_name',
label: '目标所有者',
width: 150
},
{
prop: 'operator_name',
label: '操作人',
width: 120
},
{
prop: 'related_card_count',
label: '关联卡数量',
width: 120
},
{
prop: 'remark',
label: '备注',
minWidth: 150,
showOverflowTooltip: true
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: AssetAllocationRecord) => formatDateTime(row.created_at)
}
])
onMounted(() => {
getTableData()
})
// 获取分配记录列表
const getTableData = async () => {
loading.value = true
try {
const params = {
page: pagination.page,
page_size: pagination.pageSize,
...formFilters
}
// 处理日期范围
if ((params as any).created_at_range && (params as any).created_at_range.length === 2) {
params.created_at_start = (params as any).created_at_range[0]
params.created_at_end = (params as any).created_at_range[1]
delete (params as any).created_at_range
}
// 清理空值
Object.keys(params).forEach((key) => {
if (params[key] === '' || params[key] === undefined) {
delete params[key]
}
})
const res = await CardService.getAssetAllocationRecords(params)
if (res.code === 0) {
recordList.value = res.data.list || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error(error)
ElMessage.error('获取分配记录列表失败')
} finally {
loading.value = false
}
}
// 重置搜索
const handleReset = () => {
Object.assign(formFilters, { ...initialSearchState })
pagination.page = 1
getTableData()
}
// 搜索
const handleSearch = () => {
pagination.page = 1
getTableData()
}
// 刷新表格
const handleRefresh = () => {
getTableData()
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.page = newCurrentPage
getTableData()
}
// 查看详情
const viewDetail = (row: AssetAllocationRecord) => {
router.push({
path: '/asset-management/allocation-record-detail',
query: { id: row.id }
})
}
</script>
<style lang="scss" scoped>
.page-content {
:deep(.el-radio-button__inner) {
padding: 12px 20px;
}
.asset-allocation-records-page {
// Allocation records page styles
}
</style>