feat(系统设置): 用户组管理样式调整
This commit is contained in:
parent
d34ad6459e
commit
62593baa90
|
@ -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']
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -122,3 +122,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 全局容器 **/
|
||||
.ms-contentiner {
|
||||
height: calc(100vh - 84px);
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
'h-[38px]': true,
|
||||
'items-center': true,
|
||||
'px-[8px]': true,
|
||||
'is-active': element.id === currentId,
|
||||
'px-[4px]': true,
|
||||
}"
|
||||
@click="currentId = element.id"
|
||||
@click="handleListItemClick(element)"
|
||||
>
|
||||
<div class="draglist-item flex grow flex-row justify-between">
|
||||
<div :class="'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>
|
||||
</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>
|
||||
<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="usergroup-title leading-[24px]"> {{ element.name }}</div>
|
||||
<div v-if="element.id === currentId">
|
||||
<MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</popconfirm>
|
||||
</div>
|
||||
</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>
|
||||
|
|
|
@ -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>
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
|
@ -148,6 +148,7 @@
|
|||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-menu-inner) {
|
||||
padding: 16px 16px 0;
|
||||
.arco-menu-inline-header {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface UserGroupState {
|
||||
// 当前用户组名字
|
||||
currentName: string;
|
||||
currentTitle: string;
|
||||
}
|
|
@ -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;
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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/名称搜索',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue