feat(项目管理): 项目与权限成员页面搭建

This commit is contained in:
xinxin.wu 2023-08-28 19:18:32 +08:00 committed by 刘瑞斌
parent 7b0127a5ea
commit a49eca779d
17 changed files with 641 additions and 4 deletions

View File

@ -19,11 +19,13 @@
class="pr-[5px]" class="pr-[5px]"
:style="{ :style="{
overflow: 'auto', overflow: 'auto',
width: props.width ? props.width : `calc(100vw - ${menuWidth}px - 58px)`, width: props.otherWidth
? `calc(100vw - ${menuWidth}px - ${props.otherWidth}px)`
: `calc(100vw - ${menuWidth}px - 58px)`,
height: props.autoHeight ? 'auto' : `calc(100vh - ${cardOverHeight}px)`, height: props.autoHeight ? 'auto' : `calc(100vh - ${cardOverHeight}px)`,
}" }"
> >
<div class="min-w-[1000px]"> <div :class="[`min-w-[${props.minWidth || 1000}px]`]">
<slot></slot> <slot></slot>
</div> </div>
</a-scrollbar> </a-scrollbar>
@ -70,7 +72,8 @@
specialHeight: number; // specialHeight: number; //
hideBack: boolean; // hideBack: boolean; //
autoHeight: boolean; // autoHeight: boolean; //
width?: string; // otherWidth?: number; //
minWidth?: number; //
hasBreadcrumb: boolean; // hasBreadcrumb: boolean; //
noContentPadding: boolean; // padding noContentPadding: boolean; // padding
handleBack: () => void; // handleBack: () => void; //

View File

@ -18,6 +18,12 @@ export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT = 'projectManagement', PROJECT_MANAGEMENT = 'projectManagement',
PROJECT_MANAGEMENT_INDEX = 'projectManagementIndex', PROJECT_MANAGEMENT_INDEX = 'projectManagementIndex',
PROJECT_MANAGEMENT_LOG = 'projectManagementLog', PROJECT_MANAGEMENT_LOG = 'projectManagementLog',
PROJECT_MANAGEMENT_PERMISSION = 'projectManagementPermission',
PROJECT_MANAGEMENT_PERMISSION_BASICINFO = 'projectManagementPermissionBasicInfo',
PROJECT_MANAGEMENT_PERMISSION_MENUMANAGEMENT = 'projectManagementPermissionMenuManagement',
PROJECT_MANAGEMENT_PERMISSION_VERSION = 'projectManagementPermissionVersion',
PROJECT_MANAGEMENT_PERMISSION_USERGROUP = 'projectManagementPermissionUserGroup',
PROJECT_MANAGEMENT_PERMISSION_MEMBER = 'projectManagementPermissionMember',
} }
export enum TestPlanRouteEnum { export enum TestPlanRouteEnum {

View File

@ -15,6 +15,7 @@ export enum TableKeyEnum {
SYSTEM_ORGANIZATION = 'systemOrganization', SYSTEM_ORGANIZATION = 'systemOrganization',
SYSTEM_PROJECT = 'systemProject', SYSTEM_PROJECT = 'systemProject',
SYSTEM_LOG = 'systemLog', SYSTEM_LOG = 'systemLog',
PROJECT_MEMBER = 'projectMember',
ORGANIZATION_MEMBER = 'organizationMember', ORGANIZATION_MEMBER = 'organizationMember',
ORGANIZATION_PROJECT = 'organizationProject', ORGANIZATION_PROJECT = 'organizationProject',
} }

View File

@ -27,6 +27,7 @@ export default {
'menu.uiTest': 'UI test', 'menu.uiTest': 'UI test',
'menu.performanceTest': 'Performance test', 'menu.performanceTest': 'Performance test',
'menu.projectManagement': 'Project management', 'menu.projectManagement': 'Project management',
'menu.projectManagement.projectPermission': 'Project Permission',
'menu.projectManagement.log': 'Log', 'menu.projectManagement.log': 'Log',
'menu.settings': 'Settings', 'menu.settings': 'Settings',
'menu.settings.system': 'System', 'menu.settings.system': 'System',

View File

@ -27,6 +27,7 @@ export default {
'menu.performanceTest': '性能测试', 'menu.performanceTest': '性能测试',
'menu.projectManagement': '项目管理', 'menu.projectManagement': '项目管理',
'menu.projectManagement.log': '日志', 'menu.projectManagement.log': '日志',
'menu.projectManagement.projectPermission': '项目与权限',
'menu.settings': '系统设置', 'menu.settings': '系统设置',
'menu.settings.system': '系统', 'menu.settings.system': '系统',
'menu.settings.system.user': '用户', 'menu.settings.system.user': '用户',

View File

@ -6,7 +6,7 @@ import type { AppRouteRecordRaw } from '../types';
const ProjectManagement: AppRouteRecordRaw = { const ProjectManagement: AppRouteRecordRaw = {
path: '/project-management', path: '/project-management',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT,
redirect: '/project-management/index', redirect: '/project-management/permission',
component: DEFAULT_LAYOUT, component: DEFAULT_LAYOUT,
meta: { meta: {
locale: 'menu.projectManagement', locale: 'menu.projectManagement',
@ -23,6 +23,71 @@ const ProjectManagement: AppRouteRecordRaw = {
roles: ['*'], roles: ['*'],
}, },
}, },
// 项目与权限
{
path: 'permission',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION,
component: () => import('@/views/project-management/projectAndPermission/index.vue'),
redirect: '/project-management/permission/basicInfo',
meta: {
locale: 'menu.projectManagement.projectPermission',
roles: ['*'],
isTopMenu: true,
},
children: [
// 基本信息
{
path: 'basicInfo',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_BASICINFO,
component: () => import('@/views/project-management/projectAndPermission/basicInfos/index.vue'),
meta: {
locale: 'project.permission.basicInfo',
roles: ['*'],
},
},
// 菜单管理
{
path: 'menuManagement',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENUMANAGEMENT,
component: () => import('@/views/project-management/projectAndPermission/menuManagement/index.vue'),
meta: {
locale: 'project.permission.menuManagement',
roles: ['*'],
},
},
// 项目版本
{
path: 'projectVersion',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_VERSION,
component: () => import('@/views/project-management/projectAndPermission/projectVersion/index.vue'),
meta: {
locale: 'project.permission.projectVersion',
roles: ['*'],
},
},
// 成员
{
path: 'member',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MEMBER,
component: () => import('@/views/project-management/projectAndPermission/member/index.vue'),
meta: {
locale: 'menu.settings.system.member',
roles: ['*'],
},
},
// 用户组
{
path: 'projectUserGroup',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_USERGROUP,
component: () => import('@/views/project-management/projectAndPermission/userGroup/index.vue'),
meta: {
locale: 'project.permission.userGroup',
roles: ['*'],
},
},
],
},
// 项目日志
{ {
path: 'log', path: 'log',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_LOG, name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_LOG,

View File

@ -0,0 +1,9 @@
<template>
<div> 基本信息 waiting for development</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
</script>
<style scoped></style>

View File

@ -0,0 +1,134 @@
<template>
<div class="wrapper flex" :style="{ height: 'calc(100vh - 90px)' }">
<div class="left-menu-wrapper mr-[16px] w-[208px] min-w-[208px] bg-white p-[24px]">
<div class="left-content">
<div class="mb-2 font-medium">{{ t('project.permission.projectAndPermission') }}</div>
<div class="menu">
<div
v-for="(item, index) of menuList"
:key="item.key"
class="menu-item px-2"
:class="{
'text-[--color-text-4]': item.level === 1,
'is-active': item.name === currentKey && item.level !== 1,
'cursor-pointer': item.level !== 1,
}"
:style="{
'border-top': item.level === 1 && index !== 0 ? '1px solid var(--color-border-2)' : 'none',
}"
>
<div @click="toggleMenu(item.name)">{{ t(item.title) }}</div>
</div>
</div>
</div>
</div>
<MsCard simple :other-width="290" :min-width="700">
<router-view></router-view>
</MsCard>
</div>
</template>
<script setup lang="ts">
import { ref, onBeforeMount } from 'vue';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue';
import { useRouter, useRoute } from 'vue-router';
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const menuList = ref([
{
key: 'project',
title: 'project.permission.project',
level: 1,
name: '',
},
{
key: 'projectBasicInfo',
title: 'project.permission.basicInfo',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_BASICINFO,
},
{
key: 'projectMenuManage',
title: 'project.permission.menuManagement',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MENUMANAGEMENT,
},
{
key: 'projectVersion',
title: 'project.permission.projectVersion',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_VERSION,
},
{
key: 'memberPermission',
title: 'project.permission.memberPermission',
level: 1,
name: '',
},
{
key: 'projectMember',
title: 'project.permission.member',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_MEMBER,
},
{
key: 'projectUserGroup',
title: 'project.permission.userGroup',
level: 2,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_USERGROUP,
},
]);
const currentKey = ref<string>('');
const toggleMenu = (itemName: string) => {
if (itemName) {
currentKey.value = itemName;
router.push({ name: itemName });
}
};
const setInitRoute = () => {
if (route?.name) currentKey.value = route.name as string;
};
onBeforeMount(() => {
setInitRoute();
});
</script>
<style scoped lang="less">
.left-menu-wrapper {
border-radius: 12px;
color: var(--color-text-1);
box-shadow: 0 0 10px rgb(120 56 135/ 5%);
.left-content {
width: 100%;
.menu {
.menu-item {
height: 38px;
line-height: 38px;
font-family: 'PingFang SC';
}
}
}
}
.right-menu-wrapper {
border-radius: 12px;
box-shadow: 0 0 10px rgb(120 56 135/ 5%);
@apply bg-white;
}
.is-active {
border-radius: 4px;
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-1));
}
.rightTest {
height: 100%;
}
</style>

View File

@ -0,0 +1,10 @@
export default {
'project.permission.projectAndPermission': 'Project & Permission',
'project.permission.project': 'Project',
'project.permission.basicInfo': 'Basic Info',
'project.permission.menuManagement': 'Menu Management',
'project.permission.projectVersion': 'Project Version',
'project.permission.memberPermission': 'Member Permission',
'project.permission.member': 'Member',
'project.permission.userGroup': 'User Group',
};

View File

@ -0,0 +1,10 @@
export default {
'project.permission.projectAndPermission': '项目与权限',
'project.permission.project': '项目',
'project.permission.basicInfo': '基本信息',
'project.permission.menuManagement': '菜单管理',
'project.permission.projectVersion': '项目版本',
'project.permission.memberPermission': '成员权限',
'project.permission.member': '成员',
'project.permission.userGroup': '用户组',
};

View File

@ -0,0 +1,84 @@
<template>
<MsDialog
v-model:visible="visible"
dialog-size="medium"
title="project.member.addMember"
:close="closeHandler"
:confirm="confirmFunHandler"
ok-text="project.member.add"
>
<div class="form">
<a-form ref="memberFormRef" :model="form" size="large" layout="vertical">
<a-form-item
field="memberIds"
:label="t('project.member.member')"
asterisk-position="end"
:rules="[{ required: true, message: t('project.member.selectMemberEmptyTip') }]"
>
<a-select v-model="form.memberIds" multiple :placeholder="t('project.member.selectMemberScope')" allow-clear>
<a-option v-for="item of memberList" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</a-form-item>
<a-form-item
field="userRoleIds"
:label="t('project.member.tableColumnUserGroup')"
asterisk-position="end"
:rules="[{ required: true, message: t('project.member.selectUserEmptyTip') }]"
>
<a-select v-model="form.userRoleIds" multiple allow-clear :placeholder="t('project.member.selectUserScope')">
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</a-form-item>
</a-form>
</div>
</MsDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { addOrUpdate } from '@/api/modules/setting/serviceIntegration';
import { FormInstance, Message } from '@arco-design/web-vue';
const { t } = useI18n();
const visible = ref<boolean>(false);
const initFormValue = {
userRoleIds: [],
memberIds: [],
};
const form = ref({ ...initFormValue });
const memberList = ref([
{
id: '',
name: '全部',
},
]);
const userGroupOptions = ref([
{
id: '',
name: '全部',
},
]);
const memberFormRef = ref<FormInstance | null>(null);
const confirmFunHandler = async () => {
await memberFormRef.value?.validate().then(async (error) => {
if (!error) {
console.log(error);
} else {
return false;
}
});
};
const closeHandler = () => {
memberFormRef.value?.resetFields();
visible.value = false;
};
</script>
<style scoped></style>

View File

@ -0,0 +1,209 @@
<template>
<div class="mb-4 grid grid-cols-4 gap-2">
<div class="col-span-2"
><a-button class="mr-3" type="primary" @click="addMember">{{ t('project.member.addMember') }}</a-button></div
>
<div>
<a-select v-model="searchParams.userId">
<a-option v-for="item of userGroupListOptions" :key="item.value" :value="item.value">{{
t(item.name)
}}</a-option>
<template #prefix
><span>{{ t('project.member.tableColumnUserGroup') }}</span></template
>
</a-select></div
>
<div>
<a-input-search
:max-length="250"
:placeholder="t('project.member.searchMember')"
@search="searchHandler"
@press-enter="searchHandler"
></a-input-search
></div>
</div>
<ms-base-table
v-bind="propsRes"
:action-config="tableBatchActions"
@selected-change="handleTableSelect"
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #userRole="{ record }">
<a-tooltip :content="(record.userRoleIdNameMap||[]).map((e: any) => e.name).join(',')">
<div v-if="!record.showUserSelect">
<a-tag
v-for="org of (record.userRoleIdNameMap || []).slice(0, 3)"
:key="org"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
{{ org.name }}
</a-tag>
<a-tag
v-if="record.userRoleIdNameMap.length > 3"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
+{{ record.userRoleIdNameMap.length - 3 }}
</a-tag>
</div>
<a-select v-else v-model="record.selectUserList" multiple :max-tag-count="2">
<a-option v-for="item of userGroupOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</a-tooltip>
</template>
<template #enable="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('organization.member.statusEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<MsIcon type="icon-icon_disable" class="mr-[2px]" />
{{ t('organization.member.statusDisable') }}
</div>
</template>
<template #action="{ record }">
<MsRemoveButton
position="br"
:title="t('project.member.deleteMemberTip', { name: characterLimit(record.name) })"
:sub-title-tip="t('project.member.subTitle')"
/>
</template>
</ms-base-table>
<AddMemberModal v-model:visible="addMemberVisible" />
</template>
<script setup lang="ts">
import { ref, onBeforeMount } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import useTable from '@/components/pure/ms-table/useTable';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import { getMemberList } from '@/api/modules/setting/member';
import { TableKeyEnum } from '@/enums/tableEnum';
import { useTableStore, useUserStore } from '@/store';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import { characterLimit } from '@/utils';
import AddMemberModal from './components/addMemberModal.vue';
const { t } = useI18n();
const tableStore = useTableStore();
const userStore = useUserStore();
const lastOrganizationId = userStore?.$state.lastOrganizationId;
const userGroupListOptions = ref([
{
id: '',
name: '全部',
value: '',
},
{
id: '1001',
name: '用户组1',
value: '1001',
},
]);
const columns: MsTableColumn = [
{
title: 'project.member.tableColumnEmail',
dataIndex: 'email',
showInTable: true,
width: 200,
showTooltip: true,
},
{
title: 'project.member.tableColumnName',
dataIndex: 'name',
showInTable: true,
},
{
title: 'project.member.tableColumnPhone',
dataIndex: 'phone',
showInTable: true,
width: 150,
},
{
title: 'project.member.tableColumnUserGroup',
slotName: 'userRole',
dataIndex: 'userRoleIdNameMap',
showInTable: true,
width: 300,
},
{
title: 'project.member.tableColumnStatus',
slotName: 'enable',
dataIndex: 'enable',
showInTable: true,
},
{
title: 'project.member.tableColumnActions',
slotName: 'action',
fixed: 'right',
width: 80,
showInTable: true,
},
];
tableStore.initColumn(TableKeyEnum.PROJECT_MEMBER, columns, 'drawer');
const tableBatchActions = {
baseAction: [
{
label: 'project.member.batchActionAddProject',
eventTag: 'batchAddProject',
},
{
label: 'project.member.batchActionAddUserGroup',
eventTag: 'batchAddUserGroup',
},
],
};
const tableSelected = ref<(string | number)[]>([]);
const handleTableSelect = (selectArr: (string | number)[]) => {
tableSelected.value = selectArr;
};
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getMemberList, {
tableKey: TableKeyEnum.PROJECT_MEMBER,
selectable: true,
showSetting: true,
scroll: {
x: 1000,
},
});
const initData = async () => {
setLoadListParams({ organizationId: lastOrganizationId });
await loadList();
};
const searchParams = ref({
userId: '',
});
const searchHandler = () => {};
const handleTableBatch = (actionItem: any) => {};
const userGroupOptions = ref([
{
id: '',
name: '',
},
]);
const addMemberVisible = ref<boolean>(false);
const addMember = () => {
addMemberVisible.value = true;
};
onBeforeMount(() => {
initData();
});
</script>
<style scoped></style>

View File

@ -0,0 +1,39 @@
export default {
'project.member.addMember': 'Add Member',
'project.member.updateMember': 'Update Member',
'project.member.searchMember': 'Search by name or email address',
'project.member.remove': 'Remove',
'project.member.edit': 'Edit',
'project.member.add': 'Add',
'project.member.batchActionAddProject': 'Add to project',
'project.member.batchActionAddUserGroup': 'Add to usergroup',
'project.member.tableEnable': 'Enabled',
'project.member.tableDisable': 'Disabled',
'project.member.tableColumnEmail': 'Email',
'project.member.tableColumnName': 'Name',
'project.member.tableColumnPhone': 'Phone',
'project.member.tableColumnPro': 'Project',
'project.member.tableColumnUserGroup': 'UserGroup',
'project.member.tableColumnStatus': 'Status',
'project.member.tableColumnActions': 'Actions',
'project.member.member': 'Member',
'project.member.selectMemberScope': 'Select the member you want to add. Multiple selection is supported',
'project.member.selectProjectScope': 'Select the project you want to add. Multiple selection is supported',
'project.member.selectMemberEmptyTip': 'The member can not be empty',
'project.member.selectProjectEmptyTip': 'The project can not be empty',
'project.member.selectUserEmptyTip': 'The user group can not be empty',
'project.member.Confirm': 'Confirm',
'project.member.Cancel': 'Cancel',
'project.member.deleteMemberTip': 'Are you sure to remove the user `{name}` ?',
'system.user.deleteUserTip': 'Are you sure to delete the user `{name}` ?',
'project.member.deleteMemberConfirm': 'Delete',
'project.member.deleteMemberCancel': 'Cancel',
'project.member.deleteMemberSuccess': 'Delete successful',
'project.member.batchModalSuccess': 'Successfully added',
'project.member.batchUpdateSuccess': 'Successfully updated',
'project.member.project': 'Project',
'project.member.selectUserScope': 'Please select a user group for the above members',
'project.member.statusEnable': 'Normal',
'project.member.statusDisable': 'Disabled',
'project.member.subTitle': 'When removed, you lose your organization privileges',
};

View File

@ -0,0 +1,38 @@
export default {
'project.member.addMember': '添加成员',
'project.member.updateMember': '更新成员',
'project.member.searchMember': '通过名称或邮箱搜索搜索',
'project.member.remove': '移除',
'project.member.edit': '编辑',
'project.member.add': '添加',
'project.member.batchActionAddProject': '添加至项目',
'project.member.batchActionAddUserGroup': '添加至用户组',
'project.member.tableEnable': '正常',
'project.member.tableDisable': '禁用',
'project.member.tableColumnEmail': '邮箱',
'project.member.tableColumnName': '姓名',
'project.member.tableColumnPhone': '手机',
'project.member.tableColumnPro': '项目',
'project.member.tableColumnUserGroup': '用户组',
'project.member.tableColumnStatus': '状态',
'project.member.tableColumnActions': '操作',
'project.member.member': '成员',
'project.member.selectMemberScope': '请选择需要添加的成员支持多选',
'project.member.selectProjectScope': '请选择需要添加的项目支持多选',
'project.member.selectMemberEmptyTip': '成员不能为空',
'project.member.selectProjectEmptyTip': '项目不能为空',
'project.member.selectUserEmptyTip': '用户组不能为空',
'project.member.Confirm': '确定',
'project.member.Cancel': '取消',
'project.member.deleteMemberTip': '确认移除 {name} 这个成员吗?',
'project.member.deleteMemberConfirm': '确认删除',
'project.member.deleteMemberCancel': '取消',
'project.member.deleteMemberSuccess': '删除成功',
'project.member.batchModalSuccess': '添加成功',
'project.member.batchUpdateSuccess': '更新成功',
'project.member.project': '项目',
'project.member.selectUserScope': '请为以上成员选择用户组',
'project.member.statusEnable': '正常',
'project.member.statusDisable': '禁用',
'project.member.subTitle': '移除后,将失去组织权限',
};

View File

@ -0,0 +1,9 @@
<template>
<div> 菜单管理 waiting for development</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
</script>
<style scoped></style>

View File

@ -0,0 +1,9 @@
<template>
<div>项目版本 waiting for development </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
</script>
<style scoped></style>

View File

@ -0,0 +1,9 @@
<template>
<div> 用户组 waiting for development</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
</script>
<style scoped></style>