Commit 83ab85f1 by DaiJiezhang

feat: 企业微信资产支持编辑与删除,并新增关联设备字段

企微此前只能新增。补上编辑和删除后,难点在注册手机号的联动——新增时填一个
库里没有的号码,系统会自动建一条外部号码资产(phoneLinkMode=CREATED)并把它
的 sourceAssetId 指回这条企微。

换号和删除时,那条自动新建的号码要满足三个条件才会被一并软删:
- phoneLinkMode 是 CREATED(复用的已有号码是独立资产,绝不能碰)
- 来源标记对得上,防止误删同号但另有出处的记录
- 没有别的企微、微信、抖音仍在引用它

三道闸门缺一不可。号码没改动时整段联动直接跳过,避免把复用号码误标成新建,
或把还在用的号码当成旧号清理掉。删除确认框会明说自动新建的号码将一并删除。

关联设备:表单加远程搜索选择,新增和编辑都支持,列表也展示。打开弹窗即预载
一页设备,否则用户点开下拉只看到「无数据」,得先猜着打字才有列表。已选设备
不在后端返回的前 20 条里时会被补回列表头部,避免选择框丢掉名称回落成裸 ID。

顺带修复 PhoneAssetValidationException 没有任何 handler 的问题:它会一路落进
GlobalExceptionHandler 的 Exception 兜底,用户填错手机号却收到「服务器处理
失败,请稍后重试」。同样地,接口不存在或请求方式不对此前也被兜底成 500,把
「前后端版本不同步」伪装成线上故障,排查时会往完全错误的方向查;现在分别返回
400、405、404。

列表信息密度:去掉资产 ID 列与关联字段的(ID:n)后缀,别名收进名称下方做
浅灰小字,性别做成名称右侧图标,实名状态做成实名人右上角圆点,关联方式做成
手机号右上角一字角标。注册手机号和关联设备做成链接,跳转到对应资产页并预筛
出该条——为此手机号码管理页和设备资产管理页补上了读取地址栏参数的能力,
否则链接点过去只是一个没筛选的全量列表。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent ceb45728
...@@ -2,6 +2,7 @@ package com.xyw.console.asset.controller; ...@@ -2,6 +2,7 @@ package com.xyw.console.asset.controller;
import com.xyw.console.asset.dto.CompanyPersonLookupResponse; import com.xyw.console.asset.dto.CompanyPersonLookupResponse;
import com.xyw.console.asset.dto.CompanyProfileLookupResponse; import com.xyw.console.asset.dto.CompanyProfileLookupResponse;
import com.xyw.console.asset.dto.DeviceAssetLookupResponse;
import com.xyw.console.asset.dto.PhoneAssetLookupResponse; import com.xyw.console.asset.dto.PhoneAssetLookupResponse;
import com.xyw.console.asset.dto.WecomAccountPageQuery; import com.xyw.console.asset.dto.WecomAccountPageQuery;
import com.xyw.console.asset.dto.WecomAccountPageResponse; import com.xyw.console.asset.dto.WecomAccountPageResponse;
...@@ -11,8 +12,11 @@ import com.xyw.console.auth.PagePermissionService; ...@@ -11,8 +12,11 @@ import com.xyw.console.auth.PagePermissionService;
import com.xyw.console.common.ApiResponse; import com.xyw.console.common.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
...@@ -33,6 +37,14 @@ public class WecomAccountController { ...@@ -33,6 +37,14 @@ public class WecomAccountController {
@PostMapping @PostMapping
public ApiResponse<?> create(@Valid @RequestBody WecomAccountSaveRequest request) { permissions.require(PagePermissionService.WECOM, "EDIT"); return ApiResponse.success("新增成功", service.create(request)); } public ApiResponse<?> create(@Valid @RequestBody WecomAccountSaveRequest request) { permissions.require(PagePermissionService.WECOM, "EDIT"); return ApiResponse.success("新增成功", service.create(request)); }
/** 代码作用(白话):编辑一条企微资产,改了注册手机号时由 Service 处理重新关联与旧号清理。关联文件:WecomAccountSaveRequest.java、WecomAccountService.java。关联逻辑(调用链/数据流):编辑弹窗 -> PUT -> 事务 -> 响应。 */
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @Valid @RequestBody WecomAccountSaveRequest request) { permissions.require(PagePermissionService.WECOM, "EDIT"); return ApiResponse.success("编辑成功", service.update(id, request)); }
/** 代码作用(白话):软删除一条企微资产,并顺带清理当初为它自动新建、现在没人用的外部号码。关联文件:WecomAccountService.java、PhoneAssetEntity.java。关联逻辑(调用链/数据流):删除确认 -> DELETE -> 事务 -> 列表刷新。 */
@DeleteMapping("/{id}")
public ApiResponse<Void> delete(@PathVariable Long id) { permissions.require(PagePermissionService.WECOM, "EDIT"); service.softDelete(id); return ApiResponse.success("删除成功", null); }
/** Code purpose (plain language): returns the paged enterprise WeChat asset list and optional registration-phone filter. Related files: WecomAccountPageQuery.java, WecomAccountService.java. Data flow: list query -> GET -> service.page -> table. */ /** Code purpose (plain language): returns the paged enterprise WeChat asset list and optional registration-phone filter. Related files: WecomAccountPageQuery.java, WecomAccountService.java. Data flow: list query -> GET -> service.page -> table. */
@GetMapping @GetMapping
public ApiResponse<WecomAccountPageResponse> page(@Valid WecomAccountPageQuery query) { permissions.require(PagePermissionService.WECOM, "READ"); return ApiResponse.success(service.page(query)); } public ApiResponse<WecomAccountPageResponse> page(@Valid WecomAccountPageQuery query) { permissions.require(PagePermissionService.WECOM, "READ"); return ApiResponse.success(service.page(query)); }
...@@ -45,6 +57,10 @@ public class WecomAccountController { ...@@ -45,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)); }
/** 代码作用(白话):按名称搜索可关联的有效设备,供表单下拉远程查询。关联文件:DeviceAssetLookupResponse.java、WecomAccountService.java。关联逻辑(调用链/数据流):下拉输入 -> GET lookup -> 设备选项。 */
@GetMapping("/lookups/devices")
public ApiResponse<List<DeviceAssetLookupResponse>> devices(@RequestParam(defaultValue = "") String keyword) { permissions.require(PagePermissionService.WECOM, "READ"); return ApiResponse.success(service.searchDevices(keyword)); }
/** Code purpose (plain language): searches optional WeCom owners from company people. Related files: CompanyPersonLookupResponse.java, WecomAccountService.java. Data flow: remote select -> GET -> compact options. */ /** Code purpose (plain language): searches optional WeCom owners from company people. Related files: CompanyPersonLookupResponse.java, WecomAccountService.java. Data flow: remote select -> GET -> compact options. */
@GetMapping("/lookups/company-persons") @GetMapping("/lookups/company-persons")
public ApiResponse<List<CompanyPersonLookupResponse>> companyPersons(@RequestParam(defaultValue = "") String keyword) { permissions.require(PagePermissionService.WECOM, "READ"); return ApiResponse.success(service.searchCompanyPersons(keyword)); } public ApiResponse<List<CompanyPersonLookupResponse>> companyPersons(@RequestParam(defaultValue = "") String keyword) { permissions.require(PagePermissionService.WECOM, "READ"); return ApiResponse.success(service.searchCompanyPersons(keyword)); }
......
package com.xyw.console.asset.controller;
import com.xyw.console.asset.exception.PhoneAssetValidationException;
import com.xyw.console.asset.exception.WecomAccountNotFoundException;
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 = WecomAccountController.class)
public class WecomAccountExceptionHandler {
/** 代码作用(白话):把不存在或已删除的企微记录转为 404。关联文件:WecomAccountService.java。关联逻辑(调用链/数据流):Service 查无记录 -> Advice -> ApiResponse -> 前端提示。 */
@ExceptionHandler(WecomAccountNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> notFound(WecomAccountNotFoundException exception) { return ApiResponse.error(404, exception.getMessage()); }
/**
* 代码作用(白话):把注册手机号的格式错误转为 400。
* 关联文件:WecomAccountService.java、PhoneAssetValidationException.java。
* 关联逻辑(调用链/数据流):手机号校验失败 -> Advice -> ApiResponse -> 弹窗提示。
* 这个处理器必须有:该异常此前没有任何 Advice 认领,会一路落进 GlobalExceptionHandler 的 Exception 兜底,
* 用户填错手机号却收到"服务器处理失败,请稍后重试",既看不到真正原因,也让监控分不清用户输错和服务端故障。
*/
@ExceptionHandler(PhoneAssetValidationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> phoneValidation(PhoneAssetValidationException exception) { return ApiResponse.error(400, exception.getMessage()); }
}
...@@ -12,5 +12,6 @@ public record WecomAccountSaveRequest( ...@@ -12,5 +12,6 @@ public record WecomAccountSaveRequest(
String realNameOwner, String realNameOwner,
String realNameOwnerStatus, String realNameOwnerStatus,
String gender, String gender,
Long deviceId,
Long operatorPersonId) { Long operatorPersonId) {
} }
package com.xyw.console.asset.exception;
/** 文件用途(白话):表示要编辑或删除的企业微信资产不存在或已被删除,供 Advice 转成 404。 */
public class WecomAccountNotFoundException extends RuntimeException {
/** 代码作用(白话):携带可直接展示给用户的提示。关联文件:WecomAccountExceptionHandler.java、WecomAccountService.java。关联逻辑(调用链/数据流):Service 查无记录 -> 本异常 -> Advice -> 404 JSON。 */
public WecomAccountNotFoundException(String message) { super(message); }
}
...@@ -6,10 +6,12 @@ import org.springframework.jdbc.BadSqlGrammarException; ...@@ -6,10 +6,12 @@ import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
/** 文件用途(白话):把未被业务模块处理的数据库和服务器异常统一转换为页面可展示的 JSON 错误,避免浏览器把非认证故障误认为未登录。 */ /** 文件用途(白话):把未被业务模块处理的数据库和服务器异常统一转换为页面可展示的 JSON 错误,避免浏览器把非认证故障误认为未登录。 */
@RestControllerAdvice @RestControllerAdvice
...@@ -52,6 +54,27 @@ public class GlobalExceptionHandler { ...@@ -52,6 +54,27 @@ public class GlobalExceptionHandler {
return ApiResponse.error(400, "请求格式不正确,请刷新页面后重试"); return ApiResponse.error(400, "请求格式不正确,请刷新页面后重试");
} }
/**
* 代码作用(白话):请求打到了不支持该方法的接口时返回 405,而不是让它冒充服务器故障。
* 关联文件:各 Controller、前端 API 客户端。
* 关联逻辑(调用链/数据流):路由无匹配方法 -> 本方法 -> 405 JSON -> 前端提示接口不可用。
*
* 修复要点:此前没有本处理器,前后端版本不同步(后端没重启、新接口还没加载)时浏览器只会收到
* "服务器处理失败,请稍后重试",把一眼可辨的"接口还不存在"伪装成线上故障,排查时会往完全错误的方向查。
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public ApiResponse<Void> methodNotSupported(HttpRequestMethodNotSupportedException error) {
return ApiResponse.error(405, "接口不支持该请求方式,请确认前后端版本一致");
}
/** 代码作用(白话):请求打到不存在的路径时返回 404,同样避免被当成服务器故障;关联文件:各 Controller、前端 API 客户端;关联逻辑(调用链/数据流):无匹配路由 -> 本方法 -> 404 JSON -> 前端提示。 */
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> noResource(NoResourceFoundException error) {
return ApiResponse.error(404, "接口不存在,请确认前后端版本一致");
}
/** 代码作用(白话):给未预期的服务端异常提供安全的通用提示,不把 SQL、路径或堆栈暴露给浏览器;关联文件:各 Controller、前端 API 客户端;关联逻辑(调用链/数据流):未知异常 -> 本方法 -> ApiResponse -> 页面错误提示。 */ /** 代码作用(白话):给未预期的服务端异常提供安全的通用提示,不把 SQL、路径或堆栈暴露给浏览器;关联文件:各 Controller、前端 API 客户端;关联逻辑(调用链/数据流):未知异常 -> 本方法 -> ApiResponse -> 页面错误提示。 */
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
......
...@@ -48,6 +48,24 @@ class WecomAccountControllerTest { ...@@ -48,6 +48,24 @@ class WecomAccountControllerTest {
} }
/** /**
* 代码作用(白话):证明编辑和删除都必须先通过企微页的 EDIT 权限,只读账号无法绕过侧边栏直接调接口。
* 关联文件:WecomAccountController.java、PagePermissionService.java。
* 关联逻辑(调用链/数据流):PUT/DELETE -> require(WECOM, EDIT) -> 拒绝时业务服务完全不被触碰。
*/
@Test
void blocksEditAndDeleteWithoutEditPermission() {
WecomAccountService service = org.mockito.Mockito.mock(WecomAccountService.class);
PagePermissionService permissions = org.mockito.Mockito.mock(PagePermissionService.class);
org.mockito.Mockito.doThrow(new org.springframework.security.access.AccessDeniedException("无权限"))
.when(permissions).require(PagePermissionService.WECOM, "EDIT");
WecomAccountController controller = new WecomAccountController(service, permissions);
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)));
org.junit.jupiter.api.Assertions.assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> controller.delete(1L));
org.mockito.Mockito.verifyNoInteractions(service);
}
/**
* 代码作用(白话):构造使用真实 Service 的 Controller,避免只验证模拟返回值。 * 代码作用(白话):构造使用真实 Service 的 Controller,避免只验证模拟返回值。
* 关联文件:WecomAccountController.java、WecomAccountService.java、各资产 Mapper。 * 关联文件:WecomAccountController.java、WecomAccountService.java、各资产 Mapper。
* 关联逻辑(调用链/数据流):测试 HTTP 请求 -> Controller -> Service -> Mapper 模拟数据库结果。 * 关联逻辑(调用链/数据流):测试 HTTP 请求 -> Controller -> Service -> Mapper 模拟数据库结果。
...@@ -67,7 +85,9 @@ class WecomAccountControllerTest { ...@@ -67,7 +85,9 @@ class WecomAccountControllerTest {
org.mockito.Mockito.when(assetDeviceMapper.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(device())); org.mockito.Mockito.when(assetDeviceMapper.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(device()));
org.mockito.Mockito.when(companyPersonMapper.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(operatorPerson())); org.mockito.Mockito.when(companyPersonMapper.selectList(org.mockito.ArgumentMatchers.any())).thenReturn(List.of(operatorPerson()));
return new WecomAccountController(new WecomAccountService( return new WecomAccountController(new WecomAccountService(
wecomMapper, companyProfileMapper, phoneAssetMapper, assetDeviceMapper, companyPersonMapper), org.mockito.Mockito.mock(PagePermissionService.class)); wecomMapper, companyProfileMapper, phoneAssetMapper, assetDeviceMapper, companyPersonMapper,
org.mockito.Mockito.mock(com.xyw.console.asset.mapper.WechatAccountMapper.class),
org.mockito.Mockito.mock(com.xyw.console.asset.mapper.DouyinAccountMapper.class)), org.mockito.Mockito.mock(PagePermissionService.class));
} }
/** /**
...@@ -108,9 +128,9 @@ class WecomAccountControllerTest { ...@@ -108,9 +128,9 @@ class WecomAccountControllerTest {
} }
/** /**
* 代码作用(白话):构造手机号资产关联测试数据。 * 代码作用(白话):构造手机号码管理关联测试数据。
* 关联文件:PhoneAssetEntity.java、WecomAccountService.java。 * 关联文件:PhoneAssetEntity.java、WecomAccountService.java。
* 关联逻辑(调用链/数据流):手机号资产 ID -> 手机号 -> JSON 字段 phoneNumber。 * 关联逻辑(调用链/数据流):手机号码管理 ID -> 手机号 -> JSON 字段 phoneNumber。
*/ */
private PhoneAssetEntity phoneAsset() { private PhoneAssetEntity phoneAsset() {
PhoneAssetEntity entity = new PhoneAssetEntity(); PhoneAssetEntity entity = new PhoneAssetEntity();
......
...@@ -6,7 +6,10 @@ import java.sql.SQLIntegrityConstraintViolationException; ...@@ -6,7 +6,10 @@ import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLSyntaxErrorException; import java.sql.SQLSyntaxErrorException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpMethod;
import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
/** 文件用途(白话):验证未处理的数据库异常会被转换成页面可以直接展示的统一错误消息。 */ /** 文件用途(白话):验证未处理的数据库异常会被转换成页面可以直接展示的统一错误消息。 */
class GlobalExceptionHandlerTest { class GlobalExceptionHandlerTest {
...@@ -27,4 +30,22 @@ class GlobalExceptionHandlerTest { ...@@ -27,4 +30,22 @@ class GlobalExceptionHandlerTest {
assertEquals(500, response.code()); assertEquals(500, response.code());
assertEquals("数据库字段未同步,请完成数据库迁移后重试", response.message()); assertEquals("数据库字段未同步,请完成数据库迁移后重试", response.message());
} }
/**
* 代码作用(白话):证明"接口还不存在"会返回 405 而不是 500,避免把前后端版本不同步当成服务器故障排查。
* 关联文件:GlobalExceptionHandler.java、各 Controller。
* 关联逻辑(调用链/数据流):无匹配请求方式 -> Advice -> 405 JSON -> 前端提示版本不一致。
*/
@Test void unsupportedMethodReturnsMethodNotAllowed() {
ApiResponse<Void> response = new GlobalExceptionHandler().methodNotSupported(new HttpRequestMethodNotSupportedException("PUT"));
assertEquals(405, response.code());
assertEquals("接口不支持该请求方式,请确认前后端版本一致", response.message());
}
/** 代码作用(白话):证明不存在的路径返回 404 而不是 500。关联文件:GlobalExceptionHandler.java。关联逻辑(调用链/数据流):无匹配路由 -> Advice -> 404 JSON -> 前端提示。 */
@Test void missingRouteReturnsNotFound() {
ApiResponse<Void> response = new GlobalExceptionHandler().noResource(new NoResourceFoundException(HttpMethod.GET, "/api/does-not-exist"));
assertEquals(404, response.code());
assertEquals("接口不存在,请确认前后端版本一致", response.message());
}
} }
...@@ -20,6 +20,12 @@ export function listWecomAccounts(query) { ...@@ -20,6 +20,12 @@ export function listWecomAccounts(query) {
/** Code purpose (plain language): saves the create-dialog values. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: dialog submit -> POST -> transaction -> refreshed table. */ /** Code purpose (plain language): saves the create-dialog values. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: dialog submit -> POST -> transaction -> refreshed table. */
export function createWecomAccount(form) { return request('/api/wecom-accounts', { method: 'POST', body: JSON.stringify(form) }); } export function createWecomAccount(form) { return request('/api/wecom-accounts', { method: 'POST', body: JSON.stringify(form) }); }
/** 代码作用(白话):提交编辑弹窗的内容。关联文件:WecomAccountView.js、WecomAccountController.java。关联逻辑(调用链/数据流):编辑保存 -> PUT -> 手机号重新关联与旧号清理 -> 列表刷新。 */
export function updateWecomAccount(id, form) { return request(`/api/wecom-accounts/${id}`, { method: 'PUT', body: JSON.stringify(form) }); }
/** 代码作用(白话):软删除一条企微资产。关联文件:WecomAccountView.js、WecomAccountController.java。关联逻辑(调用链/数据流):删除确认 -> DELETE -> 自动新建号码一并清理 -> 列表刷新。 */
export function deleteWecomAccount(id) { return request(`/api/wecom-accounts/${id}`, { method: 'DELETE' }); }
/** Code purpose (plain language): searches company profiles for the registration-subject picker. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: remote select -> GET lookup -> options. */ /** Code purpose (plain language): searches company profiles for the registration-subject picker. Related files: WecomAccountView.js, WecomAccountController.java. Data flow: remote select -> GET lookup -> options. */
export function searchCompanyProfiles(keyword) { return request(`/api/wecom-accounts/lookups/company-profiles?keyword=${encodeURIComponent(keyword || '')}`); } export function searchCompanyProfiles(keyword) { return request(`/api/wecom-accounts/lookups/company-profiles?keyword=${encodeURIComponent(keyword || '')}`); }
...@@ -28,3 +34,6 @@ export function searchPhoneAssets(keyword) { return request(`/api/wecom-accounts ...@@ -28,3 +34,6 @@ export function searchPhoneAssets(keyword) { return request(`/api/wecom-accounts
/** 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 || '')}`); }
/** 代码作用(白话):按设备名称搜索可关联的有效设备。关联文件:WecomAccountView.js、WecomAccountController.java。关联逻辑(调用链/数据流):关联设备下拉输入 -> GET lookup -> 设备选项。 */
export function searchDevices(keyword) { return request(`/api/wecom-accounts/lookups/devices?keyword=${encodeURIComponent(keyword || '')}`); }
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