Commit 00d54727 by DaiJiezhang

Refine phone list workspace

parent da65cb92
...@@ -75,7 +75,6 @@ ...@@ -75,7 +75,6 @@
channelOperator: '中国移动', channelOperator: '中国移动',
numberStatus: true, numberStatus: true,
linkedWecom: '张三企业微信', linkedWecom: '张三企业微信',
numberRetentionStatus: true,
updateTime: '2024-03-12 10:25', updateTime: '2024-03-12 10:25',
note: '用于客户联系' note: '用于客户联系'
} }
...@@ -529,7 +528,7 @@ function renderExternalRuntime(moduleKey) { ...@@ -529,7 +528,7 @@ function renderExternalRuntime(moduleKey) {
phoneText: { phoneText: {
add: '新增号卡', add: '新增号卡',
reset: '重置', reset: '重置',
searchPlaceholder: '搜索手机号、实名人或城市', searchPlaceholder: '搜索手机号、实名人或域名',
deleteConfirm: '确认删除这条手机号记录吗?', deleteConfirm: '确认删除这条手机号记录吗?',
deleteFailed: '删除失败,请稍后重试', deleteFailed: '删除失败,请稍后重试',
loadFailed: '加载手机号列表失败,请确认本地后端已启动' loadFailed: '加载手机号列表失败,请确认本地后端已启动'
...@@ -708,3 +707,4 @@ function initPage() { ...@@ -708,3 +707,4 @@ function initPage() {
window.updateActiveNav = updateActiveNav; window.updateActiveNav = updateActiveNav;
window.renderModule = renderModule; window.renderModule = renderModule;
document.addEventListener('DOMContentLoaded', initPage); document.addEventListener('DOMContentLoaded', initPage);
...@@ -107,31 +107,31 @@ ...@@ -107,31 +107,31 @@
/** /**
* 代码作用(白话):把外层传入的手机号记录回填到表单里,兼容列表记录和详情记录两种字段名,并把两张旧图按一个图片区域回显;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):编辑按钮/详情抽屉编辑 -> PhoneAddDialogAPI.open('edit', record) -> populateFormData() -> 表单与图片预览回填。 * 代码作用(白话):把外层传入的手机号记录回填到表单里,兼容列表记录和详情记录两种字段名,并把两张旧图按一个图片区域回显;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):编辑按钮/详情抽屉编辑 -> PhoneAddDialogAPI.open('edit', record) -> populateFormData() -> 表单与图片预览回填。
*/ */
/**
* ???????????????????????????????? `card_status` / `mobile_status` / `channel_operator`?????????????????????F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js?F:/Project/xyw_console/src/modules/shared/record-adapters.js?????????/???/???????/???? -> populateFormData() -> formData -> ?????
*/
function populateFormData(record) { function populateFormData(record) {
formData.id = record.id || null; formData.id = record.id || null;
formData.phoneNumber = record.phoneNumber || record.phone || ''; formData.phoneNumber = record.phoneNumber || record.phone || '';
formData.realPerson = record.realPerson || ''; formData.realPerson = record.realPerson || '';
formData.iccid = record.iccid || ''; formData.iccid = record.iccid || '';
formData.city = record.city || ''; formData.city = record.city || '';
formData.cardStatus = record.cardStatus || record.status || '正常'; formData.cardStatus = record.cardStatus || record.card_status || record.status || '正常';
formData.cardUsageLocation = record.cardUsageLocation || record.usageLocation || ''; formData.cardUsageLocation = record.cardUsageLocation || record.card_usage_location || record.usageLocation || '';
formData.wecom = record.wecom === true; formData.wecom = record.wecom === true;
formData.wechat = record.wechat === true; formData.wechat = record.wechat === true;
formData.outboundCall = record.outboundCall === true || record.outbound === '可外呼'; formData.outboundCall = record.outboundCall === true || record.outbound_call === true || record.outbound === '可外呼';
formData.douyinAccount = record.douyinAccount || ''; formData.douyinAccount = record.douyinAccount || '';
formData.miniProgramFiling = record.miniProgramFiling === true; formData.miniProgramFiling = record.miniProgramFiling === true;
formData.packageChange5yuan = record.packageChange5yuan === true; formData.packageChange5yuan = record.packageChange5yuan === true;
formData.channelOperator = record.channelOperator || record.carrier || ''; formData.channelOperator = record.channelOperator || record.channel_operator || record.carrier || '';
formData.numberStatus = record.numberStatus !== false; formData.numberStatus = record.numberStatus ?? record.mobile_status ?? record.number_status ?? true;
formData.linkedWecom = record.linkedWecom || ''; formData.linkedWecom = record.linkedWecom || record.linked_wecom || '';
imageItems.value = buildImageItemsFromRecord(record); imageItems.value = buildImageItemsFromRecord(record);
syncImagesToFormData(); syncImagesToFormData();
} }
/**
* 代码作用(白话):按新增或编辑模式打开手机号弹窗,并决定是重置表单还是回填旧记录;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):新增/编辑按钮 -> PhoneAddDialogAPI.open() -> open() -> visible/formData/imageItems 更新。
*/
function open(mode = 'add', record = null) { function open(mode = 'add', record = null) {
visible.value = true; visible.value = true;
isEdit.value = mode === 'edit'; isEdit.value = mode === 'edit';
......
(function initPhoneDetailDrawer() { (function initPhoneDetailDrawer() {
const { createApp, ref, reactive } = window.Vue; const { createApp, ref, reactive } = window.Vue;
const ElementPlus = window.ElementPlus; const ElementPlus = window.ElementPlus;
...@@ -85,10 +85,6 @@ ...@@ -85,10 +85,6 @@
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.realPerson || '—' }}</span> <span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.realPerson || '—' }}</span>
</div> </div>
<div style="display: flex; flex-direction: column; gap: 6px;"> <div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">ICCID</span>
<span style="font-size: 15px; font-weight: 500; color: #111827; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;">{{ recordData.iccid || '—' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">归属地</span> <span style="font-size: 13px; color: #6b7280;">归属地</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.city || '—' }}</span> <span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.city || '—' }}</span>
</div> </div>
...@@ -122,10 +118,6 @@ ...@@ -122,10 +118,6 @@
<span style="font-size: 13px; color: #6b7280;">绑定企微号</span> <span style="font-size: 13px; color: #6b7280;">绑定企微号</span>
<span style="font-size: 15px; font-weight: 400; color: #374151;">{{ recordData.linkedWecom || '—' }}</span> <span style="font-size: 15px; font-weight: 400; color: #374151;">{{ recordData.linkedWecom || '—' }}</span>
</div> </div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">抖音账号</span>
<span style="font-size: 15px; font-weight: 400; color: #374151;">{{ recordData.douyinAccount || '—' }}</span>
</div>
</div> </div>
</section> </section>
......
(function attachPhoneListRuntime() { (function attachPhoneListRuntime() {
/** /**
* 代码作用(白话):把各种日期输入统一转成可比较的时间戳,方便手机号卡页面按更新时间筛选;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):表格记录.updatedAt -> toTimestamp() -> matchesDateRange() -> filteredRecords。 * 代码作用(白话):把各种日期输入统一转成可比较的时间戳,方便手机号卡页面按更新时间筛选;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):表格记录.updatedAt -> toTimestamp() -> matchesDateRange() -> filteredRecords。
*/ */
...@@ -74,6 +74,11 @@ ...@@ -74,6 +74,11 @@
const filterStatus = ref('all'); const filterStatus = ref('all');
const filterCarrier = ref('all'); const filterCarrier = ref('all');
const filterOutbound = ref('all'); const filterOutbound = ref('all');
const phonePrefix = ref('');
const phoneLogic = ref('and');
const phoneSuffix = ref('');
const showBasicFilter = ref(false);
const showPhoneSearch = ref(false);
const allRecordsCache = ref([]); const allRecordsCache = ref([]);
const loading = ref(false); const loading = ref(false);
const multipleSelection = ref([]); const multipleSelection = ref([]);
...@@ -123,11 +128,17 @@ ...@@ -123,11 +128,17 @@
/** /**
* 代码作用(白话):按状态、运营商、外呼能力、日期和关键词筛出当前应显示的手机号记录;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):筛选控件 -> getFilteredRecords() -> totalFiltered()/visibleRows() -> 表格。 * 代码作用(白话):按状态、运营商、外呼能力、日期和关键词筛出当前应显示的手机号记录;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):筛选控件 -> getFilteredRecords() -> totalFiltered()/visibleRows() -> 表格。
*/ */
/**
* ????????????????????????????????????????????????????????? `card_status` ??????????F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js?F:/Project/xyw_console/src/modules/shared/record-adapters.js?????????/???/????????? -> getFilteredRecords() -> visibleRows -> ?????
*/
function getFilteredRecords() { function getFilteredRecords() {
let result = allRecordsCache.value.slice(); let result = allRecordsCache.value.slice();
if (filterStatus.value !== 'all') { if (filterStatus.value !== 'all') {
result = result.filter((record) => record.status === filterStatus.value); result = result.filter((record) => {
const cardStatus = record.cardStatus || record.card_status || record.status;
return cardStatus === filterStatus.value;
});
} }
if (filterCarrier.value !== 'all') { if (filterCarrier.value !== 'all') {
...@@ -140,6 +151,25 @@ ...@@ -140,6 +151,25 @@
result = result.filter((record) => matchesDateRange(record, filterDate.value)); result = result.filter((record) => matchesDateRange(record, filterDate.value));
const prefix = phonePrefix.value.trim();
const suffix = phoneSuffix.value.trim();
if (prefix || suffix) {
result = result.filter((record) => {
const phone = String(record.phone || '');
const matchPrefix = prefix ? phone.startsWith(prefix) : false;
const matchSuffix = suffix ? phone.endsWith(suffix) : false;
if (prefix && suffix) {
return phoneLogic.value === 'and'
? (matchPrefix && matchSuffix)
: (matchPrefix || matchSuffix);
}
if (prefix) {
return matchPrefix;
}
return matchSuffix;
});
}
const query = searchQuery.value.trim().toLowerCase(); const query = searchQuery.value.trim().toLowerCase();
if (!query) { if (!query) {
return result; return result;
...@@ -197,7 +227,21 @@ ...@@ -197,7 +227,21 @@
syncMountState(); syncMountState();
} }
window.Vue.watch([filterStatus, filterCarrier, filterOutbound, filterDate, searchQuery], handleFilterStateChange); window.Vue.watch([filterStatus, filterCarrier, filterOutbound, filterDate, searchQuery, phonePrefix, phoneSuffix, phoneLogic], handleFilterStateChange);
window.Vue.watch(phonePrefix, (value) => {
const cleaned = String(value).replace(/[^0-9]/g, '').slice(0, 3);
if (cleaned !== value) {
phonePrefix.value = cleaned;
}
});
window.Vue.watch(phoneSuffix, (value) => {
const cleaned = String(value).replace(/[^0-9]/g, '').slice(0, 4);
if (cleaned !== value) {
phoneSuffix.value = cleaned;
}
});
/** /**
* 代码作用(白话):把当前页码、分页大小和批量勾选状态存回壳层 mountState,保证切走再切回来还能恢复现场;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):列表交互 -> syncMountState() -> moduleMountState.phone -> renderExternalRuntime('phone') 恢复。 * 代码作用(白话):把当前页码、分页大小和批量勾选状态存回壳层 mountState,保证切走再切回来还能恢复现场;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):列表交互 -> syncMountState() -> moduleMountState.phone -> renderExternalRuntime('phone') 恢复。
...@@ -218,8 +262,11 @@ ...@@ -218,8 +262,11 @@
/** /**
* 代码作用(白话):把不同状态映射成状态点颜色,让正常、异常、外呼能力等字段在列表里更直观;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):status/wechatStatus/outbound -> getStatusClass() -> props.getPhoneStatusDotClass() -> status-dot class。 * 代码作用(白话):把不同状态映射成状态点颜色,让正常、异常、外呼能力等字段在列表里更直观;关联文件:F:/Project/xyw_console/app-v2.js;关联逻辑(调用链/消息链/数据流):status/wechatStatus/outbound -> getStatusClass() -> props.getPhoneStatusDotClass() -> status-dot class。
*/ */
function getStatusClass(status) { /**
return props.getPhoneStatusDotClass(status); * ?????????????????????????? `card_status` ??????????????????F:/Project/xyw_console/app-v2.js?F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js?????????/???/?????cardStatus -> getStatusClass() -> props.getPhoneStatusDotClass() -> status-dot class?
*/
function getStatusClass(cardStatus) {
return props.getPhoneStatusDotClass(cardStatus);
} }
/** /**
...@@ -233,12 +280,15 @@ ...@@ -233,12 +280,15 @@
* 代码作用(白话):把手机号状态和号码保留状态统一翻译成业务文案,方便列表按库表字段直观看;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatStatusText()/formatRetentionText() -> 状态列渲染。 * 代码作用(白话):把手机号状态和号码保留状态统一翻译成业务文案,方便列表按库表字段直观看;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatStatusText()/formatRetentionText() -> 状态列渲染。
*/ */
function formatStatusText(value) { function formatStatusText(value) {
if (value === false || value === 0 || value === '0' || value === 'false' || value === 'disabled' || value === '异常') {
return '异常';
}
if (value === true || value === 1 || value === '1' || value === 'true' || value === 'enabled' || value === '正常') {
return '正常';
}
return value ? '正常' : '异常'; return value ? '正常' : '异常';
} }
/**
* 代码作用(白话):把号码保留布尔值翻译成“保留/注销”,避免业务同学自己推断 0 和 1;关联文件:F:/Project/xyw_console/src/modules/shared/record-adapters.js;关联逻辑(调用链/消息链/数据流):normalizePhoneRecord() -> formatRetentionText() -> 号码保留状态列。
*/
function formatRetentionText(value) { function formatRetentionText(value) {
return value ? '保留' : '注销'; return value ? '保留' : '注销';
} }
...@@ -282,6 +332,11 @@ ...@@ -282,6 +332,11 @@
filterStatus.value = 'all'; filterStatus.value = 'all';
filterCarrier.value = 'all'; filterCarrier.value = 'all';
filterOutbound.value = 'all'; filterOutbound.value = 'all';
phonePrefix.value = '';
phoneSuffix.value = '';
phoneLogic.value = 'and';
showBasicFilter.value = false;
showPhoneSearch.value = false;
currentPage.value = 1; currentPage.value = 1;
syncMountState(); syncMountState();
} }
...@@ -356,6 +411,7 @@ ...@@ -356,6 +411,7 @@
syncMountState(); syncMountState();
return { return {
formatCellText,
carrierOptions, carrierOptions,
outboundOptions, outboundOptions,
currentPage, currentPage,
...@@ -377,6 +433,11 @@ ...@@ -377,6 +433,11 @@
filterStatus, filterStatus,
filterCarrier, filterCarrier,
filterOutbound, filterOutbound,
phonePrefix,
phoneLogic,
phoneSuffix,
showBasicFilter,
showPhoneSearch,
totalFiltered, totalFiltered,
totalPages, totalPages,
loading, loading,
...@@ -390,50 +451,71 @@ ...@@ -390,50 +451,71 @@
}, },
template: ` template: `
<div class="table-area table-area-refined"> <div class="table-area table-area-refined">
<div class="table-toolbar table-toolbar-refined"> <div class="table-toolbar table-toolbar-refined phone-toolbar-main">
<div class="toolbar-left"> <div class="toolbar-left">
<div class="toolbar-title-wrap"> <div class="toolbar-title-wrap">
<div class="toolbar-title">全部号卡 ({{ totalFiltered.toLocaleString('zh-CN') }})</div> <div class="toolbar-title">全部号卡 ({{ totalFiltered.toLocaleString('zh-CN') }})</div>
</div> </div>
<div class="filter-selects filter-selects-refined" style="display: flex; align-items: center; gap: 8px;"> </div>
<el-select v-model="filterStatus" class="custom-fselect" style="width: 120px; height: 32px;"> <div class="toolbar-right phone-toolbar-actions">
<button type="button" class="filter-toggle-btn" :class="{ 'is-active': showBasicFilter }" @click="showBasicFilter = !showBasicFilter">基础筛选</button>
<button type="button" class="filter-toggle-btn" :class="{ 'is-active': showPhoneSearch }" @click="showPhoneSearch = !showPhoneSearch">号码搜索</button>
<el-input
v-model="searchQuery"
:placeholder="phoneText.searchPlaceholder"
clearable
class="phone-search-input"
style="width: 220px; height: 32px;"
>
<template #prefix>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
</template>
</el-input>
<button class="btn btn-primary" type="button" @click="handleAdd" style="height: 32px; padding: 0 16px;">{{ phoneText.add }}</button>
<button class="btn-reset btn-reset-refined" type="button" @click="resetTableState">{{ phoneText.reset }}</button>
</div>
</div>
<div v-if="showBasicFilter" class="phone-filter-row">
<el-select v-model="filterStatus" class="custom-fselect" style="width: 120px;">
<el-option label="状态:全部" value="all"></el-option> <el-option label="状态:全部" value="all"></el-option>
<el-option v-for="label in phoneTableFilterLabels.filter((item) => item !== '全部')" :key="label" :label="label" :value="label"></el-option> <el-option v-for="label in phoneTableFilterLabels.filter((item) => item !== '全部')" :key="label" :label="label" :value="label"></el-option>
</el-select> </el-select>
<el-select v-model="filterCarrier" class="custom-fselect" style="width: 140px; height: 32px;"> <el-select v-model="filterCarrier" class="custom-fselect" style="width: 140px;">
<el-option label="运营商:全部" value="all"></el-option> <el-option label="运营商:全部" value="all"></el-option>
<el-option v-for="carrier in carrierOptions" :key="carrier" :label="carrier" :value="carrier"></el-option> <el-option v-for="carrier in carrierOptions" :key="carrier" :label="carrier" :value="carrier"></el-option>
</el-select> </el-select>
<el-select v-model="filterOutbound" class="custom-fselect" style="width: 150px; height: 32px;"> <el-select v-model="filterOutbound" class="custom-fselect" style="width: 150px;">
<el-option label="外呼:全部" value="all"></el-option> <el-option label="外呼:全部" value="all"></el-option>
<el-option v-for="outbound in outboundOptions" :key="outbound" :label="outbound" :value="outbound"></el-option> <el-option v-for="outbound in outboundOptions" :key="outbound" :label="outbound" :value="outbound"></el-option>
</el-select> </el-select>
<el-date-picker <el-date-picker
v-model="filterDate" v-model="filterDate"
type="daterange" type="daterange"
range-separator="至" range-separator="-"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
style="width: 240px; height: 32px;" class="phone-date-range"
></el-date-picker> ></el-date-picker>
</div>
<div v-if="showPhoneSearch" class="phone-filter-row">
<el-input <el-input
v-model="searchQuery" v-model="phonePrefix"
:placeholder="phoneText.searchPlaceholder" placeholder="前3位"
maxlength="3"
clearable clearable
style="width: 260px; height: 32px;" style="width: 90px;"
> ></el-input>
<template #prefix> <el-select v-model="phoneLogic" style="width: 72px;">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg> <el-option label="与" value="and"></el-option>
</template> <el-option label="或" value="or"></el-option>
</el-input> </el-select>
</div> <el-input
</div> v-model="phoneSuffix"
<div class="toolbar-right"> placeholder="后4位"
<div class="toolbar-primary-actions"> maxlength="4"
<button class="btn btn-primary" type="button" @click="handleAdd" style="height: 32px; padding: 0 16px;">{{ phoneText.add }}</button> clearable
</div> style="width: 100px;"
<button class="btn-reset btn-reset-refined" type="button" @click="resetTableState">{{ phoneText.reset }}</button> ></el-input>
</div>
</div> </div>
<div class="table-wrap table-wrap-refined" v-loading="loading"> <div class="table-wrap table-wrap-refined" v-loading="loading">
<el-table <el-table
...@@ -443,8 +525,8 @@ ...@@ -443,8 +525,8 @@
style="width: 100%" style="width: 100%"
class="domain-table domain-table-refined" class="domain-table domain-table-refined"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
:header-cell-style="{ background: '#fafafa', color: '#4b5563', fontWeight: 500, padding: '12px 20px', borderBottom: '1px solid #f3f4f6' }" :header-cell-style="{ background: 'linear-gradient(180deg, #f9fafb 0%, #f3f4f6 100%)', color: '#374151', fontWeight: 600, padding: '14px 20px', borderBottom: '1px solid #e5e7eb' }"
:cell-style="{ padding: '12px 20px', borderBottom: '1px solid #f3f4f6', color: '#111827', whiteSpace: 'nowrap' }" :cell-style="{ padding: '14px 20px', borderBottom: '1px solid #f3f4f6', color: '#111827', whiteSpace: 'nowrap' }"
> >
<el-table-column type="selection" width="50" fixed="left" /> <el-table-column type="selection" width="50" fixed="left" />
<el-table-column label="图片" min-width="160"> <el-table-column label="图片" min-width="160">
...@@ -456,7 +538,7 @@ ...@@ -456,7 +538,7 @@
<div style="display: none; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 11px; color: #94a3b8; background: #f8fafc;">占位图</div> <div style="display: none; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 11px; color: #94a3b8; background: #f8fafc;">占位图</div>
</div> </div>
</template> </template>
<div v-else style="width: 52px; height: 52px; border-radius: 10px; border: 1px dashed #cbd5e1; background: #f8fafc; display: flex; align-items: center; justify-content: center; font-size: 11px; color: #94a3b8; flex: 0 0 auto;">占位图</div> <div v-else style="width: 52px; height: 52px; border-radius: 10px; border: 1px dashed #cbd5e1; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); display: flex; align-items: center; justify-content: center; font-size: 11px; color: #94a3b8; flex: 0 0 auto;">占位图</div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -465,11 +547,12 @@ ...@@ -465,11 +547,12 @@
<a href="#" class="domain-link" @click.prevent="focusRow(scope.row.id)">{{ scope.row.phone || '-' }}</a> <a href="#" class="domain-link" @click.prevent="focusRow(scope.row.id)">{{ scope.row.phone || '-' }}</a>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="真实联系人" min-width="150"> <el-table-column label="实名人" min-width="150">
<template #default="scope"> <template #default="scope">
<span :title="scope.row.realPerson || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.realPerson) }}</span> <span :title="scope.row.realPerson || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.realPerson) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="ICCID" min-width="220"> <el-table-column label="ICCID" min-width="220">
<template #default="scope"> <template #default="scope">
<span :title="scope.row.iccid || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.iccid) }}</span> <span :title="scope.row.iccid || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.iccid) }}</span>
...@@ -483,8 +566,8 @@ ...@@ -483,8 +566,8 @@
<el-table-column label="号卡状态" min-width="120"> <el-table-column label="号卡状态" min-width="120">
<template #default="scope"> <template #default="scope">
<span class="status-chip-wrap"> <span class="status-chip-wrap">
<span class="status-dot" :class="getStatusClass(scope.row.status)"></span> <span class="status-dot" :class="getStatusClass(scope.row.cardStatus || scope.row.card_status || scope.row.status)"></span>
<span>{{ scope.row.status }}</span> <span>{{ scope.row.cardStatus || scope.row.card_status || scope.row.status }}</span>
</span> </span>
</template> </template>
</el-table-column> <el-table-column label="企业微信" min-width="110"> </el-table-column> <el-table-column label="企业微信" min-width="110">
...@@ -510,8 +593,8 @@ ...@@ -510,8 +593,8 @@
<span :title="scope.row.channelOperator || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.channelOperator) }}</span> <span :title="scope.row.channelOperator || '-'" style="display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ formatCellText(scope.row.channelOperator) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="手机号状态" min-width="120"> <el-table-column label="号码状态" min-width="120">
<template #default="scope">{{ formatStatusText(scope.row.numberStatus) }}</template> <template #default="scope">{{ formatStatusText(scope.row.numberStatus ?? scope.row.mobile_status ?? scope.row.number_status) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="绑定企业微信" min-width="180"> <el-table-column label="绑定企业微信" min-width="180">
<template #default="scope"> <template #default="scope">
...@@ -520,7 +603,7 @@ ...@@ -520,7 +603,7 @@
</el-table-column> <el-table-column label="操作" width="100" fixed="right"> </el-table-column> <el-table-column label="操作" width="100" fixed="right">
<template #default="scope"> <template #default="scope">
<div class="action-cluster" style="display: flex; flex-direction: column; align-items: flex-start; gap: 8px;"> <div class="action-cluster" style="display: flex; flex-direction: column; align-items: flex-start; gap: 8px;">
<button class="action-pill action-pill-primary" type="button" @click.stop="handleRowAction('view', scope.row.id)" style="border: none; background: transparent; color: #2563eb; cursor: pointer; padding: 0; font-weight: 600; line-height: 1;">查看</button> <button class="action-pill action-pill-primary" type="button" @click.stop="handleRowAction('view', scope.row.id)" style="border: none; background: transparent; background-image: linear-gradient(90deg, #2563eb, #60a5fa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; cursor: pointer; padding: 0; font-weight: 600; line-height: 1;">查看</button>
<button class="action-pill" type="button" @click.stop="handleRowAction('edit', scope.row.id)" style="border: none; background: transparent; color: #4b5563; cursor: pointer; padding: 0; font-weight: 500; line-height: 1;">编辑</button> <button class="action-pill" type="button" @click.stop="handleRowAction('edit', scope.row.id)" style="border: none; background: transparent; color: #4b5563; cursor: pointer; padding: 0; font-weight: 500; line-height: 1;">编辑</button>
<button class="action-pill" type="button" @click.stop="handleRowAction('delete', scope.row.id)" style="border: none; background: transparent; color: #ef4444; cursor: pointer; padding: 0; font-weight: 500; line-height: 1;">删除</button> <button class="action-pill" type="button" @click.stop="handleRowAction('delete', scope.row.id)" style="border: none; background: transparent; color: #ef4444; cursor: pointer; padding: 0; font-weight: 500; line-height: 1;">删除</button>
</div> </div>
......
...@@ -83,11 +83,16 @@ ...@@ -83,11 +83,16 @@
/** /**
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):PhoneApiClient/DATA_SOURCES.phone -> normalizePhoneRecord() -> app-v2.js 注入 -> 手机号列表/详情/编辑弹窗。 * 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):PhoneApiClient/DATA_SOURCES.phone -> normalizePhoneRecord() -> app-v2.js 注入 -> 手机号列表/详情/编辑弹窗。
*/ */
/**
* ????????????????????????????????????????????? status ??????????????F:/Project/xyw_console/src/modules/shared/record-adapters.js?F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js?F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js?????????/???/????????? -> normalizePhoneRecord() -> ??/??/?????
*/
function normalizePhoneRecord(row) { function normalizePhoneRecord(row) {
const source = row || {}; const source = row || {};
const images = buildPhoneImages(source); const images = buildPhoneImages(source);
const cardStatusRaw = source.cardStatus ?? source.card_status ?? source.status; const cardStatusRaw = source.cardStatus ?? source.card_status ?? source.status;
const cardStatus = cardStatusRaw === 0 || cardStatusRaw === '0' || cardStatusRaw === '异常' ? '异常停机' : (cardStatusRaw || '正常'); const cardStatus = cardStatusRaw === 0 || cardStatusRaw === '0' || cardStatusRaw === '异常'
? '异常停机'
: (cardStatusRaw || '正常');
const phoneNumber = source.phoneNumber || source.phone_number || source.phone || ''; const phoneNumber = source.phoneNumber || source.phone_number || source.phone || '';
const realPerson = source.realPerson || source.real_person || ''; const realPerson = source.realPerson || source.real_person || '';
const channelOperator = source.channelOperator || source.channel_operator || source.carrier || ''; const channelOperator = source.channelOperator || source.channel_operator || source.carrier || '';
...@@ -148,6 +153,7 @@ ...@@ -148,6 +153,7 @@
packageChange5yuan: packageChangeEnabled, packageChange5yuan: packageChangeEnabled,
package_change_5yuan: packageChangeEnabled, package_change_5yuan: packageChangeEnabled,
numberStatus, numberStatus,
mobileStatus: numberStatus,
mobile_status: numberStatus, mobile_status: numberStatus,
linkedWecom, linkedWecom,
linked_wecom: linkedWecom, linked_wecom: linkedWecom,
......
...@@ -1150,3 +1150,82 @@ ...@@ -1150,3 +1150,82 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
/* Phone card filter toggle buttons and collapsible filter rows */
.phone-toolbar-main {
flex-wrap: nowrap;
}
.phone-toolbar-main .toolbar-left {
flex: 0 0 auto;
min-width: 0;
}
.phone-toolbar-actions {
flex: 1 1 auto;
justify-content: flex-end;
gap: 8px;
flex-wrap: nowrap;
}
.filter-toggle-btn {
flex: 0 0 auto;
height: 32px;
padding: 0 14px;
border: 1px solid #e5e7eb;
border-radius: 6px;
background: #fff;
color: #4b5563;
font-size: 13px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.filter-toggle-btn:hover {
border-color: #93c5fd;
color: #2563eb;
}
.filter-toggle-btn.is-active {
background: #eff6ff;
border-color: #3b82f6;
color: #2563eb;
font-weight: 600;
}
.phone-search-input {
flex: 0 0 220px;
}
.phone-filter-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
padding: 12px 20px;
border-bottom: 1px solid #f3f4f6;
background: #fafbfc;
}
.phone-filter-row .el-select,
.phone-filter-row .el-date-editor,
.phone-filter-row .el-input {
margin: 0;
}
.phone-date-range.el-date-editor.el-input__wrapper,
.phone-date-range {
width: 190px !important;
flex: 0 0 190px;
}
.phone-date-range .el-range-input {
min-width: 0 !important;
width: 68px !important;
}
.phone-date-range .el-range-separator {
padding: 0 2px !important;
}
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