Files
one-pipe-system/src/views/asset-management/card-replacement-request/index.vue
2026-01-23 17:18:24 +08:00

543 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 {
margin-bottom: 8px;
font-size: 14px;
color: var(--el-text-color-secondary);
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
.stat-icon {
font-size: 40px;
opacity: 0.6;
}
}
}
</style>