Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xyw_asset_console
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DaiJiezhang
xyw_asset_console
Commits
00d54727
Commit
00d54727
authored
Jul 06, 2026
by
DaiJiezhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refine phone list workspace
parent
da65cb92
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
221 additions
and
61 deletions
+221
-61
app-v2.js
app-v2.js
+2
-2
phone-add-dialog.js
src/modules/phone/phone-add-dialog.js
+9
-9
phone-detail-drawer.js
src/modules/phone/phone-detail-drawer.js
+1
-9
phone-list-runtime.js
src/modules/phone/phone-list-runtime.js
+123
-40
record-adapters.js
src/modules/shared/record-adapters.js
+7
-1
styles-v2.css
styles-v2.css
+79
-0
No files found.
app-v2.js
View file @
00d54727
...
@@ -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
);
src/modules/phone/phone-add-dialog.js
View file @
00d54727
...
@@ -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
.
c
hannel_operator
||
record
.
c
arrier
||
''
;
formData
.
numberStatus
=
record
.
numberStatus
!==
fals
e
;
formData
.
numberStatus
=
record
.
numberStatus
??
record
.
mobile_status
??
record
.
number_status
??
tru
e
;
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'
;
...
...
src/modules/phone/phone-detail-drawer.js
View file @
00d54727
(
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>
...
...
src/modules/phone/phone-list-runtime.js
View file @
00d54727
(
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: 32
px;">
<el-select v-model="filterCarrier" class="custom-fselect" style="width: 140
px;">
<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: 32
px;">
<el-select v-model="filterOutbound" class="custom-fselect" style="width: 150
px;">
<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: '1
2
px 20px', borderBottom: '1px solid #f3f4f6', color: '#111827', whiteSpace: 'nowrap' }"
:cell-style="{ padding: '1
4
px 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>
...
...
src/modules/shared/record-adapters.js
View file @
00d54727
...
@@ -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
,
...
...
styles-v2.css
View file @
00d54727
...
@@ -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
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment