diff --git a/.gitignore b/.gitignore index 4e4ad81..e7f8960 100644 --- a/.gitignore +++ b/.gitignore @@ -168,5 +168,8 @@ fabric.properties hs_err_pid* replay_pid* - -# 如何在这个地方引用 backend 和 frontend 下级目录中的 .gitignore 中的规则?我需要全部给他们前面加上 backend/ 和 frontend/ 前缀吗? +# docker +docker-compose.yaml +Dockerfile +Caddyfile +start.sh diff --git a/backend/.gitignore b/backend/.gitignore index 7ff7906..3c3cce3 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -221,9 +221,11 @@ Temporary Items .apdisk - logs db.d/store/* .git - +# docker +docker-compose.yaml +Dockerfile +Caddyfile diff --git a/backend/settings.gradle.kts b/backend/settings.gradle.kts index 13e2abe..859e834 100644 --- a/backend/settings.gradle.kts +++ b/backend/settings.gradle.kts @@ -4,8 +4,8 @@ pluginManagement { repositories { maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") } maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/gradle-plugin/") } - mavenCentral() gradlePluginPortal() + mavenCentral() } } @@ -15,4 +15,4 @@ dependencyResolutionManagement { maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/apache-snapshots/") } mavenCentral() } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/zl/mjga/controller/DepartmentController.java b/backend/src/main/java/com/zl/mjga/controller/DepartmentController.java index 3240305..509a154 100644 --- a/backend/src/main/java/com/zl/mjga/controller/DepartmentController.java +++ b/backend/src/main/java/com/zl/mjga/controller/DepartmentController.java @@ -32,9 +32,9 @@ public class DepartmentController { } @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_DEPARTMENT_PERMISSION)") - @GetMapping("/query") - List queryDepartments() { - return departmentRepository.findAll(); + @GetMapping("/query-available") + List queryAvailableParentDepartmentsBy(@RequestParam(required = false) Long id) { + return departmentService.queryAvailableParentDepartmentsBy(id); } @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_DEPARTMENT_PERMISSION)") diff --git a/backend/src/main/java/com/zl/mjga/controller/IdentityAccessController.java b/backend/src/main/java/com/zl/mjga/controller/IdentityAccessController.java index 1df2d50..a0a7170 100644 --- a/backend/src/main/java/com/zl/mjga/controller/IdentityAccessController.java +++ b/backend/src/main/java/com/zl/mjga/controller/IdentityAccessController.java @@ -7,6 +7,7 @@ import com.zl.mjga.dto.permission.PermissionBindDto; import com.zl.mjga.dto.position.PositionBindDto; import com.zl.mjga.dto.role.RoleBindDto; import com.zl.mjga.dto.urp.*; +import com.zl.mjga.repository.PermissionRepository; import com.zl.mjga.repository.RoleRepository; import com.zl.mjga.repository.UserRepository; import com.zl.mjga.service.IdentityAccessService; @@ -17,6 +18,7 @@ import lombok.RequiredArgsConstructor; import org.jooq.generated.mjga.tables.pojos.User; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.DisabledException; import org.springframework.web.bind.annotation.*; @SuppressWarnings("PMD.AvoidDuplicateLiterals") @@ -28,11 +30,15 @@ public class IdentityAccessController { private final IdentityAccessService identityAccessService; private final UserRepository userRepository; private final RoleRepository roleRepository; + private final PermissionRepository permissionRepository; @GetMapping("/me") UserRolePermissionDto currentUser(Principal principal) { String name = principal.getName(); User user = userRepository.fetchOneByUsername(name); + if (!user.getEnable()) { + throw new DisabledException(String.format("用户 %s 被禁用", name)); + } return identityAccessService.queryUniqueUserWithRolePermission(user.getId()); } @@ -46,7 +52,7 @@ public class IdentityAccessController { @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @PostMapping("/user") - void upsertUser(@RequestBody UserUpsertDto userUpsertDto) { + void upsertUser(@RequestBody @Valid UserUpsertDto userUpsertDto) { identityAccessService.upsertUser(userUpsertDto); } @@ -56,7 +62,7 @@ public class IdentityAccessController { return identityAccessService.queryUniqueUserWithRolePermission(userId); } - @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") + @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).DELETE_USER_ROLE_PERMISSION)") @DeleteMapping("/user") void deleteUser(@RequestParam Long userId) { userRepository.deleteById(userId); @@ -89,7 +95,7 @@ public class IdentityAccessController { @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)") @DeleteMapping("/permission") void deletePermission(@RequestParam Long permissionId) { - roleRepository.deleteById(permissionId); + permissionRepository.deleteById(permissionId); } @PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_USER_ROLE_PERMISSION)") diff --git a/backend/src/main/java/com/zl/mjga/dto/department/DepartmentWithParentDto.java b/backend/src/main/java/com/zl/mjga/dto/department/DepartmentWithParentDto.java new file mode 100644 index 0000000..c6bf2a5 --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/dto/department/DepartmentWithParentDto.java @@ -0,0 +1,18 @@ +package com.zl.mjga.dto.department; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@EqualsAndHashCode +public class DepartmentWithParentDto { + @NotNull private Long id; + @NotEmpty private String name; + @NotEmpty Long parentId; + @NotEmpty String parentName; + @NotEmpty String path; +} diff --git a/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java b/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java index 51e6f0f..2a3dc46 100644 --- a/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java +++ b/backend/src/main/java/com/zl/mjga/dto/urp/UserUpsertDto.java @@ -11,7 +11,8 @@ import lombok.NoArgsConstructor; @Data public class UserUpsertDto { private Long id; - @NotEmpty private String username; + @NotEmpty + private String username; private String password; @NotNull private Boolean enable; } diff --git a/backend/src/main/java/com/zl/mjga/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/zl/mjga/exception/GlobalExceptionHandler.java index b3cd7f5..8e00649 100644 --- a/backend/src/main/java/com/zl/mjga/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/zl/mjga/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.zl.mjga.exception; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.http.*; import org.springframework.lang.Nullable; import org.springframework.security.access.AccessDeniedException; @@ -72,13 +73,31 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { throw ex; } + @ExceptionHandler(value = {DuplicateKeyException.class}) + public ResponseEntity handleDuplicateException( + DuplicateKeyException ex, WebRequest request) { + log.error("DuplicateKeyException Handled ===> ", ex); + ErrorResponseException errorResponseException = + new ErrorResponseException( + HttpStatus.INTERNAL_SERVER_ERROR, + ProblemDetail.forStatusAndDetail( + HttpStatus.INTERNAL_SERVER_ERROR, "您输入的内容已存在,请检查后重新提交"), + ex.getCause()); + return handleExceptionInternal( + errorResponseException, + errorResponseException.getBody(), + errorResponseException.getHeaders(), + errorResponseException.getStatusCode(), + request); + } + @ExceptionHandler(value = {Throwable.class}) public ResponseEntity handleException(Throwable ex, WebRequest request) { log.error("System Error Handled ===> ", ex); ErrorResponseException errorResponseException = new ErrorResponseException( HttpStatus.INTERNAL_SERVER_ERROR, - ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "System Error"), + ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "发生系统异常,请联系管理员"), ex.getCause()); return handleExceptionInternal( errorResponseException, diff --git a/backend/src/main/java/com/zl/mjga/model/urp/EPermission.java b/backend/src/main/java/com/zl/mjga/model/urp/EPermission.java index d6fb35f..ebf0fc9 100644 --- a/backend/src/main/java/com/zl/mjga/model/urp/EPermission.java +++ b/backend/src/main/java/com/zl/mjga/model/urp/EPermission.java @@ -8,5 +8,6 @@ public enum EPermission { READ_SCHEDULER_PERMISSION, WRITE_SCHEDULER_PERMISSION, WRITE_USER_ROLE_PERMISSION, + DELETE_USER_ROLE_PERMISSION, READ_USER_ROLE_PERMISSION } diff --git a/backend/src/main/java/com/zl/mjga/repository/DepartmentRepository.java b/backend/src/main/java/com/zl/mjga/repository/DepartmentRepository.java index 850ddad..341f9fa 100644 --- a/backend/src/main/java/com/zl/mjga/repository/DepartmentRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/DepartmentRepository.java @@ -1,15 +1,16 @@ package com.zl.mjga.repository; import static org.jooq.generated.mjga.Tables.*; -import static org.jooq.impl.DSL.noCondition; -import static org.jooq.impl.DSL.noField; +import static org.jooq.impl.DSL.*; +import static org.jooq.impl.SQLDataType.VARCHAR; import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.department.DepartmentQueryDto; +import com.zl.mjga.dto.department.DepartmentWithParentDto; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.jooq.*; import org.jooq.Record; -import org.jooq.generated.mjga.tables.Department; import org.jooq.generated.mjga.tables.daos.DepartmentDao; import org.jooq.impl.DSL; import org.springframework.beans.factory.annotation.Autowired; @@ -23,9 +24,39 @@ public class DepartmentRepository extends DepartmentDao { super(configuration); } + public List queryDepartmentAndSubsBy(Long id) { + CommonTableExpression cte = + name("parent_department") + .fields("id", "name", "parent_name", "parent_id", "path") + .as( + select( + DEPARTMENT.ID, + DEPARTMENT.NAME, + DEPARTMENT.NAME, + DEPARTMENT.PARENT_ID, + DEPARTMENT.NAME.cast(VARCHAR)) + .from(DEPARTMENT) + .where(DEPARTMENT.ID.eq(id)) + .unionAll( + select( + DEPARTMENT.ID, + DEPARTMENT.NAME, + field(name("parent_department", "name"), VARCHAR), + DEPARTMENT.PARENT_ID, + field(name("parent_department", "path"), VARCHAR) + .concat("->") + .concat(DEPARTMENT.NAME)) + .from(table(name("parent_department"))) + .join(DEPARTMENT) + .on( + field(name("parent_department", "id"), Long.class) + .eq(DEPARTMENT.PARENT_ID)))); + return ctx().withRecursive(cte).selectFrom(cte).fetch().into(DepartmentWithParentDto.class); + } + public Result pageFetchBy( PageRequestDto pageRequestDto, DepartmentQueryDto departmentQueryDto) { - Department parent = DEPARTMENT.as("parent"); + org.jooq.generated.mjga.tables.Department parent = DEPARTMENT.as("parent"); return ctx() .select( DEPARTMENT.asterisk(), @@ -36,7 +67,7 @@ public class DepartmentRepository extends DepartmentDao { true) .otherwise(false) .as("is_bound") - : noField(), + : noCondition(), DSL.count().over().as("total_department").convertFrom(Long::valueOf)) .from(DEPARTMENT) .leftJoin(parent) diff --git a/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java b/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java index c956e99..5984779 100644 --- a/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/PermissionRepository.java @@ -36,7 +36,7 @@ public class PermissionRepository extends PermissionDao { true) .otherwise(false) .as("is_bound") - : noField(), + : noCondition(), DSL.count().over().as("total_permission")) .from(PERMISSION) .where( diff --git a/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java b/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java index 0a951d8..3adeb39 100644 --- a/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/PositionRepository.java @@ -31,7 +31,7 @@ public class PositionRepository extends PositionDao { ? DSL.when(POSITION.ID.in(selectUsersPosition(positionQueryDto.getUserId())), true) .otherwise(false) .as("is_bound") - : noField(), + : noCondition(), DSL.count().over().as("total_position").convertFrom(Long::valueOf)) .from(POSITION) .where( diff --git a/backend/src/main/java/com/zl/mjga/repository/RoleRepository.java b/backend/src/main/java/com/zl/mjga/repository/RoleRepository.java index eb46a55..81f05be 100644 --- a/backend/src/main/java/com/zl/mjga/repository/RoleRepository.java +++ b/backend/src/main/java/com/zl/mjga/repository/RoleRepository.java @@ -41,7 +41,7 @@ public class RoleRepository extends RoleDao { ? when(ROLE.ID.in(selectUsersRoleIds(roleQueryDto.getUserId())), true) .otherwise(false) .as("is_bound") - : noField(), + : noCondition(), multiset(select(ROLE.permission().asterisk()).from(ROLE.permission())) .convertFrom(r -> r.into(Permission.class)) .as("permissions"), diff --git a/backend/src/main/java/com/zl/mjga/service/DepartmentService.java b/backend/src/main/java/com/zl/mjga/service/DepartmentService.java index 143ae87..ff38eba 100644 --- a/backend/src/main/java/com/zl/mjga/service/DepartmentService.java +++ b/backend/src/main/java/com/zl/mjga/service/DepartmentService.java @@ -6,12 +6,14 @@ import com.zl.mjga.dto.PageRequestDto; import com.zl.mjga.dto.PageResponseDto; import com.zl.mjga.dto.department.DepartmentQueryDto; import com.zl.mjga.dto.department.DepartmentRespDto; +import com.zl.mjga.dto.department.DepartmentWithParentDto; import com.zl.mjga.repository.DepartmentRepository; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jooq.Record; import org.jooq.Result; +import org.jooq.generated.mjga.tables.pojos.Department; import org.springframework.stereotype.Service; @Service @@ -21,6 +23,30 @@ public class DepartmentService { private final DepartmentRepository departmentRepository; + public List queryAvailableParentDepartmentsBy(Long id) { + List allDepartments = departmentRepository.findAll(); + if (id != null) { + List departmentWithParentList = queryDepartmentAndSubsBy(id); + allDepartments.removeIf( + department -> { + return departmentWithParentList.stream() + .anyMatch( + (departmentWithParentDto -> { + return departmentWithParentDto.getId().equals(department.getId()); + })); + }); + } + return allDepartments; + } + + public void upsertDepartment(Department department) { + departmentRepository.merge(department); + } + + public List queryDepartmentAndSubsBy(Long id) { + return departmentRepository.queryDepartmentAndSubsBy(id); + } + public PageResponseDto> pageQueryDepartment( PageRequestDto pageRequestDto, DepartmentQueryDto departmentQueryDto) { Result records = departmentRepository.pageFetchBy(pageRequestDto, departmentQueryDto); diff --git a/backend/src/main/java/com/zl/mjga/service/SignService.java b/backend/src/main/java/com/zl/mjga/service/SignService.java index 8233161..036dc24 100644 --- a/backend/src/main/java/com/zl/mjga/service/SignService.java +++ b/backend/src/main/java/com/zl/mjga/service/SignService.java @@ -27,10 +27,13 @@ public class SignService { public Long signIn(SignInDto signInDto) { User user = userRepository.fetchOneByUsername(signInDto.getUsername()); if (user == null) { - throw new BusinessException(String.format("%s user not found", signInDto.getUsername())); + throw new BusinessException("用户名不存在"); } if (!passwordEncoder.matches(signInDto.getPassword(), user.getPassword())) { - throw new BusinessException("password invalid"); + throw new BusinessException("密码错误"); + } + if (!user.getEnable()) { + throw new BusinessException("用户被禁用"); } return user.getId(); } @@ -38,8 +41,7 @@ public class SignService { @Transactional(rollbackFor = Throwable.class) public void signUp(SignUpDto signUpDto) { if (identityAccessService.isUsernameDuplicate(signUpDto.getUsername())) { - throw new BusinessException( - String.format("username %s already exist", signUpDto.getUsername())); + throw new BusinessException("用户名已存在"); } User user = new User(); user.setUsername(signUpDto.getUsername()); diff --git a/frontend/.env b/frontend/.env index 2014c6e..edcf512 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,9 +1,11 @@ -VITE_ENABLE_MOCK=true VITE_APP_PORT=5173 VITE_SOURCE_MAP=true # mock -VITE_BASE_URL=http://localhost:5173 +#ITE_ENABLE_MOCK=true +#VITE_BASE_URL=http://localhost:5173 # local -#VITE_BASE_URL=http://localhost:8080 +VITE_ENABLE_MOCK=false +VITE_BASE_URL=http://localhost:8080 # dev +#VITE_ENABLE_MOCK=false #VITE_BASE_URL=https://localhost/api diff --git a/frontend/index.html b/frontend/index.html index 9e5fc8f..b74a0a5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - Vite App + 知路后台管理
diff --git a/frontend/public/java.svg b/frontend/public/java.svg new file mode 100644 index 0000000..eba6653 --- /dev/null +++ b/frontend/public/java.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index b0e2a78..92ec80b 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -2,7 +2,7 @@ import createClient, { type Middleware } from "openapi-fetch"; import useAuthStore from "../composables/store/useAuthStore"; import { ForbiddenError, - SystemError, + RequestError, UnAuthError, InternalServerError, } from "../types/error"; @@ -22,10 +22,10 @@ const myMiddleware: Middleware = { } else if (response.status === 403) { handleForbiddenError(response); } else { - handleSystemError(response); + handleRequestError(response); } } else if (response.status >= 500) { - await handleBusinessError(response); + await handleServerError(response); } else { return response; } @@ -57,11 +57,11 @@ const handleForbiddenError = (response: Response) => { throw new ForbiddenError(response.status); }; -const handleSystemError = (response: Response) => { - throw new SystemError(response.status); +const handleRequestError = (response: Response) => { + throw new RequestError(response.status); }; -const handleBusinessError = async (response: Response) => { +const handleServerError = async (response: Response) => { const data = await response.json(); throw new InternalServerError(response.status, data.detail); }; diff --git a/frontend/src/api/mocks/userHandlers.ts b/frontend/src/api/mocks/userHandlers.ts index d13d8c3..e4e5c92 100644 --- a/frontend/src/api/mocks/userHandlers.ts +++ b/frontend/src/api/mocks/userHandlers.ts @@ -1,6 +1,6 @@ import { faker } from "@faker-js/faker"; import { http, HttpResponse } from "msw"; -import { ROLE } from "../../router/constants"; +import { ERole } from "../../router/constants"; export default [ http.get("/iam/user", () => { @@ -13,7 +13,7 @@ export default [ const generateRole = () => ({ id: faker.number.int({ min: 1, max: 100 }), code: faker.helpers.arrayElement([ - ROLE.ADMIN, + ERole.ADMIN, "editor", "viewer", "manager", @@ -60,7 +60,7 @@ export default [ const generateRole = () => ({ id: faker.number.int({ min: 1, max: 100 }), - code: [ROLE.ADMIN, "editor", "viewer", "manager"], + code: [ERole.ADMIN, "editor", "viewer", "manager"], name: faker.person.jobTitle(), permissions: faker.helpers.multiple(generatePermission, { count: { min: 1, max: 5 }, @@ -135,7 +135,7 @@ export default [ const generateRole = () => ({ id: faker.number.int({ min: 1, max: 100 }), - code: [ROLE.ADMIN, "editor", "viewer", "manager"], + code: [ERole.ADMIN, "editor", "viewer", "manager"], name: faker.person.jobTitle(), permissions: faker.helpers.multiple(generatePermission, { count: { min: 1, max: 5 }, diff --git a/frontend/src/api/schema/openapi.json b/frontend/src/api/schema/openapi.json index 9bc68c8..936563f 100644 --- a/frontend/src/api/schema/openapi.json +++ b/frontend/src/api/schema/openapi.json @@ -904,12 +904,23 @@ } } }, - "/department/query": { + "/department/query-available": { "get": { "tags": [ "department-controller" ], - "operationId": "queryDepartments", + "operationId": "queryAvailableParentDepartmentsBy", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], "responses": { "200": { "description": "OK", diff --git a/frontend/src/api/types/schema.d.ts b/frontend/src/api/types/schema.d.ts index f72ffc4..e59476f 100644 --- a/frontend/src/api/types/schema.d.ts +++ b/frontend/src/api/types/schema.d.ts @@ -436,14 +436,14 @@ export interface paths { patch?: never; trace?: never; }; - "/department/query": { + "/department/query-available": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get: operations["queryDepartments"]; + get: operations["queryAvailableParentDepartmentsBy"]; put?: never; post?: never; delete?: never; @@ -1466,9 +1466,11 @@ export interface operations { }; }; }; - queryDepartments: { + queryAvailableParentDepartmentsBy: { parameters: { - query?: never; + query?: { + id?: number; + }; header?: never; path?: never; cookie?: never; diff --git a/frontend/src/components/Alert.vue b/frontend/src/components/Alert.vue index e169427..188f32a 100644 --- a/frontend/src/components/Alert.vue +++ b/frontend/src/components/Alert.vue @@ -1,11 +1,36 @@ diff --git a/frontend/src/components/DepartmentUpsertModal.vue b/frontend/src/components/DepartmentUpsertModal.vue index 771ced3..0af8188 100644 --- a/frontend/src/components/DepartmentUpsertModal.vue +++ b/frontend/src/components/DepartmentUpsertModal.vue @@ -34,7 +34,7 @@ + placeholder="编辑时非必填" required />
@@ -81,21 +81,19 @@ const { user, onSubmit } = defineProps<{ const formData = ref(); -watch( - () => user, - (newUser) => { - formData.value = { - id: newUser?.id, - username: newUser?.username, - password: undefined, - enable: newUser?.enable, - confirmPassword: undefined, - }; - }, - { - immediate: true, - }, -); +const updateFormData = (newUser: typeof user) => { + formData.value = { + id: newUser?.id, + username: newUser?.username, + password: undefined, + enable: newUser?.enable, + confirmPassword: undefined, + }; +}; + +watch(() => user, updateFormData, { + immediate: true, +}); const handleSubmit = async () => { const userSchema = z @@ -105,19 +103,22 @@ const handleSubmit = async () => { .string({ message: "用户名不能为空", }) - .min(4, "用户名至少4个字符"), + .min(4, "用户名至少4个字符") + .max(15, "用户名最多15个字符"), enable: z.boolean(), password: z .string({ message: "密码不能为空", }) .min(5, "密码至少5个字符") + .max(20, "密码最多20个字符") .optional(), confirmPassword: z .string({ message: "密码不能为空", }) .min(5, "密码至少5个字符") + .max(20, "密码最多20个字符") .optional(), }) .refine( @@ -133,6 +134,7 @@ const handleSubmit = async () => { try { const validatedData = userSchema.parse(formData.value); await onSubmit(validatedData); + updateFormData(undefined); } catch (error) { if (error instanceof z.ZodError) { alertStore.showAlert({ diff --git a/frontend/src/components/icons/DepartmentIcon.vue b/frontend/src/components/icons/DepartmentIcon.vue new file mode 100644 index 0000000..d0f8b29 --- /dev/null +++ b/frontend/src/components/icons/DepartmentIcon.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/components/icons/PermissionIcon.vue b/frontend/src/components/icons/PermissionIcon.vue new file mode 100644 index 0000000..003dee8 --- /dev/null +++ b/frontend/src/components/icons/PermissionIcon.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/components/icons/PositionIcon.vue b/frontend/src/components/icons/PositionIcon.vue new file mode 100644 index 0000000..18fa085 --- /dev/null +++ b/frontend/src/components/icons/PositionIcon.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/components/icons/RoleIcon.vue b/frontend/src/components/icons/RoleIcon.vue new file mode 100644 index 0000000..ecaa159 --- /dev/null +++ b/frontend/src/components/icons/RoleIcon.vue @@ -0,0 +1,10 @@ + diff --git a/frontend/src/components/icons/SchedulerIcon.vue b/frontend/src/components/icons/SchedulerIcon.vue new file mode 100644 index 0000000..5c03236 --- /dev/null +++ b/frontend/src/components/icons/SchedulerIcon.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/components/icons/SettingsIcon.vue b/frontend/src/components/icons/SettingsIcon.vue new file mode 100644 index 0000000..aeadf62 --- /dev/null +++ b/frontend/src/components/icons/SettingsIcon.vue @@ -0,0 +1,16 @@ + diff --git a/frontend/src/components/icons/UsersIcon.vue b/frontend/src/components/icons/UsersIcon.vue new file mode 100644 index 0000000..fe543ac --- /dev/null +++ b/frontend/src/components/icons/UsersIcon.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/composables/department/useDepartmentQuery.ts b/frontend/src/composables/department/useDepartmentQuery.ts index f8935d7..0107316 100644 --- a/frontend/src/composables/department/useDepartmentQuery.ts +++ b/frontend/src/composables/department/useDepartmentQuery.ts @@ -4,12 +4,18 @@ import type { components } from "../../api/types/schema"; export const useDepartmentQuery = () => { const total = ref(0); - const departments = ref([]); - const allDepartments = ref([]); + const departments = ref(); + const availableDepartments = ref(); - const fetchAllDepartments = async () => { - const { data } = await client.GET("/department/query"); - allDepartments.value = data ?? []; + const fetchAvailableDepartments = async (id?: number) => { + const { data } = await client.GET("/department/query-available", { + params: { + query: { + id, + }, + }, + }); + availableDepartments.value = data ?? []; }; const fetchDepartmentWith = async ( param: { @@ -38,8 +44,8 @@ export const useDepartmentQuery = () => { return { total, departments, - allDepartments, + availableDepartments, fetchDepartmentWith, - fetchAllDepartments, + fetchAvailableDepartments, }; }; diff --git a/frontend/src/composables/store/useAlertStore.ts b/frontend/src/composables/store/useAlertStore.ts index 9bac232..1ffd1d3 100644 --- a/frontend/src/composables/store/useAlertStore.ts +++ b/frontend/src/composables/store/useAlertStore.ts @@ -17,7 +17,7 @@ const useAlertStore = defineStore("alertStore", () => { serializer: StorageSerializers.object, }, ); - + alertStorage.value.isShow = false; const showAlert = ({ content: newContent, level: newLevel, diff --git a/frontend/src/router/constants.ts b/frontend/src/router/constants.ts index 095a4ea..602d064 100644 --- a/frontend/src/router/constants.ts +++ b/frontend/src/router/constants.ts @@ -50,14 +50,19 @@ export enum RouteName { GLOBAL_NOTFOUND = "global-notfound", } -export enum ROLE { - ADMIN = "ADMIN", - USER = "USER", -} +export enum ERole { + ADMIN = "ADMIN", + USER = "GENERAL", + } -export enum PERMISSION { - USER_VIEW = "user:view", - USER_ADD = "user:add", - USER_EDIT = "user:edit", - USER_DELETE = "user:delete", -} +export enum EPermission { + READ_POSITION_PERMISSION = "READ_POSITION_PERMISSION", + WRITE_POSITION_PERMISSION = "WRITE_POSITION_PERMISSION", + READ_DEPARTMENT_PERMISSION = "READ_DEPARTMENT_PERMISSION", + WRITE_DEPARTMENT_PERMISSION = "WRITE_DEPARTMENT_PERMISSION", + READ_SCHEDULER_PERMISSION = "READ_SCHEDULER_PERMISSION", + WRITE_SCHEDULER_PERMISSION = "WRITE_SCHEDULER_PERMISSION", + WRITE_USER_ROLE_PERMISSION = "WRITE_USER_ROLE_PERMISSION", + DELETE_USER_ROLE_PERMISSION = "DELETE_USER_ROLE_PERMISSION", + READ_USER_ROLE_PERMISSION = "READ_USER_ROLE_PERMISSION", + } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index f0f914f..b05041e 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -20,7 +20,6 @@ const router = createRouter({ router.onError((err) => { console.error("router err:", err); - // TODO 增加一个错误页面 router.push(RouteName.USERVIEW); return false; }); diff --git a/frontend/src/router/modules/dashboard.ts b/frontend/src/router/modules/dashboard.ts index ae016ae..648f93f 100644 --- a/frontend/src/router/modules/dashboard.ts +++ b/frontend/src/router/modules/dashboard.ts @@ -1,7 +1,7 @@ import type { RouteRecordRaw } from "vue-router"; import Dashboard from "../../components/Dashboard.vue"; import OverView from "../../views/OverView.vue"; -import { ROLE, RouteName, RoutePath } from "../constants"; +import { EPermission, ERole, RouteName, RoutePath } from "../constants"; import userManagementRoutes from "./user"; const dashboardRoutes: RouteRecordRaw = { @@ -40,7 +40,7 @@ const dashboardRoutes: RouteRecordRaw = { component: () => import("@/views/SchedulerView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_SCHEDULER_PERMISSION, }, }, { @@ -49,7 +49,7 @@ const dashboardRoutes: RouteRecordRaw = { component: () => import("@/views/DepartmentView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_DEPARTMENT_PERMISSION, }, }, { @@ -58,7 +58,7 @@ const dashboardRoutes: RouteRecordRaw = { component: () => import("@/views/PositionView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_POSITION_PERMISSION, }, }, ], diff --git a/frontend/src/router/modules/user.ts b/frontend/src/router/modules/user.ts index ce0dca7..b594527 100644 --- a/frontend/src/router/modules/user.ts +++ b/frontend/src/router/modules/user.ts @@ -1,5 +1,5 @@ import type { RouteRecordRaw } from "vue-router"; -import { ROLE, RouteName, RoutePath } from "../constants"; +import { EPermission, ERole, RouteName, RoutePath } from "../constants"; const userManagementRoutes: RouteRecordRaw[] = [ { @@ -8,7 +8,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/UserView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_USER_ROLE_PERMISSION, }, }, { @@ -17,7 +17,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/RoleView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_USER_ROLE_PERMISSION, }, }, { @@ -26,7 +26,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/BindRoleView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.WRITE_USER_ROLE_PERMISSION, }, }, { @@ -35,7 +35,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/BindDepartmentView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.WRITE_USER_ROLE_PERMISSION, }, }, { @@ -44,7 +44,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/BindPermissionView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.WRITE_USER_ROLE_PERMISSION, }, }, { @@ -53,7 +53,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/PermissionView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.READ_USER_ROLE_PERMISSION, }, }, { @@ -62,7 +62,7 @@ const userManagementRoutes: RouteRecordRaw[] = [ component: () => import("@/views/BindPositionView.vue"), meta: { requiresAuth: true, - hasRole: ROLE.ADMIN, + hasPermission: EPermission.WRITE_USER_ROLE_PERMISSION, }, }, ]; diff --git a/frontend/src/types/error.ts b/frontend/src/types/error.ts index 37fa4db..84d485d 100644 --- a/frontend/src/types/error.ts +++ b/frontend/src/types/error.ts @@ -1,7 +1,7 @@ class HttpError extends Error { status: number; - detail: string | undefined; - constructor(message: string, status: number, detail: string | undefined) { + detail?: string; + constructor(message: string, status: number, detail?: string) { super(message); this.name = "HttpError"; this.status = status; @@ -10,44 +10,31 @@ class HttpError extends Error { } class UnAuthError extends HttpError { - constructor(status: number, detail?: string) { - super("身份认证异常", status, detail); + constructor(status: number) { + super("当前用户身份认证异常", status); this.name = "UnAuthError"; } } class ForbiddenError extends HttpError { - constructor(status: number, detail?: string) { - super("权限校验异常", status, detail); + constructor(status: number) { + super("您没有对应的权限", status); this.name = "ForbiddenError"; } } -class SystemError extends HttpError { - constructor(status: number, detail?: string) { - super("系统错误,请稍候再试", status, detail); - this.name = "SystemError"; +class RequestError extends HttpError { + constructor(status: number) { + super("请求发生异常,请检查您的输入或稍后再试", status); + this.name = "RequestError"; } } class InternalServerError extends HttpError { - constructor(status: number, detail?: string) { - super("服务器错误,请稍候再试", status, detail); + constructor(status: number, detail: string) { + super(detail, status, detail); this.name = "InternalServerError"; } } -class BadRequestError extends HttpError { - constructor(status: number, detail?: string) { - super("请求非法", status, detail); - this.name = "BadRequestError"; - } -} - -export { - UnAuthError, - ForbiddenError, - SystemError, - InternalServerError, - BadRequestError, -}; +export { UnAuthError, ForbiddenError, RequestError, InternalServerError }; diff --git a/frontend/src/utils/errorHandler.ts b/frontend/src/utils/errorHandler.ts index fc9c6ce..40a7ae2 100644 --- a/frontend/src/utils/errorHandler.ts +++ b/frontend/src/utils/errorHandler.ts @@ -4,7 +4,7 @@ import { RoutePath } from "../router/constants"; import { ForbiddenError, InternalServerError, - SystemError, + RequestError, UnAuthError, } from "../types/error"; @@ -34,7 +34,7 @@ const makeErrorHandler = level: "error", content: err.message, }); - } else if (err instanceof SystemError) { + } else if (err instanceof RequestError) { showAlert({ level: "error", content: err.message, diff --git a/frontend/src/views/BindDepartmentView.vue b/frontend/src/views/BindDepartmentView.vue index a77a486..5fcfbb6 100644 --- a/frontend/src/views/BindDepartmentView.vue +++ b/frontend/src/views/BindDepartmentView.vue @@ -96,7 +96,7 @@ {{ department.name }} - +
{{ @@ -119,6 +119,7 @@