Commit fe5a6531cf4e2680051a1301a729a226a9436d0a
1 parent
f1e20216
feat(user): wire user detail + permission categories endpoints to UserDetail page
- Backend: add GET /usr/users/{id} detail endpoint that returns the user row plus its permissionCategoryIds
- Backend: add GET /usr/permission-categories listing for the permission grid (active categories only)
- Frontend: UserDetail consumes both endpoints to populate edit form and the permission grid
Showing
11 changed files
with
212 additions
and
53 deletions
backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java
| ... | ... | @@ -4,6 +4,8 @@ import com.xly.erp.common.response.Result; |
| 4 | 4 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 5 | 5 | import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 6 | 6 | import com.xly.erp.module.usr.service.UserService; |
| 7 | +import com.xly.erp.module.usr.vo.PermissionCategoryVO; | |
| 8 | +import com.xly.erp.module.usr.vo.UserListVO; | |
| 7 | 9 | import jakarta.validation.Valid; |
| 8 | 10 | import org.springframework.web.bind.annotation.GetMapping; |
| 9 | 11 | import org.springframework.web.bind.annotation.PathVariable; |
| ... | ... | @@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; |
| 14 | 16 | import org.springframework.web.bind.annotation.RequestParam; |
| 15 | 17 | import org.springframework.web.bind.annotation.RestController; |
| 16 | 18 | |
| 19 | +import java.util.List; | |
| 17 | 20 | import java.util.Map; |
| 18 | 21 | |
| 19 | 22 | @RestController |
| ... | ... | @@ -46,4 +49,14 @@ public class UserController { |
| 46 | 49 | @RequestParam(required = false) Integer pageSize) { |
| 47 | 50 | return Result.ok(userService.list(field, match, value, pageNum, pageSize)); |
| 48 | 51 | } |
| 52 | + | |
| 53 | + @GetMapping("/users/{id}") | |
| 54 | + public Result<UserListVO> detail(@PathVariable Integer id) { | |
| 55 | + return Result.ok(userService.detail(id)); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @GetMapping("/permission-categories") | |
| 59 | + public Result<List<PermissionCategoryVO>> permissionCategories() { | |
| 60 | + return Result.ok(userService.listPermissionCategories()); | |
| 61 | + } | |
| 49 | 62 | } | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java
| 1 | 1 | package com.xly.erp.module.usr.mapper; |
| 2 | 2 | |
| 3 | +import com.xly.erp.module.usr.vo.PermissionCategoryVO; | |
| 3 | 4 | import org.apache.ibatis.annotations.Mapper; |
| 4 | 5 | import org.apache.ibatis.annotations.Param; |
| 5 | 6 | import org.apache.ibatis.annotations.Select; |
| ... | ... | @@ -15,4 +16,10 @@ public interface PermissionCategoryMapper { |
| 15 | 16 | + "<foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach>" |
| 16 | 17 | + "</script>") |
| 17 | 18 | int countActiveByIds(@Param("ids") List<Integer> ids); |
| 19 | + | |
| 20 | + @Select("SELECT iIncrement, sCategoryCode, sCategoryName, iParentId, iSortOrder " | |
| 21 | + + "FROM tPermissionCategory " | |
| 22 | + + "WHERE bDeleted = 0 " | |
| 23 | + + "ORDER BY iSortOrder ASC, iIncrement ASC") | |
| 24 | + List<PermissionCategoryVO> listActive(); | |
| 18 | 25 | } | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java
| ... | ... | @@ -31,4 +31,6 @@ public interface UserMapper extends BaseMapper<User> { |
| 31 | 31 | long countWithFilter(@Param("field") String field, |
| 32 | 32 | @Param("matchOp") String matchOp, |
| 33 | 33 | @Param("value") Object value); |
| 34 | + | |
| 35 | + UserListVO selectDetailById(@Param("id") Integer id); | |
| 34 | 36 | } | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java
| ... | ... | @@ -4,9 +4,15 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| 4 | 4 | import com.xly.erp.module.usr.entity.UserPermission; |
| 5 | 5 | import org.apache.ibatis.annotations.Delete; |
| 6 | 6 | import org.apache.ibatis.annotations.Param; |
| 7 | +import org.apache.ibatis.annotations.Select; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 7 | 10 | |
| 8 | 11 | public interface UserPermissionMapper extends BaseMapper<UserPermission> { |
| 9 | 12 | |
| 10 | 13 | @Delete("DELETE FROM tUserPermission WHERE iUserId = #{userId}") |
| 11 | 14 | int deleteByUserId(@Param("userId") Integer userId); |
| 15 | + | |
| 16 | + @Select("SELECT iCategoryId FROM tUserPermission WHERE iUserId = #{userId} ORDER BY iIncrement ASC") | |
| 17 | + List<Integer> selectCategoryIdsByUserId(@Param("userId") Integer userId); | |
| 12 | 18 | } | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/service/UserService.java
| ... | ... | @@ -4,7 +4,10 @@ import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 4 | 4 | import com.xly.erp.module.usr.dto.LoginDTO; |
| 5 | 5 | import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 6 | 6 | import com.xly.erp.module.usr.vo.LoginVO; |
| 7 | +import com.xly.erp.module.usr.vo.PermissionCategoryVO; | |
| 8 | +import com.xly.erp.module.usr.vo.UserListVO; | |
| 7 | 9 | |
| 10 | +import java.util.List; | |
| 8 | 11 | import java.util.Map; |
| 9 | 12 | |
| 10 | 13 | public interface UserService { |
| ... | ... | @@ -14,5 +17,9 @@ public interface UserService { |
| 14 | 17 | |
| 15 | 18 | Map<String, Object> list(String field, String match, String value, Integer pageNum, Integer pageSize); |
| 16 | 19 | |
| 20 | + UserListVO detail(Integer id); | |
| 21 | + | |
| 22 | + List<PermissionCategoryVO> listPermissionCategories(); | |
| 23 | + | |
| 17 | 24 | LoginVO login(LoginDTO dto); |
| 18 | 25 | } | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java
| ... | ... | @@ -11,6 +11,7 @@ import com.xly.erp.module.usr.dto.UpdateUserDTO; |
| 11 | 11 | import com.xly.erp.module.usr.entity.User; |
| 12 | 12 | import com.xly.erp.module.usr.security.LoginAttemptStore; |
| 13 | 13 | import com.xly.erp.module.usr.vo.LoginVO; |
| 14 | +import com.xly.erp.module.usr.vo.PermissionCategoryVO; | |
| 14 | 15 | import com.xly.erp.module.usr.vo.UserBriefVO; |
| 15 | 16 | import com.xly.erp.module.usr.vo.UserListVO; |
| 16 | 17 | import com.xly.erp.module.usr.entity.UserPermission; |
| ... | ... | @@ -259,6 +260,23 @@ public class UserServiceImpl implements UserService { |
| 259 | 260 | return result; |
| 260 | 261 | } |
| 261 | 262 | |
| 263 | + @Override | |
| 264 | + @Transactional(readOnly = true) | |
| 265 | + public UserListVO detail(Integer id) { | |
| 266 | + UserListVO vo = userMapper.selectDetailById(id); | |
| 267 | + if (vo == null || Boolean.TRUE.equals(vo.getBDeleted())) { | |
| 268 | + throw new BizException(40400, "用户不存在或已删除"); | |
| 269 | + } | |
| 270 | + vo.setPermissionCategoryIds(userPermissionMapper.selectCategoryIdsByUserId(id)); | |
| 271 | + return vo; | |
| 272 | + } | |
| 273 | + | |
| 274 | + @Override | |
| 275 | + @Transactional(readOnly = true) | |
| 276 | + public List<PermissionCategoryVO> listPermissionCategories() { | |
| 277 | + return permissionCategoryMapper.listActive(); | |
| 278 | + } | |
| 279 | + | |
| 262 | 280 | private Integer parseBoolean(String v) { |
| 263 | 281 | return switch (v.toLowerCase()) { |
| 264 | 282 | case "true", "1" -> 1; | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java
0 → 100644
| 1 | +package com.xly.erp.module.usr.vo; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
| 4 | + | |
| 5 | +public class PermissionCategoryVO { | |
| 6 | + | |
| 7 | + @JsonProperty("iIncrement") | |
| 8 | + private Integer iIncrement; | |
| 9 | + | |
| 10 | + @JsonProperty("sCategoryCode") | |
| 11 | + private String sCategoryCode; | |
| 12 | + | |
| 13 | + @JsonProperty("sCategoryName") | |
| 14 | + private String sCategoryName; | |
| 15 | + | |
| 16 | + @JsonProperty("iParentId") | |
| 17 | + private Integer iParentId; | |
| 18 | + | |
| 19 | + @JsonProperty("iSortOrder") | |
| 20 | + private Integer iSortOrder; | |
| 21 | + | |
| 22 | + public Integer getIIncrement() { return iIncrement; } | |
| 23 | + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } | |
| 24 | + public String getSCategoryCode() { return sCategoryCode; } | |
| 25 | + public void setSCategoryCode(String sCategoryCode) { this.sCategoryCode = sCategoryCode; } | |
| 26 | + public String getSCategoryName() { return sCategoryName; } | |
| 27 | + public void setSCategoryName(String sCategoryName) { this.sCategoryName = sCategoryName; } | |
| 28 | + public Integer getIParentId() { return iParentId; } | |
| 29 | + public void setIParentId(Integer iParentId) { this.iParentId = iParentId; } | |
| 30 | + public Integer getISortOrder() { return iSortOrder; } | |
| 31 | + public void setISortOrder(Integer iSortOrder) { this.iSortOrder = iSortOrder; } | |
| 32 | +} | ... | ... |
backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java
| ... | ... | @@ -3,6 +3,7 @@ package com.xly.erp.module.usr.vo; |
| 3 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 4 | 4 | |
| 5 | 5 | import java.time.LocalDateTime; |
| 6 | +import java.util.List; | |
| 6 | 7 | |
| 7 | 8 | public class UserListVO { |
| 8 | 9 | |
| ... | ... | @@ -30,6 +31,12 @@ public class UserListVO { |
| 30 | 31 | @JsonProperty("sLanguage") |
| 31 | 32 | private String sLanguage; |
| 32 | 33 | |
| 34 | + @JsonProperty("bCanModifyDocs") | |
| 35 | + private Boolean bCanModifyDocs; | |
| 36 | + | |
| 37 | + @JsonProperty("permissionCategoryIds") | |
| 38 | + private List<Integer> permissionCategoryIds; | |
| 39 | + | |
| 33 | 40 | @JsonProperty("bDeleted") |
| 34 | 41 | private Boolean bDeleted; |
| 35 | 42 | |
| ... | ... | @@ -58,6 +65,10 @@ public class UserListVO { |
| 58 | 65 | public void setSUserType(String sUserType) { this.sUserType = sUserType; } |
| 59 | 66 | public String getSLanguage() { return sLanguage; } |
| 60 | 67 | public void setSLanguage(String sLanguage) { this.sLanguage = sLanguage; } |
| 68 | + public Boolean getBCanModifyDocs() { return bCanModifyDocs; } | |
| 69 | + public void setBCanModifyDocs(Boolean bCanModifyDocs) { this.bCanModifyDocs = bCanModifyDocs; } | |
| 70 | + public List<Integer> getPermissionCategoryIds() { return permissionCategoryIds; } | |
| 71 | + public void setPermissionCategoryIds(List<Integer> permissionCategoryIds) { this.permissionCategoryIds = permissionCategoryIds; } | |
| 61 | 72 | public Boolean getBDeleted() { return bDeleted; } |
| 62 | 73 | public void setBDeleted(Boolean bDeleted) { this.bDeleted = bDeleted; } |
| 63 | 74 | public LocalDateTime getTLastLoginDate() { return tLastLoginDate; } | ... | ... |
backend/src/main/resources/mapper/usr/UserMapper.xml
| ... | ... | @@ -12,6 +12,7 @@ |
| 12 | 12 | s.sDepartment AS department, |
| 13 | 13 | u.sUserType AS sUserType, |
| 14 | 14 | u.sLanguage AS sLanguage, |
| 15 | + u.bCanModifyDocs AS bCanModifyDocs, | |
| 15 | 16 | u.bDeleted AS bDeleted, |
| 16 | 17 | u.tLastLoginDate AS tLastLoginDate, |
| 17 | 18 | u.sCreatedBy AS sCreatedBy, |
| ... | ... | @@ -46,4 +47,12 @@ |
| 46 | 47 | <include refid="filterClause"/> |
| 47 | 48 | </select> |
| 48 | 49 | |
| 50 | + <select id="selectDetailById" resultType="com.xly.erp.module.usr.vo.UserListVO"> | |
| 51 | + SELECT <include refid="baseSelectColumns"/> | |
| 52 | + FROM tUser u | |
| 53 | + LEFT JOIN tStaff s ON s.iIncrement = u.iStaffId AND s.bDeleted = 0 | |
| 54 | + WHERE u.iIncrement = #{id} | |
| 55 | + LIMIT 1 | |
| 56 | + </select> | |
| 57 | + | |
| 49 | 58 | </mapper> | ... | ... |
frontend/src/api/user.ts
| ... | ... | @@ -9,12 +9,22 @@ export interface UserListVO { |
| 9 | 9 | department: string | null; |
| 10 | 10 | sUserType: string; |
| 11 | 11 | sLanguage: string; |
| 12 | + bCanModifyDocs?: boolean; | |
| 13 | + permissionCategoryIds?: number[]; | |
| 12 | 14 | bDeleted: boolean; |
| 13 | 15 | tLastLoginDate: string | null; |
| 14 | 16 | sCreatedBy: string | null; |
| 15 | 17 | tCreateDate: string; |
| 16 | 18 | } |
| 17 | 19 | |
| 20 | +export interface PermissionCategoryVO { | |
| 21 | + iIncrement: number; | |
| 22 | + sCategoryCode: string; | |
| 23 | + sCategoryName: string; | |
| 24 | + iParentId: number | null; | |
| 25 | + iSortOrder: number; | |
| 26 | +} | |
| 27 | + | |
| 18 | 28 | export interface UserListPage { |
| 19 | 29 | records: UserListVO[]; |
| 20 | 30 | total: number; |
| ... | ... | @@ -37,6 +47,7 @@ export interface UserDTO { |
| 37 | 47 | sUserType: string; |
| 38 | 48 | sLanguage: string; |
| 39 | 49 | bCanModifyDocs?: boolean; |
| 50 | + permissionCategoryIds?: number[]; | |
| 40 | 51 | } |
| 41 | 52 | |
| 42 | 53 | export function listUsers(params: UserListParams = {}): Promise<UserListPage> { |
| ... | ... | @@ -47,6 +58,17 @@ export function listUsers(params: UserListParams = {}): Promise<UserListPage> { |
| 47 | 58 | }); |
| 48 | 59 | } |
| 49 | 60 | |
| 61 | +export function getUser(id: number): Promise<UserListVO> { | |
| 62 | + return request<UserListVO>({ url: `/usr/users/${id}`, method: "GET" }); | |
| 63 | +} | |
| 64 | + | |
| 65 | +export function listPermissionCategories(): Promise<PermissionCategoryVO[]> { | |
| 66 | + return request<PermissionCategoryVO[]>({ | |
| 67 | + url: "/usr/permission-categories", | |
| 68 | + method: "GET", | |
| 69 | + }); | |
| 70 | +} | |
| 71 | + | |
| 50 | 72 | export function createUser(dto: UserDTO): Promise<{ iIncrement: number; sUserNo: string }> { |
| 51 | 73 | return request({ url: "/usr/users", method: "POST", data: dto }); |
| 52 | 74 | } | ... | ... |
frontend/src/pages/usr/UserDetail.tsx
| ... | ... | @@ -20,11 +20,18 @@ import { |
| 20 | 20 | ToolbarBtnDark, |
| 21 | 21 | } from "@/components/Primitives"; |
| 22 | 22 | import StaffPicker from "@/components/StaffPicker"; |
| 23 | -import { createUser, updateUser, type UserDTO } from "@/api/user"; | |
| 23 | +import { | |
| 24 | + createUser, | |
| 25 | + getUser, | |
| 26 | + listPermissionCategories, | |
| 27 | + updateUser, | |
| 28 | + type PermissionCategoryVO, | |
| 29 | + type UserDTO, | |
| 30 | + type UserListVO, | |
| 31 | +} from "@/api/user"; | |
| 24 | 32 | import { |
| 25 | 33 | USER_TYPES, |
| 26 | 34 | LANGUAGE_OPTIONS, |
| 27 | - PERMISSION_GROUPS, | |
| 28 | 35 | SCOPE_ITEMS, |
| 29 | 36 | } from "@/utils/data"; |
| 30 | 37 | import { useAppDispatch, useAppSelector } from "@/store"; |
| ... | ... | @@ -65,22 +72,14 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 65 | 72 | const tabs = useAppSelector((s) => s.tabs.tabs); |
| 66 | 73 | const activeTabId = useAppSelector((s) => s.tabs.activeTabId); |
| 67 | 74 | const tab = tabs.find((t) => t.id === activeTabId); |
| 68 | - const snapshot = tab?.meta?.snapshot as | |
| 69 | - | { | |
| 70 | - sUserNo: string; | |
| 71 | - sUserName: string; | |
| 72 | - iStaffId: number | null; | |
| 73 | - staffName: string | null; | |
| 74 | - sUserType: string; | |
| 75 | - sLanguage: string; | |
| 76 | - bCanModifyDocs?: boolean; | |
| 77 | - tCreateDate?: string; | |
| 78 | - sCreatedBy?: string; | |
| 79 | - } | |
| 80 | - | undefined; | |
| 75 | + const snapshot = tab?.meta?.snapshot as UserListVO | undefined; | |
| 76 | + const [detail, setDetail] = useState<UserListVO | undefined>(snapshot); | |
| 77 | + const currentSnapshot = detail ?? snapshot; | |
| 78 | + const [permissionCategories, setPermissionCategories] = useState<PermissionCategoryVO[]>([]); | |
| 79 | + const [permissionCategoryIds, setPermissionCategoryIds] = useState<number[]>([]); | |
| 81 | 80 | |
| 82 | - const initialForm: FormState = | |
| 83 | - mode === "new" || !snapshot | |
| 81 | + const buildForm = (snap?: UserListVO): FormState => | |
| 82 | + mode === "new" || !snap | |
| 84 | 83 | ? { |
| 85 | 84 | sUserNo: "", |
| 86 | 85 | sUserName: "", |
| ... | ... | @@ -91,33 +90,54 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 91 | 90 | bCanModifyDocs: false, |
| 92 | 91 | } |
| 93 | 92 | : { |
| 94 | - sUserNo: snapshot.sUserNo, | |
| 95 | - sUserName: snapshot.sUserName, | |
| 96 | - iStaffId: snapshot.iStaffId, | |
| 97 | - staffName: snapshot.staffName ?? "", | |
| 98 | - sUserType: snapshot.sUserType, | |
| 99 | - sLanguage: snapshot.sLanguage, | |
| 100 | - bCanModifyDocs: !!snapshot.bCanModifyDocs, | |
| 93 | + sUserNo: snap.sUserNo, | |
| 94 | + sUserName: snap.sUserName, | |
| 95 | + iStaffId: snap.iStaffId, | |
| 96 | + staffName: snap.staffName ?? "", | |
| 97 | + sUserType: snap.sUserType, | |
| 98 | + sLanguage: snap.sLanguage, | |
| 99 | + bCanModifyDocs: !!snap.bCanModifyDocs, | |
| 101 | 100 | }; |
| 102 | 101 | |
| 103 | - const [form, setForm] = useState<FormState>(initialForm); | |
| 102 | + const [form, setForm] = useState<FormState>(() => buildForm(currentSnapshot)); | |
| 104 | 103 | const [permTab, setPermTab] = useState("groups"); |
| 105 | - // Visual-only — not persisted to backend. | |
| 106 | - const [permissions, setPermissions] = useState<Record<string, boolean>>(() => | |
| 107 | - Object.fromEntries(PERMISSION_GROUPS.map((g) => [g, false])) | |
| 108 | - ); | |
| 109 | 104 | const [tabPerms, setTabPerms] = useState<Record<string, boolean>>({}); |
| 110 | 105 | |
| 111 | 106 | useEffect(() => { |
| 112 | - setForm(initialForm); | |
| 107 | + setDetail(snapshot); | |
| 108 | + }, [snapshot]); | |
| 109 | + | |
| 110 | + useEffect(() => { | |
| 111 | + void listPermissionCategories() | |
| 112 | + .then(setPermissionCategories) | |
| 113 | + .catch(() => { | |
| 114 | + // interceptor already showed message | |
| 115 | + }); | |
| 116 | + }, []); | |
| 117 | + | |
| 118 | + useEffect(() => { | |
| 119 | + if (mode === "new" || !userId) return; | |
| 120 | + void getUser(userId) | |
| 121 | + .then((u) => { | |
| 122 | + setDetail(u); | |
| 123 | + setPermissionCategoryIds(u.permissionCategoryIds ?? []); | |
| 124 | + }) | |
| 125 | + .catch(() => { | |
| 126 | + // interceptor already showed message | |
| 127 | + }); | |
| 128 | + }, [mode, userId]); | |
| 129 | + | |
| 130 | + useEffect(() => { | |
| 131 | + setForm(buildForm(currentSnapshot)); | |
| 132 | + setPermissionCategoryIds(mode === "new" ? [] : currentSnapshot?.permissionCategoryIds ?? []); | |
| 113 | 133 | // eslint-disable-next-line react-hooks/exhaustive-deps |
| 114 | - }, [snapshot, mode]); | |
| 134 | + }, [currentSnapshot, mode]); | |
| 115 | 135 | |
| 116 | 136 | const set = <K extends keyof FormState>(k: K, v: FormState[K]) => |
| 117 | 137 | setForm((s) => ({ ...s, [k]: v })); |
| 118 | 138 | |
| 119 | 139 | const disabled = mode === "view"; |
| 120 | - const checkedCount = Object.values(permissions).filter(Boolean).length; | |
| 140 | + const checkedCount = permissionCategoryIds.length; | |
| 121 | 141 | |
| 122 | 142 | const startEdit = () => setMode("edit"); |
| 123 | 143 | const startNew = () => setMode("new"); |
| ... | ... | @@ -126,7 +146,8 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 126 | 146 | dispatch(closeTab(activeTabId)); |
| 127 | 147 | dispatch(setActiveTab("userlist")); |
| 128 | 148 | } else { |
| 129 | - setForm(initialForm); | |
| 149 | + setForm(buildForm(currentSnapshot)); | |
| 150 | + setPermissionCategoryIds(currentSnapshot?.permissionCategoryIds ?? []); | |
| 130 | 151 | setMode("view"); |
| 131 | 152 | } |
| 132 | 153 | }; |
| ... | ... | @@ -143,7 +164,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 143 | 164 | sUserType: form.sUserType, |
| 144 | 165 | sLanguage: form.sLanguage, |
| 145 | 166 | bCanModifyDocs: form.bCanModifyDocs, |
| 146 | - // permissionCategoryIds omitted: prototype permissions are by name; backend wants IDs. | |
| 167 | + permissionCategoryIds, | |
| 147 | 168 | }; |
| 148 | 169 | setSubmitting(true); |
| 149 | 170 | try { |
| ... | ... | @@ -154,6 +175,9 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 154 | 175 | dispatch(setActiveTab("userlist")); |
| 155 | 176 | } else if (userId) { |
| 156 | 177 | await updateUser(userId, dto); |
| 178 | + const refreshed = await getUser(userId); | |
| 179 | + setDetail(refreshed); | |
| 180 | + setPermissionCategoryIds(refreshed.permissionCategoryIds ?? []); | |
| 157 | 181 | message.success("保存成功"); |
| 158 | 182 | setMode("view"); |
| 159 | 183 | } |
| ... | ... | @@ -222,7 +246,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 222 | 246 | <span style={{ fontSize: 11, color: "var(--text-on-dark-muted)", marginRight: 8 }}> |
| 223 | 247 | 已选权限: |
| 224 | 248 | <span style={{ color: "#fff", fontWeight: 500 }}>{checkedCount}</span> /{" "} |
| 225 | - {PERMISSION_GROUPS.length - 1} | |
| 249 | + {permissionCategories.length} | |
| 226 | 250 | </span> |
| 227 | 251 | <span |
| 228 | 252 | style={{ |
| ... | ... | @@ -283,7 +307,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 283 | 307 | value={ |
| 284 | 308 | mode === "new" |
| 285 | 309 | ? "保存后自动生成" |
| 286 | - : fmtDateTime(snapshot?.tCreateDate) | |
| 310 | + : fmtDateTime(currentSnapshot?.tCreateDate) | |
| 287 | 311 | } |
| 288 | 312 | disabled |
| 289 | 313 | mono |
| ... | ... | @@ -291,7 +315,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 291 | 315 | </Field> |
| 292 | 316 | <Field label="制单人" required> |
| 293 | 317 | <PrimInput |
| 294 | - value={mode === "new" ? "保存后自动生成" : snapshot?.sCreatedBy ?? ""} | |
| 318 | + value={mode === "new" ? "保存后自动生成" : currentSnapshot?.sCreatedBy ?? ""} | |
| 295 | 319 | disabled |
| 296 | 320 | required |
| 297 | 321 | /> |
| ... | ... | @@ -415,9 +439,12 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 415 | 439 | <div style={{ flex: 1, overflow: "auto", background: "#fff" }}> |
| 416 | 440 | {permTab === "groups" ? ( |
| 417 | 441 | <PermissionGrid |
| 418 | - permissions={permissions} | |
| 419 | - setPermission={(g, v) => | |
| 420 | - setPermissions((s) => ({ ...s, [g]: v })) | |
| 442 | + categories={permissionCategories} | |
| 443 | + selectedIds={permissionCategoryIds} | |
| 444 | + setPermission={(id, v) => | |
| 445 | + setPermissionCategoryIds((s) => | |
| 446 | + v ? Array.from(new Set([...s, id])) : s.filter((x) => x !== id) | |
| 447 | + ) | |
| 421 | 448 | } |
| 422 | 449 | disabled={disabled} |
| 423 | 450 | /> |
| ... | ... | @@ -454,15 +481,20 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { |
| 454 | 481 | } |
| 455 | 482 | |
| 456 | 483 | interface PermissionGridProps { |
| 457 | - permissions: Record<string, boolean>; | |
| 458 | - setPermission: (g: string, v: boolean) => void; | |
| 484 | + categories: PermissionCategoryVO[]; | |
| 485 | + selectedIds: number[]; | |
| 486 | + setPermission: (id: number, v: boolean) => void; | |
| 459 | 487 | disabled?: boolean; |
| 460 | 488 | } |
| 461 | 489 | |
| 462 | -function PermissionGrid({ permissions, setPermission, disabled }: PermissionGridProps) { | |
| 490 | +function PermissionGrid({ categories, selectedIds, setPermission, disabled }: PermissionGridProps) { | |
| 463 | 491 | const [filter, setFilter] = useState(""); |
| 464 | 492 | const [hovered, setHovered] = useState<string | null>(null); |
| 465 | - const allChecked = PERMISSION_GROUPS.every((g) => permissions[g]); | |
| 493 | + const selected = new Set(selectedIds); | |
| 494 | + const visibleCategories = categories.filter( | |
| 495 | + (c) => !filter || c.sCategoryName.includes(filter) || c.sCategoryCode.includes(filter) | |
| 496 | + ); | |
| 497 | + const allChecked = categories.length > 0 && categories.every((c) => selected.has(c.iIncrement)); | |
| 466 | 498 | |
| 467 | 499 | return ( |
| 468 | 500 | <table |
| ... | ... | @@ -489,7 +521,7 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid |
| 489 | 521 | <PrimCheckbox |
| 490 | 522 | checked={allChecked} |
| 491 | 523 | onChange={(v) => { |
| 492 | - PERMISSION_GROUPS.forEach((g) => setPermission(g, v)); | |
| 524 | + categories.forEach((c) => setPermission(c.iIncrement, v)); | |
| 493 | 525 | }} |
| 494 | 526 | disabled={disabled} |
| 495 | 527 | /> |
| ... | ... | @@ -517,15 +549,15 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid |
| 517 | 549 | </tr> |
| 518 | 550 | </thead> |
| 519 | 551 | <tbody> |
| 520 | - {PERMISSION_GROUPS.filter((g) => !filter || g.includes(filter)).map((g, i) => { | |
| 521 | - const isHover = hovered === g; | |
| 522 | - const checked = !!permissions[g]; | |
| 552 | + {visibleCategories.map((c, i) => { | |
| 553 | + const isHover = hovered === c.sCategoryCode; | |
| 554 | + const checked = selected.has(c.iIncrement); | |
| 523 | 555 | return ( |
| 524 | 556 | <tr |
| 525 | - key={g} | |
| 526 | - onMouseEnter={() => setHovered(g)} | |
| 557 | + key={c.iIncrement} | |
| 558 | + onMouseEnter={() => setHovered(c.sCategoryCode)} | |
| 527 | 559 | onMouseLeave={() => setHovered(null)} |
| 528 | - onClick={disabled ? undefined : () => setPermission(g, !checked)} | |
| 560 | + onClick={disabled ? undefined : () => setPermission(c.iIncrement, !checked)} | |
| 529 | 561 | style={{ |
| 530 | 562 | background: |
| 531 | 563 | isHover && !disabled |
| ... | ... | @@ -549,7 +581,7 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid |
| 549 | 581 | > |
| 550 | 582 | <PrimCheckbox |
| 551 | 583 | checked={checked} |
| 552 | - onChange={(v) => setPermission(g, v)} | |
| 584 | + onChange={(v) => setPermission(c.iIncrement, v)} | |
| 553 | 585 | disabled={disabled} |
| 554 | 586 | /> |
| 555 | 587 | </td> |
| ... | ... | @@ -561,7 +593,7 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid |
| 561 | 593 | fontWeight: checked ? 500 : 400, |
| 562 | 594 | }} |
| 563 | 595 | > |
| 564 | - {g} | |
| 596 | + {c.sCategoryName} | |
| 565 | 597 | </td> |
| 566 | 598 | </tr> |
| 567 | 599 | ); | ... | ... |