feat: 支持企业级管理模式,项目管理,细分权限

This commit is contained in:
xiaojunnuo
2026-02-10 01:57:11 +08:00
parent d1a8dd7817
commit 37340838b6
35 changed files with 559 additions and 135 deletions
@@ -16,6 +16,7 @@ import { defineAsyncComponent } from "vue";
import NotificationSelector from "../views/certd/notification/notification-selector/index.vue"; import NotificationSelector from "../views/certd/notification/notification-selector/index.vue";
import EmailSelector from "./email-selector/index.vue"; import EmailSelector from "./email-selector/index.vue";
import ValidTimeFormat from "./valid-time-format.vue"; import ValidTimeFormat from "./valid-time-format.vue";
import ProjectSelector from "./project-selector/index.vue";
export default { export default {
install(app: any) { install(app: any) {
app.component( app.component(
@@ -45,5 +46,6 @@ export default {
app.component("ExpiresTimeText", ExpiresTimeText); app.component("ExpiresTimeText", ExpiresTimeText);
app.use(vip); app.use(vip);
app.use(Plugins); app.use(Plugins);
app.component("ProjectSelector", ProjectSelector);
}, },
}; };
@@ -0,0 +1,9 @@
import { request } from "/src/api/service";
export async function MyProjectList() {
return await request({
url: "/enterprise/project/list",
method: "post",
data: {},
});
}
@@ -0,0 +1,43 @@
<template>
<a-dropdown class="project-selector">
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item v-for="item in projectStore.myProjects" :key="item.id">
{{ item.name }}
</a-menu-item>
</a-menu>
</template>
<div class="rounded pl-2 pr-2 px-2 py-1 flex-center flex pointer bg-accent h-10 button-text">
{{ projectStore.currentProject?.name || "..." }}
<DownOutlined class="ml-1" />
</div>
</a-dropdown>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useProjectStore } from "/@/store/project";
defineOptions({
name: "ProjectSelector",
});
const projectStore = useProjectStore();
onMounted(async () => {
await projectStore.reload();
});
function handleMenuClick({ key }: any) {
projectStore.changeCurrentProject(key);
}
</script>
<style lang="less">
.project-selector {
&.button-text {
min-width: 100px;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
@@ -81,6 +81,11 @@ provide("fn:ai.open", openChat);
<template> <template>
<BasicLayout @clear-preferences-and-logout="handleLogout"> <BasicLayout @clear-preferences-and-logout="handleLogout">
<template #header-left-0>
<div class="ml-1 mr-2">
<project-selector class="flex-center header-btn" />
</div>
</template>
<template #user-dropdown> <template #user-dropdown>
<UserDropdown :avatar="avatar" :menus="menus" :text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text="" @logout="handleLogout" /> <UserDropdown :avatar="avatar" :menus="menus" :text="userStore.userInfo?.nickName || userStore.userInfo?.username" description="" tag-text="" @logout="handleLogout" />
</template> </template>
@@ -160,6 +160,8 @@ export default {
updateTime: "Update Time", updateTime: "Update Time",
triggerType: "Trigger Type", triggerType: "Trigger Type",
pipelineId: "Pipeline Id", pipelineId: "Pipeline Id",
projectName: "Project",
adminId: "Admin",
}, },
pi: { pi: {
@@ -790,6 +792,10 @@ export default {
pipelineSetting: "Pipeline Settings", pipelineSetting: "Pipeline Settings",
oauthSetting: "OAuth2 Settings", oauthSetting: "OAuth2 Settings",
networkSetting: "Network Settings", networkSetting: "Network Settings",
adminModeSetting: "Admin Mode Settings",
adminModeHelper: "enterprise mode : allow to create and manage pipelines, roles, users, etc.\n saas mode : only allow to create and manage pipelines",
enterpriseMode: "Enterprise Mode",
saasMode: "SaaS Mode",
showRunStrategy: "Show RunStrategy", showRunStrategy: "Show RunStrategy",
showRunStrategyHelper: "Allow modify the run strategy of the task", showRunStrategyHelper: "Allow modify the run strategy of the task",
@@ -868,12 +874,20 @@ export default {
fromType: "From Type", fromType: "From Type",
expirationDate: "Expiration Date", expirationDate: "Expiration Date",
}, },
ent: {
projectName: "Project Name",
projectDescription: "Project Description",
projectDetailManager: "Project Detail",
projectDetailDescription: "Manage Project Members",
projectPermission: "Permission",
permission: {
read: "Read",
write: "Write",
admin: "Admin",
},
},
addonSelector: { addonSelector: {
select: "Select", select: "Select",
placeholder: "select please", placeholder: "select please",
}, },
adminMode: {
enterpriseMode: "Enterprise Mode",
saasMode: "SaaS Mode",
},
}; };
@@ -167,6 +167,8 @@ export default {
updateTime: "更新时间", updateTime: "更新时间",
triggerType: "触发类型", triggerType: "触发类型",
pipelineId: "流水线Id", pipelineId: "流水线Id",
projectName: "项目",
adminId: "管理员",
}, },
pi: { pi: {
validTime: "流水线有效期", validTime: "流水线有效期",
@@ -214,9 +216,10 @@ export default {
orderManager: "订单管理", orderManager: "订单管理",
userSuites: "用户套餐", userSuites: "用户套餐",
netTest: "网络测试", netTest: "网络测试",
enterpriseSetting: "企业管理设置", enterpriseManager: "企业管理设置",
projectManager: "项目管理", projectManager: "项目管理",
projectUserManager: "项目用户管理", projectDetail: "项目详情",
enterpriseSetting: "企业设置",
}, },
certificateRepo: { certificateRepo: {
title: "证书仓库", title: "证书仓库",
@@ -796,6 +799,12 @@ export default {
pipelineSetting: "流水线设置", pipelineSetting: "流水线设置",
oauthSetting: "第三方登录", oauthSetting: "第三方登录",
networkSetting: "网络设置", networkSetting: "网络设置",
adminModeSetting: "管理模式",
adminModeHelper: "企业管理模式: 企业内部使用,通过项目来隔离权限,流水线、授权数据属于项目。\nsaas模式:供外部用户注册使用,各个用户之间数据隔离,流水线、授权数据属于用户。",
adminMode: "管理模式",
enterpriseMode: "企业模式",
saasMode: "SaaS模式",
showRunStrategy: "显示运行策略选择", showRunStrategy: "显示运行策略选择",
showRunStrategyHelper: "任务设置中是否允许选择运行策略", showRunStrategyHelper: "任务设置中是否允许选择运行策略",
@@ -886,11 +895,16 @@ export default {
select: "选择", select: "选择",
placeholder: "请选择", placeholder: "请选择",
}, },
adminMode: {
enterpriseMode: "企业模式",
saasMode: "SaaS模式",
},
ent: { ent: {
projectName: "项目名称", projectName: "项目名称",
projectDescription: "项目描述",
projectDetailManager: "项目详情",
projectDetailDescription: "管理项目成员",
projectPermission: "权限",
permission: {
read: "读取",
write: "写入",
admin: "管理员",
},
}, },
}; };
@@ -94,59 +94,6 @@ export const certdResources = [
keepAlive: true, keepAlive: true,
}, },
}, },
{
title: "certd.sysResources.enterpriseManager",
name: "EnterpriseManager",
path: "/sys/enterprise",
redirect: "/sys/enterprise/project",
meta: {
icon: "ion:cart-outline",
permission: "sys:settings:edit",
show: () => {
const settingStore = useSettingStore();
return settingStore.isEnterprise;
},
keepAlive: true,
},
children: [
{
title: "certd.sysResources.projectManager",
name: "ProjectManager",
path: "/sys/enterprise/project",
component: "/sys/enterprise/project/index.vue",
meta: {
show: true,
icon: "ion:cart",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "certd.sysResources.projectMemberManager",
name: "ProjectMemberManager",
path: "/sys/enterprise/project/member",
component: "/sys/enterprise/project/member/index.vue",
meta: {
isMenu: false,
show: true,
icon: "ion:cart",
permission: "sys:settings:edit",
},
},
{
title: "certd.sysResources.enterpriseSetting",
name: "EnterpriseSetting",
path: "/sys/enterprise/setting",
redirect: "/sys/settings?tab=mode",
meta: {
isMenu: true,
show: true,
icon: "ion:cart",
permission: "sys:settings:edit",
},
},
],
},
{ {
title: "certd.settings", title: "certd.settings",
name: "MineSetting", name: "MineSetting",
@@ -40,6 +40,30 @@ export const sysResources = [
permission: "sys:settings:view", permission: "sys:settings:view",
}, },
}, },
{
title: "certd.sysResources.projectManager",
name: "ProjectManager",
path: "/sys/enterprise/project",
component: "/sys/enterprise/project/index.vue",
meta: {
show: true,
icon: "ion:apps",
permission: "sys:settings:edit",
keepAlive: true,
},
},
{
title: "certd.sysResources.projectDetail",
name: "ProjectDetail",
path: "/sys/enterprise/project/detail",
component: "/sys/enterprise/project/detail/index.vue",
meta: {
isMenu: false,
show: true,
icon: "ion:apps",
permission: "sys:settings:edit",
},
},
{ {
title: "certd.sysResources.cnameSetting", title: "certd.sysResources.cnameSetting",
name: "CnameSetting", name: "CnameSetting",
@@ -0,0 +1,9 @@
import { request } from "/src/api/service";
export async function MyProjectList() {
return await request({
url: "/enterprise/project/list",
method: "post",
data: {},
});
}
@@ -0,0 +1,86 @@
import { defineStore } from "pinia";
import * as api from "./api";
import { message } from "ant-design-vue";
import { computed, ref } from "vue";
import { useSettingStore } from "../settings";
import { LocalStorage } from "/@/utils/util.storage";
export type ProjectItem = {
id: string;
name: string;
permission?: string;
};
export const useProjectStore = defineStore("app.project", () => {
const myProjects = ref([]);
const lastProjectId = LocalStorage.get("currentProjectId");
const currentProjectId = ref(lastProjectId); // 直接调用
const projects = computed(() => {
return myProjects.value;
});
const currentProject = computed(() => {
if (currentProjectId.value) {
const project = projects.value.find(item => item.id === currentProjectId.value);
if (project) {
return project;
}
}
if (projects.value.length > 0) {
return projects.value[0];
}
return null;
});
const settingStore = useSettingStore();
const isEnterprise = computed(() => {
return settingStore.isEnterprise;
});
function getSearchForm() {
if (!currentProjectId.value || !isEnterprise.value) {
return {};
}
return {
projectId: currentProjectId.value,
};
}
async function loadMyProjects(): Promise<ProjectItem[]> {
if (!isEnterprise.value) {
return [];
}
const projects = await api.MyProjectList();
myProjects.value = projects;
}
function changeCurrentProject(id: string) {
currentProjectId.value = id;
LocalStorage.set("currentProjectId", id);
message.success("切换项目成功");
}
async function reload() {
const projects = await api.MyProjectList();
myProjects.value = projects;
}
async function init() {
if (!myProjects.value) {
await reload();
}
return myProjects.value;
}
return {
projects,
myProjects,
currentProject,
isEnterprise,
getSearchForm,
loadMyProjects,
changeCurrentProject,
reload,
init,
};
});
@@ -3,6 +3,8 @@ import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common"; import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@@ -44,6 +46,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
context.typeRef = typeRef; context.typeRef = typeRef;
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
commonColumnsDefine.type.form.component.disabled = true; commonColumnsDefine.type.form.component.disabled = true;
const projectStore = useProjectStore();
return { return {
typeRef, typeRef,
crudOptions: { crudOptions: {
@@ -58,6 +61,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
search: { search: {
show: true, show: true,
initialForm: {
...projectStore.getSearchForm(),
},
}, },
form: { form: {
wrapper: { wrapper: {
@@ -141,6 +147,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
...commonColumnsDefine, ...commonColumnsDefine,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -3,6 +3,8 @@ import { useI18n } from "/src/locales";
import { ref } from "vue"; import { ref } from "vue";
import { getCommonColumnDefine } from "/@/views/certd/access/common"; import { getCommonColumnDefine } from "/@/views/certd/access/common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { projectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@@ -29,6 +31,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const typeRef = ref(); const typeRef = ref();
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
const projectStore = useProjectStore();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@@ -42,6 +45,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
confirmMessage: "授权如果已经被使用,可能会导致流水线无法正常运行,请谨慎操作", confirmMessage: "授权如果已经被使用,可能会导致流水线无法正常运行,请谨慎操作",
}, },
}, },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
rowHandle: { rowHandle: {
width: 200, width: 200,
buttons: { buttons: {
@@ -115,6 +123,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
...commonColumnsDefine, ...commonColumnsDefine,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -6,6 +6,8 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status"; import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { projectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@@ -32,6 +34,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const selectedRowKeys: Ref<any[]> = ref([]); const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys; context.selectedRowKeys = selectedRowKeys;
const projectStore = useProjectStore();
return { return {
crudOptions: { crudOptions: {
settings: { settings: {
@@ -64,6 +68,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
search: { search: {
initialForm: {
...projectStore.getSearchForm(),
},
formItem: { formItem: {
labelCol: { labelCol: {
style: { style: {
@@ -195,17 +202,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
align: "center", align: "center",
}, },
}, },
createTime: { projectId: {
title: t("certd.fields.createTime"), title: t("certd.fields.projectName"),
type: "datetime", type: "dict-select",
form: { dict: projectDict,
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
}, },
updateTime: { updateTime: {
title: t("certd.fields.updateTime"), title: t("certd.fields.updateTime"),
@@ -10,6 +10,8 @@ import { notification } from "ant-design-vue";
import CertView from "/@/views/certd/pipeline/cert-view.vue"; import CertView from "/@/views/certd/pipeline/cert-view.vue";
import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use"; import { useCertUpload } from "/@/views/certd/pipeline/cert-upload/use";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@@ -36,7 +38,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const router = useRouter(); const router = useRouter();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const projectStore = useProjectStore();
const model = useModal(); const model = useModal();
const viewCert = async (row: any) => { const viewCert = async (row: any) => {
const cert = await api.GetCert(row.id); const cert = await api.GetCert(row.id);
@@ -63,6 +65,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const expireStatus = route?.query?.expireStatus as string; const expireStatus = route?.query?.expireStatus as string;
const searchInitForm = { const searchInitForm = {
expiresLeft: expireStatus, expiresLeft: expireStatus,
...projectStore.getSearchForm(),
}; };
return { return {
crudOptions: { crudOptions: {
@@ -344,6 +347,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -13,6 +13,8 @@ import { useSiteImport } from "/@/views/certd/monitor/site/use";
import { ref } from "vue"; import { ref } from "vue";
import GroupSelector from "../../basic/group/group-selector.vue"; import GroupSelector from "../../basic/group/group-selector.vue";
import { createGroupDictRef } from "../../basic/group/api"; import { createGroupDictRef } from "../../basic/group/api";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
const api = siteInfoApi; const api = siteInfoApi;
@@ -105,6 +107,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return searchFrom.groupId; return searchFrom.groupId;
} }
} }
const projectStore = useProjectStore();
return { return {
id: "siteMonitorCrud", id: "siteMonitorCrud",
crudOptions: { crudOptions: {
@@ -289,6 +293,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
// name: "disabled", // name: "disabled",
// show: true, // show: true,
// }, // },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
columns: { columns: {
id: { id: {
title: "ID", title: "ID",
@@ -800,6 +809,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -5,6 +5,8 @@ import { forEach, get, merge, set } from "lodash-es";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { mitter } from "/@/utils/util.mitt"; import { mitter } from "/@/utils/util.mitt";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../dicts";
export function notificationProvide(api: any) { export function notificationProvide(api: any) {
provide("notificationApi", api); provide("notificationApi", api);
@@ -26,6 +28,8 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
}, },
}; };
const projectStore = useProjectStore();
provide("getCurrentPluginDefine", () => { provide("getCurrentPluginDefine", () => {
return currentDefine; return currentDefine;
}); });
@@ -247,5 +251,10 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
}, },
}, },
} as ColumnCompositionProps, } as ColumnCompositionProps,
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}; };
} }
@@ -2,6 +2,7 @@ import { ref } from "vue";
import { getCommonColumnDefine } from "./common"; import { getCommonColumnDefine } from "./common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { createNotificationApi } from "/@/views/certd/notification/api"; import { createNotificationApi } from "/@/views/certd/notification/api";
import { useProjectStore } from "/@/store/project";
const api = createNotificationApi(); const api = createNotificationApi();
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
@@ -26,6 +27,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const typeRef = ref(); const typeRef = ref();
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
const projectStore = useProjectStore();
return { return {
crudOptions: { crudOptions: {
request: { request: {
@@ -34,6 +36,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest, editRequest,
delRequest, delRequest,
}, },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: { form: {
labelCol: { labelCol: {
//固定label宽度 //固定label宽度
@@ -2,6 +2,7 @@
import { ref } from "vue"; import { ref } from "vue";
import { getCommonColumnDefine } from "../../common"; import { getCommonColumnDefine } from "../../common";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { crudBinding } = crudExpose; const { crudBinding } = crudExpose;
@@ -29,6 +30,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res; return res;
}; };
const projectStore = useProjectStore();
const selectedRowKey = ref([props.modelValue]); const selectedRowKey = ref([props.modelValue]);
const onSelectChange = (changed: any) => { const onSelectChange = (changed: any) => {
@@ -54,6 +56,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
search: { search: {
show: false, show: false,
initialForm: {
...projectStore.getSearchForm(),
},
}, },
form: { form: {
labelCol: { labelCol: {
@@ -3,6 +3,8 @@ import { useI18n } from "/src/locales";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { OPEN_API_DOC, openkeyApi } from "./api"; import { OPEN_API_DOC, openkeyApi } from "./api";
import { useModal } from "/@/use/use-modal"; import { useModal } from "/@/use/use-modal";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@@ -26,6 +28,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const res = await api.AddObj(form); const res = await api.AddObj(form);
return res; return res;
}; };
const projectStore = useProjectStore();
const model = useModal(); const model = useModal();
return { return {
crudOptions: { crudOptions: {
@@ -37,6 +40,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
search: { search: {
show: false, show: false,
initialForm: {
...projectStore.getSearchForm(),
},
}, },
form: { form: {
labelCol: { labelCol: {
@@ -163,6 +169,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
sorter: true, sorter: true,
}, },
}, },
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
createTime: { createTime: {
title: t("certd.fields.createTime"), title: t("certd.fields.createTime"),
type: "datetime", type: "datetime",
@@ -15,6 +15,7 @@ import { statusUtil } from "/@/views/certd/pipeline/pipeline/utils/util.status";
import { useCertViewer } from "/@/views/certd/pipeline/use"; import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { projectDict } from "../dicts"; import { projectDict } from "../dicts";
import { useProjectStore } from "/@/store/project";
export default function ({ crudExpose, context: { selectedRowKeys, openCertApplyDialog } }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context: { selectedRowKeys, openCertApplyDialog } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@@ -67,6 +68,8 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const projectStore = useProjectStore();
const DEFAULT_WILL_EXPIRE_DAYS = settingStore.sysPublic.defaultWillExpireDays || settingStore.sysPublic.defaultCertRenewDays || 15; const DEFAULT_WILL_EXPIRE_DAYS = settingStore.sysPublic.defaultWillExpireDays || settingStore.sysPublic.defaultCertRenewDays || 15;
function onDialogOpen(opt: any) { function onDialogOpen(opt: any) {
@@ -148,7 +151,9 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
}, },
}, },
search: { search: {
col: { span: 3 }, initialForm: {
...projectStore.getSearchForm(),
},
}, },
form: { form: {
afterSubmit({ form, res, mode }) { afterSubmit({ form, res, mode }) {
@@ -631,7 +636,7 @@ export default function ({ crudExpose, context: { selectedRowKeys, openCertApply
}, },
projectId: { projectId: {
title: t("certd.fields.projectName"), title: t("certd.fields.projectName"),
type: "number", type: "dict-select",
dict: projectDict, dict: projectDict,
}, },
updateTime: { updateTime: {
@@ -1,6 +1,8 @@
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { pipelineGroupApi } from "./api"; import { pipelineGroupApi } from "./api";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n(); const { t } = useI18n();
@@ -25,6 +27,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res; return res;
}; };
const projectStore = useProjectStore();
return { return {
crudOptions: { crudOptions: {
settings: { settings: {
@@ -44,6 +48,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
editRequest, editRequest,
delRequest, delRequest,
}, },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: { form: {
labelCol: { labelCol: {
//固定label宽度 //固定label宽度
@@ -127,6 +136,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 400, width: 400,
}, },
}, },
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -6,6 +6,8 @@ import createCrudOptionsPipeline from "../crud";
import * as pipelineApi from "../api"; import * as pipelineApi from "../api";
import { useTemplate } from "/@/views/certd/pipeline/template/use"; import { useTemplate } from "/@/views/certd/pipeline/template/use";
import { useI18n } from "/@/locales"; import { useI18n } from "/@/locales";
import { useProjectStore } from "/@/store/project";
import { projectDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const api = templateApi; const api = templateApi;
const { t } = useI18n(); const { t } = useI18n();
@@ -28,6 +30,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const res = await api.AddObj(form); const res = await api.AddObj(form);
return res; return res;
}; };
const projectStore = useProjectStore();
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
const router = useRouter(); const router = useRouter();
@@ -48,6 +52,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
router.push({ path: "/certd/pipeline/template/edit", query: { templateId: res.id, editMode: "true" } }); router.push({ path: "/certd/pipeline/template/edit", query: { templateId: res.id, editMode: "true" } });
}, },
}, },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
form: { form: {
labelCol: { labelCol: {
//固定label宽度 //固定label宽度
@@ -232,6 +241,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: projectDict,
},
}, },
}, },
}; };
@@ -0,0 +1,11 @@
import { dict } from "@fast-crud/fast-crud";
export const userDict = dict({
url: "/sys/authority/user/getSimpleUsers",
value: "id",
onReady: ({ dict }) => {
for (const item of dict.data) {
item.label = item.nickName || item.username || item.phoneCode + item.mobile;
}
},
});
@@ -6,6 +6,7 @@ import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq,
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { userDict } from "../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@@ -80,17 +81,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
form: { form: {
component: {}, component: {},
helper: t("certd.ent.projectNameHelper"),
rules: [{ required: true, message: t("certd.requiredField") }], rules: [{ required: true, message: t("certd.requiredField") }],
}, },
column: { column: {
width: 200, width: 200,
cellRender({ row }) { cellRender({ row }) {
return ( return <router-link to={{ path: `/sys/enterprise/project/detail`, query: { projectId: row.id } }}>{row.name}</router-link>;
<router-link to={`/sys/enterprise/project/detail`} query={{ projectId: row.id }}>
{row.name}
</router-link>
);
}, },
}, },
}, },
@@ -125,6 +121,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
adminId: {
title: t("certd.fields.adminId"),
type: "dict-select",
dict: userDict,
column: {
width: 160,
},
},
createTime: { createTime: {
title: t("certd.createTime"), title: t("certd.createTime"),
type: "datetime", type: "datetime",
@@ -6,6 +6,7 @@ import * as api from "./api";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales"; import { useI18n } from "/src/locales";
import { userDict } from "../../dicts";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@@ -27,6 +28,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
return res; return res;
}; };
const projectId = context.projectId;
const userStore = useUserStore(); const userStore = useUserStore();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
const selectedRowKeys: Ref<any[]> = ref([]); const selectedRowKeys: Ref<any[]> = ref([]);
@@ -60,6 +63,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
minWidth: 200, minWidth: 200,
fixed: "right", fixed: "right",
}, },
search: {
initialForm: {
projectId,
},
},
columns: { columns: {
id: { id: {
title: "ID", title: "ID",
@@ -79,6 +87,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: false, show: false,
}, },
form: { form: {
value: projectId,
show: false,
},
editForm: {
show: false, show: false,
}, },
column: { column: {
@@ -87,23 +99,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
userId: { userId: {
title: "用户ID", title: "用户",
type: "dict-select", type: "dict-select",
dict: dict({ dict: userDict,
url: "/sys/authority/user/getSimpleUsers",
value: "id",
label: "nickName",
labelBuilder: (item: any) => item.nickName || item.username || item.phoneCode + item.mobile,
}),
search: { search: {
show: false, show: true,
}, },
form: { form: {},
editForm: {
show: false, show: false,
}, },
column: { column: {
width: 200, width: 200,
show: false,
}, },
}, },
permission: { permission: {
@@ -111,9 +118,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "dict-select", type: "dict-select",
dict: dict({ dict: dict({
data: [ data: [
{ label: t("certd.read"), value: "read", color: "cyan" }, { label: t("certd.ent.permission.read"), value: "read", color: "cyan" },
{ label: t("certd.write"), value: "write", color: "blue" }, { label: t("certd.ent.permission.write"), value: "write", color: "blue" },
{ label: t("certd.admin"), value: "admin", color: "green" }, { label: t("certd.ent.permission.admin"), value: "admin", color: "green" },
], ],
}), }),
search: { search: {
@@ -1,10 +1,10 @@
<template> <template>
<fs-page class="page-project-member"> <fs-page class="page-project-detail">
<template #header> <template #header>
<div class="title"> <div class="title">
{{ t("certd.projectMemberManager") }} {{ t("certd.ent.projectDetailManager") }}
<span class="sub"> <span class="sub">
{{ t("certd.projectMemberDescription") }} {{ t("certd.ent.projectDetailDescription") }}
</span> </span>
</div> </div>
</template> </template>
@@ -30,7 +30,7 @@ import { useRoute } from "vue-router";
const { t } = useI18n(); const { t } = useI18n();
defineOptions({ defineOptions({
name: "ProjectMemberManager", name: "ProjectDetail",
}); });
const route = useRoute(); const route = useRoute();
@@ -29,7 +29,7 @@
<a-tab-pane key="network" :tab="t('certd.sys.setting.networkSetting')"> <a-tab-pane key="network" :tab="t('certd.sys.setting.networkSetting')">
<SettingNetwork v-if="activeKey === 'network'" /> <SettingNetwork v-if="activeKey === 'network'" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="mode" :tab="t('certd.adminMode')"> <a-tab-pane key="mode" :tab="t('certd.sys.setting.adminModeSetting')">
<SettingMode v-if="activeKey === 'mode'" /> <SettingMode v-if="activeKey === 'mode'" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@@ -1,7 +1,7 @@
<template> <template>
<div class="sys-settings-form sys-settings-mode"> <div class="sys-settings-form sys-settings-mode">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish"> <a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish">
<a-form-item :label="t('certd.adminMode')" :name="['public', 'adminMode']"> <a-form-item :label="t('certd.sys.setting.adminMode')" :name="['public', 'adminMode']">
<fs-dict-radio v-model:value="formState.public.adminMode" :dict="adminModeDict" /> <fs-dict-radio v-model:value="formState.public.adminMode" :dict="adminModeDict" />
</a-form-item> </a-form-item>
@@ -30,11 +30,11 @@ defineOptions({
const adminModeDict = dict({ const adminModeDict = dict({
data: [ data: [
{ {
label: t("certd.adminMode.enterpriseMode"), label: t("certd.sys.setting.enterpriseMode"),
value: "enterprise", value: "enterprise",
}, },
{ {
label: t("certd.adminMode.saasMode"), label: t("certd.sys.setting.saasMode"),
value: "saas", value: "saas",
}, },
], ],
@@ -4,6 +4,7 @@ CREATE TABLE "cd_project"
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"user_id" integer NOT NULL, "user_id" integer NOT NULL,
"name" varchar(512) NOT NULL, "name" varchar(512) NOT NULL,
"admin_id" integer NOT NULL,
"disabled" boolean NOT NULL DEFAULT (false), "disabled" boolean NOT NULL DEFAULT (false),
"create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP),
"update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)
@@ -11,6 +12,7 @@ CREATE TABLE "cd_project"
CREATE INDEX "index_project_user_id" ON "cd_project" ("user_id"); CREATE INDEX "index_project_user_id" ON "cd_project" ("user_id");
CREATE INDEX "index_project_admin_id" ON "cd_project" ("admin_id");
INSERT INTO cd_project (id, user_id, "name", "disabled") VALUES (1, 1, 'default', false); INSERT INTO cd_project (id, user_id, "name", "disabled") VALUES (1, 1, 'default', false);
@@ -38,7 +38,11 @@ export class SysProjectController extends CrudController<ProjectEntity> {
}; };
merge(bean, def); merge(bean, def);
bean.userId = this.getUserId(); bean.userId = this.getUserId();
return super.add(bean); return super.add({
...bean,
userId:0,
adminId: bean.userId,
});
} }
@Post("/update", { summary: "sys:settings:edit" }) @Post("/update", { summary: "sys:settings:edit" })
@@ -3,6 +3,7 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/c
import { ProjectMemberEntity } from "../../../modules/sys/enterprise/entity/project-member.js"; import { ProjectMemberEntity } from "../../../modules/sys/enterprise/entity/project-member.js";
import { ProjectMemberService } from "../../../modules/sys/enterprise/service/project-member-service.js"; import { ProjectMemberService } from "../../../modules/sys/enterprise/service/project-member-service.js";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import { ProjectService } from "../../../modules/sys/enterprise/service/project-service.js";
/** /**
*/ */
@@ -15,6 +16,9 @@ export class SysProjectMemberController extends CrudController<ProjectMemberEnti
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;
@Inject()
projectService: ProjectService;
getService<T>() { getService<T>() {
return this.service; return this.service;
} }
@@ -37,29 +41,71 @@ export class SysProjectMemberController extends CrudController<ProjectMemberEnti
disabled: false, disabled: false,
}; };
merge(bean, def); merge(bean, def);
bean.userId = this.getUserId();
await this.projectService.checkAdminPermission({
userId: this.getUserId(),
projectId: bean.projectId,
});
return super.add(bean); return super.add(bean);
} }
@Post("/update", { summary: "sys:settings:edit" }) @Post("/update", { summary: "sys:settings:edit" })
async update(@Body(ALL) bean: any) { async update(@Body(ALL) bean: any) {
bean.userId = this.getUserId(); if (!bean.id) {
return super.update(bean); throw new Error("id is required");
}
const projectId = await this.service.getProjectId(bean.id)
await this.projectService.checkAdminPermission({
userId: this.getUserId(),
projectId: projectId,
});
return super.update({
id: bean.id,
permission: bean.permission,
});
} }
@Post("/info", { summary: "sys:settings:view" }) @Post("/info", { summary: "sys:settings:view" })
async info(@Query("id") id: number) { async info(@Query("id") id: number) {
if (!id) {
throw new Error("id is required");
}
const projectId = await this.service.getProjectId(id)
await this.projectService.checkReadPermission({
userId: this.getUserId(),
projectId:projectId,
});
return super.info(id); return super.info(id);
} }
@Post("/delete", { summary: "sys:settings:edit" }) @Post("/delete", { summary: "sys:settings:edit" })
async delete(@Query("id") id: number) { async delete(@Query("id") id: number) {
if (!id) {
throw new Error("id is required");
}
const projectId = await this.service.getProjectId(id)
await this.projectService.checkAdminPermission({
userId: this.getUserId(),
projectId:projectId,
});
return super.delete(id); return super.delete(id);
} }
@Post("/deleteByIds", { summary: "sys:settings:edit" }) @Post("/deleteByIds", { summary: "sys:settings:edit" })
async deleteByIds(@Body("ids") ids: number[]) { async deleteByIds(@Body("ids") ids: number[]) {
const res = await this.service.delete(ids); for (const id of ids) {
return this.ok(res); if (!id) {
throw new Error("id is required");
}
const projectId = await this.service.getProjectId(id)
await this.projectService.checkAdminPermission({
userId: this.getUserId(),
projectId:projectId,
});
await this.service.delete(id as any);
}
return this.ok({});
} }
} }
@@ -20,7 +20,7 @@ export class UserProjectController extends BaseController {
@Post('/list', { summary: Constants.per.authOnly }) @Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body: any) { async list(@Body(ALL) body: any) {
const userId= this.getUserId(); const userId= this.getUserId();
const res = await this.service.getByUserId(userId); const res = await this.service.getUserProjects(userId);
return this.ok(res); return this.ok(res);
} }
@@ -10,6 +10,9 @@ export class ProjectEntity {
@Column({ name: 'user_id', comment: 'UserId' }) @Column({ name: 'user_id', comment: 'UserId' })
userId: number; userId: number;
@Column({ name: 'admin_id', comment: '管理员Id' })
adminId: number;
@Column({ name: 'name', comment: '项目名称' }) @Column({ name: 'name', comment: '项目名称' })
name: string; name: string;
@@ -46,4 +46,26 @@ export class ProjectMemberService extends BaseService<ProjectMemberEntity> {
}); });
} }
async getMember(projectId: number,userId: number) {
return await this.repository.findOne({
where: {
userId,
projectId,
},
});
}
async getProjectId(id: number) {
const member = await this.repository.findOne({
select: ['projectId'],
where: {
id: id,
},
});
if (!member) {
throw new Error('项目成员记录不存在');
}
return member.projectId;
}
} }
@@ -1,7 +1,7 @@
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core'; import { BaseService, SysSettingsService } from '@certd/lib-server';
import {BaseService, SysSettingsService} from '@certd/lib-server'; import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import {InjectEntityModel} from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import {In, Repository} from 'typeorm'; import { Repository } from 'typeorm';
import { ProjectEntity } from '../entity/project.js'; import { ProjectEntity } from '../entity/project.js';
import { ProjectMemberService } from './project-member-service.js'; import { ProjectMemberService } from './project-member-service.js';
@@ -30,45 +30,34 @@ export class ProjectService extends BaseService<ProjectEntity> {
const exist = await this.repository.findOne({ const exist = await this.repository.findOne({
where: { where: {
name, name,
userId:0, userId: 0,
}, },
}); });
if (exist) { if (exist) {
throw new Error('项目名称已存在'); throw new Error('项目名称已存在');
} }
bean.userId = 0
bean.disabled = false bean.disabled = false
return await super.add(bean) return await super.add(bean)
} }
async setDisabled(id: number, disabled: boolean) { async setDisabled(id: number, disabled: boolean) {
const project = await this.repository.findOne({
where: {
id,
userId:0,
},
});
if (!project) {
throw new Error('项目不存在');
}
await this.repository.update({ await this.repository.update({
id,
userId:0, userId:0,
}, { }, {
disabled, disabled,
}); });
project.disabled = disabled;
await this.repository.save(project);
} }
async getByUserId(userId: number) { async getUserProjects(userId: number) {
const memberList = await this.projectMemberService.getByUserId(userId); const memberList = await this.projectMemberService.getByUserId(userId);
const projectIds = memberList.map(item => item.projectId); const projectIds = memberList.map(item => item.projectId);
const projectList = await this.repository.find({ const projectList = await this.repository.createQueryBuilder('project')
where: { .where(' project.disabled = false')
id: In(projectIds), .where(' project.userId = :userId', { userId:0 })
}, .where(' project.id IN (:...projectIds) or project.adminId = :userId', { projectIds, userId })
}); .getMany();
const memberPermissionMap = memberList.reduce((prev, cur) => { const memberPermissionMap = memberList.reduce((prev, cur) => {
prev[cur.projectId] = cur.permission; prev[cur.projectId] = cur.permission;
@@ -76,9 +65,81 @@ export class ProjectService extends BaseService<ProjectEntity> {
}, {} as Record<number, string>); }, {} as Record<number, string>);
projectList.forEach(item => { projectList.forEach(item => {
item.permission = memberPermissionMap[item.id] || 'read'; if (item.adminId === userId) {
item.permission = 'admin';
}else{
item.permission = memberPermissionMap[item.id] || 'read';
}
}) })
return projectList return projectList
} }
async checkAdminPermission({userId, projectId}: {userId: number, projectId: number}) {
return await this.checkPermission({
userId,
projectId,
permission: 'admin',
})
}
async checkWritePermission({userId, projectId}: {userId: number, projectId: number}) {
return await this.checkPermission({
userId,
projectId,
permission: 'write',
})
}
async checkReadPermission({userId, projectId}: {userId: number, projectId: number}) {
return await this.checkPermission({
userId,
projectId,
permission: 'read',
})
}
async checkPermission({userId, projectId, permission}: {userId: number, projectId: number, permission: string}) {
if (permission !== 'admin' && permission !== 'write' && permission !== 'read') {
throw new Error('权限类型错误');
}
if (!userId ){
throw new Error('用户ID不能为空');
}
if (!projectId ){
throw new Error('项目ID不能为空');
}
const project = await this.findOne({
select: ['id', 'userId', 'adminId', 'disabled'],
where: {
id: projectId,
},
});
if (!project) {
throw new Error('项目不存在');
}
if (project.adminId === userId) {
//创建者拥有管理权限
return true
}
if (project.disabled) {
throw new Error('项目已禁用');
}
const member = await this.projectMemberService.getMember(projectId,userId);
if (!member) {
throw new Error('项目成员不存在');
}
if (permission === 'read') {
return true
}
if (permission === 'write') {
if (member.permission === 'admin' || member.permission === 'write') {
return true
}else{
throw new Error('权限不足');
}
}
if (member.permission !== permission) {
throw new Error('权限不足');
}
return true
}
} }