From fe5a6531cf4e2680051a1301a729a226a9436d0a Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 6 May 2026 13:45:13 +0800 Subject: [PATCH] feat(user): wire user detail + permission categories endpoints to UserDetail page --- backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java | 13 +++++++++++++ backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java | 7 +++++++ backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java | 2 ++ backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java | 6 ++++++ backend/src/main/java/com/xly/erp/module/usr/service/UserService.java | 7 +++++++ backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java | 18 ++++++++++++++++++ backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java | 32 ++++++++++++++++++++++++++++++++ backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java | 11 +++++++++++ backend/src/main/resources/mapper/usr/UserMapper.xml | 9 +++++++++ frontend/src/api/user.ts | 22 ++++++++++++++++++++++ frontend/src/pages/usr/UserDetail.tsx | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------- 11 files changed, 212 insertions(+), 53 deletions(-) create mode 100644 backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java diff --git a/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java b/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java index 3d59c63..fa8b808 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java +++ b/backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java @@ -4,6 +4,8 @@ import com.xly.erp.common.response.Result; import com.xly.erp.module.usr.dto.CreateUserDTO; import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.service.UserService; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; +import com.xly.erp.module.usr.vo.UserListVO; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; import java.util.Map; @RestController @@ -46,4 +49,14 @@ public class UserController { @RequestParam(required = false) Integer pageSize) { return Result.ok(userService.list(field, match, value, pageNum, pageSize)); } + + @GetMapping("/users/{id}") + public Result detail(@PathVariable Integer id) { + return Result.ok(userService.detail(id)); + } + + @GetMapping("/permission-categories") + public Result> permissionCategories() { + return Result.ok(userService.listPermissionCategories()); + } } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java index 171b13c..cde6145 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/PermissionCategoryMapper.java @@ -1,5 +1,6 @@ package com.xly.erp.module.usr.mapper; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @@ -15,4 +16,10 @@ public interface PermissionCategoryMapper { + "#{id}" + "") int countActiveByIds(@Param("ids") List ids); + + @Select("SELECT iIncrement, sCategoryCode, sCategoryName, iParentId, iSortOrder " + + "FROM tPermissionCategory " + + "WHERE bDeleted = 0 " + + "ORDER BY iSortOrder ASC, iIncrement ASC") + List listActive(); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java index f65f759..3372ad7 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserMapper.java @@ -31,4 +31,6 @@ public interface UserMapper extends BaseMapper { long countWithFilter(@Param("field") String field, @Param("matchOp") String matchOp, @Param("value") Object value); + + UserListVO selectDetailById(@Param("id") Integer id); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java index 81f765e..cdcd3b6 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java +++ b/backend/src/main/java/com/xly/erp/module/usr/mapper/UserPermissionMapper.java @@ -4,9 +4,15 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xly.erp.module.usr.entity.UserPermission; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; public interface UserPermissionMapper extends BaseMapper { @Delete("DELETE FROM tUserPermission WHERE iUserId = #{userId}") int deleteByUserId(@Param("userId") Integer userId); + + @Select("SELECT iCategoryId FROM tUserPermission WHERE iUserId = #{userId} ORDER BY iIncrement ASC") + List selectCategoryIdsByUserId(@Param("userId") Integer userId); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java index a7d0ec1..b1ac9e7 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java +++ b/backend/src/main/java/com/xly/erp/module/usr/service/UserService.java @@ -4,7 +4,10 @@ import com.xly.erp.module.usr.dto.CreateUserDTO; import com.xly.erp.module.usr.dto.LoginDTO; import com.xly.erp.module.usr.dto.UpdateUserDTO; import com.xly.erp.module.usr.vo.LoginVO; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; +import com.xly.erp.module.usr.vo.UserListVO; +import java.util.List; import java.util.Map; public interface UserService { @@ -14,5 +17,9 @@ public interface UserService { Map list(String field, String match, String value, Integer pageNum, Integer pageSize); + UserListVO detail(Integer id); + + List listPermissionCategories(); + LoginVO login(LoginDTO dto); } diff --git a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java b/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java index 0c7df8a..48d4e1d 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java +++ b/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; import com.xly.erp.module.usr.entity.User; import com.xly.erp.module.usr.security.LoginAttemptStore; import com.xly.erp.module.usr.vo.LoginVO; +import com.xly.erp.module.usr.vo.PermissionCategoryVO; import com.xly.erp.module.usr.vo.UserBriefVO; import com.xly.erp.module.usr.vo.UserListVO; import com.xly.erp.module.usr.entity.UserPermission; @@ -259,6 +260,23 @@ public class UserServiceImpl implements UserService { return result; } + @Override + @Transactional(readOnly = true) + public UserListVO detail(Integer id) { + UserListVO vo = userMapper.selectDetailById(id); + if (vo == null || Boolean.TRUE.equals(vo.getBDeleted())) { + throw new BizException(40400, "用户不存在或已删除"); + } + vo.setPermissionCategoryIds(userPermissionMapper.selectCategoryIdsByUserId(id)); + return vo; + } + + @Override + @Transactional(readOnly = true) + public List listPermissionCategories() { + return permissionCategoryMapper.listActive(); + } + private Integer parseBoolean(String v) { return switch (v.toLowerCase()) { case "true", "1" -> 1; diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java new file mode 100644 index 0000000..5b5fd75 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/PermissionCategoryVO.java @@ -0,0 +1,32 @@ +package com.xly.erp.module.usr.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PermissionCategoryVO { + + @JsonProperty("iIncrement") + private Integer iIncrement; + + @JsonProperty("sCategoryCode") + private String sCategoryCode; + + @JsonProperty("sCategoryName") + private String sCategoryName; + + @JsonProperty("iParentId") + private Integer iParentId; + + @JsonProperty("iSortOrder") + private Integer iSortOrder; + + public Integer getIIncrement() { return iIncrement; } + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; } + public String getSCategoryCode() { return sCategoryCode; } + public void setSCategoryCode(String sCategoryCode) { this.sCategoryCode = sCategoryCode; } + public String getSCategoryName() { return sCategoryName; } + public void setSCategoryName(String sCategoryName) { this.sCategoryName = sCategoryName; } + public Integer getIParentId() { return iParentId; } + public void setIParentId(Integer iParentId) { this.iParentId = iParentId; } + public Integer getISortOrder() { return iSortOrder; } + public void setISortOrder(Integer iSortOrder) { this.iSortOrder = iSortOrder; } +} diff --git a/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java b/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java index c2234b9..ddf6a63 100644 --- a/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java +++ b/backend/src/main/java/com/xly/erp/module/usr/vo/UserListVO.java @@ -3,6 +3,7 @@ package com.xly.erp.module.usr.vo; import com.fasterxml.jackson.annotation.JsonProperty; import java.time.LocalDateTime; +import java.util.List; public class UserListVO { @@ -30,6 +31,12 @@ public class UserListVO { @JsonProperty("sLanguage") private String sLanguage; + @JsonProperty("bCanModifyDocs") + private Boolean bCanModifyDocs; + + @JsonProperty("permissionCategoryIds") + private List permissionCategoryIds; + @JsonProperty("bDeleted") private Boolean bDeleted; @@ -58,6 +65,10 @@ public class UserListVO { public void setSUserType(String sUserType) { this.sUserType = sUserType; } public String getSLanguage() { return sLanguage; } public void setSLanguage(String sLanguage) { this.sLanguage = sLanguage; } + public Boolean getBCanModifyDocs() { return bCanModifyDocs; } + public void setBCanModifyDocs(Boolean bCanModifyDocs) { this.bCanModifyDocs = bCanModifyDocs; } + public List getPermissionCategoryIds() { return permissionCategoryIds; } + public void setPermissionCategoryIds(List permissionCategoryIds) { this.permissionCategoryIds = permissionCategoryIds; } public Boolean getBDeleted() { return bDeleted; } public void setBDeleted(Boolean bDeleted) { this.bDeleted = bDeleted; } public LocalDateTime getTLastLoginDate() { return tLastLoginDate; } diff --git a/backend/src/main/resources/mapper/usr/UserMapper.xml b/backend/src/main/resources/mapper/usr/UserMapper.xml index af78c0c..74d2c10 100644 --- a/backend/src/main/resources/mapper/usr/UserMapper.xml +++ b/backend/src/main/resources/mapper/usr/UserMapper.xml @@ -12,6 +12,7 @@ s.sDepartment AS department, u.sUserType AS sUserType, u.sLanguage AS sLanguage, + u.bCanModifyDocs AS bCanModifyDocs, u.bDeleted AS bDeleted, u.tLastLoginDate AS tLastLoginDate, u.sCreatedBy AS sCreatedBy, @@ -46,4 +47,12 @@ + + diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts index 3f5c777..47a9a88 100644 --- a/frontend/src/api/user.ts +++ b/frontend/src/api/user.ts @@ -9,12 +9,22 @@ export interface UserListVO { department: string | null; sUserType: string; sLanguage: string; + bCanModifyDocs?: boolean; + permissionCategoryIds?: number[]; bDeleted: boolean; tLastLoginDate: string | null; sCreatedBy: string | null; tCreateDate: string; } +export interface PermissionCategoryVO { + iIncrement: number; + sCategoryCode: string; + sCategoryName: string; + iParentId: number | null; + iSortOrder: number; +} + export interface UserListPage { records: UserListVO[]; total: number; @@ -37,6 +47,7 @@ export interface UserDTO { sUserType: string; sLanguage: string; bCanModifyDocs?: boolean; + permissionCategoryIds?: number[]; } export function listUsers(params: UserListParams = {}): Promise { @@ -47,6 +58,17 @@ export function listUsers(params: UserListParams = {}): Promise { }); } +export function getUser(id: number): Promise { + return request({ url: `/usr/users/${id}`, method: "GET" }); +} + +export function listPermissionCategories(): Promise { + return request({ + url: "/usr/permission-categories", + method: "GET", + }); +} + export function createUser(dto: UserDTO): Promise<{ iIncrement: number; sUserNo: string }> { return request({ url: "/usr/users", method: "POST", data: dto }); } diff --git a/frontend/src/pages/usr/UserDetail.tsx b/frontend/src/pages/usr/UserDetail.tsx index a4db38b..8244076 100644 --- a/frontend/src/pages/usr/UserDetail.tsx +++ b/frontend/src/pages/usr/UserDetail.tsx @@ -20,11 +20,18 @@ import { ToolbarBtnDark, } from "@/components/Primitives"; import StaffPicker from "@/components/StaffPicker"; -import { createUser, updateUser, type UserDTO } from "@/api/user"; +import { + createUser, + getUser, + listPermissionCategories, + updateUser, + type PermissionCategoryVO, + type UserDTO, + type UserListVO, +} from "@/api/user"; import { USER_TYPES, LANGUAGE_OPTIONS, - PERMISSION_GROUPS, SCOPE_ITEMS, } from "@/utils/data"; import { useAppDispatch, useAppSelector } from "@/store"; @@ -65,22 +72,14 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { const tabs = useAppSelector((s) => s.tabs.tabs); const activeTabId = useAppSelector((s) => s.tabs.activeTabId); const tab = tabs.find((t) => t.id === activeTabId); - const snapshot = tab?.meta?.snapshot as - | { - sUserNo: string; - sUserName: string; - iStaffId: number | null; - staffName: string | null; - sUserType: string; - sLanguage: string; - bCanModifyDocs?: boolean; - tCreateDate?: string; - sCreatedBy?: string; - } - | undefined; + const snapshot = tab?.meta?.snapshot as UserListVO | undefined; + const [detail, setDetail] = useState(snapshot); + const currentSnapshot = detail ?? snapshot; + const [permissionCategories, setPermissionCategories] = useState([]); + const [permissionCategoryIds, setPermissionCategoryIds] = useState([]); - const initialForm: FormState = - mode === "new" || !snapshot + const buildForm = (snap?: UserListVO): FormState => + mode === "new" || !snap ? { sUserNo: "", sUserName: "", @@ -91,33 +90,54 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { bCanModifyDocs: false, } : { - sUserNo: snapshot.sUserNo, - sUserName: snapshot.sUserName, - iStaffId: snapshot.iStaffId, - staffName: snapshot.staffName ?? "", - sUserType: snapshot.sUserType, - sLanguage: snapshot.sLanguage, - bCanModifyDocs: !!snapshot.bCanModifyDocs, + sUserNo: snap.sUserNo, + sUserName: snap.sUserName, + iStaffId: snap.iStaffId, + staffName: snap.staffName ?? "", + sUserType: snap.sUserType, + sLanguage: snap.sLanguage, + bCanModifyDocs: !!snap.bCanModifyDocs, }; - const [form, setForm] = useState(initialForm); + const [form, setForm] = useState(() => buildForm(currentSnapshot)); const [permTab, setPermTab] = useState("groups"); - // Visual-only — not persisted to backend. - const [permissions, setPermissions] = useState>(() => - Object.fromEntries(PERMISSION_GROUPS.map((g) => [g, false])) - ); const [tabPerms, setTabPerms] = useState>({}); useEffect(() => { - setForm(initialForm); + setDetail(snapshot); + }, [snapshot]); + + useEffect(() => { + void listPermissionCategories() + .then(setPermissionCategories) + .catch(() => { + // interceptor already showed message + }); + }, []); + + useEffect(() => { + if (mode === "new" || !userId) return; + void getUser(userId) + .then((u) => { + setDetail(u); + setPermissionCategoryIds(u.permissionCategoryIds ?? []); + }) + .catch(() => { + // interceptor already showed message + }); + }, [mode, userId]); + + useEffect(() => { + setForm(buildForm(currentSnapshot)); + setPermissionCategoryIds(mode === "new" ? [] : currentSnapshot?.permissionCategoryIds ?? []); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [snapshot, mode]); + }, [currentSnapshot, mode]); const set = (k: K, v: FormState[K]) => setForm((s) => ({ ...s, [k]: v })); const disabled = mode === "view"; - const checkedCount = Object.values(permissions).filter(Boolean).length; + const checkedCount = permissionCategoryIds.length; const startEdit = () => setMode("edit"); const startNew = () => setMode("new"); @@ -126,7 +146,8 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { dispatch(closeTab(activeTabId)); dispatch(setActiveTab("userlist")); } else { - setForm(initialForm); + setForm(buildForm(currentSnapshot)); + setPermissionCategoryIds(currentSnapshot?.permissionCategoryIds ?? []); setMode("view"); } }; @@ -143,7 +164,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { sUserType: form.sUserType, sLanguage: form.sLanguage, bCanModifyDocs: form.bCanModifyDocs, - // permissionCategoryIds omitted: prototype permissions are by name; backend wants IDs. + permissionCategoryIds, }; setSubmitting(true); try { @@ -154,6 +175,9 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { dispatch(setActiveTab("userlist")); } else if (userId) { await updateUser(userId, dto); + const refreshed = await getUser(userId); + setDetail(refreshed); + setPermissionCategoryIds(refreshed.permissionCategoryIds ?? []); message.success("保存成功"); setMode("view"); } @@ -222,7 +246,7 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { 已选权限: {checkedCount} /{" "} - {PERMISSION_GROUPS.length - 1} + {permissionCategories.length} @@ -415,9 +439,12 @@ export default function UserDetail({ userId, mode: initialMode }: Props) {
{permTab === "groups" ? ( - setPermissions((s) => ({ ...s, [g]: v })) + categories={permissionCategories} + selectedIds={permissionCategoryIds} + setPermission={(id, v) => + setPermissionCategoryIds((s) => + v ? Array.from(new Set([...s, id])) : s.filter((x) => x !== id) + ) } disabled={disabled} /> @@ -454,15 +481,20 @@ export default function UserDetail({ userId, mode: initialMode }: Props) { } interface PermissionGridProps { - permissions: Record; - setPermission: (g: string, v: boolean) => void; + categories: PermissionCategoryVO[]; + selectedIds: number[]; + setPermission: (id: number, v: boolean) => void; disabled?: boolean; } -function PermissionGrid({ permissions, setPermission, disabled }: PermissionGridProps) { +function PermissionGrid({ categories, selectedIds, setPermission, disabled }: PermissionGridProps) { const [filter, setFilter] = useState(""); const [hovered, setHovered] = useState(null); - const allChecked = PERMISSION_GROUPS.every((g) => permissions[g]); + const selected = new Set(selectedIds); + const visibleCategories = categories.filter( + (c) => !filter || c.sCategoryName.includes(filter) || c.sCategoryCode.includes(filter) + ); + const allChecked = categories.length > 0 && categories.every((c) => selected.has(c.iIncrement)); return ( { - PERMISSION_GROUPS.forEach((g) => setPermission(g, v)); + categories.forEach((c) => setPermission(c.iIncrement, v)); }} disabled={disabled} /> @@ -517,15 +549,15 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid - {PERMISSION_GROUPS.filter((g) => !filter || g.includes(filter)).map((g, i) => { - const isHover = hovered === g; - const checked = !!permissions[g]; + {visibleCategories.map((c, i) => { + const isHover = hovered === c.sCategoryCode; + const checked = selected.has(c.iIncrement); return ( setHovered(g)} + key={c.iIncrement} + onMouseEnter={() => setHovered(c.sCategoryCode)} onMouseLeave={() => setHovered(null)} - onClick={disabled ? undefined : () => setPermission(g, !checked)} + onClick={disabled ? undefined : () => setPermission(c.iIncrement, !checked)} style={{ background: isHover && !disabled @@ -549,7 +581,7 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid > setPermission(g, v)} + onChange={(v) => setPermission(c.iIncrement, v)} disabled={disabled} /> @@ -561,7 +593,7 @@ function PermissionGrid({ permissions, setPermission, disabled }: PermissionGrid fontWeight: checked ? 500 : 400, }} > - {g} + {c.sCategoryName} ); -- libgit2 0.22.2