Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
442
src/views/asset-management/asset-assign/index.vue
Normal file
442
src/views/asset-management/asset-assign/index.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 操作提示 -->
|
||||
<ElAlert type="info" :closable="false" style="margin-bottom: 20px">
|
||||
<template #title>
|
||||
<div style="line-height: 1.8">
|
||||
<p><strong>资产分配说明:</strong></p>
|
||||
<p>1. <strong>网卡批量分配</strong>:仅分配选中的网卡资产</p>
|
||||
<p>2. <strong>设备批量分配</strong>:仅分配选中的设备资产</p>
|
||||
<p>3. <strong>网卡+设备分配</strong>:如果网卡有绑定设备,将同时分配网卡和设备</p>
|
||||
<p>4. 分配后资产所有权将转移至目标代理商</p>
|
||||
</div>
|
||||
</template>
|
||||
</ElAlert>
|
||||
|
||||
<!-- 分配模式选择 -->
|
||||
<ElCard shadow="never" style="margin-bottom: 20px">
|
||||
<ElRadioGroup v-model="assignMode" size="large">
|
||||
<ElRadioButton value="sim">网卡批量分配</ElRadioButton>
|
||||
<ElRadioButton value="device">设备批量分配</ElRadioButton>
|
||||
<ElRadioButton value="both">网卡+设备分配</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElCard>
|
||||
|
||||
<!-- 网卡分配 -->
|
||||
<ElCard v-if="assignMode === 'sim' || assignMode === 'both'" shadow="never" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span style="font-weight: 500">选择网卡资产</span>
|
||||
<ElButton type="primary" size="small" :disabled="selectedSims.length === 0" @click="showAssignDialog('sim')">
|
||||
分配选中的 {{ selectedSims.length }} 张网卡
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 @selection-change="handleSimSelectionChange">
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
<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>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span style="font-weight: 500">选择设备资产</span>
|
||||
<ElButton type="primary" size="small" :disabled="selectedDevices.length === 0" @click="showAssignDialog('device')">
|
||||
分配选中的 {{ selectedDevices.length }} 个设备
|
||||
</ElButton>
|
||||
</div>
|
||||
</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 @selection-change="handleDeviceSelectionChange">
|
||||
<template #default>
|
||||
<ElTableColumn type="selection" width="55" />
|
||||
<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-if="assignForm.type === 'device'" type="success">设备资产</ElTag>
|
||||
<ElTag v-else type="warning">网卡+设备</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-if="assignForm.type === 'device'" style="font-size: 18px; font-weight: 600; color: var(--el-color-success)">
|
||||
{{ selectedDevices.length }} 个设备
|
||||
</span>
|
||||
<span v-else style="font-size: 18px; font-weight: 600">
|
||||
{{ selectedSims.length }} 张网卡 + {{ selectedSims.filter(s => s.deviceCode).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>
|
||||
|
||||
<ElAlert type="warning" :closable="false">
|
||||
分配后资产所有权将转移至目标代理商,原账号将无法管理这些资产!
|
||||
</ElAlert>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="assignDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleAssignSubmit">确认分配</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 分配记录 -->
|
||||
<ElCard shadow="never" style="margin-top: 20px">
|
||||
<template #header>
|
||||
<span style="font-weight: 500">最近分配记录</span>
|
||||
</template>
|
||||
|
||||
<ArtTable :data="assignHistory" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="分配批次号" prop="batchNo" width="180" />
|
||||
<ElTableColumn label="分配类型" prop="type" width="120">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.type === 'sim'" type="primary" size="small">网卡</ElTag>
|
||||
<ElTag v-else-if="scope.row.type === 'device'" type="success" size="small">设备</ElTag>
|
||||
<ElTag v-else type="warning" size="small">网卡+设备</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="分配数量" prop="quantity" width="100" />
|
||||
<ElTableColumn label="目标代理商" prop="targetAgentName" min-width="150" />
|
||||
<ElTableColumn label="分配说明" prop="remark" min-width="200" show-overflow-tooltip />
|
||||
<ElTableColumn label="分配时间" prop="assignTime" width="180" />
|
||||
<ElTableColumn label="操作人" prop="operator" width="100" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'AssetAssign' })
|
||||
|
||||
interface SimCard {
|
||||
id: string
|
||||
iccid: string
|
||||
imsi: string
|
||||
operator: string
|
||||
status: string
|
||||
deviceCode?: string
|
||||
remainData: string
|
||||
expireTime: string
|
||||
}
|
||||
|
||||
interface Device {
|
||||
id: string
|
||||
deviceCode: string
|
||||
deviceName: string
|
||||
deviceType: string
|
||||
iccid?: string
|
||||
onlineStatus: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
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 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[]>([
|
||||
{
|
||||
id: '1',
|
||||
iccid: '89860123456789012345',
|
||||
imsi: '460012345678901',
|
||||
operator: '中国移动',
|
||||
status: 'active',
|
||||
deviceCode: 'DEV001',
|
||||
remainData: '50GB',
|
||||
expireTime: '2026-12-31'
|
||||
},
|
||||
{
|
||||
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'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
deviceCode: 'DEV002',
|
||||
deviceName: '智能水表-002',
|
||||
deviceType: 'water_meter',
|
||||
iccid: '89860123456789012346',
|
||||
onlineStatus: 'offline',
|
||||
createTime: '2026-01-02 11:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const assignHistory = ref([
|
||||
{
|
||||
id: '1',
|
||||
batchNo: 'ASSIGN202601090001',
|
||||
type: 'sim',
|
||||
quantity: 100,
|
||||
targetAgentName: '华东区总代理',
|
||||
remark: '批量分配给华东区',
|
||||
assignTime: '2026-01-09 10:00:00',
|
||||
operator: 'admin'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
batchNo: 'ASSIGN202601080001',
|
||||
type: 'both',
|
||||
quantity: 50,
|
||||
targetAgentName: '华南区代理',
|
||||
remark: '网卡和设备一起分配',
|
||||
assignTime: '2026-01-08 14:00:00',
|
||||
operator: 'admin'
|
||||
}
|
||||
])
|
||||
|
||||
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) {
|
||||
const agent = agentList.value.find((a) => a.id === assignForm.targetAgentId)
|
||||
let quantity = 0
|
||||
|
||||
if (assignForm.type === 'sim') {
|
||||
quantity = selectedSims.value.length
|
||||
} else if (assignForm.type === 'device') {
|
||||
quantity = selectedDevices.value.length
|
||||
} else {
|
||||
quantity = selectedSims.value.length
|
||||
}
|
||||
|
||||
assignHistory.value.unshift({
|
||||
id: Date.now().toString(),
|
||||
batchNo: `ASSIGN${Date.now()}`,
|
||||
type: assignForm.type,
|
||||
quantity,
|
||||
targetAgentName: agent?.agentName || '',
|
||||
remark: assignForm.remark,
|
||||
assignTime: new Date().toLocaleString('zh-CN'),
|
||||
operator: 'admin'
|
||||
})
|
||||
|
||||
assignDialogVisible.value = false
|
||||
formRef.value.resetFields()
|
||||
selectedSims.value = []
|
||||
selectedDevices.value = []
|
||||
ElMessage.success('资产分配成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.el-radio-button__inner) {
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
523
src/views/asset-management/card-replacement-request/index.vue
Normal file
523
src/views/asset-management/card-replacement-request/index.vue
Normal file
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 搜索和筛选区 -->
|
||||
<ElRow :gutter="12">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="申请单号/ICCID" clearable />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="statusFilter" placeholder="状态筛选" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="待处理" value="pending" />
|
||||
<ElOption label="处理中" value="processing" />
|
||||
<ElOption label="已完成" value="completed" />
|
||||
<ElOption label="已拒绝" value="rejected" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElDatePicker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple @click="exportData">导出</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<ElRow :gutter="20" style="margin: 20px 0">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">待处理</div>
|
||||
<div class="stat-value" style="color: var(--el-color-warning)">{{ statistics.pending }}</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-warning)"><Clock /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">处理中</div>
|
||||
<div class="stat-value" style="color: var(--el-color-primary)">{{ statistics.processing }}</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-primary)"><Loading /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">已完成</div>
|
||||
<div class="stat-value" style="color: var(--el-color-success)">{{ statistics.completed }}</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-success)"><CircleCheck /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">已拒绝</div>
|
||||
<div class="stat-value" style="color: var(--el-color-danger)">{{ statistics.rejected }}</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-danger)"><CircleClose /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 换卡申请列表 -->
|
||||
<ArtTable :data="filteredData" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="申请单号" prop="requestNo" width="180" />
|
||||
<ElTableColumn label="旧卡ICCID" prop="oldIccid" width="200" />
|
||||
<ElTableColumn label="申请人" prop="applicant" width="120" />
|
||||
<ElTableColumn label="联系电话" prop="phone" width="130" />
|
||||
<ElTableColumn label="换卡原因" prop="reason" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn label="新卡ICCID" prop="newIccid" width="200">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.newIccid" type="success" size="small">
|
||||
{{ scope.row.newIccid }}
|
||||
</ElTag>
|
||||
<span v-else style="color: var(--el-text-color-secondary)">待填充</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.status === 'pending'" type="warning">待处理</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'processing'" type="primary">处理中</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'completed'" type="success">已完成</ElTag>
|
||||
<ElTag v-else type="danger">已拒绝</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="申请时间" prop="applyTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="240">
|
||||
<template #default="scope">
|
||||
<el-button link :icon="View" @click="viewDetail(scope.row)">详情</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'pending'"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleProcess(scope.row)"
|
||||
>
|
||||
处理
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'processing'"
|
||||
link
|
||||
type="success"
|
||||
@click="fillNewIccid(scope.row)"
|
||||
>
|
||||
填充新卡
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === 'pending'"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleReject(scope.row)"
|
||||
>
|
||||
拒绝
|
||||
</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="换卡申请详情" width="800px" align-center>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="申请单号">{{ currentRequest.requestNo }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="申请人">{{ currentRequest.applicant }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系电话">{{ currentRequest.phone }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="申请时间">{{ currentRequest.applyTime }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="旧卡ICCID" :span="2">
|
||||
<ElTag type="warning">{{ currentRequest.oldIccid }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="新卡ICCID" :span="2">
|
||||
<ElTag v-if="currentRequest.newIccid" type="success">{{ currentRequest.newIccid }}</ElTag>
|
||||
<span v-else style="color: var(--el-text-color-secondary)">待填充</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="换卡原因" :span="2">
|
||||
{{ currentRequest.reason }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="详细说明" :span="2">
|
||||
{{ currentRequest.description || '无' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag v-if="currentRequest.status === 'pending'" type="warning">待处理</ElTag>
|
||||
<ElTag v-else-if="currentRequest.status === 'processing'" type="primary">处理中</ElTag>
|
||||
<ElTag v-else-if="currentRequest.status === 'completed'" type="success">已完成</ElTag>
|
||||
<ElTag v-else type="danger">已拒绝</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="处理人">
|
||||
{{ currentRequest.processor || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="处理时间" :span="2">
|
||||
{{ currentRequest.processTime || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentRequest.rejectReason" label="拒绝原因" :span="2">
|
||||
<span style="color: var(--el-color-danger)">{{ currentRequest.rejectReason }}</span>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 填充新卡对话框 -->
|
||||
<ElDialog v-model="fillDialogVisible" title="填充新卡ICCID" width="500px" align-center>
|
||||
<ElForm ref="fillFormRef" :model="fillForm" :rules="fillRules" label-width="100px">
|
||||
<ElFormItem label="旧卡ICCID">
|
||||
<ElInput :value="currentRequest.oldIccid" disabled />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="新卡ICCID" prop="newIccid">
|
||||
<ElInput v-model="fillForm.newIccid" placeholder="请输入新卡ICCID" maxlength="20" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="验证新卡">
|
||||
<ElButton @click="validateNewIccid">验证ICCID</ElButton>
|
||||
<div v-if="validationResult" style="margin-top: 8px">
|
||||
<ElTag v-if="validationResult === 'success'" type="success" size="small">
|
||||
验证通过,该卡可用
|
||||
</ElTag>
|
||||
<ElTag v-else type="danger" size="small">
|
||||
验证失败,{{ validationMessage }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="备注">
|
||||
<ElInput v-model="fillForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElAlert type="info" :closable="false">
|
||||
填充新卡后,系统将自动完成换卡操作,旧卡将被停用
|
||||
</ElAlert>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="fillDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleFillSubmit">确认填充</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 拒绝对话框 -->
|
||||
<ElDialog v-model="rejectDialogVisible" title="拒绝换卡申请" width="500px" align-center>
|
||||
<ElForm ref="rejectFormRef" :model="rejectForm" :rules="rejectRules" label-width="100px">
|
||||
<ElFormItem label="拒绝原因" prop="reason">
|
||||
<ElInput
|
||||
v-model="rejectForm.reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入拒绝原因"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="rejectDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="danger" @click="handleRejectSubmit">确认拒绝</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { View, Clock, Loading as LoadingIcon, CircleCheck, CircleClose } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CardReplacementRequest' })
|
||||
|
||||
interface ReplacementRequest {
|
||||
id: string
|
||||
requestNo: string
|
||||
oldIccid: string
|
||||
newIccid?: string
|
||||
applicant: string
|
||||
phone: string
|
||||
reason: string
|
||||
description?: string
|
||||
status: 'pending' | 'processing' | 'completed' | 'rejected'
|
||||
applyTime: string
|
||||
processor?: string
|
||||
processTime?: string
|
||||
rejectReason?: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const statusFilter = ref('')
|
||||
const dateRange = ref<[Date, Date] | null>(null)
|
||||
const detailDialogVisible = ref(false)
|
||||
const fillDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const fillFormRef = ref<FormInstance>()
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
const validationResult = ref<string>('')
|
||||
const validationMessage = ref('')
|
||||
|
||||
const statistics = reactive({
|
||||
pending: 15,
|
||||
processing: 8,
|
||||
completed: 102,
|
||||
rejected: 5
|
||||
})
|
||||
|
||||
const fillForm = reactive({
|
||||
newIccid: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const fillRules = reactive<FormRules>({
|
||||
newIccid: [
|
||||
{ required: true, message: '请输入新卡ICCID', trigger: 'blur' },
|
||||
{ len: 20, message: 'ICCID长度必须为20位', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const rejectForm = reactive({
|
||||
reason: ''
|
||||
})
|
||||
|
||||
const rejectRules = reactive<FormRules>({
|
||||
reason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const mockData = ref<ReplacementRequest[]>([
|
||||
{
|
||||
id: '1',
|
||||
requestNo: 'REP202601090001',
|
||||
oldIccid: '89860123456789012345',
|
||||
applicant: '张三',
|
||||
phone: '13800138000',
|
||||
reason: '卡片损坏',
|
||||
description: '卡片物理损坏无法使用',
|
||||
status: 'pending',
|
||||
applyTime: '2026-01-09 09:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
requestNo: 'REP202601080002',
|
||||
oldIccid: '89860123456789012346',
|
||||
newIccid: '89860123456789012350',
|
||||
applicant: '李四',
|
||||
phone: '13900139000',
|
||||
reason: '信号不稳定',
|
||||
description: '长期信号不稳定,影响使用',
|
||||
status: 'processing',
|
||||
applyTime: '2026-01-08 14:20:00',
|
||||
processor: 'admin'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
requestNo: 'REP202601070003',
|
||||
oldIccid: '89860123456789012347',
|
||||
newIccid: '89860123456789012351',
|
||||
applicant: '王五',
|
||||
phone: '13700137000',
|
||||
reason: '卡片丢失',
|
||||
description: '卡片意外丢失',
|
||||
status: 'completed',
|
||||
applyTime: '2026-01-07 10:00:00',
|
||||
processor: 'admin',
|
||||
processTime: '2026-01-07 15:30:00'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
requestNo: 'REP202601060004',
|
||||
oldIccid: '89860123456789012348',
|
||||
applicant: '赵六',
|
||||
phone: '13600136000',
|
||||
reason: '套餐到期',
|
||||
description: '套餐到期需要换新卡',
|
||||
status: 'rejected',
|
||||
applyTime: '2026-01-06 11:00:00',
|
||||
processor: 'admin',
|
||||
processTime: '2026-01-06 12:00:00',
|
||||
rejectReason: '套餐到期应该续费而不是换卡'
|
||||
}
|
||||
])
|
||||
|
||||
const currentRequest = ref<ReplacementRequest>({
|
||||
id: '',
|
||||
requestNo: '',
|
||||
oldIccid: '',
|
||||
applicant: '',
|
||||
phone: '',
|
||||
reason: '',
|
||||
status: 'pending',
|
||||
applyTime: ''
|
||||
})
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.requestNo.includes(searchQuery.value) ||
|
||||
item.oldIccid.includes(searchQuery.value) ||
|
||||
(item.newIccid && item.newIccid.includes(searchQuery.value))
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
data = data.filter((item) => item.status === statusFilter.value)
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const handleSearch = () => {}
|
||||
|
||||
const exportData = () => {
|
||||
ElMessage.success('数据导出中...')
|
||||
}
|
||||
|
||||
const viewDetail = (row: ReplacementRequest) => {
|
||||
currentRequest.value = { ...row }
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleProcess = (row: ReplacementRequest) => {
|
||||
ElMessageBox.confirm('确定要处理该换卡申请吗?', '处理确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
row.status = 'processing'
|
||||
row.processor = 'admin'
|
||||
ElMessage.success('已标记为处理中')
|
||||
})
|
||||
}
|
||||
|
||||
const fillNewIccid = (row: ReplacementRequest) => {
|
||||
currentRequest.value = { ...row }
|
||||
fillForm.newIccid = ''
|
||||
fillForm.remark = ''
|
||||
validationResult.value = ''
|
||||
fillDialogVisible.value = true
|
||||
}
|
||||
|
||||
const validateNewIccid = () => {
|
||||
if (!fillForm.newIccid) {
|
||||
ElMessage.warning('请先输入新卡ICCID')
|
||||
return
|
||||
}
|
||||
|
||||
if (fillForm.newIccid.length !== 20) {
|
||||
validationResult.value = 'error'
|
||||
validationMessage.value = 'ICCID长度必须为20位'
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟验证
|
||||
setTimeout(() => {
|
||||
const exists = mockData.value.some((item) => item.oldIccid === fillForm.newIccid)
|
||||
if (exists) {
|
||||
validationResult.value = 'error'
|
||||
validationMessage.value = '该ICCID已被使用'
|
||||
} else {
|
||||
validationResult.value = 'success'
|
||||
validationMessage.value = ''
|
||||
ElMessage.success('验证通过')
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleFillSubmit = async () => {
|
||||
if (!fillFormRef.value) return
|
||||
|
||||
if (validationResult.value !== 'success') {
|
||||
ElMessage.warning('请先验证新卡ICCID')
|
||||
return
|
||||
}
|
||||
|
||||
await fillFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const index = mockData.value.findIndex((item) => item.id === currentRequest.value.id)
|
||||
if (index !== -1) {
|
||||
mockData.value[index].newIccid = fillForm.newIccid
|
||||
mockData.value[index].status = 'completed'
|
||||
mockData.value[index].processTime = new Date().toLocaleString('zh-CN')
|
||||
|
||||
statistics.processing--
|
||||
statistics.completed++
|
||||
}
|
||||
|
||||
fillDialogVisible.value = false
|
||||
ElMessage.success('新卡填充成功,换卡操作已完成')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleReject = (row: ReplacementRequest) => {
|
||||
currentRequest.value = { ...row }
|
||||
rejectForm.reason = ''
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleRejectSubmit = async () => {
|
||||
if (!rejectFormRef.value) return
|
||||
await rejectFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const index = mockData.value.findIndex((item) => item.id === currentRequest.value.id)
|
||||
if (index !== -1) {
|
||||
mockData.value[index].status = 'rejected'
|
||||
mockData.value[index].rejectReason = rejectForm.reason
|
||||
mockData.value[index].processor = 'admin'
|
||||
mockData.value[index].processTime = new Date().toLocaleString('zh-CN')
|
||||
|
||||
statistics.pending--
|
||||
statistics.rejected++
|
||||
}
|
||||
|
||||
rejectDialogVisible.value = false
|
||||
ElMessage.success('已拒绝该换卡申请')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
.stat-card {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 40px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user