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
4fa4ae75
Commit
4fa4ae75
authored
Jul 08, 2026
by
DaiJiezhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refine phone record adapter flow
parent
755bba6d
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
119 additions
and
61 deletions
+119
-61
pom.xml
backend/pom.xml
+10
-0
WxPhoneService.java
...in/java/com/xyw/console/phone/service/WxPhoneService.java
+2
-1
phone-list-runtime.js
src/modules/phone/phone-list-runtime.js
+75
-35
record-adapters.js
src/modules/shared/record-adapters.js
+31
-24
styles-v2.css
styles-v2.css
+1
-1
No files found.
backend/pom.xml
View file @
4fa4ae75
...
...
@@ -59,6 +59,16 @@
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-maven-plugin
</artifactId>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-compiler-plugin
</artifactId>
<version>
3.11.0
</version>
<configuration>
<source>
17
</source>
<target>
17
</target>
<release>
17
</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
backend/src/main/java/com/xyw/console/phone/service/WxPhoneService.java
View file @
4fa4ae75
...
...
@@ -156,7 +156,8 @@ public class WxPhoneService {
return
null
;
}
String
trimmedValue
=
value
.
trim
();
return
trimmedValue
.
isEmpty
()
?
null
:
trimmedValue
;
// 支持空值提交:空字符串表示用户主动清空,应保存为空字符串而非 null
return
trimmedValue
;
}
/**
...
...
src/modules/phone/phone-list-runtime.js
View file @
4fa4ae75
...
...
@@ -65,7 +65,7 @@
* 代码作用(白话):初始化手机号卡页面的分页、筛选、全字段渲染和图片列辅助能力,让壳层传入的数据与动作变成真正可交互的表格;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/phone/phone-detail-drawer.js、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):renderExternalRuntime('phone') -> mountPhoneListComponent() -> PhoneListTable.setup() -> 列表渲染/弹窗/抽屉/删除。
*/
setup
(
props
)
{
const
{
computed
,
ref
}
=
window
.
Vue
;
const
{
computed
,
ref
,
onBeforeUnmount
}
=
window
.
Vue
;
const
currentPage
=
ref
(
props
.
mountState
.
currentPage
||
1
);
const
pageSize
=
ref
(
props
.
mountState
.
pageSize
||
props
.
phoneTablePageSizes
[
0
]);
const
selectedRecordId
=
ref
(
props
.
globalState
.
selectedBySource
.
phone
||
null
);
...
...
@@ -89,6 +89,10 @@
* 代码作用(白话):内联编辑状态,记录当前正在编辑的单元格 { recordId, field, value },同一时刻只允许一个单元格处于编辑态;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):双击单元格 -> handleCellDblclick() -> editingCell -> 渲染输入控件 -> 失焦/回车 -> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords()。
*/
const
editingCell
=
ref
(
null
);
/**
* 代码作用(白话):防止重复保存的标志位,避免 mousedown 和 blur 同时触发导致多次提交;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):handleInlineSave() -> inlineSaving -> 防止并发保存。
*/
let
inlineSaving
=
false
;
/**
* 代码作用(白话):定义哪些字段支持双击内联编辑以及对应的控件类型,避免模板里写死判断逻辑;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):handleCellDblclick() -> isFieldEditable() -> 渲染 input/select/switch。
...
...
@@ -133,37 +137,64 @@
}
/**
* 代码作用(白话):内联编辑确认后构造完整保存载荷并调用保存接口,成功后刷新列表并退出编辑态;
关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):回车/失焦
-> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords() -> editingCell = null。
* 代码作用(白话):内联编辑确认后构造完整保存载荷并调用保存接口,成功后刷新列表并退出编辑态;
支持空值提交,允许用户清空字段内容后保存空字符串;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js、F:/Project/xyw_console/src/modules/shared/phone-api-client.js;关联逻辑(调用链/消息链/数据流):回车/失焦/点击外部
-> handleInlineSave() -> buildSavePayload() -> props.savePhoneRecord() -> loadAllRecords() -> editingCell = null。
*/
async
function
handleInlineSave
()
{
if
(
!
editingCell
.
value
)
{
return
;
}
const
{
recordId
,
field
,
value
}
=
editingCell
.
value
;
const
record
=
allRecordsCache
.
value
.
find
((
item
)
=>
item
.
id
===
recordId
);
if
(
!
record
)
{
editingCell
.
value
=
null
;
// 防止重复保存(blur 和 mousedown 可能同时触发)
if
(
inlineSaving
)
{
return
;
}
const
updatedRecord
=
{
...
record
};
if
(
field
===
'outbound'
)
{
updatedRecord
.
outboundCall
=
value
===
'可外呼'
;
}
else
{
updatedRecord
[
field
]
=
value
;
}
const
images
=
getDisplayImages
(
record
);
const
payload
=
buildSavePayload
(
updatedRecord
,
images
);
inlineSaving
=
true
;
try
{
await
props
.
savePhoneRecord
(
payload
);
if
(
window
.
ElementPlus
?.
ElMessage
)
{
window
.
ElementPlus
.
ElMessage
.
success
(
'修改成功'
);
const
{
recordId
,
field
,
value
}
=
editingCell
.
value
;
const
record
=
allRecordsCache
.
value
.
find
((
item
)
=>
item
.
id
===
recordId
);
if
(
!
record
)
{
editingCell
.
value
=
null
;
return
;
}
const
updatedRecord
=
{
...
record
};
if
(
field
===
'outbound'
)
{
updatedRecord
.
outboundCall
=
value
===
'可外呼'
;
}
else
{
// 支持空值提交:允许保存空字符串
updatedRecord
[
field
]
=
value
??
''
;
}
const
images
=
getDisplayImages
(
record
);
const
payload
=
buildSavePayload
(
updatedRecord
,
images
);
try
{
await
props
.
savePhoneRecord
(
payload
);
if
(
window
.
ElementPlus
?.
ElMessage
)
{
window
.
ElementPlus
.
ElMessage
.
success
(
'修改成功'
);
}
await
loadAllRecords
();
}
catch
(
error
)
{
props
.
notifyError
(
error
.
message
||
'保存失败,请稍后重试'
);
}
finally
{
editingCell
.
value
=
null
;
}
await
loadAllRecords
();
}
catch
(
error
)
{
props
.
notifyError
(
error
.
message
||
'保存失败,请稍后重试'
);
}
finally
{
editingCell
.
value
=
null
;
inlineSaving
=
false
;
}
}
/**
* 代码作用(白话):处理 document 级 mousedown 事件,点击非内联编辑区域时自动提交保存;关联文件:F:/Project/xyw_console/src/modules/phone/phone-list-runtime.js;关联逻辑(调用链/消息链/数据流):点击页面任意位置 -> handleDocumentMousedown() -> 判断是否点击编辑区域外 -> handleInlineSave()。
*/
function
handleDocumentMousedown
(
event
)
{
if
(
!
editingCell
.
value
)
{
return
;
}
// 检查点击目标是否在编辑单元格内
const
target
=
event
.
target
;
const
editingElement
=
document
.
querySelector
(
'.phone-inline-edit-cell'
);
if
(
editingElement
&&
editingElement
.
contains
(
target
))
{
return
;
// 点击在编辑区域内,不处理
}
// 点击在编辑区域外,触发保存
handleInlineSave
();
}
/**
...
...
@@ -405,28 +436,28 @@
}
/**
* 代码作用(白话):按后端 WxPhoneSaveRequest 字段把列表记录拼成完整保存载荷,保证删除图片走 PUT 全量更新时不丢字段;
关联文件:F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):
handleClearImage() -> buildSavePayload() -> props.savePhoneRecord() -> 后端全量更新。
* 代码作用(白话):按后端 WxPhoneSaveRequest 字段把列表记录拼成完整保存载荷,保证删除图片走 PUT 全量更新时不丢字段;
支持空值提交,使用 ?? 或显式判断区分 undefined/null 与空字符串;关联文件:F:/Project/xyw_console/backend/src/main/java/com/xyw/console/phone/dto/WxPhoneSaveRequest.java、F:/Project/xyw_console/src/modules/phone/phone-add-dialog.js;关联逻辑(调用链/消息链/数据流):handleInlineSave()/
handleClearImage() -> buildSavePayload() -> props.savePhoneRecord() -> 后端全量更新。
*/
function
buildSavePayload
(
record
,
images
)
{
return
{
id
:
record
.
id
,
phoneNumber
:
record
.
phoneNumber
||
record
.
phone
||
''
,
realPerson
:
record
.
realPerson
||
''
,
iccid
:
record
.
iccid
||
''
,
city
:
record
.
city
||
''
,
imageAttachment1
:
images
[
0
]
||
''
,
imageAttachment2
:
images
[
1
]
||
''
,
cardStatus
:
record
.
cardStatus
||
record
.
status
||
'正常'
,
cardUsageLocation
:
record
.
cardUsageLocation
||
record
.
usageLocation
||
''
,
phoneNumber
:
record
.
phoneNumber
??
record
.
phone
??
''
,
realPerson
:
record
.
realPerson
??
''
,
iccid
:
record
.
iccid
??
''
,
city
:
record
.
city
??
''
,
imageAttachment1
:
images
[
0
]
??
''
,
imageAttachment2
:
images
[
1
]
??
''
,
cardStatus
:
record
.
cardStatus
??
record
.
status
??
'正常'
,
cardUsageLocation
:
record
.
cardUsageLocation
??
record
.
usageLocation
??
''
,
wecom
:
record
.
wecom
===
true
,
wechat
:
record
.
wechat
===
true
,
outboundCall
:
record
.
outboundCall
===
true
,
douyinAccount
:
record
.
douyinAccount
||
''
,
douyinAccount
:
record
.
douyinAccount
??
''
,
miniProgramFiling
:
record
.
miniProgramFiling
===
true
,
packageChange5yuan
:
record
.
packageChange5yuan
===
true
,
channelOperator
:
record
.
channelOperator
||
record
.
carrier
||
''
,
channelOperator
:
record
.
channelOperator
??
record
.
carrier
??
''
,
numberStatus
:
record
.
numberStatus
??
true
,
linkedWecom
:
record
.
linkedWecom
||
''
linkedWecom
:
record
.
linkedWecom
??
''
};
}
...
...
@@ -617,8 +648,17 @@
props
.
openAddDialog
(
'add'
);
}
window
.
Vue
.
onMounted
(
loadAllRecords
);
syncMountState
();
window
.
Vue
.
onMounted
(()
=>
{
loadAllRecords
();
syncMountState
();
// 添加 document 级 mousedown 监听,点击非编辑区域时自动保存
document
.
addEventListener
(
'mousedown'
,
handleDocumentMousedown
,
true
);
});
window
.
Vue
.
onBeforeUnmount
(()
=>
{
// 清理事件监听
document
.
removeEventListener
(
'mousedown'
,
handleDocumentMousedown
,
true
);
});
return
{
formatCellText
,
...
...
src/modules/shared/record-adapters.js
View file @
4fa4ae75
(
function
attachRecordAdapters
()
{
(
function
attachRecordAdapters
()
{
/**
* 代码作用(白话):把企微原始记录整理成列表、详情、编辑弹窗都能直接消费的统一字段;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/src/modules/wechat/wechat-list-runtime.js、F:/Project/xyw_console/src/modules/wechat/wechat-add-dialog.js;关联逻辑(调用链/消息链/数据流):后端/本地记录 -> normalizeWechatRecord() -> app-v2.js 注入 -> 企微列表/详情/编辑弹窗。
*/
...
...
@@ -81,7 +81,7 @@
}
/**
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;关联文件: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 注入 -> 手机号列表/详情/编辑弹窗。
* 代码作用(白话):把手机号原始记录整理成列表、详情抽屉、编辑弹窗共用的统一字段,避免每个组件自己猜字段名;
支持空值提交,使用 ?? 运算符区分 undefined/null 与空字符串;
关联文件: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() -> ??/??/?????
...
...
@@ -89,41 +89,47 @@
function
normalizePhoneRecord
(
row
)
{
const
source
=
row
||
{};
const
images
=
buildPhoneImages
(
source
);
// cardStatus 支持空值:空字符串表示用户主动清空,应保持为空
const
cardStatusRaw
=
source
.
cardStatus
??
source
.
card_status
??
source
.
status
;
const
cardStatus
=
cardStatusRaw
===
0
||
cardStatusRaw
===
'0'
||
cardStatusRaw
===
'异常'
?
'异常停机'
:
(
cardStatusRaw
||
'正常'
);
const
phoneNumber
=
source
.
phoneNumber
||
source
.
phone_number
||
source
.
phone
||
''
;
const
realPerson
=
source
.
realPerson
||
source
.
real_person
||
''
;
const
channelOperator
=
source
.
channelOperator
||
source
.
channel_operator
||
source
.
carrier
||
''
;
const
updatedAt
=
source
.
updatedAt
||
source
.
updated_at
||
source
.
updateTime
||
''
;
const
usageLocation
=
source
.
cardUsageLocation
||
source
.
card_usage_location
||
source
.
usageLocation
||
''
;
:
(
cardStatusRaw
??
'正常'
);
const
phoneNumber
=
source
.
phoneNumber
??
source
.
phone_number
??
source
.
phone
??
''
;
// realPerson 支持空值:空字符串表示用户主动清空
const
realPerson
=
source
.
realPerson
??
source
.
real_person
??
''
;
// channelOperator 支持空值:空字符串表示用户主动清空
const
channelOperator
=
source
.
channelOperator
??
source
.
channel_operator
??
source
.
carrier
??
''
;
const
updatedAt
=
source
.
updatedAt
??
source
.
updated_at
??
source
.
updateTime
??
''
;
const
usageLocation
=
source
.
cardUsageLocation
??
source
.
card_usage_location
??
source
.
usageLocation
??
''
;
const
wecomEnabled
=
toBooleanFlag
(
source
.
wecom
,
false
);
const
wechatEnabled
=
toBooleanFlag
(
source
.
wechat
,
false
);
const
outboundEnabled
=
toBooleanFlag
(
source
.
outboundCall
??
source
.
outbound_call
??
source
.
outbound
,
false
);
const
miniProgramFiled
=
toBooleanFlag
(
source
.
miniProgramFiling
??
source
.
mini_program_filing
,
false
);
const
packageChangeEnabled
=
toBooleanFlag
(
source
.
packageChange5yuan
??
source
.
package_change_5yuan
,
false
);
const
numberStatus
=
toBooleanFlag
(
source
.
numberStatus
??
source
.
mobile_status
??
source
.
number_status
,
true
);
const
wechatStatus
=
source
.
wechatStatus
||
(
numberStatus
?
'正常'
:
'异常停机'
);
const
outbound
=
source
.
outbound
||
(
outboundEnabled
?
'可外呼'
:
'不可外呼'
);
const
linkedWecom
=
source
.
linkedWecom
||
source
.
linked_wecom
||
''
;
const
douyinAccount
=
source
.
douyinAccount
||
source
.
douyin_account
||
''
;
const
wechatStatus
=
source
.
wechatStatus
??
(
numberStatus
?
'正常'
:
'异常停机'
);
const
outbound
=
source
.
outbound
??
(
outboundEnabled
?
'可外呼'
:
'不可外呼'
);
// linkedWecom 支持空值:空字符串表示用户主动清空
const
linkedWecom
=
source
.
linkedWecom
??
source
.
linked_wecom
??
''
;
// douyinAccount 支持空值:空字符串表示用户主动清空
const
douyinAccount
=
source
.
douyinAccount
??
source
.
douyin_account
??
''
;
return
{
...
source
,
id
:
source
.
id
,
images
,
primaryImage
:
images
[
0
]
||
''
,
imageAttachment1
:
images
[
0
]
||
source
.
imageAttachment1
||
source
.
image_attachment_1
||
''
,
image_attachment_1
:
images
[
0
]
||
source
.
imageAttachment1
||
source
.
image_attachment_1
||
''
,
imageAttachment2
:
images
[
1
]
||
source
.
imageAttachment2
||
source
.
image_attachment_2
||
''
,
image_attachment_2
:
images
[
1
]
||
source
.
imageAttachment2
||
source
.
image_attachment_2
||
''
,
primaryImage
:
images
[
0
]
??
''
,
imageAttachment1
:
images
[
0
]
??
source
.
imageAttachment1
??
source
.
image_attachment_1
??
''
,
image_attachment_1
:
images
[
0
]
??
source
.
imageAttachment1
??
source
.
image_attachment_1
??
''
,
imageAttachment2
:
images
[
1
]
??
source
.
imageAttachment2
??
source
.
image_attachment_2
??
''
,
image_attachment_2
:
images
[
1
]
??
source
.
imageAttachment2
??
source
.
image_attachment_2
??
''
,
phone
:
phoneNumber
,
phoneNumber
,
phone_number
:
phoneNumber
,
realPerson
,
real_person
:
realPerson
,
city
:
source
.
city
||
''
,
// city 支持空值:空字符串表示用户主动清空
city
:
source
.
city
??
''
,
usageLocation
,
cardUsageLocation
:
usageLocation
,
card_usage_location
:
usageLocation
,
...
...
@@ -140,12 +146,13 @@
wechat
:
wechatEnabled
,
wecom
:
wecomEnabled
,
updatedAt
,
updated_at
:
source
.
updated_at
||
updatedAt
,
updateTime
:
source
.
updateTime
||
updatedAt
,
owner
:
source
.
owner
||
realPerson
,
project
:
source
.
project
||
source
.
note
||
'暂无备注'
,
note
:
source
.
note
||
source
.
project
||
''
,
iccid
:
source
.
iccid
||
''
,
updated_at
:
source
.
updated_at
??
updatedAt
,
updateTime
:
source
.
updateTime
??
updatedAt
,
owner
:
source
.
owner
??
realPerson
,
project
:
source
.
project
??
source
.
note
??
'暂无备注'
,
note
:
source
.
note
??
source
.
project
??
''
,
// iccid 支持空值:空字符串表示用户主动清空
iccid
:
source
.
iccid
??
''
,
douyinAccount
,
douyin_account
:
douyinAccount
,
miniProgramFiling
:
miniProgramFiled
,
...
...
styles-v2.css
View file @
4fa4ae75
...
...
@@ -1325,7 +1325,7 @@
}
.phone-image-action--preview
:hover
{
color
:
#
2563eb
;
color
:
#
fff
;
background
:
rgba
(
255
,
255
,
255
,
0.18
);
}
...
...
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