mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-22 10:53:41 +08:00
fix bugs
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -168,5 +168,8 @@ fabric.properties
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
|
||||
# 如何在这个地方引用 backend 和 frontend 下级目录中的 .gitignore 中的规则?我需要全部给他们前面加上 backend/ 和 frontend/ 前缀吗?
|
||||
# docker
|
||||
docker-compose.yaml
|
||||
Dockerfile
|
||||
Caddyfile
|
||||
start.sh
|
||||
|
||||
6
backend/.gitignore
vendored
6
backend/.gitignore
vendored
@@ -221,9 +221,11 @@ Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
|
||||
logs
|
||||
db.d/store/*
|
||||
.git
|
||||
|
||||
|
||||
# docker
|
||||
docker-compose.yaml
|
||||
Dockerfile
|
||||
Caddyfile
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ public class DepartmentController {
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).READ_DEPARTMENT_PERMISSION)")
|
||||
@GetMapping("/query")
|
||||
List<Department> queryDepartments() {
|
||||
return departmentRepository.findAll();
|
||||
@GetMapping("/query-available")
|
||||
List<Department> queryAvailableParentDepartmentsBy(@RequestParam(required = false) Long id) {
|
||||
return departmentService.queryAvailableParentDepartmentsBy(id);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_DEPARTMENT_PERMISSION)")
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Object> 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<Object> 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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<DepartmentWithParentDto> 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<Record> 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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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<Department> queryAvailableParentDepartmentsBy(Long id) {
|
||||
List<Department> allDepartments = departmentRepository.findAll();
|
||||
if (id != null) {
|
||||
List<DepartmentWithParentDto> 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<DepartmentWithParentDto> queryDepartmentAndSubsBy(Long id) {
|
||||
return departmentRepository.queryDepartmentAndSubsBy(id);
|
||||
}
|
||||
|
||||
public PageResponseDto<List<DepartmentRespDto>> pageQueryDepartment(
|
||||
PageRequestDto pageRequestDto, DepartmentQueryDto departmentQueryDto) {
|
||||
Result<Record> records = departmentRepository.pageFetchBy(pageRequestDto, departmentQueryDto);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/java.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>知路后台管理</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
7
frontend/public/java.svg
Normal file
7
frontend/public/java.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256">
|
||||
<g fill="none">
|
||||
<rect width="256" height="256" fill="#f4f2ed" rx="60" />
|
||||
<path fill="#4e7896" d="M101.634 182.619s-7.68 4.674 5.345 6.011c15.728 2.004 24.044 1.669 41.407-1.668c0 0 4.674 3.009 11.02 5.344c-39.075 16.696-88.497-1.002-57.772-9.687m-5.009-21.705s-8.35 6.346 4.674 7.679c17.028 1.669 30.391 2.004 53.433-2.667c0 0 3.009 3.341 8.015 5.01c-47.083 14.025-99.85 1.333-66.122-10.019zm92.17 38.07s5.676 4.674-6.346 8.35c-22.376 6.678-93.839 8.685-113.876 0c-7.009-3.009 6.347-7.352 10.686-8.015c4.342-1.002 6.678-1.002 6.678-1.002c-7.68-5.344-51.095 11.02-22.041 15.729c79.813 13.027 145.603-5.676 124.896-15.028zm-83.488-60.781s-36.402 8.685-13.028 11.687c10.019 1.333 29.721 1.002 48.089-.335c15.028-1.334 30.09-4.007 30.09-4.007s-5.345 2.338-9.017 4.674c-37.099 9.693-108.23 5.351-87.858-4.668c17.37-8.35 31.724-7.351 31.724-7.351m65.116 36.401c37.407-19.37 20.037-38.07 8.015-35.731c-3.009.667-4.342 1.334-4.342 1.334s1.001-2.004 3.34-2.667c23.709-8.35 42.413 25.046-7.679 38.07c0 0 .335-.335.666-1.002zm-61.444 52.76c36.067 2.339 91.168-1.334 92.505-18.369c0 0-2.667 6.678-29.72 11.688c-30.722 5.676-68.796 5.009-91.168 1.333c0 0 4.674 4.007 28.386 5.344z" />
|
||||
<path fill="#f58219" d="M147.685 28s20.704 21.039-19.702 52.76c-32.394 25.712-7.351 40.408 0 57.101c-19.035-17.028-32.722-32.059-23.377-46.085C118.331 71.083 156.062 61.064 147.685 28M137 123.842c9.683 11.02-2.667 21.039-2.667 21.039s24.711-12.686 13.359-28.387c-10.354-15.028-18.368-22.376 25.046-47.425c0 0-68.461 17.028-35.731 54.766z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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",
|
||||
|
||||
10
frontend/src/api/types/schema.d.ts
vendored
10
frontend/src/api/types/schema.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
<template>
|
||||
<div id="globalAlert" :class="['flex space-x-2 items-center rounded-lg p-4 mb-4 text-sm fixed top-8 right-5 transition-all duration-200 ease-out z-50', alertStore.levelClassName, alertStore.alertStorage.isShow ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-full']" role="alert">
|
||||
<svg v-if="alertStore.alertStorage.level==='info'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
||||
<svg v-else-if="alertStore.alertStorage.level === 'warning'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
|
||||
<svg v-if="alertStore.alertStorage.level === 'success'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>
|
||||
<svg v-if="alertStore.alertStorage.level === 'error'" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>
|
||||
<span class="font-medium">{{ alertStore.alertStorage.content }}</span>
|
||||
</div>
|
||||
<div id="globalAlert"
|
||||
:class="['flex space-x-2 items-center rounded-lg p-4 mb-4 text-sm fixed top-8 right-5 transition-all duration-200 ease-out z-50', alertStore.levelClassName, alertStore.alertStorage.isShow ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-full']"
|
||||
role="alert">
|
||||
<svg v-if="alertStore.alertStorage.level==='info'" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="lucide lucide-info-icon lucide-info">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 16v-4" />
|
||||
<path d="M12 8h.01" />
|
||||
</svg>
|
||||
<svg v-else-if="alertStore.alertStorage.level === 'warning'" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert">
|
||||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
||||
<path d="M12 9v4" />
|
||||
<path d="M12 17h.01" />
|
||||
</svg>
|
||||
<svg v-if="alertStore.alertStorage.level === 'success'" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</svg>
|
||||
<svg v-if="alertStore.alertStorage.level === 'error'" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="m15 9-6 6" />
|
||||
<path d="m9 9 6 6" />
|
||||
</svg>
|
||||
<span class="font-medium">{{ alertStore.alertStorage.content }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">上级部门</label>
|
||||
<select id="category" v-model="formData.parentId"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
<option v-for="department in allDepartments" :key="department.id" :value="department.id"
|
||||
<option v-for="department in availableDepartments" :key="department.id" :value="department.id"
|
||||
:selected="department.id === formData.parentId">{{
|
||||
department.name
|
||||
}}</option>
|
||||
@@ -61,26 +61,23 @@ import type { DepartmentUpsertModel } from "../types/department";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
const { department, allDepartments, onSubmit } = defineProps<{
|
||||
const { department, availableDepartments, onSubmit } = defineProps<{
|
||||
department?: components["schemas"]["Department"];
|
||||
allDepartments: components["schemas"]["Department"][];
|
||||
availableDepartments?: components["schemas"]["Department"][];
|
||||
closeModal: () => void;
|
||||
onSubmit: (department: DepartmentUpsertModel) => Promise<void>;
|
||||
}>();
|
||||
|
||||
const formData = ref();
|
||||
|
||||
watch(
|
||||
() => department,
|
||||
(newDepartment) => {
|
||||
formData.value = {
|
||||
id: newDepartment?.id,
|
||||
name: newDepartment?.name,
|
||||
parentId: newDepartment?.parentId,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
const updateFormData = (newDepartment: typeof department) => {
|
||||
formData.value = {
|
||||
id: newDepartment?.id,
|
||||
name: newDepartment?.name,
|
||||
parentId: newDepartment?.parentId,
|
||||
};
|
||||
};
|
||||
watch(() => department, updateFormData, { immediate: true });
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const schema = z.object({
|
||||
@@ -90,12 +87,14 @@ const handleSubmit = async () => {
|
||||
.string({
|
||||
message: "部门名称不能为空",
|
||||
})
|
||||
.min(2, "部门名称至少2个字符"),
|
||||
.min(2, "部门名称至少2个字符")
|
||||
.max(15, "部门名称最多15个字符"),
|
||||
});
|
||||
|
||||
try {
|
||||
const validatedData = schema.parse(formData.value);
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
alertStore.showAlert({
|
||||
|
||||
@@ -60,17 +60,15 @@ const alertStore = useAlertStore();
|
||||
|
||||
const formData = ref();
|
||||
|
||||
watch(
|
||||
() => permission,
|
||||
(newPermission) => {
|
||||
formData.value = {
|
||||
id: newPermission?.id,
|
||||
name: newPermission?.name,
|
||||
code: newPermission?.code,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
const updateFormData = (newPermission: typeof permission) => {
|
||||
formData.value = {
|
||||
id: newPermission?.id,
|
||||
name: newPermission?.name,
|
||||
code: newPermission?.code,
|
||||
};
|
||||
};
|
||||
|
||||
watch(() => permission, updateFormData, { immediate: true });
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const permissionSchema = z.object({
|
||||
@@ -79,17 +77,20 @@ const handleSubmit = async () => {
|
||||
.string({
|
||||
message: "权限名称不能为空",
|
||||
})
|
||||
.min(2, "权限名称至少2个字符"),
|
||||
.min(2, "权限名称至少2个字符")
|
||||
.max(15, "权限名称最多15个字符"),
|
||||
code: z
|
||||
.string({
|
||||
message: "权限代码不能为空",
|
||||
})
|
||||
.min(2, "权限代码至少2个字符"),
|
||||
.min(2, "权限代码至少2个字符")
|
||||
.max(15, "权限代码最多15个字符"),
|
||||
});
|
||||
|
||||
try {
|
||||
const validatedData = permissionSchema.parse(formData.value);
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
alertStore.showAlert({
|
||||
|
||||
@@ -48,7 +48,6 @@ import { onMounted, ref, watch } from "vue";
|
||||
import { z } from "zod";
|
||||
import type { components } from "../api/types/schema";
|
||||
import type { PositionUpsertModel } from "../types/position";
|
||||
import { tr } from "@faker-js/faker";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
@@ -60,17 +59,14 @@ const { id, position, onSubmit } = defineProps<{
|
||||
}>();
|
||||
|
||||
const formData = ref();
|
||||
const updateFormData = (newPosition: typeof position) => {
|
||||
formData.value = {
|
||||
id: newPosition?.id,
|
||||
name: newPosition?.name,
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => position,
|
||||
(newPosition) => {
|
||||
formData.value = {
|
||||
id: newPosition?.id,
|
||||
name: newPosition?.name,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(() => position, updateFormData, { immediate: true });
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const schema = z.object({
|
||||
@@ -79,12 +75,14 @@ const handleSubmit = async () => {
|
||||
.string({
|
||||
message: "岗位名称不能为空",
|
||||
})
|
||||
.min(2, "岗位名称至少2个字符"),
|
||||
.min(2, "岗位名称至少2个字符")
|
||||
.max(15, "岗位名称最多15个字符"),
|
||||
});
|
||||
|
||||
try {
|
||||
const validatedData = schema.parse(formData.value);
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
alertStore.showAlert({
|
||||
|
||||
@@ -60,17 +60,15 @@ const { role, onSubmit } = defineProps<{
|
||||
|
||||
const formData = ref();
|
||||
|
||||
watch(
|
||||
() => role,
|
||||
(newRole) => {
|
||||
formData.value = {
|
||||
id: newRole?.id,
|
||||
name: newRole?.name,
|
||||
code: newRole?.code,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
const updateFormData = (newRole: typeof role) => {
|
||||
formData.value = {
|
||||
id: newRole?.id,
|
||||
name: newRole?.name,
|
||||
code: newRole?.code,
|
||||
};
|
||||
};
|
||||
|
||||
watch(() => role, updateFormData, { immediate: true });
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const roleSchema = z.object({
|
||||
@@ -79,17 +77,20 @@ const handleSubmit = async () => {
|
||||
.string({
|
||||
message: "角色名称不能为空",
|
||||
})
|
||||
.min(2, "角色名称至少2个字符"),
|
||||
.min(2, "角色名称至少2个字符")
|
||||
.max(15, "角色名称最多15个字符"),
|
||||
code: z
|
||||
.string({
|
||||
message: "角色代码不能为空",
|
||||
})
|
||||
.min(2, "角色代码至少2个字符"),
|
||||
.min(2, "角色代码至少2个字符")
|
||||
.max(15, "角色代码最多15个字符"),
|
||||
});
|
||||
|
||||
try {
|
||||
const validatedData = roleSchema.parse(formData.value);
|
||||
await onSubmit(validatedData);
|
||||
updateFormData(undefined);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
alertStore.showAlert({
|
||||
|
||||
@@ -54,9 +54,9 @@ const formData = ref();
|
||||
watch(
|
||||
() => job,
|
||||
(newJob) => {
|
||||
formData.value = {
|
||||
cronExpression: newJob?.cronExpression,
|
||||
};
|
||||
formData.value = {
|
||||
cronExpression: newJob?.cronExpression,
|
||||
};
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
@@ -4,130 +4,13 @@
|
||||
aria-label="Sidebar">
|
||||
<div class="h-full px-3 pb-4 overflow-y-auto bg-white dark:bg-gray-800">
|
||||
<ul class="space-y-2 font-medium">
|
||||
<!-- <li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.OVERVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide shrink-0 text-gray-600 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white lucide-chart-pie-icon lucide-chart-pie">
|
||||
<path
|
||||
d="M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z" />
|
||||
<path d="M21.21 15.89A10 10 0 1 1 8 2.83" />
|
||||
</svg>
|
||||
<span>
|
||||
总览
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li> -->
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.USERVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-users-icon lucide-users shrink-0 text-gray-700 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
<span>
|
||||
用户管理
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.ROLEVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-shield-user-icon lucide-shield-user shrink-0 text-gray-700 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white">
|
||||
<path
|
||||
d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
|
||||
<path d="M6.376 18.91a6 6 0 0 1 11.249.003" />
|
||||
<circle cx="12" cy="11" r="4" />
|
||||
</svg>
|
||||
<span>
|
||||
角色管理
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.PERMISSIONVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-key-round-icon lucide-key-round shrink-0 text-gray-700 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white">
|
||||
<path
|
||||
d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
||||
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
|
||||
</svg>
|
||||
<span>
|
||||
权限管理
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.DEPARTMENTVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class=" text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 15v3c0 .5523.44772 1 1 1h4v-4m-5 0v-4m0 4h5m-5-4V6c0-.55228.44772-1 1-1h16c.5523 0 1 .44772 1 1v1.98935M3 11h5v4m9.4708 4.1718-.8696-1.4388-2.8164-.235-2.573-4.2573 1.4873-2.8362 1.4441 2.3893c.3865.6396 1.2183.8447 1.8579.4582.6396-.3866.8447-1.2184.4582-1.858l-1.444-2.38925h3.1353l2.6101 4.27715-1.0713 2.5847.8695 1.4388" />
|
||||
</svg>
|
||||
|
||||
<span>
|
||||
部门管理
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.POSITIONVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M14.6144 7.19994c.3479.48981.5999 1.15357.5999 1.80006 0 1.6569-1.3432 3-3 3-1.6569 0-3.00004-1.3431-3.00004-3 0-.67539.22319-1.29865.59983-1.80006M6.21426 6v4m0-4 6.00004-3 6 3-6 2-2.40021-.80006M6.21426 6l3.59983 1.19994M6.21426 19.8013v-2.1525c0-1.6825 1.27251-3.3075 2.95093-3.6488l3.04911 2.9345 3-2.9441c1.7026.3193 3 1.9596 3 3.6584v2.1525c0 .6312-.5373 1.1429-1.2 1.1429H7.41426c-.66274 0-1.2-.5117-1.2-1.1429Z" />
|
||||
</svg>
|
||||
|
||||
<span>
|
||||
岗位管理
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.SETTINGS}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-user-cog-icon lucide-user-cog shrink-0 text-gray-700 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white">
|
||||
<circle cx="18" cy="15" r="3" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M10 15H6a4 4 0 0 0-4 4v2" />
|
||||
<path d="m21.7 16.4-.9-.3" />
|
||||
<path d="m15.2 13.9-.9-.3" />
|
||||
<path d="m16.6 18.7.3-.9" />
|
||||
<path d="m19.1 12.2.3-.9" />
|
||||
<path d="m19.6 18.7-.4-1" />
|
||||
<path d="m16.8 12.3-.4-1" />
|
||||
<path d="m14.3 16.6 1-.4" />
|
||||
<path d="m20.7 13.8 1-.4" />
|
||||
</svg>
|
||||
<span>
|
||||
个人中心
|
||||
</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="`${RoutePath.DASHBOARD}/${RoutePath.SCHEDULERVIEW}`"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
|
||||
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<span>
|
||||
定时任务
|
||||
</span>
|
||||
<li v-for="item in menuItems" :key="item.path">
|
||||
<RouterLink :to="item.path"
|
||||
class="flex items-center p-2 gap-x-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group"
|
||||
:class="{ 'bg-gray-100 dark:bg-gray-700': isActive(item.path) }">
|
||||
<component :is="item.icon"
|
||||
class="shrink-0 text-gray-700 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" />
|
||||
<span>{{ item.title }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -139,7 +22,62 @@
|
||||
import { RoutePath } from "@/router/constants";
|
||||
import { initFlowbite } from "flowbite";
|
||||
import { onMounted } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { RouterLink, useRoute } from "vue-router";
|
||||
|
||||
import DepartmentIcon from "./icons/DepartmentIcon.vue";
|
||||
import PermissionIcon from "./icons/PermissionIcon.vue";
|
||||
import PositionIcon from "./icons/PositionIcon.vue";
|
||||
import RoleIcon from "./icons/RoleIcon.vue";
|
||||
import SchedulerIcon from "./icons/SchedulerIcon.vue";
|
||||
import SettingsIcon from "./icons/SettingsIcon.vue";
|
||||
// 导入图标组件
|
||||
import UsersIcon from "./icons/UsersIcon.vue";
|
||||
|
||||
// 菜单配置
|
||||
const menuItems = [
|
||||
{
|
||||
title: "用户管理",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.USERVIEW}`,
|
||||
icon: UsersIcon,
|
||||
},
|
||||
{
|
||||
title: "角色管理",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.ROLEVIEW}`,
|
||||
icon: RoleIcon,
|
||||
},
|
||||
{
|
||||
title: "权限管理",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.PERMISSIONVIEW}`,
|
||||
icon: PermissionIcon,
|
||||
},
|
||||
{
|
||||
title: "部门管理",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.DEPARTMENTVIEW}`,
|
||||
icon: DepartmentIcon,
|
||||
},
|
||||
{
|
||||
title: "岗位管理",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.POSITIONVIEW}`,
|
||||
icon: PositionIcon,
|
||||
},
|
||||
{
|
||||
title: "个人中心",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.SETTINGS}`,
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: "定时任务",
|
||||
path: `${RoutePath.DASHBOARD}/${RoutePath.SCHEDULERVIEW}`,
|
||||
icon: SchedulerIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 判断当前路由是否激活
|
||||
const isActive = (path: string) => {
|
||||
return route.path === path;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initFlowbite();
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
class="block mb-2 text-sm font-medium autocompletetext-gray-900 dark:text-white">密码</label>
|
||||
<input type="password" id="password" autocomplete="new-password" v-model="formData.password"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="非必填" required />
|
||||
placeholder="编辑时非必填" required />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label for="confirm_password"
|
||||
@@ -42,7 +42,7 @@
|
||||
<input type="password" id="confirm_password" autocomplete="new-password"
|
||||
v-model="formData.confirmPassword"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
required placeholder="非必填" />
|
||||
required placeholder="编辑时非必填" />
|
||||
</div>
|
||||
<div class="col-span-2 sm:col-span-1">
|
||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">状态</label>
|
||||
@@ -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({
|
||||
|
||||
7
frontend/src/components/icons/DepartmentIcon.vue
Normal file
7
frontend/src/components/icons/DepartmentIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg class="text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 15v3c0 .5523.44772 1 1 1h4v-4m-5 0v-4m0 4h5m-5-4V6c0-.55228.44772-1 1-1h16c.5523 0 1 .44772 1 1v1.98935M3 11h5v4m9.4708 4.1718-.8696-1.4388-2.8164-.235-2.573-4.2573 1.4873-2.8362 1.4441 2.3893c.3865.6396 1.2183.8447 1.8579.4582.6396-.3866.8447-1.2184.4582-1.858l-1.444-2.38925h3.1353l2.6101 4.27715-1.0713 2.5847.8695 1.4388" />
|
||||
</svg>
|
||||
</template>
|
||||
9
frontend/src/components/icons/PermissionIcon.vue
Normal file
9
frontend/src/components/icons/PermissionIcon.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-key-round-icon lucide-key-round">
|
||||
<path
|
||||
d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
||||
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
7
frontend/src/components/icons/PositionIcon.vue
Normal file
7
frontend/src/components/icons/PositionIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M14.6144 7.19994c.3479.48981.5999 1.15357.5999 1.80006 0 1.6569-1.3432 3-3 3-1.6569 0-3.00004-1.3431-3.00004-3 0-.67539.22319-1.29865.59983-1.80006M6.21426 6v4m0-4 6.00004-3 6 3-6 2-2.40021-.80006M6.21426 6l3.59983 1.19994M6.21426 19.8013v-2.1525c0-1.6825 1.27251-3.3075 2.95093-3.6488l3.04911 2.9345 3-2.9441c1.7026.3193 3 1.9596 3 3.6584v2.1525c0 .6312-.5373 1.1429-1.2 1.1429H7.41426c-.66274 0-1.2-.5117-1.2-1.1429Z" />
|
||||
</svg>
|
||||
</template>
|
||||
10
frontend/src/components/icons/RoleIcon.vue
Normal file
10
frontend/src/components/icons/RoleIcon.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-shield-user-icon lucide-shield-user">
|
||||
<path
|
||||
d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
|
||||
<path d="M6.376 18.91a6 6 0 0 1 11.249.003" />
|
||||
<circle cx="12" cy="11" r="4" />
|
||||
</svg>
|
||||
</template>
|
||||
7
frontend/src/components/icons/SchedulerIcon.vue
Normal file
7
frontend/src/components/icons/SchedulerIcon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</template>
|
||||
16
frontend/src/components/icons/SettingsIcon.vue
Normal file
16
frontend/src/components/icons/SettingsIcon.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-cog-icon lucide-user-cog">
|
||||
<circle cx="18" cy="15" r="3" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M10 15H6a4 4 0 0 0-4 4v2" />
|
||||
<path d="m21.7 16.4-.9-.3" />
|
||||
<path d="m15.2 13.9-.9-.3" />
|
||||
<path d="m16.6 18.7.3-.9" />
|
||||
<path d="m19.1 12.2.3-.9" />
|
||||
<path d="m19.6 18.7-.4-1" />
|
||||
<path d="m16.8 12.3-.4-1" />
|
||||
<path d="m14.3 16.6 1-.4" />
|
||||
<path d="m20.7 13.8 1-.4" />
|
||||
</svg>
|
||||
</template>
|
||||
9
frontend/src/components/icons/UsersIcon.vue
Normal file
9
frontend/src/components/icons/UsersIcon.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users-icon lucide-users">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -4,12 +4,18 @@ import type { components } from "../../api/types/schema";
|
||||
|
||||
export const useDepartmentQuery = () => {
|
||||
const total = ref<number>(0);
|
||||
const departments = ref<components["schemas"]["DepartmentRespDto"][]>([]);
|
||||
const allDepartments = ref<components["schemas"]["Department"][]>([]);
|
||||
const departments = ref<components["schemas"]["DepartmentRespDto"][]>();
|
||||
const availableDepartments = ref<components["schemas"]["Department"][]>();
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ const useAlertStore = defineStore("alertStore", () => {
|
||||
serializer: StorageSerializers.object,
|
||||
},
|
||||
);
|
||||
|
||||
alertStorage.value.isShow = false;
|
||||
const showAlert = ({
|
||||
content: newContent,
|
||||
level: newLevel,
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ const router = createRouter({
|
||||
|
||||
router.onError((err) => {
|
||||
console.error("router err:", err);
|
||||
// TODO 增加一个错误页面
|
||||
router.push(RouteName.USERVIEW);
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<td scope="row" class="px-6 py-4whitespace-nowrap font-medium text-gray-900 dark:text-white">
|
||||
{{ department.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="department.isBound ? 'bg-green-500' : 'bg-red-500'">
|
||||
</div> {{
|
||||
@@ -119,6 +119,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import BindModal from "@/components/PopupModal.vue";
|
||||
import UnModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
@@ -129,7 +130,6 @@ import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useDepartmentBind } from "../composables/department/useDepartmentBind";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const departmentName = ref<string>("");
|
||||
const checkedDepartmentIds = ref<number[]>([]);
|
||||
@@ -137,7 +137,7 @@ const departmentBindModal = ref<ModalInterface>();
|
||||
const departmentUnbindModal = ref<ModalInterface>();
|
||||
const allChecked = ref<boolean>(false);
|
||||
const $route = useRoute();
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("BIND");
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
@@ -151,6 +151,7 @@ const handleBindDepartmentSubmit = async () => {
|
||||
checkedDepartmentIds.value,
|
||||
);
|
||||
clearCheckedDepartment();
|
||||
allChecked.value = false;
|
||||
departmentBindModal.value?.hide();
|
||||
alertStore.showAlert({
|
||||
content: "操作成功",
|
||||
@@ -169,6 +170,7 @@ const handleUnbindDepartmentSubmit = async () => {
|
||||
checkedDepartmentIds.value,
|
||||
);
|
||||
clearCheckedDepartment();
|
||||
allChecked.value = false;
|
||||
departmentUnbindModal.value?.hide();
|
||||
alertStore.showAlert({
|
||||
content: "操作成功",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ permission.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="permission.isBound ? 'bg-green-500' : 'bg-red-500'">
|
||||
</div> {{
|
||||
@@ -120,6 +120,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import BindModal from "@/components/PopupModal.vue";
|
||||
import UnModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
@@ -130,7 +131,6 @@ import { useRoute } from "vue-router";
|
||||
import { usePermissionBind } from "../composables/permission/usePermissionBind";
|
||||
import usePermissionsQuery from "../composables/permission/usePermissionQuery";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const permissionName = ref<string>("");
|
||||
const checkedPermissionIds = ref<number[]>([]);
|
||||
@@ -138,7 +138,7 @@ const permissionBindModal = ref<ModalInterface>();
|
||||
const permissionUnbindModal = ref<ModalInterface>();
|
||||
const allChecked = ref<boolean>(false);
|
||||
const $route = useRoute();
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("BIND");
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const { total, permissions, fetchPermissionsWith } = usePermissionsQuery();
|
||||
@@ -155,6 +155,7 @@ const handleBindPermissionSubmit = async () => {
|
||||
level: "success",
|
||||
});
|
||||
clearCheckedRoleIds();
|
||||
allChecked.value = false;
|
||||
await fetchPermissionsWith({
|
||||
name: permissionName.value,
|
||||
roleId: Number($route.params.roleId),
|
||||
@@ -173,6 +174,7 @@ const handleUnbindPermissionSubmit = async () => {
|
||||
level: "success",
|
||||
});
|
||||
clearCheckedRoleIds();
|
||||
allChecked.value = false;
|
||||
await fetchPermissionsWith({
|
||||
name: permissionName.value,
|
||||
roleId: Number($route.params.roleId),
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ position.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="position.isBound ? 'bg-green-500' : 'bg-red-500'">
|
||||
</div> {{
|
||||
@@ -116,6 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import BindModal from "@/components/PopupModal.vue";
|
||||
import UnModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
@@ -126,7 +127,6 @@ import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const positionName = ref<string>("");
|
||||
const checkedPositionIds = ref<number[]>([]);
|
||||
@@ -134,7 +134,7 @@ const positionBindModal = ref<ModalInterface>();
|
||||
const positionUnbindModal = ref<ModalInterface>();
|
||||
const allChecked = ref<boolean>(false);
|
||||
const $route = useRoute();
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("BIND");
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
@@ -155,6 +155,7 @@ const handleBindPositionSubmit = async () => {
|
||||
bindState: bindState.value,
|
||||
});
|
||||
clearCheckedPositionIds();
|
||||
allChecked.value = false;
|
||||
};
|
||||
|
||||
const handleUnbindPositionSubmit = async () => {
|
||||
@@ -170,6 +171,7 @@ const handleUnbindPositionSubmit = async () => {
|
||||
bindState: bindState.value,
|
||||
});
|
||||
clearCheckedPositionIds();
|
||||
allChecked.value = false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<td scope="row" class="px-6 py-4 whitespace-nowrap dark:text-white">
|
||||
{{ role.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="role.isBound ? 'bg-green-500' : 'bg-red-500'">
|
||||
</div> {{
|
||||
@@ -118,18 +118,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import BindModal from "@/components/PopupModal.vue";
|
||||
import UnModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import { useRolesQuery } from "@/composables/role/useRolesQuery";
|
||||
import { RouteName } from "@/router/constants";
|
||||
import { tr } from "@faker-js/faker";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRoleBind } from "../composables/role/useRoleBind";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import { tr } from "@faker-js/faker";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const roleName = ref<string>("");
|
||||
const checkedRoleIds = ref<number[]>([]);
|
||||
@@ -137,7 +137,7 @@ const roleBindModal = ref<ModalInterface>();
|
||||
const roleUnbindModal = ref<ModalInterface>();
|
||||
const allChecked = ref<boolean>(false);
|
||||
const $route = useRoute();
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("BIND");
|
||||
const bindState = ref<"BIND" | "ALL" | "UNBIND">("ALL");
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const { total, roles, fetchRolesWith } = useRolesQuery();
|
||||
@@ -150,6 +150,7 @@ const handleBindRoleSubmit = async () => {
|
||||
});
|
||||
roleBindModal.value?.hide();
|
||||
clearCheckedRoleIds();
|
||||
allChecked.value = false;
|
||||
alertStore.showAlert({
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
@@ -164,6 +165,7 @@ const handleBindRoleSubmit = async () => {
|
||||
const handleUnbindRoleSubmit = async () => {
|
||||
await unbindRole(Number($route.params.userId), checkedRoleIds.value);
|
||||
clearCheckedRoleIds();
|
||||
allChecked.value = false;
|
||||
roleUnbindModal.value?.hide();
|
||||
alertStore.showAlert({
|
||||
content: "操作成功",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['部门管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">部门管理</h1>
|
||||
@@ -31,68 +31,70 @@
|
||||
新增部门
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">上级部门</th>
|
||||
<th scope="col" class="px-6 py-3">部门名称</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="department in departments" :key="department.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + department.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + department.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
{{ !department.parentName ? '无' : department.parentName }}
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 ">
|
||||
{{ department.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertDepartmentClick(department)"
|
||||
class="flex items-center justify-center min-w-25 gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center justify-center min-w-25 gap-x-1
|
||||
bg-red-700 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5"
|
||||
@click="handleDeleteDepartmentClick(department)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">上级部门</th>
|
||||
<th scope="col" class="px-6 py-3">部门名称</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="department in departments" :key="department.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + department.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + department.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ !department.parentName ? '无' : department.parentName }}
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 ">
|
||||
{{ department.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertDepartmentClick(department)"
|
||||
class="flex items-center gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center block gap-x-1
|
||||
bg-red-700 hover:bg-red-800 focus:outline-none dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 block text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
@click="handleDeleteDepartmentClick(department)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<TablePagination :total="total" :pageChange="handlePageChange" />
|
||||
</div>
|
||||
|
||||
@@ -100,16 +102,18 @@
|
||||
departmentDeleteModal!.hide();
|
||||
}" :onSubmit="handleDeleteDepartmentSubmit" title="确定删除该部门吗" content="删除部门"></DepartmentDeleteModal>
|
||||
<DepartmentUpsertModal :id="'department-upsert-modal'" :onSubmit="handleUpsertDepartmentSubmit" :closeModal="() => {
|
||||
availableDepartments = undefined
|
||||
departmentUpsertModal!.hide();
|
||||
}" :department="selectedDepartment" :allDepartments="allDepartments">
|
||||
}" :department="selectedDepartment" :availableDepartments="availableDepartments">
|
||||
</DepartmentUpsertModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import DepartmentUpsertModal from "@/components/DepartmentUpsertModal.vue";
|
||||
import DepartmentDeleteModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import DepartmentUpsertModal from "@/components/DepartmentUpsertModal.vue";
|
||||
import { RouteName } from "@/router/constants";
|
||||
import type { DepartmentUpsertModel } from "@/types/department";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import type { components } from "../api/types/schema";
|
||||
@@ -117,8 +121,6 @@ import useDepartmentDelete from "../composables/department/useDepartmentDelete";
|
||||
import { useDepartmentQuery } from "../composables/department/useDepartmentQuery";
|
||||
import { useDepartmentUpsert } from "../composables/department/useDepartmentUpsert";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import type { DepartmentUpsertModel } from "@/types/department";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const name = ref<string>("");
|
||||
const selectedDepartment = ref<components["schemas"]["Department"]>();
|
||||
@@ -127,10 +129,10 @@ const departmentDeleteModal = ref<ModalInterface>();
|
||||
|
||||
const {
|
||||
departments,
|
||||
allDepartments,
|
||||
availableDepartments,
|
||||
fetchDepartmentWith,
|
||||
fetchAllDepartments,
|
||||
total,
|
||||
fetchAvailableDepartments,
|
||||
} = useDepartmentQuery();
|
||||
|
||||
const { deleteDepartment } = useDepartmentDelete();
|
||||
@@ -139,7 +141,6 @@ const { upsertDepartment } = useDepartmentUpsert();
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchAllDepartments();
|
||||
await fetchDepartmentWith({
|
||||
name: name.value,
|
||||
});
|
||||
@@ -178,13 +179,13 @@ const handleUpsertDepartmentSubmit = async (
|
||||
await fetchDepartmentWith({
|
||||
name: name.value,
|
||||
});
|
||||
fetchAllDepartments();
|
||||
};
|
||||
|
||||
const handleUpsertDepartmentClick = async (
|
||||
department?: components["schemas"]["Department"],
|
||||
) => {
|
||||
selectedDepartment.value = department;
|
||||
await fetchAvailableDepartments(selectedDepartment.value?.id);
|
||||
await nextTick(() => {
|
||||
departmentUpsertModal.value?.show();
|
||||
});
|
||||
@@ -201,7 +202,6 @@ const handleDeleteDepartmentSubmit = async () => {
|
||||
await fetchDepartmentWith({
|
||||
name: name.value,
|
||||
});
|
||||
fetchAllDepartments();
|
||||
};
|
||||
|
||||
const handleDeleteDepartmentClick = async (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['权限管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">权限管理</h1>
|
||||
@@ -31,66 +31,68 @@
|
||||
新增权限
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">权限名称</th>
|
||||
<th scope="col" class="px-6 py-3">权限编码</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="permission in permissions" :key="permission.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + permission.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + permission.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ permission.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">{{ permission.code }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertPermissionClick(permission)"
|
||||
class="flex items-center block gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center block gap-x-1
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">权限名称</th>
|
||||
<th scope="col" class="px-6 py-3">权限编码</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="permission in permissions" :key="permission.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + permission.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + permission.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ permission.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ permission.code }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertPermissionClick(permission)"
|
||||
class="flex items-center justify-center min-w-25 gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center justify-center min-w-25 gap-x-1
|
||||
bg-red-700 hover:bg-red-800 focus:outline-none dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 block text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
@click="handleDeletePermissionClick(permission)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
focus:ring-red-500 text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 "
|
||||
@click="handleDeletePermissionClick(permission)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<TablePagination :pageChange="handlePageChange" :total="total" />
|
||||
</div>
|
||||
@@ -110,15 +112,14 @@ import PermissionDeleteModal from "@/components/PopupModal.vue";
|
||||
import usePermissionDelete from "@/composables/permission/usePermissionDelete";
|
||||
|
||||
import type { components } from "@/api/types/schema";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import { RouteName } from "@/router/constants";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import usePermissionsQuery from "../composables/permission/usePermissionQuery";
|
||||
import usePermissionUpsert from "../composables/permission/usePermissionUpsert";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import type { PermissionUpsertModel } from "../types/permission";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const permissionName = ref<string>("");
|
||||
const selectedPermission = ref<components["schemas"]["PermissionRespDto"]>();
|
||||
@@ -175,7 +176,7 @@ const handleUpsertPermissionClick = async (
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteModalSubmit = async (event: Event) => {
|
||||
const handleDeleteModalSubmit = async () => {
|
||||
if (!selectedPermission?.value?.id) return;
|
||||
await deletePermission(selectedPermission.value.id);
|
||||
permissionDeleteModal.value?.hide();
|
||||
@@ -183,6 +184,9 @@ const handleDeleteModalSubmit = async (event: Event) => {
|
||||
content: "删除成功",
|
||||
level: "success",
|
||||
});
|
||||
await fetchPermissionsWith({
|
||||
name: permissionName.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeletePermissionClick = async (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['岗位管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">岗位管理</h1>
|
||||
@@ -32,63 +32,65 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">岗位名称</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="position in positions" :key="position.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + position.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + position.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 ">
|
||||
{{ position.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertPositionClick(position)"
|
||||
class="flex items-center block gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center block gap-x-1
|
||||
bg-red-700 hover:bg-red-800 focus:outline-none dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 block text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
@click="handleDeletePositionClick(position)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">岗位名称</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="position in positions" :key="position.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + position.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + position.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 ">
|
||||
{{ position.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertPositionClick(position)"
|
||||
class="flex items-center justify-center min-w-25 gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center justify-center min-w-25 gap-x-1
|
||||
bg-red-700 hover:bg-red-800 dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
@click="handleDeletePositionClick(position)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<TablePagination :total="total" :pageChange="handlePageChange" />
|
||||
</div>
|
||||
|
||||
@@ -103,9 +105,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import PositionDeleteModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import PositionUpsertModal from "@/components/PositionUpsertModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import usePositionDelete from "@/composables/position/usePositionDelete";
|
||||
import { usePositionQuery } from "@/composables/position/usePositionQuery";
|
||||
import { usePositionUpsert } from "@/composables/position/usePositionUpsert";
|
||||
@@ -114,7 +117,6 @@ import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import type { components } from "../api/types/schema";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const name = ref<string>("");
|
||||
const selectedPosition = ref<components["schemas"]["Position"]>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['角色管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">角色管理</h1>
|
||||
@@ -31,77 +31,77 @@
|
||||
新增角色
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">角色名称</th>
|
||||
<th scope="col" class="px-6 py-3">角色编码</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
<th scope="col" class="px-6 py-3">分配</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="role in roles" :key="role.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + role.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + role.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ role.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">{{ role.code }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertRoleClick(role)"
|
||||
class="flex items-center block gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center block gap-x-1
|
||||
<div class="relative overflow-x-auto ">
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">角色名称</th>
|
||||
<th scope="col" class="px-6 py-3">角色编码</th>
|
||||
<th scope="col" class="px-6 py-3">分配</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="role in roles" :key="role.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + role.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + role.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ role.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ role.code }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div>
|
||||
<button
|
||||
class="flex itmes-center justify-center min-w-min text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindPermissionClick(role)" type="button">
|
||||
<span>分配权限</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertRoleClick(role)"
|
||||
class="flex items-center justify-center min-w-25 gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 "
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center justify-center min-w-25 block gap-x-1
|
||||
bg-red-700 hover:bg-red-800 focus:outline-none dark:bg-red-600 dark:hover:bg-red-700
|
||||
focus:ring-red-500 block text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
@click="handleDeleteRoleClick(role)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div>
|
||||
<button
|
||||
class="flex itmes-center text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindPermissionClick(role)" type="button">
|
||||
分配权限
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
focus:ring-red-500 block text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5"
|
||||
@click="handleDeleteRoleClick(role)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<TablePagination :pageChange="handlePageChange" :total="total" />
|
||||
</div>
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import RoleDeleteModal from "@/components/PopupModal.vue";
|
||||
import RoleUpsertModal from "@/components/RoleUpsertModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
@@ -128,7 +129,6 @@ import { useRouter } from "vue-router";
|
||||
import type { components } from "../api/types/schema";
|
||||
import { useRoleUpsert } from "../composables/role/useRoleUpsert";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const roleName = ref<string>("");
|
||||
const selectedRole = ref<components["schemas"]["RoleDto"]>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['任务管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">任务管理</h1>
|
||||
@@ -27,86 +27,97 @@
|
||||
<!-- Create Modal toggle -->
|
||||
</div>
|
||||
|
||||
<table
|
||||
class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400 overflow-x-auto">
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">任务</th>
|
||||
<th scope="col" class="px-6 py-3">触发器</th>
|
||||
<th scope="col" class="px-6 py-3">开始</th>
|
||||
<th scope="col" class="px-6 py-3">结束</th>
|
||||
<th scope="col" class="px-6 py-3">下次执行</th>
|
||||
<th scope="col" class="px-6 py-3">上次执行</th>
|
||||
<th scope="col" class="px-6 py-3">类型</th>
|
||||
<th scope="col" class="px-6 py-3">Cron</th>
|
||||
<th scope="col" class="px-6 py-3">状态</th>
|
||||
<th scope="col" class="px-6 py-3">编辑</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="job in jobs" :key="job.triggerName"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + job.name" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + job.name" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{{
|
||||
`${job.name}:${job.group}` }}</td>
|
||||
<td class="px-6 py-4">{{ `${job.triggerName}:${job.triggerGroup}` }}</td>
|
||||
<td class="px-6 py-4">{{ new Date(job.startTime!).toLocaleString() }}</td>
|
||||
<td class="px-6 py-4">{{ job.endTime ? new Date(job.endTime).toLocaleString() : undefined }}</td>
|
||||
<td class="px-6 py-4">{{ job.nextFireTime ? new Date(job.nextFireTime).toLocaleString() : undefined}}</td>
|
||||
<td class="px-6 py-4">{{ job.previousFireTime && job.previousFireTime > 0 ? new
|
||||
Date(job.previousFireTime).toLocaleString() :
|
||||
undefined
|
||||
}}
|
||||
</td>
|
||||
<td class="px-6 py-4">{{ job.schedulerType }}</td>
|
||||
<td class="px-6 py-4">{{ job.cronExpression }}</td>
|
||||
<td class="px-6 py-4">{{ job.triggerState }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleCronUpdateClick(job)" :disabled="job.schedulerType !== 'CRON'"
|
||||
:class="['flex items-center gap-x-1 block text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800' , { 'opacity-50 cursor-not-allowed': job.schedulerType !== 'CRON' }]"
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button
|
||||
:class="['text-white bg-green-700 hover:bg-green-800 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-900 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center']"
|
||||
@click="handleResumeJobClick(job)" type="button">
|
||||
恢复
|
||||
</button>
|
||||
<button
|
||||
:class="['bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900 text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center']"
|
||||
@click="handlePauseJobClick(job)" type="button">
|
||||
暂停
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="relative overflow-x-auto">
|
||||
<table
|
||||
class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400 overflow-x-auto">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">任务</th>
|
||||
<th scope="col" class="px-6 py-3">触发器</th>
|
||||
<th scope="col" class="px-6 py-3">开始</th>
|
||||
<th scope="col" class="px-6 py-3">结束</th>
|
||||
<th scope="col" class="px-6 py-3">下次执行</th>
|
||||
<th scope="col" class="px-6 py-3">上次执行</th>
|
||||
<th scope="col" class="px-6 py-3">类型</th>
|
||||
<th scope="col" class="px-6 py-3">Cron</th>
|
||||
<th scope="col" class="px-6 py-3">状态</th>
|
||||
<th scope="col" class="px-6 py-3">编辑</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="job in jobs" :key="job.triggerName"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + job.triggerName" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + job.triggerName" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{{
|
||||
`${job.name}:${job.group}` }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{
|
||||
`${job.triggerName}:${job.triggerGroup}` }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ new
|
||||
Date(job.startTime!).toLocaleString() }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.endTime ? new
|
||||
Date(job.endTime).toLocaleString() : undefined }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.nextFireTime ? new
|
||||
Date(job.nextFireTime).toLocaleString() : undefined}}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.previousFireTime &&
|
||||
job.previousFireTime
|
||||
> 0 ? new
|
||||
Date(job.previousFireTime).toLocaleString() :
|
||||
undefined
|
||||
}}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.schedulerType }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.cronExpression }}</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">{{ job.triggerState }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleCronUpdateClick(job)" :disabled="job.schedulerType !== 'CRON'"
|
||||
:class="['flex items-center justify-center gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800' , { 'opacity-50 cursor-not-allowed': job.schedulerType !== 'CRON' }]"
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center justify-center gap-x-2">
|
||||
<button
|
||||
:class="['text-white bg-green-700 hover:bg-green-800 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-900 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center']"
|
||||
@click="handleResumeJobClick(job)" type="button">
|
||||
<span>恢复</span>
|
||||
</button>
|
||||
<button
|
||||
:class="['bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900 text-white focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center']"
|
||||
@click="handlePauseJobClick(job)" type="button">
|
||||
<span>暂停</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<TablePagination :pageChange="handlePageChange" :total="total" />
|
||||
</div>
|
||||
|
||||
@@ -122,18 +133,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SchedulerUpdateModal from "@/components/SchedulerUpdateModal.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import PopupModal from "@/components/PopupModal.vue";
|
||||
import SchedulerUpdateModal from "@/components/SchedulerUpdateModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import { useJobControl } from "@/composables/job/useJobControl";
|
||||
import { useJobsPaginationQuery } from "@/composables/job/useJobQuery";
|
||||
import { useJobUpdate } from "@/composables/job/useJobUpdate";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import { RouteName } from "@/router/constants";
|
||||
import { Modal, type ModalInterface, initFlowbite } from "flowbite";
|
||||
import { nextTick, onMounted, ref } from "vue";
|
||||
import type { components } from "../api/types/schema";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const jobName = ref<string>("");
|
||||
const jobResumeModal = ref<ModalInterface>();
|
||||
|
||||
@@ -6,33 +6,6 @@
|
||||
</div>
|
||||
<!-- Right Content -->
|
||||
<div class="col-span-full xl:col-auto">
|
||||
<div
|
||||
class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
|
||||
<div class="items-center sm:flex xl:block 2xl:flex sm:space-x-4 xl:space-x-0 2xl:space-x-4">
|
||||
<img class="mb-4 rounded-lg w-28 h-28 sm:mb-0 xl:mb-4 2xl:mb-0" src="/trump.jpg" alt="Jese picture">
|
||||
<div>
|
||||
<h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">个人资料</h3>
|
||||
<div class="mb-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
JPG, GIF or PNG. Max size of 800K
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button type="button" disabled
|
||||
class="cursor-not-allowed inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white rounded-lg bg-blue-400 dark:bg-blue-500 ">
|
||||
<svg class="w-4 h-4 mr-2 -ml-1" fill="currentColor" viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.5 13a3.5 3.5 0 01-.369-6.98 4 4 0 117.753-1.977A4.5 4.5 0 1113.5 13H11V9.413l1.293 1.293a1 1 0 001.414-1.414l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L9 9.414V13H5.5z">
|
||||
</path>
|
||||
<path d="M9 13h2v5a1 1 0 11-2 0v-5z"></path>
|
||||
</svg>
|
||||
Upload picture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-span-1 row-start-3">
|
||||
<div
|
||||
@@ -62,8 +35,9 @@
|
||||
</div>
|
||||
<div class="col-span-6 ">
|
||||
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">状态</label>
|
||||
<select id="category" v-model="userForm.enable"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
|
||||
<select id="category" disabled v-model="userForm.enable"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5
|
||||
opacity-50 cursor-not-allowed dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
|
||||
<option :value=true>启用</option>
|
||||
<option :value=false>禁用</option>
|
||||
</select>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative overflow-x-auto px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="px-4 pt-6 xl:grid-cols-3 xl:gap-4 sm:rounded-lg">
|
||||
<div class="mb-4 col-span-full">
|
||||
<Breadcrumbs :names="['用户管理']" />
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">用户管理</h1>
|
||||
@@ -31,101 +31,98 @@
|
||||
新增用户
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">用户名</th>
|
||||
<th scope="col" class="px-6 py-3">邮箱</th>
|
||||
<th scope="col" class="px-6 py-3">创建时间</th>
|
||||
<th scope="col" class="px-6 py-3">状态</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
<th scope="col" class="px-6 py-3">分配</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + user.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + user.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ user.username }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ user.username }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ user.createTime }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="user.enable ? 'bg-blue-500' : 'bg-red-500'"></div> {{
|
||||
user.enable === true ? "启用" : "禁用" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<!-- Edit Modal toggle -->
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertUserClick(user)"
|
||||
class="flex items-center block gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center block gap-x-1
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left rtl:text-right shadow-lg rounded-lg text-gray-500 dark:text-gray-400">
|
||||
<thead class="text-xs uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
||||
<tr>
|
||||
<th scope="col" class="p-4">
|
||||
<div class="flex items-center">
|
||||
<input id="checkbox-all-search" disabled type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label for="checkbox-all-search" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">用户名</th>
|
||||
<th scope="col" class="px-6 py-3">创建时间</th>
|
||||
<th scope="col" class="px-6 py-3">状态</th>
|
||||
<th scope="col" class="px-6 py-3">分配</th>
|
||||
<th scope="col" class="px-6 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id"
|
||||
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<td class="w-4 p-4">
|
||||
<div class="flex items-center">
|
||||
<input :id="'checkbox-table-search-' + user.id" type="checkbox" disabled
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600">
|
||||
<label :for="'checkbox-table-search-' + user.id" class="sr-only">checkbox</label>
|
||||
</div>
|
||||
</td>
|
||||
<td scope="row"
|
||||
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white max-w-sm overflow-hidden text-ellipsis">
|
||||
{{ user.username }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
{{ user.createTime }}
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full me-2" :class="user.enable ? 'bg-blue-500' : 'bg-red-500'"></div> {{
|
||||
user.enable === true ? "启用" : "禁用" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button
|
||||
class="text-gray-900 bg-white border min-w-25 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindRoleClick(user)" type="button">
|
||||
分配角色
|
||||
</button>
|
||||
<button
|
||||
class="text-gray-900 bg-white border min-w-25 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindPositionClick(user)" type="button">
|
||||
分配岗位
|
||||
</button>
|
||||
<button
|
||||
class="text-gray-900 bg-white border min-w-25 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindDepartmentClick(user)" type="button">
|
||||
分配部门
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 max-w-sm overflow-hidden text-ellipsis">
|
||||
<!-- Edit Modal toggle -->
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button @click="handleUpsertUserClick(user)"
|
||||
class="flex items-center justify-center min-w-25 gap-x-1 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path>
|
||||
<path fill-rule="evenodd"
|
||||
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center justify-center min-w-25 gap-x-1
|
||||
bg-red-700 hover:bg-red-800 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700
|
||||
dark:focus:ring-red-900 block text-white focus:ring-4 focus:outline-nonefont-medium rounded-lg text-sm px-4 py-2.5 text-center"
|
||||
@click="handleDeleteUserClick(user)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<button
|
||||
class="flex itmes-center text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindRoleClick(user)" type="button">
|
||||
分配角色
|
||||
</button>
|
||||
<button
|
||||
class="flex itmes-center text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindPositionClick(user)" type="button">
|
||||
分配岗位
|
||||
</button>
|
||||
<button
|
||||
class="flex itmes-center text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-4 py-2.5 text-center dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
|
||||
@click="handleBindDepartmentClick(user)" type="button">
|
||||
分配部门
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
dark:focus:ring-red-900 text-white focus:ring-4 focus:outline-nonefont-medium rounded-lg text-sm px-4 py-2.5"
|
||||
@click="handleDeleteUserClick(user)" type="button">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span>删除</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<TablePagination :pageChange="handlePageChange" :total="total" />
|
||||
</div>
|
||||
|
||||
@@ -139,7 +136,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
import UserDeleteModal from "@/components/PopupModal.vue";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import UserUpsertModal from "@/components/UserUpsertModal.vue";
|
||||
import useUserDelete from "@/composables/user/useUserDelete";
|
||||
import { useUserQuery } from "@/composables/user/useUserQuery";
|
||||
@@ -151,8 +150,6 @@ import { useRouter } from "vue-router";
|
||||
import type { components } from "../api/types/schema";
|
||||
import useAlertStore from "../composables/store/useAlertStore";
|
||||
import { useUserUpsert } from "../composables/user/useUserUpsert";
|
||||
import TablePagination from "@/components/TablePagination.vue";
|
||||
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
||||
|
||||
const username = ref<string>("");
|
||||
const selectedUser = ref<components["schemas"]["UserRolePermissionDto"]>();
|
||||
|
||||
Reference in New Issue
Block a user