From 6163c3f08e9b8c9cd1cd564fb3e0133926c955e7 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 28 Feb 2026 00:49:02 +0800 Subject: [PATCH] chore: join project --- packages/ui/certd-client/src/api/tools.ts | 18 +-- .../src/locales/langs/en-US/certd.ts | 4 + .../src/locales/langs/zh-CN/certd.ts | 8 +- packages/ui/certd-client/src/router/guard.ts | 6 +- .../src/router/source/modules/certd.ts | 8 +- .../ui/certd-client/src/views/certd/dicts.ts | 24 +++ .../src/views/certd/project/blank.vue | 122 -------------- .../src/views/certd/project/join.vue | 153 ++++++++++++++++++ .../db/migration/v10038__admin_mode.sql | 1 + .../user/enterprise/project-controller.ts | 80 +++++++++ .../sys/enterprise/entity/project-member.ts | 3 + .../modules/sys/enterprise/entity/project.ts | 6 + .../service/project-member-service.ts | 5 +- .../sys/enterprise/service/project-service.ts | 87 +++++++++- 14 files changed, 379 insertions(+), 146 deletions(-) delete mode 100644 packages/ui/certd-client/src/views/certd/project/blank.vue create mode 100644 packages/ui/certd-client/src/views/certd/project/join.vue diff --git a/packages/ui/certd-client/src/api/tools.ts b/packages/ui/certd-client/src/api/tools.ts index 193a570c1..f889197eb 100644 --- a/packages/ui/certd-client/src/api/tools.ts +++ b/packages/ui/certd-client/src/api/tools.ts @@ -19,30 +19,30 @@ export function parse(jsonString = "{}", defaultValue = {}) { /** * @description 接口请求返回 * @param {Any} data 返回值 - * @param {String} msg 状态信息 + * @param {String} message 状态信息 * @param {Number} code 状态码 */ -export function response(data = {}, msg = "", code = 0) { - return [200, { code, msg, data }]; +export function response(data = {}, message = "", code = 0) { + return [200, { code, message, data }]; } /** * @description 接口请求返回 正确返回 * @param {Any} data 返回值 - * @param {String} msg 状态信息 + * @param {String} message 状态信息 */ -export function responseSuccess(data = {}, msg = "成功") { - return response(data, msg); +export function responseSuccess(data = {}, message = "成功") { + return response(data, message); } /** * @description 接口请求返回 错误返回 * @param {Any} data 返回值 - * @param {String} msg 状态信息 + * @param {String} message 状态信息 * @param {Number} code 状态码 */ -export function responseError(data = {}, msg = "请求失败", code = 500) { - return response(data, msg, code); +export function responseError(data = {}, message = "请求失败", code = 500) { + return response(data, message, code); } /** diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index 585816573..5d56e96cb 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -218,6 +218,7 @@ export default { projectUserManager: "Project User Management", myProjectManager: "My Projects", myProjectDetail: "Project Detail", + projectJoin: "Join Project", }, certificateRepo: { title: "Certificate Repository", @@ -830,6 +831,9 @@ export default { fetchFailed: "Failed to fetch project list", applySuccess: "Application successful, waiting for admin approval", applyFailed: "Application failed, please try again later", + leave: "Leave Project", + leaveSuccess: "Leave project successful", + leaveFailed: "Leave project failed, please try again later", }, addonSelector: { select: "Select", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index 803e51b48..1d2d8b819 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -224,6 +224,7 @@ export default { enterpriseSetting: "企业设置", myProjectManager: "我的项目", myProjectDetail: "项目详情", + projectJoin: "加入项目", }, certificateRepo: { title: "证书仓库", @@ -839,12 +840,15 @@ export default { project: { noProjectJoined: "您还没有加入任何项目", applyToJoin: "请申请加入项目以开始使用", - systemProjects: "系统项目列表", + projectList: "项目列表", createdAt: "创建时间", applyJoin: "申请加入", - noSystemProjects: "暂无系统项目", + noProjects: "暂无项目", fetchFailed: "获取项目列表失败", applySuccess: "申请成功,等待管理员审核", applyFailed: "申请失败,请稍后重试", + leave: "退出项目", + leaveSuccess: "退出项目成功", + leaveFailed: "退出项目失败,请稍后重试", }, }; diff --git a/packages/ui/certd-client/src/router/guard.ts b/packages/ui/certd-client/src/router/guard.ts index ab370f432..12bc772bc 100644 --- a/packages/ui/certd-client/src/router/guard.ts +++ b/packages/ui/certd-client/src/router/guard.ts @@ -10,7 +10,7 @@ import { usePermissionStore } from "/@/plugin/permission/store.permission"; import util from "/@/plugin/permission/util.permission"; import { useUserStore } from "/@/store/user"; import { useProjectStore } from "../store/project"; -export const PROJECT_BLANK_PATH = "/certd/project/blank"; +export const PROJECT_JOIN_PATH = "/certd/project/join"; function buildAccessedMenus(menus: any) { if (menus == null) { return; @@ -131,10 +131,10 @@ function setupAccessGuard(router: Router) { if (projectStore.isEnterprise) { //加载我的项目 await projectStore.init(); - if (!projectStore.currentProject && to.path !== PROJECT_BLANK_PATH) { + if (!projectStore.currentProject && to.path !== PROJECT_JOIN_PATH) { //没有项目 return { - path: PROJECT_BLANK_PATH, + path: PROJECT_JOIN_PATH, replace: true, }; } diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index c7582f0f2..990aac7eb 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -43,10 +43,10 @@ export const certdResources = [ }, }, { - title: "certd.sysResources.myProjectBlank", - name: "MyProjectBlank", - path: "/certd/project/blank", - component: "/certd/project/blank.vue", + title: "certd.sysResources.projectJoin", + name: "ProjectJoin", + path: "/certd/project/join", + component: "/certd/project/join.vue", meta: { isMenu: false, show: true, diff --git a/packages/ui/certd-client/src/views/certd/dicts.ts b/packages/ui/certd-client/src/views/certd/dicts.ts index 4b22d682a..3ce74fa29 100644 --- a/packages/ui/certd-client/src/views/certd/dicts.ts +++ b/packages/ui/certd-client/src/views/certd/dicts.ts @@ -24,6 +24,29 @@ const projectPermissionDict = dict({ ], }); +const projectMemberStatusDict = dict({ + data: [ + { + value: "pending", + label: "待审核", + color: "orange", + icon: "material-symbols:hourglass-top", + }, + { + value: "approved", + label: "已加入", + color: "green", + icon: "material-symbols:done-all", + }, + { + value: "rejected", + label: "已拒绝", + color: "red", + icon: "material-symbols:close", + }, + ], +}); + const myProjectDict = dict({ url: "/enterprise/project/list", getData: async () => { @@ -56,5 +79,6 @@ export function useDicts() { projectPermissionDict, myProjectDict, userDict, + projectMemberStatusDict, }; } diff --git a/packages/ui/certd-client/src/views/certd/project/blank.vue b/packages/ui/certd-client/src/views/certd/project/blank.vue deleted file mode 100644 index 53d783fec..000000000 --- a/packages/ui/certd-client/src/views/certd/project/blank.vue +++ /dev/null @@ -1,122 +0,0 @@ - - - - - diff --git a/packages/ui/certd-client/src/views/certd/project/join.vue b/packages/ui/certd-client/src/views/certd/project/join.vue new file mode 100644 index 000000000..56150dd66 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/project/join.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/packages/ui/certd-server/db/migration/v10038__admin_mode.sql b/packages/ui/certd-server/db/migration/v10038__admin_mode.sql index 8f81bdb16..8e7a88f9f 100644 --- a/packages/ui/certd-server/db/migration/v10038__admin_mode.sql +++ b/packages/ui/certd-server/db/migration/v10038__admin_mode.sql @@ -82,6 +82,7 @@ CREATE TABLE "cd_project_member" "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) ); +ALTER TABLE cd_project_member ADD COLUMN status varchar(128); CREATE INDEX "index_project_member_user_id" ON "cd_project_member" ("user_id"); CREATE INDEX "index_project_member_project_id" ON "cd_project_member" ("project_id"); diff --git a/packages/ui/certd-server/src/controller/user/enterprise/project-controller.ts b/packages/ui/certd-server/src/controller/user/enterprise/project-controller.ts index 66fbb60a7..61803aaea 100644 --- a/packages/ui/certd-server/src/controller/user/enterprise/project-controller.ts +++ b/packages/ui/certd-server/src/controller/user/enterprise/project-controller.ts @@ -2,6 +2,7 @@ import { BaseController, Constants } from '@certd/lib-server'; import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; import { AuthService } from '../../../modules/sys/authority/service/auth-service.js'; import { ProjectService } from '../../../modules/sys/enterprise/service/project-service.js'; +import { ProjectMemberService } from '../../../modules/sys/enterprise/service/project-member-service.js'; /** */ @@ -10,6 +11,10 @@ import { ProjectService } from '../../../modules/sys/enterprise/service/project- export class UserProjectController extends BaseController { @Inject() service: ProjectService; + + @Inject() + projectMemberService: ProjectMemberService; + @Inject() authService: AuthService; @@ -17,6 +22,11 @@ export class UserProjectController extends BaseController { return this.service; } + /** + * 我的项目 + * @param body + * @returns + */ @Post('/list', { summary: Constants.per.authOnly }) async list(@Body(ALL) body: any) { const userId= this.getUserId(); @@ -24,4 +34,74 @@ export class UserProjectController extends BaseController { return this.ok(res); } + + /** + * + * @param body 所有项目 + * @returns + */ + @Post('/all', { summary: Constants.per.authOnly }) + async all(@Body(ALL) body: any) { + const userId= this.getUserId(); + const res = await this.service.getAllWithStatus(userId); + return this.ok(res); + } + + @Post('/applyJoin', { summary: Constants.per.authOnly }) + async applyJoin(@Body(ALL) body: any) { + const userId= this.getUserId(); + const res = await this.service.applyJoin({ userId, projectId: body.projectId }); + return this.ok(res); + } + + @Post('/updateMember', { summary: Constants.per.authOnly }) + async updateMember(@Body(ALL) body: any) { + const {projectId} = await this.getProjectUserIdAdmin(); + const {status,permission,userId} = body; + const member = await this.projectMemberService.findOne({ + where: { + projectId, + userId, + }, + }); + if (!member) { + throw new Error('成员不存在'); + } + const res = await this.projectMemberService.update({ + id: member.id, + status, + permission, + }); + return this.ok(res); + } + + @Post('/approveJoin', { summary: Constants.per.authOnly }) + async approveJoin(@Body(ALL) body: any) { + const {projectId} = await this.getProjectUserIdAdmin(); + const {status,permission,userId} = body; + const res = await this.service.approveJoin({ userId, projectId: projectId,status,permission }); + return this.ok(res); + } + + @Post('/delete', { summary: Constants.per.authOnly }) + async delete(@Body(ALL) body: any) { + const {projectId} = await this.getProjectUserIdAdmin(); + await this.projectMemberService.deleteWhere({ + projectId, + userId: this.getUserId(), + }); + return this.ok(); + } + + @Post('/leave', { summary: Constants.per.authOnly }) + async leave(@Body(ALL) body: any) { + const {projectId} = body + const userId = this.getUserId(); + await this.projectMemberService.deleteWhere({ + projectId, + userId, + }); + return this.ok(); + } + } diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project-member.ts b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project-member.ts index b0fb8a60d..516b65b8c 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project-member.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project-member.ts @@ -16,6 +16,9 @@ export class ProjectMemberEntity { @Column({ name: 'permission', comment: '权限' }) permission: string; // read / write / admin + @Column({ name: 'status', comment: '申请状态' }) + status: string; // pending / approved / rejected + @Column({ name: 'create_time', comment: '创建时间', diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts index 23a81fe95..52c12f015 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/entity/project.ts @@ -34,4 +34,10 @@ export class ProjectEntity { // user permission read write admin permission:string + } + +export type ProjectMemberItem = { + memberId: number; + status: string; +} & ProjectEntity diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts index d005494be..d70340315 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-member-service.ts @@ -18,7 +18,7 @@ export class ProjectMemberService extends BaseService { return this.repository; } - async add(bean: ProjectMemberEntity) { + async add(bean: Partial) { const {projectId, userId} = bean; if (!projectId) { throw new Error('项目ID不能为空'); @@ -38,10 +38,11 @@ export class ProjectMemberService extends BaseService { return await super.add(bean) } - async getByUserId(userId: number) { + async getByUserId(userId: number,status?:string) { return await this.repository.find({ where: { userId, + status, }, }); } diff --git a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts index c959f3c02..d99f4a625 100644 --- a/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts +++ b/packages/ui/certd-server/src/modules/sys/enterprise/service/project-service.ts @@ -3,7 +3,7 @@ import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { LRUCache } from 'lru-cache'; import { Repository } from 'typeorm'; -import { ProjectEntity } from '../entity/project.js'; +import { ProjectEntity, ProjectMemberItem } from '../entity/project.js'; import { ProjectMemberService } from './project-member-service.js'; const projectCache = new LRUCache({ @@ -65,7 +65,7 @@ export class ProjectService extends BaseService { async getUserProjects(userId: number) { - const memberList = await this.projectMemberService.getByUserId(userId); + const memberList = await this.projectMemberService.getByUserId(userId,'approved'); const projectIds = memberList.map(item => item.projectId); const projectList = await this.repository.createQueryBuilder('project') .where(' project.disabled = false') @@ -89,6 +89,39 @@ export class ProjectService extends BaseService { return projectList } + async getAllWithStatus(userId: number) : Promise { + let projectList:any = await this.find({ + where: { + disabled: false, + userId: 0, + }, + }) + const projectMemberItemList:ProjectMemberItem[] = projectList + + const memberList = await this.projectMemberService.getByUserId(userId); + + const memberMap = memberList.reduce((prev, cur) => { + prev[cur.projectId] = cur as any; + return prev; + }, {} as Record); + + projectMemberItemList.forEach(item => { + if (item.adminId === userId) { + item.permission = 'admin'; + item.status = 'approved'; + item.memberId = userId + } else { + const memberItem :any = memberMap[item.id] + if (memberItem) { + item.permission = memberItem.permission; + item.status = memberItem.status; + item.memberId = memberItem.userId + } + } + }) + return projectMemberItemList + } + async checkAdminPermission({ userId, projectId }: { userId: number, projectId: number }) { return await this.checkPermission({ userId, @@ -143,8 +176,8 @@ export class ProjectService extends BaseService { throw new Error('项目已禁用'); } const member = await this.projectMemberService.getMember(projectId, userId); - if (!member) { - throw new Error(`用户${userId}不是该项目${projectId}成员`); + if (!member || member.status !== 'approved') { + throw new Error(`用户${userId}还不是项目${projectId}的成员`); } savedPermission = member.permission; } @@ -169,4 +202,50 @@ export class ProjectService extends BaseService { } return true } + + + async applyJoin({ userId, projectId }: { userId: number, projectId: number }) { + const project = await this.info(projectId); + if (!project) { + throw new Error('项目不存在'); + } + if (project.disabled) { + throw new Error('项目已禁用'); + } + if (project.adminId === userId) { + throw new Error('申请用户已经是该项目的管理员'); + } + const member = await this.projectMemberService.getMember(projectId, userId); + if (member && member.status === 'approved') { + throw new Error('用户已加入项目'); + } + if (member){ + this.projectMemberService.update({ + id: member.id, + status: 'pending', + }) + }else{ + // 加入项目 + await this.projectMemberService.add({ + userId, + projectId, + permission: 'read', + status: 'pending', + }) + } + } + + async approveJoin({ userId, projectId,status,permission }: { userId: number, projectId: number,status:string,permission:string }) { + const member = await this.projectMemberService.getMember(projectId, userId); + if (!member) { + throw new Error('找不到用户的申请记录'); + } + + await this.projectMemberService.update({ + id: member.id, + status: status, + permission, + }) + } + }