feat(系统设置): 用户组管理样式调整

This commit is contained in:
RubyLiu 2023-06-15 16:41:45 +08:00 committed by 刘瑞斌
parent d34ad6459e
commit 62593baa90
17 changed files with 775 additions and 198 deletions

View File

@ -5,11 +5,13 @@
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {};
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink'];
RouterView: typeof import('vue-router')['RouterView'];
AFomrItem: typeof import('@arco-design/web-vue')['FomrItem']
ASelectOption: typeof import('@arco-design/web-vue')['SelectOption']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

View File

@ -37,7 +37,7 @@
"@7polo/kity": "2.0.8",
"@7polo/kityminder-core": "1.4.53",
"@arco-design/web-vue": "^2.47.0",
"@arco-themes/vue-ms-theme-default": "^0.0.12",
"@arco-themes/vue-ms-theme-default": "^0.0.14",
"@form-create/arco-design": "^3.1.21",
"@vueuse/core": "^9.13.0",
"ace-builds": "^1.22.0",

View File

@ -122,3 +122,8 @@
}
}
}
/** 全局容器 **/
.ms-contentiner {
height: calc(100vh - 84px);
}

View File

@ -0,0 +1,106 @@
<template>
<a-modal
v-model:visible="currentVisible"
width="680px"
:ok-text="t('system.userGroup.create')"
@ok="handelOk"
@cancel="handleCancel"
>
<template #title> {{ t('system.userGroup.createUserGroup') }} </template>
<div class="form">
<a-form :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
:label="t('system.userGroup.userGroupName')"
:rules="[{ required: true, message: t('system.userGroup.userGroupNameIsNotNone') }]"
>
<a-input v-model="form.name" :placeholder="t('system.userGroup.pleaseInputUserGroupName')" />
</a-form-item>
<a-form-item
field="authScope"
:label="t('system.userGroup.authScope')"
:rules="[{ required: true, message: t('system.userGroup.authScopeIsNotNone') }]"
>
<a-select
v-model="form.authScope"
:virtual-list-props="{ height: 200 }"
:placeholder="t('system.userGroup.pleaseSelectAuthScope')"
:options="authOptions"
: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>
</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 { UserOption } from './type';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'cancel'): void;
}>();
const form = reactive({
name: '',
authScope: '',
});
const allOption: UserOption[] = [
{ id: 1, name: 'llb', email: 'name@163.com' },
{
id: 2,
name: 'rubyliu',
email: 'rubyliu@163.com',
},
{
id: 3,
name: 'jack',
email: 'jack@163.com',
},
];
const authOptions = ref<UserOption[]>(allOption);
const loading = ref(false);
const fieldNames = { value: 'id', label: 'name' };
const currentVisible = ref(props.visible);
const handleSearch = (value: string) => {
if (value) {
loading.value = true;
window.setTimeout(() => {
authOptions.value = authOptions.value.filter((item) => item.name.includes(value));
loading.value = false;
}, 60);
} else {
authOptions.value = allOption;
}
};
watchEffect(() => {
currentVisible.value = props.visible;
});
const handleCancel = () => {
emit('cancel');
};
const handelOk = () => {
// eslint-disable-next-line no-console
console.log('ok');
handleCancel();
};
</script>

View File

@ -0,0 +1,116 @@
<template>
<a-modal
v-model:visible="currentVisible"
width="680px"
:ok-text="t('system.userGroup.add')"
@ok="handelOk"
@cancel="handleCancel"
>
<template #title> {{ t('system.userGroup.addUser') }} </template>
<div class="form">
<a-form :model="form" size="large" :style="{ width: '600px' }" layout="vertical">
<a-form-item
field="name"
: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>
</a-form-item>
</a-form>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect, onUnmounted } from 'vue';
import { UserOption } from './type';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
}>();
const fieldNames = { value: 'id', label: 'name' };
const currentVisible = ref(props.visible);
const loading = ref(false);
const form = reactive({
name: [],
});
const labelCache = new Map();
const allOption: UserOption[] = [
{ id: 1, name: 'llb', email: 'name@163.com' },
{
id: 2,
name: 'rubyliu',
email: 'rubyliu@163.com',
},
{
id: 3,
name: 'jack',
email: 'jack@163.com',
},
];
const userOptions = ref<UserOption[]>(allOption);
watchEffect(() => {
currentVisible.value = props.visible;
});
const emit = defineEmits<{
(e: 'cancel'): void;
}>();
const handleCancel = () => {
emit('cancel');
};
const handelOk = () => {
// eslint-disable-next-line no-console
console.log('ok');
handleCancel();
};
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;
}
};
onUnmounted(() => {
labelCache.clear();
});
</script>
<style lang="less" scoped>
.option-name {
color: var(--color-text-1);
}
.option-email {
color: var(--color-text-4);
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<a-table
:scroll="{ y: '860px', x: '1440px' }"
:columns="columns"
:data="data"
:span-method="dataSpanMethod"
:bordered="{ wrapper: true, cell: true }"
size="small"
/>
</template>
<script setup lang="ts">
import { TableData, TableColumnData } from '@arco-design/web-vue';
import { RenderFunction, VNodeChild } from 'vue';
export declare type OperationName = 'selection-checkbox' | 'selection-radio' | 'expand' | 'drag-handle';
export interface TableOperationColumn {
name: OperationName | string;
title?: string | RenderFunction;
width?: number;
fixed?: boolean;
render?: (record: TableData) => VNodeChild;
isLastLeftFixed?: boolean;
}
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Salary',
dataIndex: 'salary',
},
{
title: 'Address',
dataIndex: 'address',
},
{
title: 'Email',
dataIndex: 'email',
},
];
const data = [
{
key: '1',
name: 'Jane Doe',
salary: 23000,
address: '32 Park Road, London',
email: 'jane.doe@example.com',
},
{
key: '2',
name: 'Alisa Ross',
salary: 25000,
address: '35 Park Road, London',
email: 'alisa.ross@example.com',
},
{
key: '3',
name: 'Kevin Sandra',
salary: 22000,
address: '31 Park Road, London',
email: 'kevin.sandra@example.com',
},
{
key: '4',
name: 'Ed Hellen',
salary: 17000,
address: '42 Park Road, London',
email: 'ed.hellen@example.com',
},
{
key: '5',
name: 'William Smith',
salary: 27000,
address: '62 Park Road, London',
email: 'william.smith@example.com',
},
];
const dataSpanMethod = ({
record,
column,
}: {
record: TableData;
column: TableColumnData | TableOperationColumn;
rowIndex: number;
columnIndex: number;
}): { rowspan?: number; colspan?: number } | void => {
if (record.name === 'Alisa Ross' && column.dataIndex === 'salary') {
return {
rowspan: 2,
};
}
return {};
};
</script>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="user-group-left">
<a-input-search
v-model="searchKey"
class="w-[252px]"
@ -7,139 +7,146 @@
@press-enter="searchData"
/>
<div class="mt-2 flex flex-col">
<div class="second-color">
<icon-plus-circle v-if="!systemHidden" @click="handleSystemHidden" />
<icon-minus-circle v-if="systemHidden" @click="handleSystemHidden" />
<span class="ml-1"> {{ t('system.userGroup.inSystem') }}</span>
<div class="flex h-[38px] items-center justify-between px-[8px] leading-[24px]">
<div class="second-color"> {{ t('system.userGroup.global') }}</div>
<div class="primary-color" style="font-size: 20px"><icon-plus-circle-fill /></div>
</div>
<div v-if="systemHidden">
<div>
<div
v-for="element in systemList"
v-for="element in customList"
:key="element.id"
:class="{
'flex': true,
'h-[38px]': true,
'items-center': true,
'px-[8px]': true,
'is-active': element.id === currentId,
'px-[4px]': true,
}"
@click="currentId = element.id"
@click="handleListItemClick(element)"
>
<popconfirm
:visible="popVisible[element.id]"
:type="popType"
:default-name="popDefaultName"
position="bl"
@cancel="() => handlePopConfirmCancel(element.id)"
>
<div class="draglist-item flex grow flex-row justify-between">
<div :class="'ml-[20px]'">{{ element.name }}</div>
<div class="usergroup-title leading-[24px]"> {{ element.name }}</div>
<div v-if="element.id === currentId">
<a-popconfirm position="rb">
<template #content>
<a-button type="primary" @click="addUser(element.id)">{{ t('system.userGroup.addUser') }}</a-button>
</template>
<icon-plus />
</a-popconfirm>
<MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" />
</div>
</div>
</popconfirm>
</div>
</div>
</div>
</div>
<a-divider />
<div class="second-color flex items-center justify-between px-[4px]">
<div>
<icon-plus-circle v-if="!customHidden" @click="handleCustomHidden" />
<icon-minus-circle v-if="customHidden" @click="handleCustomHidden" />
<span class="ml-1"> {{ t('system.userGroup.customUserGroup') }}</span>
</div>
<div class="flex items-center">
<icon-plus-circle class="primary-color text-xl" @click="addSystemUserGroup" />
</div>
</div>
<div class="mt-[16px] px-[4px]">
<div v-if="customShowEmpty" class="custom-empty">{{ t('system.userGroup.emptyUserGroup') }}</div>
<draggable v-else v-model="customList" class="list-group" item-key="name" handle=".handle">
<template #item="{ element }">
<div
:class="{
'flex': true,
' h-[38px]': true,
'items-center': true,
'is-active': element.id === currentId,
'px-[4px]': true,
}"
@click="currentId = element.id"
>
<div v-if="element.id === currentId" class="handle"><icon-drag-dot-vertical /></div>
<div class="draglist-item flex grow flex-row justify-between">
<div :class="element.id === currentId ? 'ml-[8px]' : 'ml-[20px]'">{{ element.name }}</div>
<div v-if="element.id === currentId">
<a-popconfirm position="rb">
<template #content>
<a-button type="primary" @click="addUser(element.id)">{{
t('system.userGroup.addUser')
}}</a-button>
</template>
<icon-plus />
</a-popconfirm>
</div>
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
<AddUserModal :visible="addUserVisible" @cancel="addUserVisible = false" />
<AddUserGroupModal :visible="addUserGroupVisible" @cancel="addUserGroupVisible = false" />
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { ref, onBeforeMount } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import draggable from 'vuedraggable';
import { UserGroupListItem } from './type';
import { PopVisibleItem, RenameType, UserGroupListItem } from './type';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import AddUserModal from './addUserModal.vue';
import AddUserGroupModal from './addUserGroupModal.vue';
import popconfirm from './popconfirm.vue';
import useUserGroupStore from '@/store/modules/system/usergroup';
const { t } = useI18n();
const searchKey = ref('');
// true +; false -
const systemHidden = ref(true);
const customHidden = ref(true);
const store = useUserGroupStore();
// loading
const loading = ref(false);
const currentId = ref(0);
const systemList = ref<UserGroupListItem[]>([
{ name: '系统管理员1', id: 1 },
{ name: '系统管理员2', id: 2 },
{ name: '系统管理员3', id: 3 },
]);
const addUserVisible = ref(false);
const addUserGroupVisible = ref(false);
//
const popVisible = ref<PopVisibleItem>({});
//
const popType = ref<RenameType>('rename');
const popDefaultName = ref('');
//
const customList = ref<UserGroupListItem[]>([
{ name: '自定义用户组 1 (系统)', id: 4 },
{ name: '自定义用户组 2 (系统)', id: 5 },
{ name: '自定义用户组 3 (系统)', id: 6 },
{ name: '系统管理员', title: '', id: 1, authScope: 'system' },
{ name: '系统成员', title: '', id: 2, authScope: 'system' },
{ name: '组织管理员', title: '', id: 3, authScope: 'system' },
{ name: '组织成员', title: '', id: 4, authScope: 'system' },
{ name: '项目管理员', title: '', id: 5, authScope: 'system' },
{ name: '项目成员', title: '', id: 6, authScope: 'system' },
{ name: '自定义用户组1', title: '项目', id: 7, authScope: 'project' },
{ name: '自定义用户组2', title: '组织', id: 8, authScope: 'oraganation' },
]);
// const customList = ref<UserGroupListItem[]>([]);
const customAction: ActionsItem[] = [
{
label: 'system.userGroup.rename',
danger: false,
eventTag: 'rename',
},
{
label: 'system.userGroup.changeAuthScope',
danger: false,
eventTag: 'changeAuthScope',
},
{
isDivider: true,
},
{
label: 'system.userGroup.delete',
danger: true,
eventTag: 'delete',
},
];
const currentSystemId = ref(0);
const handleSystemHidden = () => {
systemHidden.value = !systemHidden.value;
};
const handleCustomHidden = () => {
customHidden.value = !customHidden.value;
};
const addUser = (id: number) => {
currentSystemId.value = id;
addUserVisible.value = true;
};
const addSystemUserGroup = () => {
// eslint-disable-next-line no-console
console.log('addSystemUserGroup');
addUserGroupVisible.value = true;
};
function searchData(keyword: string) {
// eslint-disable-next-line no-console
console.log(keyword);
}
const customShowEmpty = computed(() => {
return !loading.value && !customList.value.length;
const handleMoreAction = (item: ActionsItem, id: number) => {
if (item.eventTag !== 'delete') {
popType.value = item.eventTag as RenameType;
const tmpObj = customList.value.filter((ele) => ele.id === id)[0];
popVisible.value = { ...popVisible.value, [id]: true };
if (item.eventTag === 'rename') {
popDefaultName.value = tmpObj.name;
} else {
popDefaultName.value = tmpObj.authScope;
}
}
};
const handlePopConfirmCancel = (id: number) => {
popVisible.value = { ...popVisible.value, [id]: false };
};
onBeforeMount(() => {
const tmpObj: PopVisibleItem = {};
customList.value.forEach((element) => {
tmpObj[element.id] = false;
});
popVisible.value = tmpObj;
});
const handleListItemClick = (element: UserGroupListItem) => {
const { id, name, title } = element;
currentId.value = id;
store.setInfo({ currentName: name, currentTitle: title });
};
</script>
<style scoped lang="less">
@ -168,4 +175,16 @@
line-height: 20px;
overflow-wrap: break-word;
}
.button-icon {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-9));
}
.usergroup-title {
color: var(--color-text-1);
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<a-popconfirm
:popup-visible="renameVisible"
:ok-text="t('system.userGroup.confirm')"
:cancel-text="t('system.userGroup.cancel')"
:blur-to-close="false"
:click-to-close="false"
:click-outside-to-close="false"
@ok="handleSubmit('rename')"
@cancel="handleRenameCancel"
@popup-visible-change="() => (form.name = '')"
>
<template #icon>{{ null }}</template>
<template #content>
<a-form :model="form" :label-col-props="{ span: 0 }" :wrapper-col-props="{ span: 24 }">
<a-form-item>
<div class="title">{{ message.title }}</div>
</a-form-item>
<a-form-item field="name" :rules="[{ required: true, message: message.rule }]">
<a-input v-model="form.name" />
</a-form-item>
</a-form>
</template>
<slot></slot>
</a-popconfirm>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
import { watchEffect, reactive, ref, computed } from 'vue';
import { CustomMoreActionItem, RenameType } from './type';
const { t } = useI18n();
const form = reactive({
name: '',
});
const props = defineProps<{
visible: boolean;
defaultName: string;
type: RenameType;
}>();
const message = computed(() => {
if (props.type === 'rename') {
return {
rule: t('system.userGroup.userGroupNameIsNotNone'),
title: t('system.userGroup.rename'),
};
}
return {
rule: t('system.userGroup.userGroupAuthScopeIsNotNone'),
title: t('system.userGroup.changeAuthScope'),
};
});
const emit = defineEmits<{
(e: 'submit', value: CustomMoreActionItem): void;
}>();
const renameVisible = ref(props.visible);
const handleSubmit = (type: string) => {
// eslint-disable-next-line no-console
emit('submit', { eventKey: type, name: form.name });
};
const handleRenameCancel = () => {
form.name = '';
};
watchEffect(() => {
renameVisible.value = props.visible;
form.name = props.defaultName;
});
</script>
<style lang="less" scoped>
.title {
color: var(--color-text-1);
}
.error-6 {
color: rgb(var(--danger-6));
&:hover {
color: rgb(var(--danger-6));
}
}
:deep(.arco-form-item) {
margin-bottom: 8px;
}
.button-icon {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
color: rgb(var(--primary-5));
background-color: rgb(var(--primary-9));
}
</style>

View File

@ -2,4 +2,19 @@ export interface UserGroupListItem {
name: string;
id: number;
title?: string;
authScope: string;
}
export interface UserOption {
id: number;
name: string;
email: string;
}
export interface CustomMoreActionItem {
eventKey: string;
name: string;
}
export interface PopVisibleItem {
[key: number]: boolean;
}
export type RenameType = 'rename' | 'auth';

View File

@ -0,0 +1,102 @@
<template>
<MsBaseTable v-bind="propsRes" v-on="propsEvent"> </MsBaseTable>
</template>
<script lang="ts" setup>
import { getTableList } from '@/api/modules/api-test/index';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import { onMounted } from 'vue';
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
filterable: {
filters: [
{
text: '> 20000',
value: '20000',
},
{
text: '> 30000',
value: '30000',
},
],
filter: (value, record) => record.salary > value,
multiple: true,
},
},
{
title: '接口名称',
dataIndex: 'name',
width: 200,
},
{
title: '请求类型',
dataIndex: 'method',
},
{
title: '责任人',
dataIndex: 'username',
},
{
title: '路径',
dataIndex: 'path',
},
{
title: '标签',
dataIndex: 'tags',
},
{
title: '更新时间',
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '用例数',
dataIndex: 'caseTotal',
},
{
title: '用例状态',
dataIndex: 'caseStatus',
},
{
title: '用例通过率',
dataIndex: 'casePassingRate',
},
{
title: '接口状态',
dataIndex: 'status',
},
{
title: '创建时间',
slotName: 'createTime',
width: 200,
},
{
title: '描述',
dataIndex: 'description',
},
{
title: '操作',
slotName: 'action',
fixed: 'right',
width: 200,
},
];
const { propsRes, propsEvent, loadList } = useTable(getTableList, {
columns,
scroll: { y: 750, x: 2000 },
selectable: true,
});
const fetchData = async () => {
await loadList();
};
onMounted(() => {
fetchData();
});
</script>

View File

@ -148,6 +148,7 @@
<style lang="less" scoped>
:deep(.arco-menu-inner) {
padding: 16px 16px 0;
.arco-menu-inline-header {
@apply flex items-center;
}

View File

@ -29,7 +29,7 @@ export default function useTableProps(loadListFunc: GetListFunc, props?: Partial
bordered: true,
showPagination: true,
size: 'small',
scroll: { y: '550px', x: '1400px' },
scroll: { y: '860px', x: '1400px' },
checkable: true,
loading: true,
data: [] as MsTableData,

View File

@ -0,0 +1,5 @@
export interface UserGroupState {
// 当前用户组名字
currentName: string;
currentTitle: string;
}

View File

@ -0,0 +1,22 @@
import { defineStore } from 'pinia';
import { UserGroupState } from './types';
const useUserGroupStore = defineStore('userGroup', {
state: (): UserGroupState => ({
currentName: '',
currentTitle: '',
}),
getters: {
userGroupInfo(state: UserGroupState): UserGroupState {
return state;
},
},
actions: {
// 设置当前用户组信息
setInfo(partial: Partial<UserGroupState>) {
this.$patch(partial);
},
},
});
export default useUserGroupStore;

View File

@ -9,6 +9,29 @@ export default {
addUser: 'Add user',
emptyUserGroup:
'There are currently no custom user groups available. Please click "Create" or "+" above to create a user group',
rename: 'Rename',
changeAuthScope: 'Change auth scope',
delete: 'Delete',
user: 'user',
add: 'Add',
cancel: 'Cancel',
create: 'Create',
createUserGroup: 'create user group',
userGroupName: 'User group name',
authScope: 'Auth scope',
auth: 'Auth',
deleteSuccess: 'Delete success',
revokeSuccess: 'Revoke success',
revoke: 'revoke',
pleaseSelectUser: 'Please select user',
pleaseInputUserGroupName: 'Please input user group name,and not equal to system user group name',
pleaseSelectAuthScope: 'Please select auth scope',
userGroupNameIsNotNone: 'User group name is not none',
authScopeIsNotNone: 'Auth scope is not none',
userIsNotNone: 'User is not none',
confirm: 'Confirm',
global: 'Global',
searchPlacehoder: 'Search by ID/Name',
},
},
};

View File

@ -1,6 +1,7 @@
export default {
system: {
userGroup: {
global: '全局用户组',
searchHolder: '请输入用户组名称',
inSystem: '系统内置',
customUserGroup: '自定义用户组',
@ -8,6 +9,29 @@ export default {
addSystemUser: '添加系统用户',
addUser: '添加成员',
emptyUserGroup: '暂无自定义用户组,请点击上方创建或 “+” 创建用户组',
rename: '重命名',
changeAuthScope: '修改权限范围',
delete: '删除',
user: '成员',
add: '添加',
confirm: '确认',
create: '创建',
cancel: '取消',
createUserGroup: '创建用户组',
userGroupName: '用户组名称',
authScope: '权限范围',
auth: '权限',
deleteSuccess: '删除成功',
revokeSuccess: '撤销成功',
revoke: '撤销',
pleaseSelectUserGroup: '请选择用户组',
pleaseSelectUser: '请选择成员',
userGroupNameIsNotNone: '用户组名称不能为空',
authScopeIsNotNone: '权限范围不能为空',
userIsNotNone: '成员不能为空',
pleaseInputUserGroupName: '请输入用户组名称,且不与其他用户组名称重复',
pleaseSelectAuthScope: '请选择用户组所属的权限范围',
searchPlacehoder: '通过ID/名称搜索',
},
},
};

View File

@ -1,115 +1,56 @@
<template>
<div class="flex h-[100vh] flex-row bg-white px-[20px] py-[16px] pb-0">
<div class="h-[730px] w-[300px] p-6">
<div class="user-group flex flex-row bg-white">
<div class="user-group-left">
<UserGroupLeft />
</div>
<div class="grow-1 overflow-scroll">
<MsBaseTable v-bind="propsRes" v-on="propsEvent"> </MsBaseTable>
<div class="grow-1 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')">
<template #prefix>
<icon-search />
</template>
</a-input>
<a-radio-group v-model="currentTable" class="ml-[14px]" type="button">
<a-radio value="auth">{{ t('system.userGroup.auth') }}</a-radio>
<a-radio value="user">{{ t('system.userGroup.user') }}</a-radio>
</a-radio-group>
</div>
</div>
<div class="mt-[16px]">
<user-table v-if="currentTable === 'user'" />
<auth-table v-if="currentTable === 'auth'" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import UserGroupLeft from '@/components/bussiness/usergroup/index.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import { getTableList } from '@/api/modules/api-test/index';
import useUserGroupStore from '@/store/modules/system/usergroup';
import UserTable from '@/components/bussiness/usergroup/userTable.vue';
import AuthTable from '@/components/bussiness/usergroup/authTable.vue';
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'num',
filterable: {
filters: [
{
text: '> 20000',
value: '20000',
},
{
text: '> 30000',
value: '30000',
},
],
filter: (value, record) => record.salary > value,
multiple: true,
},
},
{
title: '接口名称',
dataIndex: 'name',
width: 200,
},
{
title: '请求类型',
dataIndex: 'method',
},
{
title: '责任人',
dataIndex: 'username',
},
{
title: '路径',
dataIndex: 'path',
},
{
title: '标签',
dataIndex: 'tags',
},
{
title: '更新时间',
dataIndex: 'updateTime',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '用例数',
dataIndex: 'caseTotal',
},
{
title: '用例状态',
dataIndex: 'caseStatus',
},
{
title: '用例通过率',
dataIndex: 'casePassingRate',
},
{
title: '接口状态',
dataIndex: 'status',
},
{
title: '创建时间',
slotName: 'createTime',
width: 200,
},
{
title: '描述',
dataIndex: 'description',
},
{
title: '操作',
slotName: 'action',
fixed: 'right',
width: 200,
},
];
const currentTable = ref('auth');
const { propsRes, propsEvent, loadList } = useTable(getTableList, {
columns,
scroll: { y: 750, x: 2000 },
selectable: true,
});
const { t } = useI18n();
const fetchData = async () => {
await loadList();
};
onMounted(() => {
fetchData();
});
const store = useUserGroupStore();
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.title {
color: var(--color-text-1);
}
.user-group {
height: calc(100vh - 72px);
}
.user-group-left {
position: relative;
padding: 24px;
border-right: 1px solid var(--color-border);
}
</style>