feat(系统设置): 组织与项目静态页面

This commit is contained in:
RubyLiu 2023-08-10 19:07:56 +08:00 committed by fit2-zhao
parent 146ce20a59
commit 280ed916d6
28 changed files with 965 additions and 58 deletions

View File

@ -17,8 +17,8 @@ http{
add_header X-Frame-Options "SAMEORIGIN";
try_files $uri $uri/ /index.html;
location ^~ /app/ {
proxy_pass http://172.16.200.18:8081/app/;
location ^~ /front/ {
proxy_pass http://172.16.200.18:8081/;
proxy_connect_timeout 2s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;

View File

@ -0,0 +1,23 @@
import MSR from '@/api/http/index';
import * as orgUrl from '@/api/requrls/setting/system/organizationAndProject';
import { TableQueryParams } from '@/models/common';
// 获取组织列表
export function postOrgTable(data: TableQueryParams) {
return MSR.post({ url: orgUrl.postOrgTableUrl, data });
}
// 获取项目列表
export function postProjectTable(data: TableQueryParams) {
return MSR.post({ url: orgUrl.postProjectTableUrl, data });
}
// 根据组织id获取项目列表
export function postProjectTableByOrgId(data: TableQueryParams) {
return MSR.post({ url: orgUrl.postProjectTableByOrgUrl, data });
}
// 根据组织id获取用户列表
export function postUserTableByOrgId(data: TableQueryParams) {
return MSR.post({ url: orgUrl.postOrgMemberUrl, data });
}

View File

@ -0,0 +1,46 @@
// 修改组织
export const postModifyOrgUrl = '/system/organization/update';
// 获取系统下所有组织-下拉选项
export const postOrgOptionsUrl = '/system/organization/option/all';
// 获取系统下所有组织-Table
export const postOrgTableUrl = '/system/organization/list';
// 获取组织下所有项目-Table
export const postProjectTableByOrgUrl = '/system/organization/list-project';
// 获取组织成员
export const postOrgMemberUrl = '/system/organization/list-member';
// 添加组织
export const postAddOrgUrl = '/system/organization/add';
// 添加组织成员
export const postAddOrgMemberUrl = '/system/organization/member/add';
// 删除组织成员
export const getDeleteOrgMemberUrl = '/system/organization/remove-member/';
// 恢复组织
export const getRecoverOrgUrl = '/system/organization/recover/';
// 启用组织
export const getEnableOrgUrl = '/system/organization/enable/';
// 禁用组织
export const getDisableOrgUrl = '/system/organization/disable/';
// 删除组织
export const getDeleteOrgUrl = '/system/organization/delete/';
// 获取系统默认组织
export const getOrgDefaultUrl = '/system/organization/default';
// 项目
// 更新项目信息
export const postModifyProjectUrl = '/system/project/update';
// 获取项目列表
export const postProjectTableUrl = '/system/project/list';
// 获取项目成员
export const postProjectMemberUrl = '/system/project/member/list';
// 添加项目
export const postAddProjectUrl = '/system/project/add';
// 添加项目成员
export const postAddProjectMemberUrl = '/system/project/member/add';
// 撤销项目
export const getRevokeProjectUrl = '/system/project/revoke/';
// 移除项目成员
export const getDeleteProjectMemberUrl = '/system/project/remove-member/';
// 根据ID获取项目信息
export const getProjectInfoUrl = '/system/project/get/';
// 删除项目
export const getDeleteProjectUrl = '/system/project/delete/';

View File

@ -0,0 +1,72 @@
<template>
<a-select
:value="value"
:disabled="props.disabled"
multiple
:virtual-list-props="{ height: 200 }"
:placeholder="props.placeholder ? t(props.placeholder) : t('common.pleaseSelect')"
:options="userOptions"
:field-names="fieldNames"
@search="handleSearch"
@change="handleChange"
>
<template #label="{ data }">
<span class="option-name"> {{ data.name }} </span>
</template>
<template #option="{ data }">
<span class="option-name"> {{ data.name }} </span>
<span class="option-email"> {{ `(${data.email})` }} </span>
</template>
</a-select>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { ref, onMounted } from 'vue';
import { getUserList } from '@/api/modules/setting/usergroup';
export interface MsUserSelectorProps {
value: string[];
disabled?: boolean;
placeholder?: string;
}
export interface UserItem {
id: string;
name: string;
email: string;
}
const fieldNames = { value: 'id', label: 'name' };
const { t } = useI18n();
const props = defineProps<MsUserSelectorProps>();
const emit = defineEmits<{
(e: 'update:value', value: string[]): void;
}>();
const allOption = ref<UserItem[]>([]);
const userOptions = ref<UserItem[]>([]);
const initUserList = async () => {
const res = await getUserList();
allOption.value = res;
userOptions.value = res;
};
const handleSearch = (value: string) => {
if (value) {
window.setTimeout(() => {
userOptions.value = userOptions.value.filter((item) => item.name.includes(value));
}, 60);
} else {
userOptions.value = allOption.value;
}
};
const handleChange = (value: string | number | Record<string, any> | (string | number | Record<string, any>)[]) => {
emit('update:value', value as string[]);
};
onMounted(() => {
initUserList();
});
</script>

View File

@ -390,6 +390,8 @@ export default defineComponent({
return null;
}
if (props.total === 0) return null;
return (
<div class={cls.value}>
{props.showTotal && (

View File

@ -1,7 +1,7 @@
<template>
<div class="ms-base-tale">
<select-all
v-if="attrs.showSelectAll"
v-if="attrs.selectable && attrs.showSelectAll"
class="custom-action"
:total="selectTotal"
:current="selectCurrent"
@ -15,7 +15,7 @@
>
<template #columns>
<a-table-column
v-for="(item, idx) in columns"
v-for="(item, idx) in currentColumns"
:key="idx"
:width="item.width"
:align="item.align"
@ -35,7 +35,7 @@
:tooltip="item.tooltip"
>
<template #title>
<div v-if="attrs.showSetting && idx === columns.length - 1" class="column-selector">
<div v-if="attrs.showSetting && idx === currentColumns.length - 1" class="column-selector">
<div class="title">{{ t(item.title as string) }}</div>
<ColumnSelector :table-key="(attrs.tableKey as string)" @close="handleColumnSelectorClose" />
</div>
@ -45,14 +45,16 @@
<template #cell="{ column, record, rowIndex }">
<div class="flex flex-row items-center">
<template v-if="item.dataIndex === SpecialColumnEnum.ENABLE">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('system.user.tableEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<MsIcon type="icon-icon_disable" class="mr-[2px]" />
{{ t('system.user.tableDisable') }}
</div>
<slot name="enable" v-bind="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('msTable.enable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<MsIcon type="icon-icon_disable" class="mr-[2px]" />
{{ t('msTable.disable') }}
</div>
</slot>
</template>
<template v-else>
<a-input
@ -124,12 +126,14 @@
const { t } = useI18n();
const tableStore = useTableStore();
const appStore = useAppStore();
const columns = ref<MsTableColumn>([]);
const currentColumns = ref<MsTableColumn>([]);
const props = defineProps<{
selectedKeys?: (string | number)[];
actionConfig?: BatchActionConfig;
noDisable?: boolean;
showSetting?: boolean;
columns: MsTableColumn;
}>();
const emit = defineEmits<{
(e: 'selectedChange', value: (string | number)[]): void;
@ -160,7 +164,13 @@
};
const initColumn = () => {
columns.value = tableStore.getShowInTableColumns(attrs.tableKey as string);
let tmpArr: MsTableColumn = [];
if (props.showSetting) {
tmpArr = tableStore.getShowInTableColumns(attrs.tableKey as string);
} else {
tmpArr = props.columns;
}
currentColumns.value = tmpArr;
};
//
const selectionChange = (arr: (string | number)[], setCurrentSelect: boolean, isAll = false) => {

View File

@ -16,7 +16,7 @@
</template>
<div v-if="props.actionConfig.moreAction" class="relative top-[2px] ml-3 inline-block">
<a-dropdown position="tr" @select="handleSelect">
<a-button type="outline"><a-icon-more /></a-button>
<a-button type="outline"><MsIcon type="icon-icon_more_outlined" /></a-button>
<template #content>
<template v-for="element in props.actionConfig.moreAction" :key="element.label">
<a-divider v-if="element.isDivider" margin="0" />
@ -34,6 +34,7 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { BatchActionConfig, BatchActionParams } from './type';
import MsIcon from '../ms-icon-font/index.vue';
const { t } = useI18n();
const props = defineProps<{

View File

@ -2,6 +2,8 @@ export default {
msTable: {
current: 'Select Current Page',
all: 'Select All Pages',
enable: 'Enable',
disable: 'Disable',
batch: {
title: '批量操作',
selected: '已选择 {count} 项',

View File

@ -2,6 +2,8 @@ export default {
msTable: {
current: '全选当前页',
all: '全选所有页',
enable: '启用',
disable: '禁用',
batch: {
title: '批量操作',
selected: '已选择 {count} 项',

View File

@ -2,7 +2,7 @@
<div class="ms-table-select-all items-center text-base">
<a-checkbox v-model="checked" class="text-base" :indeterminate="indeterminate" @change="handleCheckChange" />
<a-dropdown position="bl" @select="handleSelect">
<a-icon-down class="dropdown-icon ml-0.5" />
<MsIcon type="icon-icon_down_outlined" class="ml-0.5" />
<template #content>
<a-doption :value="SelectAllEnum.CURRENT">{{ t('msTable.current') }}</a-doption>
<a-doption :value="SelectAllEnum.ALL">{{ t('msTable.all') }}</a-doption>
@ -15,6 +15,7 @@
import { ref, watchEffect } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { SelectAllEnum } from './type';
import MsIcon from '../ms-icon-font/index.vue';
const { t } = useI18n();

View File

@ -33,6 +33,8 @@ export interface MsTableProps {
scroll?: {
x?: number | string;
y?: number | string;
maxHeight?: number | string;
minWidth?: number | string;
};
// 表格是否可拖拽
enableDrag?: boolean;
@ -63,9 +65,9 @@ export interface MsTableProps {
pageSimple?: boolean;
// 编辑的key默认为name
editKey?: string;
[key: string]: any;
// 是否展示禁用的行
noDisable?: boolean;
[key: string]: any;
}
export interface MsTableSelectAll {

View File

@ -20,7 +20,7 @@ export default function useTableProps(
loadListFunc: (v: TableQueryParams) => Promise<any>,
props?: Partial<MsTableProps>,
// 数据处理的回调函数
callBack?: (item: TableData) => TableData,
dataTransform?: (item: TableData) => TableData,
// 编辑操作的保存回调函数
saveCallBack?: (item: TableData) => Promise<any>
) {
@ -35,7 +35,7 @@ export default function useTableProps(
bordered: true,
showPagination: true,
size: 'small',
scroll: { y: '860px', x: '1400px' },
scroll: { maxHeight: '600px', x: '1400px' },
checkable: true,
loading: true,
data: [] as MsTableData,
@ -166,8 +166,8 @@ export default function useTableProps(
if (item.createTime) {
item.createTime = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
}
if (callBack) {
item = callBack(item);
if (dataTransform) {
item = dataTransform(item);
}
return item;
});

View File

@ -13,4 +13,6 @@ export enum TableKeyEnum {
SYSTEM_RESOURCEPOOL = 'systemResourcePool',
SYSTEM_AUTH = 'systemAuth',
ORGANNATIONMEMBER = 'member',
SYSTEM_ORGANIZATION = 'systemOrganization',
SYSTEM_PROJECT = 'systemProject',
}

View File

@ -15,6 +15,7 @@ Object.keys(_Vmodules).forEach((key) => {
if (!defaultModule) return;
result = { ...result, ...defaultModule };
});
console.log('result', result);
export default {
message: {
'menu.workplace': 'Workplace',

View File

@ -42,6 +42,16 @@ const Setting: AppRouteRecordRaw = {
isTopMenu: true,
},
},
{
path: 'organization-and-project',
name: 'settingSystemOrganization',
component: () => import('@/views/setting/system/organizationAndProject/index.vue'),
meta: {
locale: 'menu.settings.system.organizationAndProject',
roles: ['*'],
isTopMenu: true,
},
},
{
path: 'resourcePool',
name: 'settingSystemResourcePool',

View File

@ -18,6 +18,14 @@ const msTableStore = defineStore('msTable', {
initColumn(tableKey: string, column: MsTableColumn, mode: TableOpenDetailMode) {
if (!this.selectorColumnMap.has(tableKey)) {
const tmpMap = this.selectorColumnMap;
column.forEach((item) => {
if (item.showInTable === undefined) {
item.showInTable = true;
}
if (item.showDrag === undefined) {
item.showDrag = false;
}
});
tmpMap.set(tableKey, { mode, column });
this.selectorColumnMap = tmpMap;
}

View File

@ -0,0 +1,85 @@
<template>
<a-modal
v-model:visible="currentVisible"
width="680px"
:ok-text="t('system.organization.create')"
unmount-on-close
:on-before-ok="handleBeforeOk"
@cancel="handleCancel"
>
<template #title> {{ t('system.organization.createOrganization') }} </template>
<div class="form">
<a-form ref="formRef" :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
required
:label="t('system.organization.organizationName')"
:rules="[
{ required: true, message: t('system.organization.organizationNameRequired') },
{ validator: validateName },
]"
>
<a-input v-model="form.name" :placeholder="t('system.organization.organizationNamePlaceholder')" />
</a-form-item>
<a-form-item field="name" :label="t('system.organization.organizationAdmin')">
<MsUserSelector v-model:value="form.admin" placeholder="system.organization.organizationAdminPlaceholder" />
</a-form-item>
<a-form-item field="description" :label="t('system.organization.description')">
<a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" />
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect } from 'vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const formRef = ref<FormInstance>();
const emit = defineEmits<{
(e: 'cancel'): void;
}>();
const form = reactive({
name: '',
admin: [],
description: '',
});
const currentVisible = ref(props.visible);
const validateName = (value: string, callback: (error?: string) => void) => {
if (value !== '') {
// const isExist = props.list.some((item) => item.name === value);
// if (isExist) {
// callback(t('system.organization.userGroupNameIsExist', { name: value }));
// }
callback();
}
};
watchEffect(() => {
currentVisible.value = props.visible;
});
const handleCancel = () => {
emit('cancel');
};
const handleBeforeOk = () => {
formRef.value?.validate((errors: undefined | Record<string, ValidatedError>) => {
if (errors) {
return false;
}
return true;
});
};
</script>

View File

@ -0,0 +1,98 @@
<template>
<MsDrawer
:width="680"
:visible="props.visible"
unmount-on-close
:footer="false"
:title="t('system.organization.projectName', { name: props.currentName })"
@cancel="handleCancel"
>
<div>
<div class="flex flex-row justify-end">
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.user.searchUser')"
class="w-[230px]"
@search="searchUser"
@press-enter="searchUser"
></a-input-search>
</div>
<ms-base-table v-bind="propsRes" v-on="propsEvent" />
</div>
</MsDrawer>
</template>
<script lang="ts" setup>
import { postProjectTableByOrgId } from '@/api/modules/setting/system/organizationAndProject';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import { useI18n } from '@/hooks/useI18n';
import { watch, ref } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
export interface projectDrawerProps {
visible: boolean;
organizationId: string;
currentName: string;
}
const { t } = useI18n();
const props = defineProps<projectDrawerProps>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void;
}>();
const keyword = ref('');
const projectColumn: MsTableColumn = [
{
title: 'system.organization.ID',
dataIndex: 'num',
},
{
title: 'system.project.name',
dataIndex: 'name',
},
{
title: 'system.organization.status',
dataIndex: 'enable',
},
{
title: 'system.organization.creator',
dataIndex: 'createUser',
},
{
title: 'system.organization.createTime',
dataIndex: 'createTime',
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postProjectTableByOrgId, {
columns: projectColumn,
showSetting: false,
scroll: { y: 'auto', x: '600px' },
selectable: false,
noDisable: false,
});
async function searchUser() {
setKeyword(keyword.value);
await loadList();
}
const handleCancel = () => {
emit('update:visible', false);
};
const fetchData = async () => {
await loadList();
};
watch(
() => props.organizationId,
(organizationId) => {
setLoadListParams({ organizationId });
fetchData();
}
);
</script>

View File

@ -0,0 +1,196 @@
<template>
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
<template #memberCount="{ record }">
<span class="primary-color" @click="showUserDrawer(record)">{{ record.memberCount }}</span>
</template>
<template #projectCount="{ record }">
<span class="primary-color" @click="showProjectDrawer(record)">{{ record.projectCount }}</span>
</template>
<template #operation="{ record }">
<template v-if="!record.enable">
<MsButton @click="handleEnable(record)">{{ t('system.organization.enable') }}</MsButton>
<MsButton @click="handleDelete(record)">{{ t('system.organization.delete') }}</MsButton>
</template>
<template v-else>
<MsButton @click="showOrganizationModal(record)">{{ t('system.organization.edit') }}</MsButton>
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton>
<MsButton @click="handleEnd(record)">{{ t('system.organization.end') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
</template>
</template>
</MsBaseTable>
<AddOrganizationModal :visible="userVisible" @cancel="handleAddUserModalCancel" />
<ProjectDrawer v-bind="currentProjectDrawer" @cancel="handleProjectDrawerCancel" />
<UserDrawer v-bind="currentUserDrawer" @cancel="handleUserDrawerCancel" />
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { useTableStore } from '@/store';
import { ref, reactive, watchEffect } from 'vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { postOrgTable } from '@/api/modules/setting/system/organizationAndProject';
import { TableKeyEnum } from '@/enums/tableEnum';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import AddOrganizationModal from './addOrganizationModal.vue';
import ProjectDrawer from './projectDrawer.vue';
import { TableData } from '@arco-design/web-vue';
import UserDrawer from './userDrawer.vue';
export interface SystemOrganizationProps {
keyword: string;
}
const props = defineProps<SystemOrganizationProps>();
const { t } = useI18n();
const tableStore = useTableStore();
const userVisible = ref(false);
const currentProjectDrawer = reactive({
visible: false,
organizationId: '',
currentName: '',
});
const currentUserDrawer = reactive({
visible: false,
organizationId: '',
});
const tableActions: ActionsItem[] = [
{
label: 'system.user.delete',
eventTag: 'delete',
danger: true,
},
];
const handleDelete = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const handleMoreAction = (tag: string, record: any) => {
if (tag === 'delete') {
handleDelete(record);
}
};
const handleEnable = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const handleEnd = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const showOrganizationModal = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const showAddUserModal = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
userVisible.value = true;
};
const handleProjectDrawerCancel = () => {
currentProjectDrawer.visible = false;
};
const showProjectDrawer = (record: TableData) => {
currentProjectDrawer.visible = true;
currentProjectDrawer.organizationId = record.id;
currentProjectDrawer.currentName = record.name;
};
const showUserDrawer = (record: TableData) => {
currentUserDrawer.visible = true;
currentUserDrawer.organizationId = record.id;
};
const handleUserDrawerCancel = () => {
currentUserDrawer.visible = false;
};
const organizationColumns: MsTableColumn = [
{
title: 'system.organization.ID',
dataIndex: 'num',
width: 100,
ellipsis: true,
},
{
title: 'system.organization.name',
dataIndex: 'name',
},
{
title: 'system.organization.member',
slotName: 'memberCount',
},
{
title: 'system.organization.project',
slotName: 'projectCount',
},
{
title: 'system.organization.status',
dataIndex: 'enable',
},
{
title: 'system.organization.description',
dataIndex: 'description',
},
{
title: 'system.organization.createUser',
dataIndex: 'createUser',
},
{
title: 'system.organization.createTime',
dataIndex: 'createTime',
width: 230,
},
{
title: 'system.organization.operation',
slotName: 'operation',
fixed: 'right',
width: 208,
},
];
tableStore.initColumn(TableKeyEnum.SYSTEM_ORGANIZATION, organizationColumns, 'drawer');
const { propsRes, propsEvent, loadList, setKeyword } = useTable(postOrgTable, {
tableKey: TableKeyEnum.SYSTEM_ORGANIZATION,
scroll: { y: 'auto', x: '1300px' },
selectable: false,
noDisable: false,
size: 'default',
});
const fetchData = async () => {
await loadList();
};
const handleAddUserModalCancel = () => {
userVisible.value = false;
};
watchEffect(() => {
setKeyword(props.keyword);
fetchData();
});
</script>
<style lang="scss" scoped>
.primary-color {
color: rgb(var(--primary-5));
cursor: pointer;
}
</style>

View File

@ -0,0 +1,137 @@
<template>
<MsBaseTable v-bind="propsRes" v-on="propsEvent">
<template #operation="{ record }">
<template v-if="!record.enable">
<MsButton @click="handleEnable(record)">{{ t('system.organization.enable') }}</MsButton>
<MsButton @click="handleDelete(record)">{{ t('system.organization.delete') }}</MsButton>
</template>
<template v-else>
<MsButton @click="showOrganizationModal(record)">{{ t('system.organization.edit') }}</MsButton>
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addUser') }}</MsButton>
<MsButton @click="handleEnd(record)">{{ t('system.organization.end') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
</template>
</template>
</MsBaseTable>
<AddOrganizationModal :visible="userVisible" @cancel="handleAddUserModalCancel" />
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { useTableStore } from '@/store';
import { watchEffect, ref } from 'vue';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { postUserByUserGroup } from '@/api/modules/setting/usergroup';
import { TableKeyEnum } from '@/enums/tableEnum';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import AddOrganizationModal from './addOrganizationModal.vue';
const { t } = useI18n();
const tableStore = useTableStore();
const userVisible = ref(false);
const tableActions: ActionsItem[] = [
{
label: 'system.user.delete',
eventTag: 'delete',
danger: true,
},
];
const handleDelete = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const handleMoreAction = (tag: string, record: any) => {
if (tag === 'delete') {
handleDelete(record);
}
};
const handleEnable = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const handleEnd = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const showOrganizationModal = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
};
const showAddUserModal = (record: any) => {
// eslint-disable-next-line no-console
console.log(record);
userVisible.value = true;
};
const projectColumn: MsTableColumn = [
{
title: 'system.organization.ID',
dataIndex: 'id',
},
{
title: 'system.organization.name',
dataIndex: 'name',
},
{
title: 'system.organization.member',
dataIndex: 'name',
},
{
title: 'system.organization.project',
dataIndex: 'project',
},
{
title: 'system.organization.enabled',
dataIndex: 'enabled',
},
{
title: 'system.organization.description',
dataIndex: 'description',
},
{
title: 'system.organization.createUser',
dataIndex: 'createUser',
},
{
title: 'system.organization.createTime',
dataIndex: 'createTime',
},
{
title: 'system.organization.operation',
slotName: 'operation',
fixed: 'right',
width: 200,
},
];
tableStore.initColumn(TableKeyEnum.SYSTEM_PROJECT, projectColumn, 'drawer');
const { propsRes, propsEvent, loadList } = useTable(postUserByUserGroup, {
tableKey: TableKeyEnum.SYSTEM_PROJECT,
scroll: { y: 'auto', x: '600px' },
selectable: false,
noDisable: false,
});
const fetchData = async () => {
await loadList();
};
const handleAddUserModalCancel = () => {
userVisible.value = false;
};
watchEffect(() => {
fetchData();
});
</script>

View File

@ -0,0 +1,106 @@
<template>
<MsDrawer
:width="680"
:visible="props.visible"
unmount-on-close
:footer="false"
:title="t('system.organization.addMember')"
@cancel="handleCancel"
>
<div>
<div class="flex flex-row justify-between">
<a-button type="primary" @click="handleAddMember">
{{ t('system.organization.addMember') }}
</a-button>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.user.searchUser')"
class="w-[230px]"
@search="searchUser"
@press-enter="searchUser"
></a-input-search>
</div>
<ms-base-table class="mt-[16px]" v-bind="propsRes" v-on="propsEvent" />
</div>
</MsDrawer>
</template>
<script lang="ts" setup>
import { postUserTableByOrgId } from '@/api/modules/setting/system/organizationAndProject';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import { useI18n } from '@/hooks/useI18n';
import { watch, ref } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
export interface projectDrawerProps {
visible: boolean;
organizationId: string;
}
const { t } = useI18n();
const props = defineProps<projectDrawerProps>();
const emit = defineEmits<{
(e: 'update:visible', v: boolean): void;
}>();
const keyword = ref('');
const projectColumn: MsTableColumn = [
{
title: 'system.organization.ID',
dataIndex: 'num',
},
{
title: 'system.project.name',
dataIndex: 'name',
},
{
title: 'system.organization.status',
dataIndex: 'enable',
},
{
title: 'system.organization.creator',
dataIndex: 'createUser',
},
{
title: 'system.organization.createTime',
dataIndex: 'createTime',
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(postUserTableByOrgId, {
columns: projectColumn,
showSetting: false,
scroll: { y: 'auto', x: '600px' },
selectable: false,
size: 'small',
noDisable: false,
});
async function searchUser() {
setKeyword(keyword.value);
await loadList();
}
const handleCancel = () => {
emit('update:visible', false);
};
const fetchData = async () => {
await loadList();
};
const handleAddMember = () => {
// TODO add member
emit('update:visible', false);
};
watch(
() => props.organizationId,
(organizationId) => {
setLoadListParams({ organizationId });
fetchData();
}
);
</script>

View File

@ -0,0 +1,59 @@
<template>
<MsCard simple>
<div class="mb-4 flex items-center justify-between">
<div>
<a-button type="primary" @click="handleAddOrganization">{{
currentTable === 'organization'
? t('system.organization.createOrganization')
: t('system.organization.createProject')
}}</a-button>
</div>
<div class="flex items-center">
<a-input-search
:placeholder="t('system.user.searchUser')"
class="w-[230px]"
@change="handleKeywordChange"
@search="handleKeywordChange"
></a-input-search>
<a-radio-group v-model="currentTable" class="ml-[14px]" type="button">
<a-radio value="organization">{{
t('system.organization.organizationCount', { count: organizationCount })
}}</a-radio>
<a-radio value="project">{{ t('system.organization.projectCount', { count: projectCount }) }}</a-radio>
</a-radio-group>
</div>
</div>
<div>
<SystemOrganization v-if="currentTable === 'organization'" :keyword="currentKeyword" />
<SystemProject v-if="currentTable === 'project'" :keyword="currentKeyword" />
</div>
</MsCard>
<AddOrganizationModal :visible="organizationVisible" @cancel="handleAddOrganizationCancel" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue';
import AddOrganizationModal from './components/addOrganizationModal.vue';
import SystemOrganization from './components/systemOrganization.vue';
import SystemProject from './components/systemProject.vue';
const { t } = useI18n();
const currentTable = ref('organization');
const organizationVisible = ref(false);
const organizationCount = ref(0);
const projectCount = ref(0);
const currentKeyword = ref('');
const handleKeywordChange = (value: string) => {
currentKeyword.value = value;
};
const handleAddOrganization = () => {
organizationVisible.value = true;
};
const handleAddOrganizationCancel = () => {
organizationVisible.value = false;
};
</script>

View File

@ -0,0 +1,37 @@
export default {
'system.organization.create': 'Create',
'system.organization.cancel': 'Cancel',
'system.organization.add': 'Add',
'system.organization.delete': 'Delete',
'system.organization.edit': 'Edit',
'system.organization.save': 'Save',
'system.organization.end': 'End',
'system.organization.addMember': 'Add Member',
'system.organization.addMemberPlaceholder': 'Please select member',
'system.organization.addMemberRequired': 'Please select member',
'system.organization.searchPlaceholder': 'Please enter the organization name to query',
'system.organization.addOrganization': 'Add organization',
'system.organization.createOrganization': 'Create organization',
'system.organization.organizationName': 'Organization name',
'system.organization.organizationNamePlaceholder':
'Please enter the organization name, which cannot be duplicated with other organization names',
'system.organization.organizationNameRequired': 'Organization name cannot be empty',
'system.organization.organizationNameDuplicate': 'Already have {name} please change',
'system.organization.organizationAdmin': 'Organization administrator',
'system.organization.organizationAdminPlaceholder':
'The organization administrator defaults to the person who created the organization',
'system.organization.description': 'Description',
'system.organization.descriptionPlaceholder': 'Please describe the organization',
'system.organization.ID': 'ID',
'system.organization.name': 'Name',
'system.organization.member': 'Member',
'system.organization.project': 'Project',
'system.organization.status': 'Status',
'system.organization.creator': 'Creator',
'system.organization.createTime': 'Create Time',
'system.organization.operation': 'Operation',
'system.organization.organizationCount': 'Organization({count})',
'system.organization.projectCount': 'Project({count})',
'system.organization.projectName': 'Project({name})',
'system.project.name': 'Project name',
};

View File

@ -0,0 +1,35 @@
export default {
'system.organization.create': '创建',
'system.organization.cancel': '取消',
'system.organization.add': '添加',
'system.organization.delete': '删除',
'system.organization.edit': '编辑',
'system.organization.save': '保存',
'system.organization.end': '结束',
'system.organization.addMember': '添加成员',
'system.organization.addMemberPlaceholder': '请选择成员',
'system.organization.addMemberRequired': '请选择成员',
'system.organization.searchPlaceholder': '请输入组织名称回车查询',
'system.organization.addOrganization': '添加组织',
'system.organization.createOrganization': '创建组织',
'system.organization.organizationName': '组织名称',
'system.organization.organizationNamePlaceholder': '请输入组织名称,不可与其他组织名称重复',
'system.organization.organizationNameRequired': '组织名称不能为空',
'system.organization.organizationNameDuplicate': '已有 {name} 请更改',
'system.organization.organizationAdmin': '组织管理员',
'system.organization.organizationAdminPlaceholder': '默认选择创建组织人为组织管理员',
'system.organization.description': '描述',
'system.organization.descriptionPlaceholder': '请对组织进行描述',
'system.organization.ID': 'ID',
'system.organization.name': '名称',
'system.organization.member': '成员',
'system.organization.project': '项目',
'system.organization.status': '状态',
'system.organization.creator': '创建人',
'system.organization.createTime': '创建时间',
'system.organization.operation': '操作',
'system.organization.organizationCount': '组织({count})',
'system.organization.projectCount': '项目({count})',
'system.organization.projectName': '项目({name})',
'system.project.name': '项目名称',
};

View File

@ -16,23 +16,7 @@
:label="t('system.userGroup.user')"
:rules="[{ required: true, message: t('system.userGroup.pleaseSelectUser') }]"
>
<a-select
v-model="form.name"
multiple
:virtual-list-props="{ height: 200 }"
:placeholder="t('system.userGroup.pleaseSelectUser')"
:options="userOptions"
:field-names="fieldNames"
@search="handleSearch"
>
<template #label="{ data }">
<span class="option-name"> {{ data.name }} </span>
</template>
<template #option="{ data }">
<span class="option-name"> {{ data.name }} </span>
<span class="option-email"> {{ `(${data.email})` }} </span>
</template>
</a-select>
<ms-user-selector v-model:value="form.name" />
</a-form-item>
</a-form>
</div>
@ -46,6 +30,7 @@
import { useUserGroupStore } from '@/store';
import { getUserList, addUserToUserGroup } from '@/api/modules/setting/usergroup';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{
@ -59,8 +44,6 @@
(e: 'submit', value: string[]): void;
}>();
const fieldNames = { value: 'id', label: 'name' };
const currentVisible = ref(props.visible);
const loading = ref(false);
@ -102,18 +85,6 @@
});
};
const handleSearch = (value: string) => {
if (value) {
loading.value = true;
window.setTimeout(() => {
userOptions.value = userOptions.value.filter((item) => item.name.includes(value));
loading.value = false;
}, 60);
} else {
userOptions.value = allOption.value;
}
};
onMounted(() => {
initUserList();
});

View File

@ -2,7 +2,7 @@
<div class="usergroup-auth-table">
<a-table
:span-method="dataSpanMethod"
:scroll="{ y: '860px', x: '800px' }"
:scroll="{ y: '500px', x: '800px' }"
:data="tableData"
:loading="loading"
:bordered="{ wrapper: true, cell: true }"
@ -286,6 +286,7 @@
<style scoped lang="less">
.usergroup-auth-table {
position: relative;
min-height: calc(100vh - 230px);
:deep(.arco-table-container) {
border-top: 1px solid var(--color-text-n8) !important;
border-right: 1px solid var(--color-text-n8) !important;

View File

@ -59,7 +59,7 @@
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(postUserByUserGroup, {
tableKey: TableKeyEnum.USERGROUPUSER,
scroll: { y: 'auto', x: '600px' },
scroll: { x: '600px' },
selectable: true,
noDisable: true,
});

View File

@ -8,8 +8,8 @@
<icon-double-right v-else class="icon" @click="collapse = true" />
</div>
</div>
<div class="grow-1 w-[100%] overflow-x-scroll p-[24px]">
<div class="grow-1 flex flex-row items-center justify-between">
<div class="w-[100%] overflow-x-scroll p-[24px]">
<div class="flex flex-row items-center justify-between">
<div class="title">{{ store.userGroupInfo.currentName }}</div>
<div class="flex items-center">
<a-input class="w-[240px]" :placeholder="t('system.userGroup.searchPlacehoder')">
@ -23,7 +23,7 @@
</a-radio-group>
</div>
</div>
<div class="grow-1 mt-[16px]">
<div class="mt-[16px]">
<user-table v-if="currentTable === 'user'" />
<auth-table v-if="currentTable === 'auth'" />
</div>