Commit 45443f00 by DaiJiezhang

feat: refine phone asset list management

parent ec930cf8
package com.xyw.console.asset.dto; package com.xyw.console.asset.dto;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
public record PhoneAssetPageQuery(@Min(1) Integer page, @Min(1) @Max(100) Integer size, String phoneNumber, String iccid) {
/** 代码作用(白话):在调用方未传分页参数时提供安全默认值。关联文件:PhoneAssetController.java。关联逻辑(调用链/数据流):HTTP 查询参数 -> 默认值 -> Service Page。 */ /** 文件用途(白话):接收手机号资产列表的分页和筛选条件,不改变新增、编辑接口的数据结构。 */
public int resolvedPage(){ return page == null ? 1 : page; } public record PhoneAssetPageQuery(
/** 代码作用(白话):限制每页返回数量,防止一次取走过多数据。关联文件:PhoneAssetService.java。关联逻辑(调用链/数据流):HTTP 查询参数 -> 默认值 -> Mapper limit。 */ @Min(1) Integer page,
public int resolvedSize(){ return size == null ? 20 : size; } @Min(1) @Max(100) Integer size,
String phoneNumber,
String iccid,
String realNameOwner,
String disposalStatus) {
/** 代码作用(白话):在调用方未传页码时提供安全默认值。关联文件:PhoneAssetController.java。关联逻辑(调用链/数据流):HTTP 查询参数 -> 默认页码 -> Service 分页查询。 */
public int resolvedPage() { return page == null ? 1 : page; }
/** 代码作用(白话):限制每页返回数量,防止一次取走过多数据。关联文件:PhoneAssetService.java。关联逻辑(调用链/数据流):HTTP 查询参数 -> 默认页大小 -> Mapper limit。 */
public int resolvedSize() { return size == null ? 20 : size; }
} }
\ No newline at end of file
package com.xyw.console.asset.service; package com.xyw.console.asset.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xyw.console.asset.dto.*; import com.xyw.console.asset.dto.*;
...@@ -15,29 +16,41 @@ public class PhoneAssetService { ...@@ -15,29 +16,41 @@ public class PhoneAssetService {
private final PhoneAssetMapper mapper; private final PhoneAssetMapper mapper;
/** 代码作用(白话):接收手机号资产表的数据库访问入口。关联文件:PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):Controller -> Service -> Mapper -> as_phone_asset。 */ /** 代码作用(白话):接收手机号资产表的数据库访问入口。关联文件:PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):Controller -> Service -> Mapper -> as_phone_asset。 */
public PhoneAssetService(PhoneAssetMapper mapper) { this.mapper = mapper; } public PhoneAssetService(PhoneAssetMapper mapper) { this.mapper = mapper; }
/** 代码作用(白话):查询未删除手机号资产并转换成前端列表字段。关联文件:PhoneAssetController.java、PhoneAssetMapper.java。关联逻辑(调用链/数据流):GET 请求 -> page -> Mapper -> as_phone_asset -> Response。 */ /** 代码作用(白话):按分页、手机号片段、ICCID、实名人和使用状态读取未删除资产。关联文件:PhoneAssetPageQuery.java、PhoneAssetController.java。关联逻辑(调用链/数据流):GET 参数 -> 查询条件 -> Mapper -> Response。 */
public PhoneAssetPageResponse page(PhoneAssetPageQuery query) { public PhoneAssetPageResponse page(PhoneAssetPageQuery query) {
Page<PhoneAssetEntity> page = mapper.selectPage(new Page<>(query.resolvedPage(), query.resolvedSize()), activeQuery().eq(query.phoneNumber()!=null&&!query.phoneNumber().isBlank(), PhoneAssetEntity::getPhoneNumber, query.phoneNumber()).eq(query.iccid()!=null&&!query.iccid().isBlank(), PhoneAssetEntity::getIccid, query.iccid()).orderByDesc(PhoneAssetEntity::getId)); Page<PhoneAssetEntity> page = mapper.selectPage(new Page<>(query.resolvedPage(), query.resolvedSize()), activeQuery(query));
List<PhoneAssetResponse> records = page.getRecords().stream().map(this::toResponse).toList(); List<PhoneAssetResponse> records = page.getRecords().stream().map(this::toResponse).toList();
return new PhoneAssetPageResponse(records, page.getTotal(), query.resolvedPage(), query.resolvedSize()); return new PhoneAssetPageResponse(records, page.getTotal(), query.resolvedPage(), query.resolvedSize());
} }
/** 代码作用(白话):新增手机号资产并初始化审计字段和内部关联快照。关联文件:PhoneAssetSaveRequest.java、PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):POST 请求 -> DTO -> Service.create -> Mapper.insert -> as_phone_asset。 */ /** 代码作用(白话):组合现有资产筛选条件,保证旧的完整号码与 ICCID 参数继续可用。关联文件:PhoneAssetPageQuery.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):Query DTO -> LambdaQueryWrapper -> SQL where 条件。 */
public PhoneAssetResponse create(PhoneAssetSaveRequest request) { private LambdaQueryWrapper<PhoneAssetEntity> activeQuery(PhoneAssetPageQuery query) {
PhoneAssetEntity entity=new PhoneAssetEntity(); applyEditableFields(entity,request); LocalDateTime now=LocalDateTime.now(); entity.setCreateTime(now); entity.setUpdateTime(now); entity.setDeleteTime(0L); entity.setLinkedWecomAccounts("[]"); entity.setLinkedWechatAccounts("[]"); entity.setLinkedDouyinAccounts("[]"); entity.setLinkedDomainAccounts("[]"); entity.setLinkedMerchants("[]"); if(mapper.insert(entity)!=1) throw new IllegalStateException("手机号资产新增失败"); return toResponse(entity); LambdaQueryWrapper<PhoneAssetEntity> wrapper = new LambdaQueryWrapper<PhoneAssetEntity>().eq(PhoneAssetEntity::getDeleteTime, 0L);
} applyPhoneNumberFilter(wrapper, query.phoneNumber());
/** 代码作用(白话):更新有效资产的用户可写字段。关联文件:PhoneAssetSaveRequest.java、PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):PUT 请求 -> Service 查找 -> Mapper.updateById -> Response。 */ wrapper.eq(hasText(query.iccid()), PhoneAssetEntity::getIccid, query.iccid())
public PhoneAssetResponse update(Long id, PhoneAssetSaveRequest request) { .like(hasText(query.realNameOwner()), PhoneAssetEntity::getRealNameOwner, query.realNameOwner())
PhoneAssetEntity entity=requireActiveEntity(id); applyEditableFields(entity,request); entity.setUpdateTime(LocalDateTime.now()); if(mapper.updateById(entity)!=1) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); return toResponse(entity); .eq(hasText(query.disposalStatus()), PhoneAssetEntity::getDisposalStatus, query.disposalStatus())
.orderByDesc(PhoneAssetEntity::getId);
return wrapper;
} }
/** 代码作用(白话):将无关联检查的资产标记为删除,供 Controller 删除接口调用。关联文件:PhoneAssetController.java、PhoneAssetMapper.java。关联逻辑(调用链/数据流):DELETE 请求 -> Service -> Mapper.updateById -> delete_time 更新。 */ /** 代码作用(白话):按 3、4、7、11 位手机号规则追加查询条件,其余长度保持原有精确查询。关联文件:PhoneAssetPageQuery.java、PhoneAssetView.js。关联逻辑(调用链/数据流):搜索输入 -> phoneNumber 参数 -> 前缀/后缀/精确 SQL 匹配。 */
public void softDelete(Long id) { private void applyPhoneNumberFilter(LambdaQueryWrapper<PhoneAssetEntity> wrapper, String phoneNumber) {
PhoneAssetEntity entity=requireActiveEntity(id); entity.setDeleteTime(System.currentTimeMillis()); entity.setUpdateTime(LocalDateTime.now()); if(mapper.updateById(entity)!=1) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); if (!hasText(phoneNumber)) return;
if (phoneNumber.length() == 3) wrapper.likeRight(PhoneAssetEntity::getPhoneNumber, phoneNumber);
else if (phoneNumber.length() == 4) wrapper.likeLeft(PhoneAssetEntity::getPhoneNumber, phoneNumber);
else if (phoneNumber.length() == 7) wrapper.likeRight(PhoneAssetEntity::getPhoneNumber, phoneNumber.substring(0, 3)).likeLeft(PhoneAssetEntity::getPhoneNumber, phoneNumber.substring(3));
else wrapper.eq(PhoneAssetEntity::getPhoneNumber, phoneNumber);
} }
/** 代码作用(白话):构造只包含未删除资产的查询条件。关联文件:PhoneAssetEntity.java、PhoneAssetMapper.java。关联逻辑(调用链/数据流):Service 查询 -> delete_time = 0 -> Mapper。 */ /** 代码作用(白话):判断筛选文本是否有内容,避免空字符串参与数据库筛选。关联文件:PhoneAssetPageQuery.java、PhoneAssetService.java。关联逻辑(调用链/数据流):请求参数 -> hasText -> 是否追加条件。 */
private LambdaQueryWrapper<PhoneAssetEntity> activeQuery(){ return new LambdaQueryWrapper<PhoneAssetEntity>().eq(PhoneAssetEntity::getDeleteTime,0L); } private boolean hasText(String value) { return value != null && !value.isBlank(); }
/** 代码作用(白话):新增手机号资产并初始化审计字段和内部关联快照。关联文件:PhoneAssetSaveRequest.java、PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):POST 请求 -> DTO -> Service.create -> Mapper.insert -> as_phone_asset。 */
public PhoneAssetResponse create(PhoneAssetSaveRequest request) { PhoneAssetEntity entity=new PhoneAssetEntity(); applyEditableFields(entity,request); LocalDateTime now=LocalDateTime.now(); entity.setCreateTime(now); entity.setUpdateTime(now); entity.setDeleteTime(0L); entity.setLinkedWecomAccounts("[]"); entity.setLinkedWechatAccounts("[]"); entity.setLinkedDouyinAccounts("[]"); entity.setLinkedDomainAccounts("[]"); entity.setLinkedMerchants("[]"); if(mapper.insert(entity)!=1) throw new IllegalStateException("手机号资产新增失败"); return toResponse(entity); }
/** 代码作用(白话):更新有效资产的用户可写字段。关联文件:PhoneAssetSaveRequest.java、PhoneAssetMapper.java、PhoneAssetController.java。关联逻辑(调用链/数据流):PUT 请求 -> Service 查找 -> Mapper.updateById -> Response。 */
public PhoneAssetResponse update(Long id, PhoneAssetSaveRequest request) { PhoneAssetEntity entity=requireActiveEntity(id); applyEditableFields(entity,request); entity.setUpdateTime(LocalDateTime.now()); if(mapper.updateById(entity)!=1) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); return toResponse(entity); }
/** 代码作用(白话):将资产标记为删除,供 Controller 删除接口调用。关联文件:PhoneAssetController.java、PhoneAssetMapper.java。关联逻辑(调用链/数据流):DELETE 请求 -> Service -> Mapper.updateById。 */
public void softDelete(Long id) { PhoneAssetEntity entity=requireActiveEntity(id); entity.setDeleteTime(System.currentTimeMillis()); entity.setUpdateTime(LocalDateTime.now()); if(mapper.updateById(entity)!=1) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); }
/** 代码作用(白话):读取一条仍有效的资产,供编辑和删除共用。关联文件:PhoneAssetMapper.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):Service 查询 -> 无记录 -> 404 异常。 */ /** 代码作用(白话):读取一条仍有效的资产,供编辑和删除共用。关联文件:PhoneAssetMapper.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):Service 查询 -> 无记录 -> 404 异常。 */
private PhoneAssetEntity requireActiveEntity(Long id){ PhoneAssetEntity entity=mapper.selectOne(activeQuery().eq(PhoneAssetEntity::getId,id)); if(entity==null) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); return entity; } private PhoneAssetEntity requireActiveEntity(Long id){ PhoneAssetEntity entity=mapper.selectOne(new LambdaQueryWrapper<PhoneAssetEntity>().eq(PhoneAssetEntity::getDeleteTime,0L).eq(PhoneAssetEntity::getId,id)); if(entity==null) throw new PhoneAssetNotFoundException("手机号资产不存在或已删除"); return entity; }
/** 代码作用(白话):把表单允许提交的字段写入实体,并统一处理手机号和默认处置状态。关联文件:PhoneAssetSaveRequest.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):弹窗表单 -> DTO -> Entity -> Mapper 保存。 */ /** 代码作用(白话):把表单允许提交的字段写入实体,并统一处理手机号和默认使用状态。关联文件:PhoneAssetSaveRequest.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):弹窗表单 -> DTO -> Entity -> Mapper 保存。 */
private void applyEditableFields(PhoneAssetEntity entity, PhoneAssetSaveRequest request){ entity.setPhoneNumber(normalizePhoneNumber(request.phoneNumber())); entity.setCardType(request.cardType()); entity.setIccid(request.iccid()); entity.setRealNameOwner(request.realNameOwner()); entity.setManagementType(request.managementType()); entity.setDisposalStatus(request.disposalStatus()==null||request.disposalStatus().isBlank()?"正常使用":request.disposalStatus()); entity.setDeviceId(request.deviceId()); } private void applyEditableFields(PhoneAssetEntity entity, PhoneAssetSaveRequest request){ entity.setPhoneNumber(normalizePhoneNumber(request.phoneNumber())); entity.setCardType(request.cardType()); entity.setIccid(request.iccid()); entity.setRealNameOwner(request.realNameOwner()); entity.setManagementType(request.managementType()); entity.setDisposalStatus(request.disposalStatus()==null||request.disposalStatus().isBlank()?"正常使用":request.disposalStatus()); entity.setDeviceId(request.deviceId()); }
/** 代码作用(白话):按已确认规则清理手机号并验证其最终格式。关联文件:PhoneAssetSaveRequest.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):前端输入 -> 去空格和 +86 -> 格式失败返回 400。 */ /** 代码作用(白话):按已确认规则清理手机号并验证其最终格式。关联文件:PhoneAssetSaveRequest.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):前端输入 -> 去空格和 +86 -> 格式失败返回 400。 */
private String normalizePhoneNumber(String value){ String normalized=value==null?"":value.trim(); if(normalized.startsWith("+86")) normalized=normalized.substring(3); if(!normalized.matches("\\d{11}")) throw new PhoneAssetValidationException("手机号必须是 11 位数字"); return normalized; } private String normalizePhoneNumber(String value){ String normalized=value==null?"":value.trim(); if(normalized.startsWith("+86")) normalized=normalized.substring(3); if(!normalized.matches("\\d{11}")) throw new PhoneAssetValidationException("手机号必须是 11 位数字"); return normalized; }
......
import { nextTick, onMounted, reactive, ref } from 'vue/dist/vue.esm-bundler.js'; import { nextTick, onMounted, reactive, ref } from 'vue/dist/vue.esm-bundler.js';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn.mjs';
import { createPhoneAsset, deletePhoneAsset, listPhoneAssets, updatePhoneAsset } from './phone-api-client.js'; import { createPhoneAsset, deletePhoneAsset, listPhoneAssets, updatePhoneAsset } from './phone-api-client.js';
/** /**
...@@ -14,13 +15,16 @@ export default { ...@@ -14,13 +15,16 @@ export default {
* 关联逻辑(调用链/数据流):路由进入 -> setup 初始化 -> 用户操作 -> 接口调用或弹窗状态变化。 * 关联逻辑(调用链/数据流):路由进入 -> setup 初始化 -> 用户操作 -> 接口调用或弹窗状态变化。
*/ */
setup() { setup() {
const elementLocale = zhCn;
const loading = ref(false); const loading = ref(false);
const saving = ref(false); const saving = ref(false);
const records = ref([]); const records = ref([]);
const total = ref(0); const total = ref(0);
const dialogVisible = ref(false); const dialogVisible = ref(false);
const editingId = ref(null); const editingId = ref(null);
const filters = reactive({ page: 1, size: 20, phoneNumber: '', iccid: '' }); const filters = reactive({ page: 1, size: 20, phoneNumber: '', iccid: '', realNameOwner: '', disposalStatus: '' });
let searchTimer = null;
let latestRequest = 0;
const form = reactive({ const form = reactive({
phoneNumber: '', phoneNumber: '',
cardType: '', cardType: '',
...@@ -37,15 +41,17 @@ export default { ...@@ -37,15 +41,17 @@ export default {
* 关联逻辑(调用链/数据流):查询/分页/保存成功 -> listPhoneAssets -> GET 接口 -> records 与 total。 * 关联逻辑(调用链/数据流):查询/分页/保存成功 -> listPhoneAssets -> GET 接口 -> records 与 total。
*/ */
async function loadPage() { async function loadPage() {
const requestId = ++latestRequest;
loading.value = true; loading.value = true;
try { try {
const result = await listPhoneAssets(filters); const result = await listPhoneAssets(filters);
if (requestId !== latestRequest) return;
records.value = result.records; records.value = result.records;
total.value = result.total; total.value = result.total;
} catch (error) { } catch (error) {
ElMessage.error(error.message); if (requestId === latestRequest) ElMessage.error(error.message);
} finally { } finally {
loading.value = false; if (requestId === latestRequest) loading.value = false;
} }
} }
...@@ -215,35 +221,20 @@ export default { ...@@ -215,35 +221,20 @@ export default {
} }
} }
/** /** 代码作用(白话):停止输入约 300 毫秒后自动查询,避免连续输入反复请求。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):输入事件 -> 防抖计时 -> loadPage -> GET 接口。 */
* 代码作用(白话):按当前筛选值从第一页重新查询。 function scheduleSearch() { window.clearTimeout(searchTimer); searchTimer = window.setTimeout(() => { filters.page = 1; loadPage(); }, 300); }
* 关联文件:PhoneAssetView.js、phone-api-client.js。 /** 代码作用(白话):把列表手机号筛选值限定为 11 位数字,粘贴内容也按相同规则处理。关联文件:PhoneAssetView.js、PhoneAssetService.java。关联逻辑(调用链/数据流):手机号输入 -> limitSearchPhone -> scheduleSearch -> 后端号码匹配。 */
* 关联逻辑(调用链/数据流):查询按钮或表单提交 -> page=1 -> loadPage -> GET 接口。 function limitSearchPhone(value) { filters.phoneNumber = String(value || '').replace(/\D/g, '').slice(0, 11); scheduleSearch(); }
*/ /** 代码作用(白话):按 Enter 时立刻执行当前筛选。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):Enter -> 清除计时 -> page=1 -> loadPage。 */
function submitSearch() { function submitSearch() { window.clearTimeout(searchTimer); filters.page = 1; loadPage(); }
filters.page = 1; /** 代码作用(白话):切换使用状态后马上查询,不等待输入防抖。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):状态 change -> page=1 -> loadPage。 */
loadPage(); function changeStatus() { submitSearch(); }
} /** 代码作用(白话):清空所有筛选条件并恢复第一页列表。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):重置 -> filters 默认值 -> loadPage。 */
function resetSearch() { window.clearTimeout(searchTimer); Object.assign(filters, { page: 1, size: 20, phoneNumber: '', iccid: '', realNameOwner: '', disposalStatus: '' }); loadPage(); }
/** /** 代码作用(白话):切换页码并读取对应数据。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):分页器 -> filters.page -> loadPage。 */
* 代码作用(白话):清空筛选条件并重新加载默认列表。 function changePage(page) { filters.page = page; loadPage(); }
* 关联文件:PhoneAssetView.js、phone-api-client.js。 /** 代码作用(白话):切换每页条数后回到第一页查询。关联文件:PhoneAssetView.js、phone-api-client.js。关联逻辑(调用链/数据流):每页条数 -> size/page -> loadPage。 */
* 关联逻辑(调用链/数据流):重置按钮 -> filters 默认值 -> loadPage -> GET 接口。 function changePageSize(size) { filters.size = size; filters.page = 1; loadPage(); }
*/
function resetSearch() {
Object.assign(filters, { page: 1, size: 20, phoneNumber: '', iccid: '' });
loadPage();
}
/**
* 代码作用(白话):切换页码并读取该页的数据。
* 关联文件:PhoneAssetView.js、phone-api-client.js。
* 关联逻辑(调用链/数据流):分页器 -> filters.page -> loadPage -> GET 接口。
*/
function changePage(page) {
filters.page = page;
loadPage();
}
/** /**
* 代码作用(白话):页面首次显示时请求手机号资产列表。 * 代码作用(白话):页面首次显示时请求手机号资产列表。
...@@ -256,15 +247,19 @@ export default { ...@@ -256,15 +247,19 @@ export default {
return { return {
changePage, changePage,
changePageSize,
changeStatus,
confirmDelete, confirmDelete,
dialogVisible, dialogVisible,
editingId, editingId,
elementLocale,
filters, filters,
form, form,
handleIccidPaste, handleIccidPaste,
handlePhonePaste, handlePhonePaste,
limitIccid, limitIccid,
limitPhoneNumber, limitPhoneNumber,
limitSearchPhone,
loading, loading,
openCreate, openCreate,
openEdit, openEdit,
...@@ -272,46 +267,17 @@ export default { ...@@ -272,46 +267,17 @@ export default {
resetDialogScroll, resetDialogScroll,
resetSearch, resetSearch,
saving, saving,
scheduleSearch,
submitForm, submitForm,
submitSearch, submitSearch,
total total
}; };
}, },
template: ` template: `
<section class="phone-asset-page"> <el-config-provider :locale="elementLocale"><section class="phone-asset-page phone-asset-list-page">
<header class="page-header"> <header class="phone-asset-list-page__header"><h2>手机号资产</h2><el-button class="phone-asset-list-page__add" type="primary" @click="openCreate">新增手机号资产</el-button></header>
<div> <section class="phone-asset-list-page__panel phone-asset-list-page__search" aria-label="筛选手机号资产"><el-form class="phone-asset-list-page__filters" @submit.prevent="submitSearch"><el-input v-model="filters.phoneNumber" maxlength="11" inputmode="numeric" placeholder="手机号前3位、后4位或完整号码" @input="limitSearchPhone" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.iccid" maxlength="20" placeholder="请输入 ICCID" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-input v-model="filters.realNameOwner" placeholder="请输入实名人" @input="scheduleSearch" @keydown.enter.prevent="submitSearch" /><el-select v-model="filters.disposalStatus" placeholder="全部状态" clearable @change="changeStatus"><el-option label="全部状态" value="" /><el-option label="正常使用" value="正常使用" /><el-option label="闲置" value="闲置" /><el-option label="停机" value="停机" /><el-option label="已注销" value="已注销" /></el-select><el-button @click="resetSearch">重置</el-button></el-form></section>
<p class="eyebrow">手机号资产</p> <section class="phone-asset-list-page__panel phone-asset-list-page__table"><header class="phone-asset-list-page__table-header"><h3>资产列表</h3><span>共 {{ total }} 条</span></header><el-table v-loading="loading" :data="records" empty-text="暂无匹配数据" class="phone-asset-list-page__grid"><el-table-column prop="phoneNumber" label="手机号" min-width="150" show-overflow-tooltip /><el-table-column prop="cardType" label="运营商" min-width="110" show-overflow-tooltip /><el-table-column prop="iccid" label="ICCID" min-width="220" show-overflow-tooltip /><el-table-column prop="realNameOwner" label="实名人" min-width="150" show-overflow-tooltip /><el-table-column prop="managementType" label="管理模式" min-width="120" show-overflow-tooltip /><el-table-column label="使用状态" min-width="130"><template #default="{ row }"><span class="phone-asset-list-page__status"><i :class="['phone-asset-list-page__status-dot', row.disposalStatus]"></i>{{ row.disposalStatus || '未设置' }}</span></template></el-table-column><el-table-column prop="deviceId" label="关联设备(ID)" min-width="150" show-overflow-tooltip /><el-table-column label="操作" width="120"><template #default="{ row }"><span class="phone-asset-list-page__actions"><el-button link @click="openEdit(row)">编辑</el-button><el-button link type="danger" @click="confirmDelete(row)">删除</el-button></span></template></el-table-column></el-table><footer v-if="total" class="phone-asset-list-page__pagination"><span>共 {{ total }} 条</span><el-pagination layout="sizes, prev, pager, next, jumper" :current-page="filters.page" :page-size="filters.size" :page-sizes="[5,10,20,50]" :total="total" @current-change="changePage" @size-change="changePageSize" /></footer></section>
<h2>手机号资产</h2>
<p>管理手机号卡的基础信息与关联状态。</p>
</div>
<el-button type="primary" @click="openCreate">新增手机号资产</el-button>
</header>
<article class="reference-card">
<el-form inline @submit.prevent="submitSearch">
<el-form-item label="手机号"><el-input v-model="filters.phoneNumber" maxlength="11" clearable /></el-form-item>
<el-form-item label="ICCID"><el-input v-model="filters.iccid" clearable /></el-form-item>
<el-form-item><el-button type="primary" @click="submitSearch">查询</el-button><el-button @click="resetSearch">重置</el-button></el-form-item>
</el-form>
<el-table v-loading="loading" :data="records" empty-text="暂无数据">
<el-table-column prop="phoneNumber" label="手机号" min-width="140" />
<el-table-column prop="cardType" label="卡类型" min-width="120" />
<el-table-column prop="iccid" label="ICCID" min-width="180" />
<el-table-column prop="realNameOwner" label="实名归属" min-width="140" />
<el-table-column prop="managementType" label="管理方式" min-width="120" />
<el-table-column prop="disposalStatus" label="处置状态" min-width="120" />
<el-table-column prop="deviceId" label="关联设备 ID" min-width="130" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="danger" @click="confirmDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="total" background layout="total, prev, pager, next" :current-page="filters.page" :page-size="filters.size" :total="total" @current-change="changePage" />
</article>
<el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" :title="editingId === null ? '新增手机号资产' : '编辑手机号资产'" width="560px" @opened="resetDialogScroll"> <el-dialog v-model="dialogVisible" class="phone-asset-modal" modal-class="phone-asset-modal-mask" :title="editingId === null ? '新增手机号资产' : '编辑手机号资产'" width="560px" @opened="resetDialogScroll">
<el-form class="phone-asset-modal__form" label-width="96px"> <el-form class="phone-asset-modal__form" label-width="96px">
<el-form-item class="phone-asset-modal__form-row" label="手机号" required> <el-form-item class="phone-asset-modal__form-row" label="手机号" required>
...@@ -337,6 +303,6 @@ export default { ...@@ -337,6 +303,6 @@ export default {
<el-button type="primary" :loading="saving" @click="submitForm">确认保存</el-button> <el-button type="primary" :loading="saving" @click="submitForm">确认保存</el-button>
</template> </template>
</el-dialog> </el-dialog>
</section> </section></el-config-provider>
` `
}; };
\ No newline at end of file
...@@ -258,3 +258,18 @@ body { margin: 0; } ...@@ -258,3 +258,18 @@ body { margin: 0; }
font-weight: 550; font-weight: 550;
background: #f4f4f5; background: #f4f4f5;
} }
/* 手机号资产列表:参考 mobile-number-asset-management-v4.html,仅作用于当前列表页。 */
.phone-asset-list-page { width:100%; max-width:1680px; margin:0 auto; padding:34px 4px 56px; }
.phone-asset-list-page__header { display:flex; align-items:center; justify-content:space-between; gap:24px; margin-bottom:24px; }
.phone-asset-list-page__header h2 { margin:0; color:#111113; font-size:29px; font-weight:700; line-height:1.25; letter-spacing:-.035em; }
.phone-asset-list-page__add.el-button { height:38px; padding:0 16px; border-color:#18181b; border-radius:6px; background:#18181b; font-size:14px; font-weight:600; }
.phone-asset-list-page__panel { border:1px solid #e5e5e8; border-radius:12px; background:#fff; box-shadow:0 10px 30px rgba(15,23,42,.05); }
.phone-asset-list-page__search { margin-bottom:16px; padding:20px 22px; }
.phone-asset-list-page__filters { display:flex; align-items:center; flex-wrap:wrap; gap:12px; }
.phone-asset-list-page__filters .el-input { width:210px; }.phone-asset-list-page__filters .el-input:nth-child(2){width:240px}.phone-asset-list-page__filters .el-input:nth-child(3){width:190px}.phone-asset-list-page__filters .el-select{width:160px}
.phone-asset-list-page__filters .el-input__wrapper,.phone-asset-list-page__filters .el-select__wrapper { min-height:38px; border-radius:6px; box-shadow:0 0 0 1px #e5e5e8 inset; }.phone-asset-list-page__filters .el-button{height:38px;border-radius:6px;color:#52525b;border-color:#e5e5e8}.phone-asset-list-page__filters .el-input__wrapper:hover,.phone-asset-list-page__filters .el-select__wrapper:hover{box-shadow:0 0 0 1px #d4d4d8 inset}
.phone-asset-list-page__table { overflow:hidden; }.phone-asset-list-page__table-header{display:flex;align-items:center;gap:9px;min-height:62px;padding:0 22px;border-bottom:1px solid #e5e5e8}.phone-asset-list-page__table-header h3{margin:0;color:#18181b;font-size:15px;font-weight:650}.phone-asset-list-page__table-header span{color:#8b8b93;font-size:13px}
.phone-asset-list-page__grid .el-table__header th.el-table__cell{height:46px;background:#fafafa;color:#71717a;font-size:13px;font-weight:600}.phone-asset-list-page__grid .el-table__cell{height:56px;color:#3f3f46;font-size:14px}.phone-asset-list-page__grid .el-table__row:hover>td.el-table__cell{background:#fafafa}.phone-asset-list-page__status{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}.phone-asset-list-page__status-dot{width:7px;height:7px;border-radius:50%;background:#a1a1aa;box-shadow:0 0 0 3px rgba(161,161,170,.12)}.phone-asset-list-page__status-dot.正常使用{background:#2f9e68;box-shadow:0 0 0 3px rgba(47,158,104,.1)}.phone-asset-list-page__status-dot.闲置,.phone-asset-list-page__status-dot.停机{background:#d28b24;box-shadow:0 0 0 3px rgba(210,139,36,.11)}.phone-asset-list-page__actions{display:inline-flex;gap:14px}.phone-asset-list-page__actions .el-button{padding:0;color:#3f3f46;font-size:13px}.phone-asset-list-page__actions .el-button--danger{color:#e5484d}
.phone-asset-list-page__pagination{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:66px;padding:12px 22px;border-top:1px solid #e5e5e8;color:#71717a;font-size:13px}.phone-asset-list-page__pagination .el-pagination{justify-content:flex-end}.phone-asset-list-page__pagination .el-pager li,.phone-asset-list-page__pagination .btn-prev,.phone-asset-list-page__pagination .btn-next{min-width:34px;height:34px;border:1px solid #e5e5e8;border-radius:6px;background:#fff}.phone-asset-list-page__pagination .el-pager li.is-active{background:#18181b;color:#fff}
@media(max-width:900px){.phone-asset-list-page{padding:26px 0 40px}.phone-asset-list-page__pagination{align-items:flex-start;flex-direction:column}.phone-asset-list-page__pagination .el-pagination{width:100%;justify-content:space-between}}
@media(max-width:640px){.phone-asset-list-page{padding:20px 0 32px}.phone-asset-list-page__header{align-items:stretch;flex-direction:column;margin-bottom:18px}.phone-asset-list-page__add.el-button{width:100%}.phone-asset-list-page__header h2{font-size:25px}.phone-asset-list-page__search{padding:18px}.phone-asset-list-page__filters,.phone-asset-list-page__filters .el-input,.phone-asset-list-page__filters .el-select,.phone-asset-list-page__filters .el-button{width:100%!important}.phone-asset-list-page__table-header,.phone-asset-list-page__pagination{padding-left:18px;padding-right:18px}.phone-asset-list-page__pagination .el-pagination__jump{display:none}}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment