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
0
Merge Requests
0
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
41b3e2f2
Commit
41b3e2f2
authored
Aug 12, 2026
by
DaiJiezhang
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feat/device-image-and-wecom-crud' into 'master'
完善企微新增手机号信息 See merge request
!12
parents
870deb9d
25564ae2
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
105 additions
and
18 deletions
+105
-18
PhoneAssetExceptionHandler.java
.../console/asset/controller/PhoneAssetExceptionHandler.java
+20
-0
WecomAccountController.java
.../xyw/console/asset/controller/WecomAccountController.java
+4
-0
WecomAccountSaveRequest.java
...va/com/xyw/console/asset/dto/WecomAccountSaveRequest.java
+5
-2
PhoneAssetService.java
...java/com/xyw/console/asset/service/PhoneAssetService.java
+3
-1
WecomAccountService.java
...va/com/xyw/console/asset/service/WecomAccountService.java
+19
-4
PhoneAssetControllerTest.java
...yw/console/asset/controller/PhoneAssetControllerTest.java
+2
-2
WecomAccountControllerTest.java
.../console/asset/controller/WecomAccountControllerTest.java
+1
-1
WecomAccountServiceTest.java
...om/xyw/console/asset/service/WecomAccountServiceTest.java
+4
-4
PhoneAssetView.js
frontend/src/modules/phone/PhoneAssetView.js
+11
-2
WecomAccountView.js
frontend/src/modules/wecom/WecomAccountView.js
+0
-0
wecom-api-client.js
frontend/src/modules/wecom/wecom-api-client.js
+3
-0
app.css
frontend/src/styles/app.css
+33
-2
No files found.
backend/src/main/java/com/xyw/console/asset/controller/PhoneAssetExceptionHandler.java
0 → 100644
View file @
41b3e2f2
package
com
.
xyw
.
console
.
asset
.
controller
;
import
com.xyw.console.asset.exception.PhoneAssetNotFoundException
;
import
com.xyw.console.asset.exception.PhoneAssetValidationException
;
import
com.xyw.console.common.ApiResponse
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.web.bind.annotation.ExceptionHandler
;
import
org.springframework.web.bind.annotation.ResponseStatus
;
import
org.springframework.web.bind.annotation.RestControllerAdvice
;
/** 文件用途(白话):把手机号码管理的业务异常转换成前端可读的统一 JSON 错误。 */
@RestControllerAdvice
(
assignableTypes
=
PhoneAssetController
.
class
)
public
class
PhoneAssetExceptionHandler
{
/** 代码作用(白话):把手机号格式、ICCID 和实名人必填等校验失败转为 400。关联文件:PhoneAssetService.java。关联逻辑(调用链/数据流):Service 校验异常 -> Advice -> ApiResponse -> 弹窗提示。 */
@ExceptionHandler
(
PhoneAssetValidationException
.
class
)
@ResponseStatus
(
HttpStatus
.
BAD_REQUEST
)
public
ApiResponse
<
Void
>
validation
(
PhoneAssetValidationException
exception
)
{
return
ApiResponse
.
error
(
400
,
exception
.
getMessage
());
}
/** 代码作用(白话):把不存在的手机号码管理转为 404。关联文件:PhoneAssetService.java。关联逻辑(调用链/数据流):Service 查无记录 -> Advice -> ApiResponse -> 前端提示。 */
@ExceptionHandler
(
PhoneAssetNotFoundException
.
class
)
@ResponseStatus
(
HttpStatus
.
NOT_FOUND
)
public
ApiResponse
<
Void
>
notFound
(
PhoneAssetNotFoundException
exception
)
{
return
ApiResponse
.
error
(
404
,
exception
.
getMessage
());
}
}
backend/src/main/java/com/xyw/console/asset/controller/WecomAccountController.java
View file @
41b3e2f2
...
@@ -57,6 +57,10 @@ public class WecomAccountController {
...
@@ -57,6 +57,10 @@ public class WecomAccountController {
@GetMapping
(
"/lookups/phone-assets"
)
@GetMapping
(
"/lookups/phone-assets"
)
public
ApiResponse
<
List
<
PhoneAssetLookupResponse
>>
phoneAssets
(
@RequestParam
(
defaultValue
=
""
)
String
keyword
)
{
permissions
.
require
(
PagePermissionService
.
WECOM
,
"READ"
);
return
ApiResponse
.
success
(
service
.
searchPhoneAssets
(
keyword
));
}
public
ApiResponse
<
List
<
PhoneAssetLookupResponse
>>
phoneAssets
(
@RequestParam
(
defaultValue
=
""
)
String
keyword
)
{
permissions
.
require
(
PagePermissionService
.
WECOM
,
"READ"
);
return
ApiResponse
.
success
(
service
.
searchPhoneAssets
(
keyword
));
}
/** 代码作用(白话):查询注册手机号在手机号码管理中是否已存在,供企微表单决定是否展开新增手机号区块。关联文件:WecomAccountService.java、WecomAccountView.js。关联逻辑(调用链/数据流):手机号输入 -> GET phone-exists -> 精确查询 -> 布尔结果。 */
@GetMapping
(
"/lookups/phone-exists"
)
public
ApiResponse
<
Boolean
>
phoneExists
(
@RequestParam
(
defaultValue
=
""
)
String
phoneNumber
)
{
permissions
.
require
(
PagePermissionService
.
WECOM
,
"READ"
);
return
ApiResponse
.
success
(
service
.
phoneExists
(
phoneNumber
));
}
/** 代码作用(白话):按名称搜索可关联的有效设备,供表单下拉远程查询。关联文件:DeviceAssetLookupResponse.java、WecomAccountService.java。关联逻辑(调用链/数据流):下拉输入 -> GET lookup -> 设备选项。 */
/** 代码作用(白话):按名称搜索可关联的有效设备,供表单下拉远程查询。关联文件:DeviceAssetLookupResponse.java、WecomAccountService.java。关联逻辑(调用链/数据流):下拉输入 -> GET lookup -> 设备选项。 */
@GetMapping
(
"/lookups/devices"
)
@GetMapping
(
"/lookups/devices"
)
public
ApiResponse
<
List
<
DeviceAssetLookupResponse
>>
devices
(
@RequestParam
(
defaultValue
=
""
)
String
keyword
)
{
permissions
.
require
(
PagePermissionService
.
WECOM
,
"READ"
);
return
ApiResponse
.
success
(
service
.
searchDevices
(
keyword
));
}
public
ApiResponse
<
List
<
DeviceAssetLookupResponse
>>
devices
(
@RequestParam
(
defaultValue
=
""
)
String
keyword
)
{
permissions
.
require
(
PagePermissionService
.
WECOM
,
"READ"
);
return
ApiResponse
.
success
(
service
.
searchDevices
(
keyword
));
}
...
...
backend/src/main/java/com/xyw/console/asset/dto/WecomAccountSaveRequest.java
View file @
41b3e2f2
...
@@ -2,7 +2,7 @@ package com.xyw.console.asset.dto;
...
@@ -2,7 +2,7 @@ package com.xyw.console.asset.dto;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotBlank
;
/**
???????????????????????? Controller ???????????
*/
/**
代码作用(白话):限定新增和编辑时浏览器可以提交的企业微信资产字段,并携带注册手机号为新增号码时所需的手机号信息。关联文件:WecomAccountController.java、WecomAccountService.java。关联逻辑(调用链/数据流):弹窗表单 -> DTO -> Service 写入允许修改的实体字段。
*/
public
record
WecomAccountSaveRequest
(
public
record
WecomAccountSaveRequest
(
@NotBlank
String
wecomName
,
@NotBlank
String
wecomName
,
String
wecomAlias
,
String
wecomAlias
,
...
@@ -13,5 +13,8 @@ public record WecomAccountSaveRequest(
...
@@ -13,5 +13,8 @@ public record WecomAccountSaveRequest(
String
realNameOwnerStatus
,
String
realNameOwnerStatus
,
String
gender
,
String
gender
,
Long
deviceId
,
Long
deviceId
,
Long
operatorPersonId
)
{
Long
operatorPersonId
,
String
cardType
,
String
iccid
,
String
phoneRealNameOwner
)
{
}
}
backend/src/main/java/com/xyw/console/asset/service/PhoneAssetService.java
View file @
41b3e2f2
...
@@ -69,7 +69,9 @@ public class PhoneAssetService {
...
@@ -69,7 +69,9 @@ public class PhoneAssetService {
/** 代码作用(白话):读取一条仍有效的资产,供编辑和删除共用。关联文件:PhoneAssetMapper.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):Service 查询 -> 无记录 -> 404 异常。 */
/** 代码作用(白话):读取一条仍有效的资产,供编辑和删除共用。关联文件:PhoneAssetMapper.java、PhoneAssetExceptionHandler.java。关联逻辑(调用链/数据流):Service 查询 -> 无记录 -> 404 异常。 */
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
;
}
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
){
validateRequiredIdentityFields
(
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
());
}
/** 代码作用(白话):运营商为虚拟号码时实名人和 ICCID 可不填,其余运营商必须填写。关联文件:PhoneAssetSaveRequest.java、PhoneAssetValidationException.java。关联逻辑(调用链/数据流):DTO -> 校验 -> 失败抛 400 -> 前端保留弹窗并提示。 */
private
void
validateRequiredIdentityFields
(
PhoneAssetSaveRequest
request
){
if
(
"虚拟号码"
.
equals
(
request
.
cardType
()))
return
;
if
(!
hasText
(
request
.
iccid
()))
throw
new
PhoneAssetValidationException
(
"ICCID 不能为空"
);
if
(!
hasText
(
request
.
realNameOwner
()))
throw
new
PhoneAssetValidationException
(
"实名人不能为空"
);
}
/** 代码作用(白话):按已确认规则清理手机号并验证其最终格式。关联文件: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
;
}
/** 代码作用(白话):只挑选列表需要展示的字段,并按传入的设备名映射补上关联设备名称。关联文件:PhoneAssetResponse.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):实体 + 设备名映射 -> DTO -> ApiResponse -> 表格。 */
/** 代码作用(白话):只挑选列表需要展示的字段,并按传入的设备名映射补上关联设备名称。关联文件:PhoneAssetResponse.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):实体 + 设备名映射 -> DTO -> ApiResponse -> 表格。 */
...
...
backend/src/main/java/com/xyw/console/asset/service/WecomAccountService.java
View file @
41b3e2f2
...
@@ -84,7 +84,7 @@ public class WecomAccountService {
...
@@ -84,7 +84,7 @@ public class WecomAccountService {
PhoneAssetEntity
phone
=
phoneAssetMapper
.
selectOne
(
new
LambdaQueryWrapper
<
PhoneAssetEntity
>().
eq
(
PhoneAssetEntity:
:
getDeleteTime
,
0L
).
eq
(
PhoneAssetEntity:
:
getPhoneNumber
,
phoneNumber
));
PhoneAssetEntity
phone
=
phoneAssetMapper
.
selectOne
(
new
LambdaQueryWrapper
<
PhoneAssetEntity
>().
eq
(
PhoneAssetEntity:
:
getDeleteTime
,
0L
).
eq
(
PhoneAssetEntity:
:
getPhoneNumber
,
phoneNumber
));
boolean
created
=
phone
==
null
;
boolean
created
=
phone
==
null
;
LocalDateTime
now
=
LocalDateTime
.
now
();
LocalDateTime
now
=
LocalDateTime
.
now
();
if
(
created
)
{
phone
=
new
PhoneAssetEntity
();
phone
.
setPhoneNumber
(
phoneNumber
);
phone
.
setNumberType
(
"EXTERNAL"
);
phone
.
setSourceAssetType
(
"WECOM"
);
phone
.
setDisposalStatus
(
"正常使用"
);
phone
.
setCreateTime
(
now
);
phone
.
setUpdateTime
(
now
);
phone
.
setDeleteTime
(
0L
);
phone
.
setLinkedWecomAccounts
(
"[]"
);
phone
.
setLinkedWechatAccounts
(
"[]"
);
phone
.
setLinkedDouyinAccounts
(
"[]"
);
phone
.
setLinkedDomainAccounts
(
"[]"
);
phone
.
setLinkedMerchants
(
"[]"
);
if
(
phoneAssetMapper
.
insert
(
phone
)
!=
1
)
throw
new
IllegalStateException
(
"手机号码管理新增失败"
);
}
if
(
created
)
{
phone
=
new
PhoneAssetEntity
();
phone
.
setPhoneNumber
(
phoneNumber
);
phone
.
setNumberType
(
"EXTERNAL"
);
phone
.
setSourceAssetType
(
"WECOM"
);
applyPhoneIdentity
(
phone
,
request
);
phone
.
setDisposalStatus
(
"正常使用"
);
phone
.
setCreateTime
(
now
);
phone
.
setUpdateTime
(
now
);
phone
.
setDeleteTime
(
0L
);
phone
.
setLinkedWecomAccounts
(
"[]"
);
phone
.
setLinkedWechatAccounts
(
"[]"
);
phone
.
setLinkedDouyinAccounts
(
"[]"
);
phone
.
setLinkedDomainAccounts
(
"[]"
);
phone
.
setLinkedMerchants
(
"[]"
);
if
(
phoneAssetMapper
.
insert
(
phone
)
!=
1
)
throw
new
IllegalStateException
(
"手机号码管理新增失败"
);
}
WecomAccountEntity
entity
=
new
WecomAccountEntity
();
applyEditableFields
(
entity
,
request
);
entity
.
setPhoneAssetId
(
phone
.
getId
());
entity
.
setPhoneLinkMode
(
created
?
"CREATED"
:
"EXISTING"
);
entity
.
setCreateTime
(
now
);
entity
.
setUpdateTime
(
now
);
entity
.
setDeleteTime
(
0L
);
if
(
wecomAccountMapper
.
insert
(
entity
)
!=
1
)
throw
new
IllegalStateException
(
"企业微信资产新增失败"
);
if
(
created
)
{
phone
.
setSourceAssetId
(
entity
.
getId
());
phoneAssetMapper
.
updateById
(
phone
);
}
return
toResponse
(
entity
,
Map
.
of
(),
Map
.
of
(
phone
.
getId
(),
phone
.
getPhoneNumber
()),
Map
.
of
(),
Map
.
of
());
WecomAccountEntity
entity
=
new
WecomAccountEntity
();
applyEditableFields
(
entity
,
request
);
entity
.
setPhoneAssetId
(
phone
.
getId
());
entity
.
setPhoneLinkMode
(
created
?
"CREATED"
:
"EXISTING"
);
entity
.
setCreateTime
(
now
);
entity
.
setUpdateTime
(
now
);
entity
.
setDeleteTime
(
0L
);
if
(
wecomAccountMapper
.
insert
(
entity
)
!=
1
)
throw
new
IllegalStateException
(
"企业微信资产新增失败"
);
if
(
created
)
{
phone
.
setSourceAssetId
(
entity
.
getId
());
phoneAssetMapper
.
updateById
(
phone
);
}
return
toResponse
(
entity
,
Map
.
of
(),
Map
.
of
(
phone
.
getId
(),
phone
.
getPhoneNumber
()),
Map
.
of
(),
Map
.
of
());
}
}
...
@@ -104,7 +104,7 @@ public class WecomAccountService {
...
@@ -104,7 +104,7 @@ public class WecomAccountService {
LocalDateTime
now
=
LocalDateTime
.
now
();
LocalDateTime
now
=
LocalDateTime
.
now
();
// 号码没变就不动关联,避免把 EXISTING 误改成 CREATED、或把还在用的号码当成旧号清理掉。
// 号码没变就不动关联,避免把 EXISTING 误改成 CREATED、或把还在用的号码当成旧号清理掉。
if
(!
phoneNumber
.
equals
(
currentPhoneNumber
(
previousPhoneAssetId
)))
{
if
(!
phoneNumber
.
equals
(
currentPhoneNumber
(
previousPhoneAssetId
)))
{
PhoneAssetEntity
phone
=
attachPhone
(
phoneNumber
,
entity
.
getId
(),
now
);
PhoneAssetEntity
phone
=
attachPhone
(
phoneNumber
,
entity
.
getId
(),
now
,
request
);
entity
.
setPhoneAssetId
(
phone
.
getId
());
entity
.
setPhoneAssetId
(
phone
.
getId
());
entity
.
setPhoneLinkMode
(
isCreatedFor
(
phone
,
entity
.
getId
())
?
"CREATED"
:
"EXISTING"
);
entity
.
setPhoneLinkMode
(
isCreatedFor
(
phone
,
entity
.
getId
())
?
"CREATED"
:
"EXISTING"
);
releaseAutoCreatedPhone
(
previousPhoneAssetId
,
previousLinkMode
,
entity
.
getId
(),
now
);
releaseAutoCreatedPhone
(
previousPhoneAssetId
,
previousLinkMode
,
entity
.
getId
(),
now
);
...
@@ -149,11 +149,11 @@ public class WecomAccountService {
...
@@ -149,11 +149,11 @@ public class WecomAccountService {
* 关联文件:PhoneAssetEntity.java、PhoneAssetMapper.java。
* 关联文件:PhoneAssetEntity.java、PhoneAssetMapper.java。
* 关联逻辑(调用链/数据流):号码 -> 查已有 -> 命中则复用,未命中则新建 EXTERNAL/WECOM 记录。
* 关联逻辑(调用链/数据流):号码 -> 查已有 -> 命中则复用,未命中则新建 EXTERNAL/WECOM 记录。
*/
*/
private
PhoneAssetEntity
attachPhone
(
String
phoneNumber
,
Long
wecomAccountId
,
LocalDateTime
now
)
{
private
PhoneAssetEntity
attachPhone
(
String
phoneNumber
,
Long
wecomAccountId
,
LocalDateTime
now
,
WecomAccountSaveRequest
request
)
{
PhoneAssetEntity
phone
=
phoneAssetMapper
.
selectOne
(
new
LambdaQueryWrapper
<
PhoneAssetEntity
>().
eq
(
PhoneAssetEntity:
:
getDeleteTime
,
0L
).
eq
(
PhoneAssetEntity:
:
getPhoneNumber
,
phoneNumber
));
PhoneAssetEntity
phone
=
phoneAssetMapper
.
selectOne
(
new
LambdaQueryWrapper
<
PhoneAssetEntity
>().
eq
(
PhoneAssetEntity:
:
getDeleteTime
,
0L
).
eq
(
PhoneAssetEntity:
:
getPhoneNumber
,
phoneNumber
));
if
(
phone
!=
null
)
return
phone
;
if
(
phone
!=
null
)
return
phone
;
phone
=
new
PhoneAssetEntity
();
phone
=
new
PhoneAssetEntity
();
phone
.
setPhoneNumber
(
phoneNumber
);
phone
.
setNumberType
(
"EXTERNAL"
);
phone
.
setSourceAssetType
(
"WECOM"
);
phone
.
setSourceAssetId
(
wecomAccountId
);
phone
.
setDisposalStatus
(
"正常使用"
);
phone
.
setPhoneNumber
(
phoneNumber
);
phone
.
setNumberType
(
"EXTERNAL"
);
phone
.
setSourceAssetType
(
"WECOM"
);
phone
.
setSourceAssetId
(
wecomAccountId
);
applyPhoneIdentity
(
phone
,
request
);
phone
.
setDisposalStatus
(
"正常使用"
);
phone
.
setCreateTime
(
now
);
phone
.
setUpdateTime
(
now
);
phone
.
setDeleteTime
(
0L
);
phone
.
setCreateTime
(
now
);
phone
.
setUpdateTime
(
now
);
phone
.
setDeleteTime
(
0L
);
phone
.
setLinkedWecomAccounts
(
"[]"
);
phone
.
setLinkedWechatAccounts
(
"[]"
);
phone
.
setLinkedDouyinAccounts
(
"[]"
);
phone
.
setLinkedDomainAccounts
(
"[]"
);
phone
.
setLinkedMerchants
(
"[]"
);
phone
.
setLinkedWecomAccounts
(
"[]"
);
phone
.
setLinkedWechatAccounts
(
"[]"
);
phone
.
setLinkedDouyinAccounts
(
"[]"
);
phone
.
setLinkedDomainAccounts
(
"[]"
);
phone
.
setLinkedMerchants
(
"[]"
);
if
(
phoneAssetMapper
.
insert
(
phone
)
!=
1
)
throw
new
IllegalStateException
(
"手机号码管理新增失败"
);
if
(
phoneAssetMapper
.
insert
(
phone
)
!=
1
)
throw
new
IllegalStateException
(
"手机号码管理新增失败"
);
...
@@ -201,6 +201,21 @@ public class WecomAccountService {
...
@@ -201,6 +201,21 @@ public class WecomAccountService {
entity
.
setGender
(
request
.
gender
());
entity
.
setDeviceId
(
request
.
deviceId
());
entity
.
setOperatorPersonId
(
request
.
operatorPersonId
());
entity
.
setGender
(
request
.
gender
());
entity
.
setDeviceId
(
request
.
deviceId
());
entity
.
setOperatorPersonId
(
request
.
operatorPersonId
());
}
}
/** 代码作用(白话):企微为新增注册手机号自动建号时,把运营商、ICCID 和手机号实名人写入号码资产,并套用与手机号码管理一致的必填规则(运营商为虚拟号码时 ICCID 和手机号实名人可不填)。关联文件:PhoneAssetEntity.java、PhoneAssetValidationException.java。关联逻辑(调用链/数据流):企微建号分支 -> 字段写入 -> 校验失败抛 400 -> 前端提示。 */
private
void
applyPhoneIdentity
(
PhoneAssetEntity
phone
,
WecomAccountSaveRequest
request
)
{
phone
.
setCardType
(
request
.
cardType
());
phone
.
setIccid
(
request
.
iccid
());
phone
.
setRealNameOwner
(
request
.
phoneRealNameOwner
());
if
(
"虚拟号码"
.
equals
(
request
.
cardType
()))
return
;
if
(!
hasText
(
request
.
iccid
()))
throw
new
PhoneAssetValidationException
(
"ICCID 不能为空"
);
if
(!
hasText
(
request
.
phoneRealNameOwner
()))
throw
new
PhoneAssetValidationException
(
"手机号实名人不能为空"
);
}
/** 代码作用(白话):判断指定号码在手机号码管理中是否存在(未删除),供企微表单决定是否展开新增手机号区块。关联文件:WecomAccountController.java、PhoneAssetMapper.java。关联逻辑(调用链/数据流):手机号输入 -> GET phone-exists -> 精确查询 -> 布尔结果。 */
public
boolean
phoneExists
(
String
phoneNumber
)
{
return
phoneAssetMapper
.
selectCount
(
new
LambdaQueryWrapper
<
PhoneAssetEntity
>().
eq
(
PhoneAssetEntity:
:
getDeleteTime
,
0L
).
eq
(
PhoneAssetEntity:
:
getPhoneNumber
,
phoneNumber
))
>
0
;
}
/** 代码作用(白话):确认选中的关联设备仍然有效,阻止伪造或已删除的设备 ID 写入。关联文件:AssetDeviceMapper.java、WecomAccountSaveRequest.java。关联逻辑(调用链/数据流):表单 deviceId -> 有效性查询 -> 参数错误或继续保存。 */
/** 代码作用(白话):确认选中的关联设备仍然有效,阻止伪造或已删除的设备 ID 写入。关联文件:AssetDeviceMapper.java、WecomAccountSaveRequest.java。关联逻辑(调用链/数据流):表单 deviceId -> 有效性查询 -> 参数错误或继续保存。 */
private
void
requireActiveDevice
(
Long
deviceId
)
{
private
void
requireActiveDevice
(
Long
deviceId
)
{
if
(
deviceId
==
null
)
return
;
if
(
deviceId
==
null
)
return
;
...
...
backend/src/test/java/com/xyw/console/asset/controller/PhoneAssetControllerTest.java
View file @
41b3e2f2
...
@@ -36,7 +36,7 @@ class PhoneAssetControllerTest {
...
@@ -36,7 +36,7 @@ class PhoneAssetControllerTest {
MockMvc
mockMvc
=
MockMvcBuilders
.
standaloneSetup
(
new
PhoneAssetController
(
new
PhoneAssetService
(
mapper
,
mock
(
AssetDeviceMapper
.
class
)),
mock
(
PagePermissionService
.
class
))).
build
();
MockMvc
mockMvc
=
MockMvcBuilders
.
standaloneSetup
(
new
PhoneAssetController
(
new
PhoneAssetService
(
mapper
,
mock
(
AssetDeviceMapper
.
class
)),
mock
(
PagePermissionService
.
class
))).
build
();
mockMvc
.
perform
(
post
(
"/api/phone-assets"
).
contentType
(
MediaType
.
APPLICATION_JSON
)
mockMvc
.
perform
(
post
(
"/api/phone-assets"
).
contentType
(
MediaType
.
APPLICATION_JSON
)
.
content
(
"{\"phoneNumber\":\"13812345678\",\"realNameOwner\":\"张三\"}"
))
.
content
(
"{\"phoneNumber\":\"13812345678\",\"
cardType\":\"移动\",\"iccid\":\"测试 ICCID\",\"
realNameOwner\":\"张三\"}"
))
.
andExpect
(
status
().
isOk
());
.
andExpect
(
status
().
isOk
());
}
}
...
@@ -55,7 +55,7 @@ class PhoneAssetControllerTest {
...
@@ -55,7 +55,7 @@ class PhoneAssetControllerTest {
MockMvc
mockMvc
=
MockMvcBuilders
.
standaloneSetup
(
new
PhoneAssetController
(
new
PhoneAssetService
(
mapper
,
mock
(
AssetDeviceMapper
.
class
)),
mock
(
PagePermissionService
.
class
))).
build
();
MockMvc
mockMvc
=
MockMvcBuilders
.
standaloneSetup
(
new
PhoneAssetController
(
new
PhoneAssetService
(
mapper
,
mock
(
AssetDeviceMapper
.
class
)),
mock
(
PagePermissionService
.
class
))).
build
();
mockMvc
.
perform
(
put
(
"/api/phone-assets/1"
).
contentType
(
MediaType
.
APPLICATION_JSON
)
mockMvc
.
perform
(
put
(
"/api/phone-assets/1"
).
contentType
(
MediaType
.
APPLICATION_JSON
)
.
content
(
"{\"phoneNumber\":\"13912345678\",\"realNameOwner\":\"李四\"}"
))
.
content
(
"{\"phoneNumber\":\"13912345678\",\"
cardType\":\"移动\",\"iccid\":\"测试 ICCID\",\"
realNameOwner\":\"李四\"}"
))
.
andExpect
(
status
().
isOk
());
.
andExpect
(
status
().
isOk
());
}
}
...
...
backend/src/test/java/com/xyw/console/asset/controller/WecomAccountControllerTest.java
View file @
41b3e2f2
...
@@ -60,7 +60,7 @@ class WecomAccountControllerTest {
...
@@ -60,7 +60,7 @@ class WecomAccountControllerTest {
.
when
(
permissions
).
require
(
PagePermissionService
.
WECOM
,
"EDIT"
);
.
when
(
permissions
).
require
(
PagePermissionService
.
WECOM
,
"EDIT"
);
WecomAccountController
controller
=
new
WecomAccountController
(
service
,
permissions
);
WecomAccountController
controller
=
new
WecomAccountController
(
service
,
permissions
);
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertThrows
(
org
.
springframework
.
security
.
access
.
AccessDeniedException
.
class
,
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertThrows
(
org
.
springframework
.
security
.
access
.
AccessDeniedException
.
class
,
()
->
controller
.
update
(
1L
,
new
com
.
xyw
.
console
.
asset
.
dto
.
WecomAccountSaveRequest
(
"名称"
,
null
,
null
,
null
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
)));
()
->
controller
.
update
(
1L
,
new
com
.
xyw
.
console
.
asset
.
dto
.
WecomAccountSaveRequest
(
"名称"
,
null
,
null
,
null
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
,
null
,
null
,
null
)));
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertThrows
(
org
.
springframework
.
security
.
access
.
AccessDeniedException
.
class
,
()
->
controller
.
delete
(
1L
));
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertThrows
(
org
.
springframework
.
security
.
access
.
AccessDeniedException
.
class
,
()
->
controller
.
delete
(
1L
));
org
.
mockito
.
Mockito
.
verifyNoInteractions
(
service
);
org
.
mockito
.
Mockito
.
verifyNoInteractions
(
service
);
}
}
...
...
backend/src/test/java/com/xyw/console/asset/service/WecomAccountServiceTest.java
View file @
41b3e2f2
...
@@ -193,7 +193,7 @@ class WecomAccountServiceTest {
...
@@ -193,7 +193,7 @@ class WecomAccountServiceTest {
void
createsWithAnExistingPhoneAsset
()
{
void
createsWithAnExistingPhoneAsset
()
{
WecomAccountMapper
wecomMapper
=
mock
(
WecomAccountMapper
.
class
);
PhoneAssetMapper
phoneMapper
=
mock
(
PhoneAssetMapper
.
class
);
WecomAccountMapper
wecomMapper
=
mock
(
WecomAccountMapper
.
class
);
PhoneAssetMapper
phoneMapper
=
mock
(
PhoneAssetMapper
.
class
);
PhoneAssetEntity
phone
=
phoneAsset
();
when
(
phoneMapper
.
selectOne
(
any
())).
thenReturn
(
phone
);
when
(
wecomMapper
.
insert
(
any
(
WecomAccountEntity
.
class
))).
thenReturn
(
1
);
PhoneAssetEntity
phone
=
phoneAsset
();
when
(
phoneMapper
.
selectOne
(
any
())).
thenReturn
(
phone
);
when
(
wecomMapper
.
insert
(
any
(
WecomAccountEntity
.
class
))).
thenReturn
(
1
);
WecomAccountResponse
result
=
service
(
wecomMapper
,
mock
(
CompanyProfileMapper
.
class
),
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
)).
create
(
new
WecomAccountSaveRequest
(
"测试企微"
,
"别名"
,
null
,
null
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
));
WecomAccountResponse
result
=
service
(
wecomMapper
,
mock
(
CompanyProfileMapper
.
class
),
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
)).
create
(
new
WecomAccountSaveRequest
(
"测试企微"
,
"别名"
,
null
,
null
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
,
null
,
null
,
null
));
assertEquals
(
20L
,
result
.
phoneAssetId
());
assertEquals
(
"EXISTING"
,
result
.
phoneLinkMode
());
assertEquals
(
20L
,
result
.
phoneAssetId
());
assertEquals
(
"EXISTING"
,
result
.
phoneLinkMode
());
}
}
...
@@ -202,7 +202,7 @@ class WecomAccountServiceTest {
...
@@ -202,7 +202,7 @@ class WecomAccountServiceTest {
void
createsAnExternalPhoneAssetForANewNumber
()
{
void
createsAnExternalPhoneAssetForANewNumber
()
{
WecomAccountMapper
wecomMapper
=
mock
(
WecomAccountMapper
.
class
);
PhoneAssetMapper
phoneMapper
=
mock
(
PhoneAssetMapper
.
class
);
WecomAccountMapper
wecomMapper
=
mock
(
WecomAccountMapper
.
class
);
PhoneAssetMapper
phoneMapper
=
mock
(
PhoneAssetMapper
.
class
);
when
(
phoneMapper
.
selectOne
(
any
())).
thenReturn
(
null
);
when
(
phoneMapper
.
insert
(
any
(
PhoneAssetEntity
.
class
))).
thenAnswer
(
call
->
{
((
PhoneAssetEntity
)
call
.
getArgument
(
0
)).
setId
(
70L
);
return
1
;
});
when
(
wecomMapper
.
insert
(
any
(
WecomAccountEntity
.
class
))).
thenAnswer
(
call
->
{
((
WecomAccountEntity
)
call
.
getArgument
(
0
)).
setId
(
80L
);
return
1
;
});
when
(
phoneMapper
.
selectOne
(
any
())).
thenReturn
(
null
);
when
(
phoneMapper
.
insert
(
any
(
PhoneAssetEntity
.
class
))).
thenAnswer
(
call
->
{
((
PhoneAssetEntity
)
call
.
getArgument
(
0
)).
setId
(
70L
);
return
1
;
});
when
(
wecomMapper
.
insert
(
any
(
WecomAccountEntity
.
class
))).
thenAnswer
(
call
->
{
((
WecomAccountEntity
)
call
.
getArgument
(
0
)).
setId
(
80L
);
return
1
;
});
WecomAccountResponse
result
=
service
(
wecomMapper
,
mock
(
CompanyProfileMapper
.
class
),
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
)).
create
(
new
WecomAccountSaveRequest
(
"测试企微"
,
null
,
null
,
null
,
"13912345678"
,
null
,
null
,
null
,
null
,
null
));
WecomAccountResponse
result
=
service
(
wecomMapper
,
mock
(
CompanyProfileMapper
.
class
),
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
)).
create
(
new
WecomAccountSaveRequest
(
"测试企微"
,
null
,
null
,
null
,
"13912345678"
,
null
,
null
,
null
,
null
,
null
,
"移动"
,
"测试 ICCID"
,
"测试实名人"
));
assertEquals
(
"CREATED"
,
result
.
phoneLinkMode
());
verify
(
phoneMapper
).
updateById
(
any
(
PhoneAssetEntity
.
class
));
assertEquals
(
"CREATED"
,
result
.
phoneLinkMode
());
verify
(
phoneMapper
).
updateById
(
any
(
PhoneAssetEntity
.
class
));
}
}
...
@@ -221,7 +221,7 @@ class WecomAccountServiceTest {
...
@@ -221,7 +221,7 @@ class WecomAccountServiceTest {
WecomAccountService
service
=
service
(
wecomMapper
,
companyProfileMapper
,
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
));
WecomAccountService
service
=
service
(
wecomMapper
,
companyProfileMapper
,
phoneMapper
,
mock
(
AssetDeviceMapper
.
class
),
mock
(
CompanyPersonMapper
.
class
));
IllegalArgumentException
error
=
assertThrows
(
IllegalArgumentException
.
class
,
()
->
service
.
create
(
IllegalArgumentException
error
=
assertThrows
(
IllegalArgumentException
.
class
,
()
->
service
.
create
(
new
WecomAccountSaveRequest
(
"测试企微"
,
null
,
null
,
99L
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
)));
new
WecomAccountSaveRequest
(
"测试企微"
,
null
,
null
,
99L
,
"13812345678"
,
null
,
null
,
null
,
null
,
null
,
null
,
null
,
null
)));
assertEquals
(
"注册主体不存在或已删除"
,
error
.
getMessage
());
assertEquals
(
"注册主体不存在或已删除"
,
error
.
getMessage
());
}
}
...
@@ -363,6 +363,6 @@ class WecomAccountServiceTest {
...
@@ -363,6 +363,6 @@ class WecomAccountServiceTest {
/** 代码作用(白话):构造一份只关心手机号的编辑表单。关联文件:WecomAccountSaveRequest.java。关联逻辑(调用链/数据流):测试 -> DTO -> Service.update。 */
/** 代码作用(白话):构造一份只关心手机号的编辑表单。关联文件:WecomAccountSaveRequest.java。关联逻辑(调用链/数据流):测试 -> DTO -> Service.update。 */
private
WecomAccountSaveRequest
saveRequest
(
String
phoneNumber
)
{
private
WecomAccountSaveRequest
saveRequest
(
String
phoneNumber
)
{
return
new
WecomAccountSaveRequest
(
"编辑后的企微"
,
"别名"
,
"account"
,
null
,
phoneNumber
,
"张三"
,
"在职"
,
"男"
,
null
,
null
);
return
new
WecomAccountSaveRequest
(
"编辑后的企微"
,
"别名"
,
"account"
,
null
,
phoneNumber
,
"张三"
,
"在职"
,
"男"
,
null
,
null
,
"移动"
,
"测试 ICCID"
,
"测试实名人"
);
}
}
}
}
frontend/src/modules/phone/PhoneAssetView.js
View file @
41b3e2f2
...
@@ -222,6 +222,15 @@ export default {
...
@@ -222,6 +222,15 @@ export default {
* 关联逻辑(调用链/数据流):确认保存 -> POST/PUT -> 成功提示 -> dialogVisible=false -> loadPage。
* 关联逻辑(调用链/数据流):确认保存 -> POST/PUT -> 成功提示 -> dialogVisible=false -> loadPage。
*/
*/
async
function
submitForm
()
{
async
function
submitForm
()
{
// 运营商为虚拟号码时实名人/ICCID 可不填,其余运营商必须填写
if
(
form
.
cardType
!==
'虚拟号码'
&&
(
!
form
.
iccid
||
!
form
.
iccid
.
trim
()))
{
ElMessage
.
warning
(
'请填写 ICCID'
);
return
;
}
if
(
form
.
cardType
!==
'虚拟号码'
&&
(
!
form
.
realNameOwner
||
!
form
.
realNameOwner
.
trim
()))
{
ElMessage
.
warning
(
'请填写实名人'
);
return
;
}
saving
.
value
=
true
;
saving
.
value
=
true
;
try
{
try
{
const
payload
=
{
...
form
,
phoneNumber
:
normalizePhoneNumber
(
form
.
phoneNumber
)
};
const
payload
=
{
...
form
,
phoneNumber
:
normalizePhoneNumber
(
form
.
phoneNumber
)
};
...
@@ -331,11 +340,11 @@ export default {
...
@@ -331,11 +340,11 @@ export default {
<template #suffix><span class="phone-asset-modal__character-count" aria-live="polite">{{ form.phoneNumber.length }}/11</span></template>
<template #suffix><span class="phone-asset-modal__character-count" aria-live="polite">{{ form.phoneNumber.length }}/11</span></template>
</el-input>
</el-input>
</el-form-item>
</el-form-item>
<el-form-item class="phone-asset-modal__form-row" label="实名人"><el-input v-model="form.realNameOwner" maxlength="20" placeholder="请输入实名人" /></el-form-item>
<el-form-item class="phone-asset-modal__form-row" label="实名人"
:required="form.cardType !== '虚拟号码'"
><el-input v-model="form.realNameOwner" maxlength="20" placeholder="请输入实名人" /></el-form-item>
<el-form-item class="phone-asset-modal__form-row" label="运营商">
<el-form-item class="phone-asset-modal__form-row" label="运营商">
<el-select v-model="form.cardType" clearable placeholder="请选择" popper-class="phone-asset-modal__select-popper"><el-option label="移动" value="移动" /><el-option label="电信" value="电信" /><el-option label="联通" value="联通" /><el-option label="电销卡「联通」" value="电销卡「联通」" /><el-option label="广电" value="广电" /><el-option label="虚拟号码" value="虚拟号码" /></el-select>
<el-select v-model="form.cardType" clearable placeholder="请选择" popper-class="phone-asset-modal__select-popper"><el-option label="移动" value="移动" /><el-option label="电信" value="电信" /><el-option label="联通" value="联通" /><el-option label="电销卡「联通」" value="电销卡「联通」" /><el-option label="广电" value="广电" /><el-option label="虚拟号码" value="虚拟号码" /></el-select>
</el-form-item>
</el-form-item>
<el-form-item class="phone-asset-modal__form-row" label="ICCID">
<el-form-item class="phone-asset-modal__form-row" label="ICCID"
:required="form.cardType !== '虚拟号码'"
>
<el-input v-model="form.iccid" class="phone-asset-modal__count-input" maxlength="20" autocomplete="off" placeholder="请输入 ICCID" @input="limitIccid" @paste="handleIccidPaste">
<el-input v-model="form.iccid" class="phone-asset-modal__count-input" maxlength="20" autocomplete="off" placeholder="请输入 ICCID" @input="limitIccid" @paste="handleIccidPaste">
<template #suffix><span class="phone-asset-modal__character-count" aria-live="polite">{{ form.iccid.length }}/20</span></template>
<template #suffix><span class="phone-asset-modal__character-count" aria-live="polite">{{ form.iccid.length }}/20</span></template>
</el-input>
</el-input>
...
...
frontend/src/modules/wecom/WecomAccountView.js
View file @
41b3e2f2
This diff is collapsed.
Click to expand it.
frontend/src/modules/wecom/wecom-api-client.js
View file @
41b3e2f2
...
@@ -32,6 +32,9 @@ export function searchCompanyProfiles(keyword) { return request(`/api/wecom-acco
...
@@ -32,6 +32,9 @@ export function searchCompanyProfiles(keyword) { return request(`/api/wecom-acco
/** Code purpose (plain language): searches existing phone assets for the registration-phone input. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: autocomplete -> GET lookup -> selectable numbers. */
/** Code purpose (plain language): searches existing phone assets for the registration-phone input. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: autocomplete -> GET lookup -> selectable numbers. */
export
function
searchPhoneAssets
(
keyword
)
{
return
request
(
`/api/wecom-accounts/lookups/phone-assets?keyword=
${
encodeURIComponent
(
keyword
||
''
)}
`
);
}
export
function
searchPhoneAssets
(
keyword
)
{
return
request
(
`/api/wecom-accounts/lookups/phone-assets?keyword=
${
encodeURIComponent
(
keyword
||
''
)}
`
);
}
/** 代码作用(白话):查询注册手机号在手机号码管理中是否已存在。关联文件:WecomAccountView.js、WecomAccountController.java。关联逻辑(调用链/数据流):手机号输入 -> GET phone-exists -> 布尔结果 -> 决定是否展开新增手机号区块。 */
export
function
checkPhoneExists
(
phoneNumber
)
{
return
request
(
`/api/wecom-accounts/lookups/phone-exists?phoneNumber=
${
encodeURIComponent
(
phoneNumber
||
''
)}
`
);
}
/** Code purpose (plain language): searches optional WeCom owners. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: remote select -> GET lookup -> options. */
/** Code purpose (plain language): searches optional WeCom owners. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: remote select -> GET lookup -> options. */
export
function
searchCompanyPersons
(
keyword
)
{
return
request
(
`/api/wecom-accounts/lookups/company-persons?keyword=
${
encodeURIComponent
(
keyword
||
''
)}
`
);
}
export
function
searchCompanyPersons
(
keyword
)
{
return
request
(
`/api/wecom-accounts/lookups/company-persons?keyword=
${
encodeURIComponent
(
keyword
||
''
)}
`
);
}
...
...
frontend/src/styles/app.css
View file @
41b3e2f2
...
@@ -51,8 +51,10 @@ body { margin: 0; }
...
@@ -51,8 +51,10 @@ body { margin: 0; }
}
}
.phone-asset-modal
{
.phone-asset-modal
{
width
:
min
(
560px
,
100%
)
!important
;
width
:
min
(
560px
,
100%
)
!important
;
max-height
:
calc
(
100vh
-
48px
);
height
:
min
(
680px
,
calc
(
100vh
-
48px
)
);
margin
:
0
!important
;
margin
:
0
!important
;
display
:
flex
;
flex-direction
:
column
;
overflow
:
hidden
;
overflow
:
hidden
;
border
:
1px
solid
rgba
(
228
,
228
,
231
,
0.9
);
border
:
1px
solid
rgba
(
228
,
228
,
231
,
0.9
);
border-radius
:
12px
;
border-radius
:
12px
;
...
@@ -96,15 +98,44 @@ body { margin: 0; }
...
@@ -96,15 +98,44 @@ body { margin: 0; }
height
:
15px
;
height
:
15px
;
}
}
.phone-asset-modal
.el-dialog__body
{
.phone-asset-modal
.el-dialog__body
{
max-height
:
calc
(
100vh
-
206px
)
;
flex
:
1
1
auto
;
min-height
:
0
;
min-height
:
0
;
padding
:
4px
24px
8px
;
padding
:
4px
24px
8px
;
overflow-y
:
auto
;
overflow-y
:
auto
;
}
}
/* 弹窗正文的滚动条缩窄并去掉轨道色,使其融入表单背景。 */
@media
(
min-width
:
601px
)
{
.phone-asset-modal
.el-dialog__body
{
scrollbar-width
:
thin
;
scrollbar-color
:
#d4d4d8
transparent
;
}
.phone-asset-modal
.el-dialog__body
::-webkit-scrollbar
{
width
:
6px
;
}
.phone-asset-modal
.el-dialog__body
::-webkit-scrollbar-track
{
background
:
transparent
;
}
.phone-asset-modal
.el-dialog__body
::-webkit-scrollbar-thumb
{
border-radius
:
999px
;
background
:
#d4d4d8
;
}
.phone-asset-modal
.el-dialog__body
::-webkit-scrollbar-thumb:hover
{
background
:
#a1a1aa
;
}
}
.phone-asset-modal__form
{
.phone-asset-modal__form
{
display
:
grid
;
display
:
grid
;
gap
:
14px
;
gap
:
14px
;
}
}
/* 企微新增注册手机号为系统不存在的号码时,"新增手机号"区块的分隔标题 */
.phone-asset-modal__form-divider
{
margin
:
4px
0
0
;
padding-top
:
12px
;
border-top
:
1px
dashed
#e4e4e7
;
color
:
#52525b
;
font-size
:
13px
;
font-weight
:
600
;
}
.phone-asset-modal__form-row.el-form-item
{
.phone-asset-modal__form-row.el-form-item
{
display
:
grid
;
display
:
grid
;
grid-template-columns
:
96px
minmax
(
0
,
1
fr
);
grid-template-columns
:
96px
minmax
(
0
,
1
fr
);
...
...
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