From 0e5cd86ed1941f0a215b78e1ba34fa7b97419e26 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 3 Feb 2026 13:59:05 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8C=96=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E9=9A=A7=E9=81=93=E6=9D=83=E9=99=90=E5=88=86=E9=85=8D=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E9=80=89=E9=9A=A7=E9=81=93=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=86=E9=85=8D=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增批量分配接口 POST /tunnel/user/batch-assign - 支持一次选择多个隧道进行分配 - 每个隧道可单独设置限速规则 - flow/num/flowResetTime/expTime 自动从用户设置继承 - 前端表单简化为隧道多选列表+限速选择 - 已分配的隧道显示'已分配'标记且不可重复选择 --- .../common/dto/UserTunnelBatchAssignDto.java | 24 ++ .../com/admin/common/dto/UserTunnelDto.java | 15 +- .../admin/controller/TunnelController.java | 8 + .../com/admin/service/UserTunnelService.java | 72 ++--- .../service/impl/UserTunnelServiceImpl.java | 298 +++++++++++------- vite-frontend/src/api/index.ts | 2 + vite-frontend/src/pages/user.tsx | 219 +++++++------ vite-frontend/src/types/index.ts | 9 + 8 files changed, 369 insertions(+), 278 deletions(-) create mode 100644 springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java new file mode 100644 index 0000000..0d60c20 --- /dev/null +++ b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelBatchAssignDto.java @@ -0,0 +1,24 @@ +package com.admin.common.dto; + +import lombok.Data; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class UserTunnelBatchAssignDto { + + @NotNull(message = "用户ID不能为空") + private Integer userId; + + @NotEmpty(message = "隧道列表不能为空") + private List tunnels; + + @Data + public static class TunnelAssignItem { + @NotNull(message = "隧道ID不能为空") + private Integer tunnelId; + + private Integer speedId; + } +} diff --git a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java index b411682..2afd2e8 100644 --- a/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java +++ b/springboot-backend/src/main/java/com/admin/common/dto/UserTunnelDto.java @@ -13,28 +13,15 @@ public class UserTunnelDto { @NotNull(message = "隧道ID不能为空") private Integer tunnelId; - @NotNull(message = "流量限制不能为空") @Min(value = 0, message = "流量限制不能小于0") private Long flow; - @NotNull(message = "转发数量不能为空") @Min(value = 0, message = "转发数量不能小于0") private Integer num; - /** - * 流量重置时间(时间戳) - */ - @NotNull(message = "流量重置时间不能为空") private Long flowResetTime; - /** - * 到期时间(时间戳) - */ - @NotNull(message = "到期时间不能为空") private Long expTime; - /** - * 限速规则ID(可选,null表示不限速) - */ private Integer speedId; -} \ No newline at end of file +} diff --git a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java b/springboot-backend/src/main/java/com/admin/controller/TunnelController.java index 96db499..13c4e64 100644 --- a/springboot-backend/src/main/java/com/admin/controller/TunnelController.java +++ b/springboot-backend/src/main/java/com/admin/controller/TunnelController.java @@ -5,6 +5,7 @@ import com.admin.common.annotation.RequireRole; import com.admin.common.dto.TunnelDto; import com.admin.common.dto.TunnelUpdateDto; +import com.admin.common.dto.UserTunnelBatchAssignDto; import com.admin.common.dto.UserTunnelDto; import com.admin.common.dto.UserTunnelQueryDto; import com.admin.common.dto.UserTunnelUpdateDto; @@ -78,6 +79,13 @@ public class TunnelController extends BaseController { public R assignUserTunnel(@Validated @RequestBody UserTunnelDto userTunnelDto) { return userTunnelService.assignUserTunnel(userTunnelDto); } + + @LogAnnotation + @RequireRole + @PostMapping("/user/batch-assign") + public R batchAssignUserTunnel(@Validated @RequestBody UserTunnelBatchAssignDto batchAssignDto) { + return userTunnelService.batchAssignUserTunnel(batchAssignDto); + } /** * 查询用户隧道权限列表 diff --git a/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java b/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java index e40131e..737b4bd 100644 --- a/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java +++ b/springboot-backend/src/main/java/com/admin/service/UserTunnelService.java @@ -1,49 +1,23 @@ -package com.admin.service; - -import com.admin.common.dto.UserTunnelDto; -import com.admin.common.dto.UserTunnelQueryDto; -import com.admin.common.dto.UserTunnelUpdateDto; -import com.admin.common.lang.R; -import com.admin.entity.UserTunnel; -import com.baomidou.mybatisplus.extension.service.IService; - -/** - *

- * 用户隧道权限服务类 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -public interface UserTunnelService extends IService { - - /** - * 分配用户隧道权限 - * @param userTunnelDto 用户隧道权限数据 - * @return 结果 - */ - R assignUserTunnel(UserTunnelDto userTunnelDto); - - /** - * 查询用户隧道权限列表 - * @param queryDto 查询条件 - * @return 结果 - */ - R getUserTunnelList(UserTunnelQueryDto queryDto); - - /** - * 删除用户隧道权限 - * @param id ID - * @return 结果 - */ - R removeUserTunnel(Integer id); - - - /** - * 更新用户隧道权限(包含流量、流量重置时间、到期时间) - * @param updateDto 更新数据 - * @return 结果 - */ - R updateUserTunnel(UserTunnelUpdateDto updateDto); - -} +package com.admin.service; + +import com.admin.common.dto.UserTunnelBatchAssignDto; +import com.admin.common.dto.UserTunnelDto; +import com.admin.common.dto.UserTunnelQueryDto; +import com.admin.common.dto.UserTunnelUpdateDto; +import com.admin.common.lang.R; +import com.admin.entity.UserTunnel; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface UserTunnelService extends IService { + + R assignUserTunnel(UserTunnelDto userTunnelDto); + + R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto); + + R getUserTunnelList(UserTunnelQueryDto queryDto); + + R removeUserTunnel(Integer id); + + R updateUserTunnel(UserTunnelUpdateDto updateDto); + +} diff --git a/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java b/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java index aba06f7..d181424 100644 --- a/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java +++ b/springboot-backend/src/main/java/com/admin/service/impl/UserTunnelServiceImpl.java @@ -1,120 +1,178 @@ -package com.admin.service.impl; - -import com.admin.common.dto.*; -import com.admin.common.lang.R; -import com.admin.entity.UserTunnel; -import com.admin.mapper.TunnelMapper; -import com.admin.mapper.UserTunnelMapper; -import com.admin.service.TunnelService; -import com.admin.service.UserTunnelService; -import com.admin.service.ForwardService; -import com.admin.service.NodeService; -import com.admin.common.utils.GostUtil; -import com.admin.entity.Forward; -import com.admin.entity.Tunnel; -import com.admin.entity.Node; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Map; - -/** - *

- * 用户隧道权限服务实现类 - * 提供用户隧道权限的分配、查询、更新和删除功能 - * 支持流量限制、数量限制、过期时间和限速规则的管理 - *

- * - * @author QAQ - * @since 2025-06-03 - */ -@Service -public class UserTunnelServiceImpl extends ServiceImpl implements UserTunnelService { - - - @Resource - @Lazy - private ForwardService forwardService; - - @Override - public R assignUserTunnel(UserTunnelDto userTunnelDto) { - int count = this.count(new QueryWrapper().eq("user_id", userTunnelDto.getUserId()).eq("tunnel_id", userTunnelDto.getTunnelId())); - if (count > 0) return R.err("该用户已拥有此隧道权限"); - UserTunnel userTunnel = new UserTunnel(); - BeanUtils.copyProperties(userTunnelDto, userTunnel); - userTunnel.setStatus(1); - this.save(userTunnel); - return R.ok(); - } - - @Override - public R getUserTunnelList(UserTunnelQueryDto queryDto) { - List userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId()); - return R.ok(userTunnelWithDetails); - } - - @Override - public R removeUserTunnel(Integer id) { - UserTunnel userTunnel = this.getById(id); - if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录"); - - List forwardList = forwardService.list(new QueryWrapper().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId())); - for (Forward forward : forwardList) { - forwardService.deleteForward(forward.getId()); - } - this.removeById(id); - return R.ok(); - } - - @Override - public R updateUserTunnel(UserTunnelUpdateDto updateDto) { - UserTunnel userTunnel = this.getById(updateDto.getId()); - if (userTunnel == null) return R.err("隧道不存在"); - boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId()); - userTunnel.setFlow(updateDto.getFlow()); - userTunnel.setNum(updateDto.getNum()); - updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime()); - updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime()); - updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus()); - userTunnel.setSpeedId(updateDto.getSpeedId()); - this.updateById(userTunnel); - if (speedChanged) { - List forwardList = forwardService.list(new QueryWrapper().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId())); - for (Forward forward : forwardList) { - ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto(); - forwardUpdateDto.setId(forward.getId()); - forwardUpdateDto.setUserId(forward.getUserId()); - forwardUpdateDto.setName(forward.getName()); - forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr()); - forwardUpdateDto.setStrategy(forward.getStrategy()); - forwardService.updateForward(forwardUpdateDto); - } - } - return R.err("用户隧道权限更新失败"); - } - - private void updateOptionalProperty(java.util.function.Consumer setter, T value) { - if (value != null) { - setter.accept(value); - } - } - - private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) { - if (oldSpeedId == null && newSpeedId == null) { - return false; - } - if (oldSpeedId == null || newSpeedId == null) { - return true; - } - return !oldSpeedId.equals(newSpeedId); - } - -} +package com.admin.service.impl; + +import com.admin.common.dto.*; +import com.admin.common.lang.R; +import com.admin.entity.User; +import com.admin.entity.UserTunnel; +import com.admin.mapper.UserTunnelMapper; +import com.admin.service.UserService; +import com.admin.service.UserTunnelService; +import com.admin.service.ForwardService; +import com.admin.entity.Forward; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class UserTunnelServiceImpl extends ServiceImpl implements UserTunnelService { + + @Resource + @Lazy + private ForwardService forwardService; + + @Resource + @Lazy + private UserService userService; + + @Override + public R assignUserTunnel(UserTunnelDto userTunnelDto) { + int count = this.count(new QueryWrapper() + .eq("user_id", userTunnelDto.getUserId()) + .eq("tunnel_id", userTunnelDto.getTunnelId())); + if (count > 0) return R.err("该用户已拥有此隧道权限"); + + User user = userService.getById(userTunnelDto.getUserId()); + if (user == null) return R.err("用户不存在"); + + UserTunnel userTunnel = new UserTunnel(); + userTunnel.setUserId(userTunnelDto.getUserId()); + userTunnel.setTunnelId(userTunnelDto.getTunnelId()); + userTunnel.setSpeedId(userTunnelDto.getSpeedId()); + userTunnel.setStatus(1); + userTunnel.setInFlow(0L); + userTunnel.setOutFlow(0L); + + userTunnel.setFlow(userTunnelDto.getFlow() != null ? userTunnelDto.getFlow() : user.getFlow()); + userTunnel.setNum(userTunnelDto.getNum() != null ? userTunnelDto.getNum() : user.getNum()); + userTunnel.setFlowResetTime(userTunnelDto.getFlowResetTime() != null ? userTunnelDto.getFlowResetTime() : user.getFlowResetTime()); + userTunnel.setExpTime(userTunnelDto.getExpTime() != null ? userTunnelDto.getExpTime() : user.getExpTime()); + + this.save(userTunnel); + return R.ok(); + } + + @Override + public R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto) { + User user = userService.getById(batchAssignDto.getUserId()); + if (user == null) return R.err("用户不存在"); + + List requestedTunnelIds = batchAssignDto.getTunnels().stream() + .map(UserTunnelBatchAssignDto.TunnelAssignItem::getTunnelId) + .collect(Collectors.toList()); + + Set existingTunnelIds = this.list( + new QueryWrapper() + .eq("user_id", batchAssignDto.getUserId()) + .in("tunnel_id", requestedTunnelIds) + ).stream().map(UserTunnel::getTunnelId).collect(Collectors.toSet()); + + List toSave = new ArrayList<>(); + List skippedIds = new ArrayList<>(); + + for (UserTunnelBatchAssignDto.TunnelAssignItem item : batchAssignDto.getTunnels()) { + if (existingTunnelIds.contains(item.getTunnelId())) { + skippedIds.add(item.getTunnelId()); + continue; + } + + UserTunnel ut = new UserTunnel(); + ut.setUserId(batchAssignDto.getUserId()); + ut.setTunnelId(item.getTunnelId()); + ut.setSpeedId(item.getSpeedId()); + ut.setStatus(1); + ut.setInFlow(0L); + ut.setOutFlow(0L); + ut.setFlow(user.getFlow()); + ut.setNum(user.getNum()); + ut.setFlowResetTime(user.getFlowResetTime()); + ut.setExpTime(user.getExpTime()); + toSave.add(ut); + } + + if (toSave.isEmpty()) { + return R.err("所选隧道用户均已拥有权限"); + } + + this.saveBatch(toSave); + + if (!skippedIds.isEmpty()) { + return R.ok("成功分配 " + toSave.size() + " 个隧道,跳过 " + skippedIds.size() + " 个已有权限的隧道"); + } + return R.ok(); + } + + @Override + public R getUserTunnelList(UserTunnelQueryDto queryDto) { + List userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId()); + return R.ok(userTunnelWithDetails); + } + + @Override + public R removeUserTunnel(Integer id) { + UserTunnel userTunnel = this.getById(id); + if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录"); + + List forwardList = forwardService.list(new QueryWrapper() + .eq("user_id", userTunnel.getUserId()) + .eq("tunnel_id", userTunnel.getTunnelId())); + for (Forward forward : forwardList) { + forwardService.deleteForward(forward.getId()); + } + this.removeById(id); + return R.ok(); + } + + @Override + public R updateUserTunnel(UserTunnelUpdateDto updateDto) { + UserTunnel userTunnel = this.getById(updateDto.getId()); + if (userTunnel == null) return R.err("隧道不存在"); + boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId()); + userTunnel.setFlow(updateDto.getFlow()); + userTunnel.setNum(updateDto.getNum()); + updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime()); + updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime()); + updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus()); + userTunnel.setSpeedId(updateDto.getSpeedId()); + this.updateById(userTunnel); + if (speedChanged) { + List forwardList = forwardService.list(new QueryWrapper() + .eq("user_id", userTunnel.getUserId()) + .eq("tunnel_id", userTunnel.getTunnelId())); + for (Forward forward : forwardList) { + ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto(); + forwardUpdateDto.setId(forward.getId()); + forwardUpdateDto.setUserId(forward.getUserId()); + forwardUpdateDto.setName(forward.getName()); + forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr()); + forwardUpdateDto.setStrategy(forward.getStrategy()); + forwardService.updateForward(forwardUpdateDto); + } + } + return R.ok(); + } + + private void updateOptionalProperty(java.util.function.Consumer setter, T value) { + if (value != null) { + setter.accept(value); + } + } + + private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) { + if (oldSpeedId == null && newSpeedId == null) { + return false; + } + if (oldSpeedId == null || newSpeedId == null) { + return true; + } + return !oldSpeedId.equals(newSpeedId); + } + +} diff --git a/vite-frontend/src/api/index.ts b/vite-frontend/src/api/index.ts index 56c31ce..6e72465 100644 --- a/vite-frontend/src/api/index.ts +++ b/vite-frontend/src/api/index.ts @@ -46,6 +46,8 @@ export const updateTunnelOrder = (data: { tunnels: Array<{ id: number; inx: numb // 用户隧道权限管理操作 - 全部使用POST请求 export const assignUserTunnel = (data: any) => Network.post("/tunnel/user/assign", data); +export const batchAssignUserTunnel = (data: { userId: number; tunnels: Array<{ tunnelId: number; speedId?: number | null }> }) => + Network.post("/tunnel/user/batch-assign", data); export const getUserTunnelList = (queryData: any = {}) => Network.post("/tunnel/user/list", queryData); export const removeUserTunnel = (params: any) => Network.post("/tunnel/user/remove", params); export const updateUserTunnel = (data: any) => Network.post("/tunnel/user/update", data); diff --git a/vite-frontend/src/pages/user.tsx b/vite-frontend/src/pages/user.tsx index a6c1170..d5ce2e8 100644 --- a/vite-frontend/src/pages/user.tsx +++ b/vite-frontend/src/pages/user.tsx @@ -31,6 +31,7 @@ import { UserForm, UserTunnel, UserTunnelForm, + TunnelAssignItem, Tunnel, SpeedLimit, Pagination as PaginationType @@ -42,6 +43,7 @@ import { deleteUser, getTunnelList, assignUserTunnel, + batchAssignUserTunnel, getUserTunnelList, removeUserTunnel, updateUserTunnel, @@ -143,6 +145,7 @@ export default function UserPage() { speedId: null }); const [assignLoading, setAssignLoading] = useState(false); + const [batchTunnelSelections, setBatchTunnelSelections] = useState>(new Map()); // 编辑隧道权限相关状态 const { isOpen: isEditTunnelModalOpen, onOpen: onEditTunnelModalOpen, onClose: onEditTunnelModalClose } = useDisclosure(); @@ -342,6 +345,7 @@ export default function UserPage() { flowResetTime: 0, speedId: null }); + setBatchTunnelSelections(new Map()); onTunnelModalOpen(); loadUserTunnels(user.id); }; @@ -385,6 +389,37 @@ export default function UserPage() { } }; + const handleBatchAssignTunnel = async () => { + if (batchTunnelSelections.size === 0 || !currentUser) { + toast.error('请选择至少一个隧道'); + return; + } + + setAssignLoading(true); + try { + const tunnelsToAssign: TunnelAssignItem[] = Array.from(batchTunnelSelections.entries()).map( + ([tunnelId, speedId]) => ({ tunnelId, speedId }) + ); + + const response = await batchAssignUserTunnel({ + userId: currentUser.id, + tunnels: tunnelsToAssign + }); + + if (response.code === 0) { + toast.success(response.msg || '分配成功'); + setBatchTunnelSelections(new Map()); + loadUserTunnels(currentUser.id); + } else { + toast.error(response.msg || '分配失败'); + } + } catch (error) { + toast.error('分配失败'); + } finally { + setAssignLoading(false); + } + }; + const handleEditTunnel = (userTunnel: UserTunnel) => { setEditTunnelForm({ ...userTunnel, @@ -526,6 +561,34 @@ export default function UserPage() { speedLimit => speedLimit.tunnelId === editTunnelForm?.tunnelId ); + const getSpeedLimitsForTunnel = (tunnelId: number) => { + return speedLimits.filter(sl => sl.tunnelId === tunnelId); + }; + + const toggleTunnelSelection = (tunnelId: number) => { + setBatchTunnelSelections(prev => { + const newMap = new Map(prev); + if (newMap.has(tunnelId)) { + newMap.delete(tunnelId); + } else { + newMap.set(tunnelId, null); + } + return newMap; + }); + }; + + const updateTunnelSpeedLimit = (tunnelId: number, speedId: number | null) => { + setBatchTunnelSelections(prev => { + const newMap = new Map(prev); + newMap.set(tunnelId, speedId); + return newMap; + }); + }; + + const isTunnelAssigned = (tunnelId: number) => { + return userTunnels.some(ut => ut.tunnelId === tunnelId); + }; + return (
@@ -878,107 +941,73 @@ export default function UserPage() {

分配新权限

-
- - - - - { - const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); - setTunnelForm(prev => ({ ...prev, flow: value })); - }} - min="1" - max="99999" - /> - - { - const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999); - setTunnelForm(prev => ({ ...prev, num: value })); - }} - min="1" - max="99999" - /> - - - - { - if (date) { - const jsDate = new Date(date.year, date.month - 1, date.day, 23, 59, 59); - setTunnelForm(prev => ({ ...prev, expTime: jsDate })); - } else { - setTunnelForm(prev => ({ ...prev, expTime: null })); - } - }} - showMonthAndYearPickers - className="cursor-pointer" - /> +
+ 流量限制、转发数量、到期时间、流量重置时间将自动继承用户设置 +
+ +
+ {tunnels.map(tunnel => { + const isAssigned = isTunnelAssigned(tunnel.id); + const isSelected = batchTunnelSelections.has(tunnel.id); + const tunnelSpeedLimits = getSpeedLimitsForTunnel(tunnel.id); + + return ( +
+
+ toggleTunnelSelection(tunnel.id)} + className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary disabled:opacity-50" + /> + + {tunnel.name} + + {isAssigned && ( + 已分配 + )} +
+ + {isSelected && !isAssigned && ( + + )} +
+ ); + })} + {tunnels.length === 0 && ( +
暂无可用隧道
+ )}
diff --git a/vite-frontend/src/types/index.ts b/vite-frontend/src/types/index.ts index d42edf7..c14a6ac 100644 --- a/vite-frontend/src/types/index.ts +++ b/vite-frontend/src/types/index.ts @@ -58,6 +58,15 @@ export interface UserTunnelForm { speedId: number | null; } +export interface TunnelAssignItem { + tunnelId: number; + speedId: number | null; +} + +export interface UserTunnelBatchAssignForm { + tunnels: TunnelAssignItem[]; +} + export interface Tunnel { id: number; name: string;