初始化项目
This commit is contained in:
181
src/views/manage/role/index.vue
Normal file
181
src/views/manage/role/index.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<script setup lang="tsx">
|
||||
import { Button, Popconfirm, Tag } from 'ant-design-vue';
|
||||
import type { Key } from 'ant-design-vue/es/_util/type';
|
||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusRecord } from '@/constants/business';
|
||||
import RoleOperateDrawer from './modules/role-operate-drawer.vue';
|
||||
import RoleSearch from './modules/role-search.vue';
|
||||
|
||||
const wrapperEl = shallowRef<HTMLElement | null>(null);
|
||||
const { height: wrapperElHeight } = useElementSize(wrapperEl);
|
||||
|
||||
const scrollConfig = computed(() => {
|
||||
return {
|
||||
y: wrapperElHeight.value - 72,
|
||||
x: 702
|
||||
};
|
||||
});
|
||||
|
||||
const { columns, columnChecks, data, loading, getData, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||
apiFn: doGetRoleList,
|
||||
apiParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
status: undefined,
|
||||
roleName: undefined,
|
||||
roleKey: undefined
|
||||
},
|
||||
rowKey: 'roleId',
|
||||
columns: () => [
|
||||
{
|
||||
key: 'roleKey',
|
||||
dataIndex: 'roleKey',
|
||||
title: $t('page.manage.role.roleCode'),
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'roleName',
|
||||
dataIndex: 'roleName',
|
||||
title: $t('page.manage.role.roleName'),
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
title: $t('page.manage.role.roleStatus'),
|
||||
align: 'center',
|
||||
customRender: ({ record }) => {
|
||||
if (record.status === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tagMap: Record<Api.Common.EnableStatus, string> = {
|
||||
'0': 'success',
|
||||
'1': 'warning'
|
||||
};
|
||||
|
||||
const label = $t(enableStatusRecord[record.status]);
|
||||
|
||||
return <Tag color={tagMap[record.status]}>{label}</Tag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'remark',
|
||||
dataIndex: 'remark',
|
||||
title: $t('page.manage.role.roleDesc')
|
||||
},
|
||||
{
|
||||
key: 'createTime',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
title: '创建时间'
|
||||
},
|
||||
{
|
||||
key: 'operate',
|
||||
title: $t('common.operate'),
|
||||
align: 'center',
|
||||
width: 200,
|
||||
customRender: ({ record }) =>
|
||||
!record.admin && (
|
||||
<div class="flex justify-around gap-8px">
|
||||
{isShowBtn('system:role:edit') && (
|
||||
<Button size="small" onClick={() => edit(record.roleId)}>
|
||||
{$t('common.edit')}
|
||||
</Button>
|
||||
)}
|
||||
{isShowBtn('system:role:remove') && (
|
||||
<Popconfirm onConfirm={() => handleDelete(record.roleId)} title={$t('common.confirmDelete')}>
|
||||
<Button danger size="small">
|
||||
{$t('common.delete')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const {
|
||||
drawerVisible,
|
||||
operateType,
|
||||
editingData,
|
||||
handleAdd,
|
||||
handleEdit,
|
||||
checkedRowKeys,
|
||||
onBatchDeleted,
|
||||
onDeleted
|
||||
// closeDrawer
|
||||
} = useTableOperate(data, { getData, idKey: 'roleId' });
|
||||
|
||||
async function handleBatchDelete() {
|
||||
const { error } = await doDeleteRole(checkedRowKeys.value.join(','));
|
||||
if (!error) {
|
||||
onBatchDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
const { error } = await doDeleteRole(id);
|
||||
if (!error) {
|
||||
onDeleted();
|
||||
}
|
||||
}
|
||||
|
||||
function edit(id: number) {
|
||||
handleEdit(id);
|
||||
}
|
||||
|
||||
function handleRoleSelectChange(selectedRowKeys: Key[]) {
|
||||
checkedRowKeys.value = selectedRowKeys as number[];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||
<RoleSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
|
||||
<ACard
|
||||
:title="$t('page.manage.role.title')"
|
||||
:bordered="false"
|
||||
:body-style="{ flex: 1, overflow: 'hidden' }"
|
||||
class="flex-col-stretch sm:flex-1-hidden card-wrapper"
|
||||
>
|
||||
<template #extra>
|
||||
<TableHeaderOperation
|
||||
v-model:columns="columnChecks"
|
||||
:show-delete="true"
|
||||
:disabled-delete="checkedRowKeys.length === 0"
|
||||
:loading="loading"
|
||||
@add="handleAdd"
|
||||
@delete="handleBatchDelete"
|
||||
@refresh="getData"
|
||||
/>
|
||||
</template>
|
||||
<ATable
|
||||
ref="wrapperEl"
|
||||
:columns="columns"
|
||||
:data-source="data"
|
||||
:loading="loading"
|
||||
:row-selection="{
|
||||
selectedRowKeys: checkedRowKeys,
|
||||
onChange: handleRoleSelectChange,
|
||||
getCheckboxProps: record => ({ disabled: record.admin })
|
||||
}"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:pagination="mobilePagination"
|
||||
:scroll="scrollConfig"
|
||||
class="h-full"
|
||||
/>
|
||||
<RoleOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
@submitted="getData"
|
||||
/>
|
||||
</ACard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
123
src/views/manage/role/modules/menu-auth.vue
Normal file
123
src/views/manage/role/modules/menu-auth.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataNode } from 'ant-design-vue/es/tree';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuAuthModal'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the roleId */
|
||||
roleId: number;
|
||||
type: 'add' | 'edit';
|
||||
drawerVisible: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const menuIds = defineModel<number[]>('menuIds', { default: [] });
|
||||
|
||||
const tree = shallowRef<DataNode[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.drawerVisible,
|
||||
val => {
|
||||
if (val) {
|
||||
init();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
async function getTree() {
|
||||
const { error, data } = await fetchGetMenuTree();
|
||||
|
||||
if (!error) {
|
||||
tree.value = recursiveTransform(data);
|
||||
}
|
||||
}
|
||||
|
||||
function recursiveTransform(data: Api.SystemManage.MenuTree[]): DataNode[] {
|
||||
return data.map(item => {
|
||||
const { id: key, label } = item;
|
||||
|
||||
if (item.children) {
|
||||
return {
|
||||
key,
|
||||
title: label,
|
||||
children: recursiveTransform(item.children)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
title: label
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getChecks() {
|
||||
const { data, error } = await doGetRoleMenuList(props.roleId);
|
||||
if (!error) {
|
||||
if (props.type === 'edit') {
|
||||
tree.value = recursiveTransform(data.menus);
|
||||
nextTick(() => {
|
||||
menuIds.value = data.checkedKeys;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
if (props.type === 'edit') {
|
||||
await getChecks();
|
||||
} else {
|
||||
await getTree();
|
||||
}
|
||||
}
|
||||
|
||||
function clearChecks() {
|
||||
menuIds.value = [];
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clearChecks,
|
||||
checkedKeys: menuIds,
|
||||
tree
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border-0.5 border-gray-300 rounded-md p-2 transition-all dark:border-dark-300" hover="border-gray-500">
|
||||
<SimpleScrollbar>
|
||||
<ATree
|
||||
v-model:checked-keys="menuIds"
|
||||
:selectable="false"
|
||||
:virtual="false"
|
||||
:tree-data="tree"
|
||||
checkable
|
||||
block-node
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.ant-tree .ant-tree-switcher) {
|
||||
line-height: normal;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-tree .ant-tree-checkbox) {
|
||||
margin-block-start: unset;
|
||||
}
|
||||
|
||||
:deep(.ant-tree .ant-tree-title) {
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
167
src/views/manage/role/modules/role-operate-drawer.vue
Normal file
167
src/views/manage/role/modules/role-operate-drawer.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<script setup lang="ts">
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useAntdForm, useFormRules } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions } from '@/constants/business';
|
||||
import { doPostRole, doPutRole } from '@/service/api/role';
|
||||
import MenuAuth from './menu-auth.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'RoleOperateDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: AntDesign.TableOperateType;
|
||||
/** the edit row data */
|
||||
rowData?: Api.SystemManage.Role | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
const menuAuthRef = ref<InstanceType<typeof MenuAuth> | null>(null);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const { formRef, validate, resetFields } = useAntdForm();
|
||||
const { defaultRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<AntDesign.TableOperateType, string> = {
|
||||
add: $t('page.manage.role.addRole'),
|
||||
edit: $t('page.manage.role.editRole')
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Pick<Api.SystemManage.Role, 'roleName' | 'roleKey' | 'remark' | 'status'> & { menuIds: number[] };
|
||||
|
||||
const model = ref<Model>(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
roleName: '',
|
||||
roleKey: '',
|
||||
remark: '',
|
||||
status: '0',
|
||||
menuIds: []
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Exclude<keyof Model, 'remark' | 'menuIds'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
roleName: defaultRequiredRule,
|
||||
roleKey: defaultRequiredRule,
|
||||
status: defaultRequiredRule
|
||||
};
|
||||
|
||||
const roleId = computed(() => props.rowData?.roleId || -1);
|
||||
|
||||
function handleUpdateModelWhenEdit() {
|
||||
if (props.operateType === 'add') {
|
||||
model.value = createDefaultModel();
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
model.value = { ...props.rowData, menuIds: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
const menuIds = transformMenuChildWithRootIds(menuAuthRef.value?.tree || [], model.value.menuIds);
|
||||
|
||||
const { error } = await (props.operateType === 'edit' ? doPutRole : doPostRole)({
|
||||
...model.value,
|
||||
menuIds,
|
||||
menuCheckStrictly: true
|
||||
} as Api.SystemManage.Role);
|
||||
|
||||
if (!error) {
|
||||
authStore.refreshUserInfo();
|
||||
$message?.success($t(props.operateType === 'add' ? 'common.addSuccess' : 'common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
val => {
|
||||
if (val) {
|
||||
handleUpdateModelWhenEdit();
|
||||
resetFields();
|
||||
} else {
|
||||
menuAuthRef.value?.clearChecks();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ADrawer
|
||||
v-model:open="visible"
|
||||
:body-style="{ paddingRight: '0px', paddingTop: '0', paddingBottom: '0' }"
|
||||
:title="title"
|
||||
:width="460"
|
||||
>
|
||||
<SimpleScrollbar>
|
||||
<AForm ref="formRef" py-20px pr-20px layout="vertical" :model="model" :rules="rules">
|
||||
<AFormItem :label="$t('page.manage.role.roleName')" name="roleName">
|
||||
<AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleCode')" name="roleKey">
|
||||
<AInput v-model:value="model.roleKey" :placeholder="$t('page.manage.role.form.roleCode')" />
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleStatus')" name="status">
|
||||
<ARadioGroup v-model:value="model.status">
|
||||
<ARadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value">
|
||||
{{ $t(item.label) }}
|
||||
</ARadio>
|
||||
</ARadioGroup>
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleDesc')" name="roleDesc">
|
||||
<AInput v-model:value="model.remark" :placeholder="$t('page.manage.role.form.roleDesc')" />
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem>
|
||||
<MenuAuth
|
||||
ref="menuAuthRef"
|
||||
v-model:menu-ids="model.menuIds"
|
||||
:drawer-visible="visible"
|
||||
:type="operateType"
|
||||
:role-id="roleId"
|
||||
/>
|
||||
<template #label>
|
||||
<div class="w-full flex-between">
|
||||
<span>{{ $t('page.manage.role.menuAuth') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</SimpleScrollbar>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex-y-center justify-end gap-12px">
|
||||
<AButton @click="closeDrawer">{{ $t('common.cancel') }}</AButton>
|
||||
<AButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</AButton>
|
||||
</div>
|
||||
</template>
|
||||
</ADrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
73
src/views/manage/role/modules/role-search.vue
Normal file
73
src/views/manage/role/modules/role-search.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions } from '@/constants/business';
|
||||
|
||||
defineOptions({
|
||||
name: 'RoleSearch'
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'reset'): void;
|
||||
(e: 'search'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const model = defineModel<Api.SystemManage.RoleSearchParams>('model', { required: true });
|
||||
|
||||
function reset() {
|
||||
emit('reset');
|
||||
}
|
||||
|
||||
function search() {
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ACard :title="$t('common.search')" :bordered="false" class="card-wrapper">
|
||||
<AForm :model="model" :label-width="80">
|
||||
<ARow :gutter="[16, 16]" wrap>
|
||||
<ACol :span="24" :md="12" :lg="6">
|
||||
<AFormItem :label="$t('page.manage.role.roleName')" name="roleName" class="m-0">
|
||||
<AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :md="12" :lg="6">
|
||||
<AFormItem :label="$t('page.manage.role.roleCode')" name="roleCode" class="m-0">
|
||||
<AInput v-model:value="model.roleKey" :placeholder="$t('page.manage.role.form.roleCode')" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :md="12" :lg="6">
|
||||
<AFormItem :label="$t('page.manage.role.roleStatus')" name="status" class="m-0">
|
||||
<ASelect v-model:value="model.status" :placeholder="$t('page.manage.role.form.roleStatus')" allow-clear>
|
||||
<ASelectOption v-for="option in enableStatusOptions" :key="option.value" :value="option.value">
|
||||
{{ $t(option.label) }}
|
||||
</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :md="12" :lg="6">
|
||||
<AFormItem class="m-0">
|
||||
<div class="w-full flex-y-center justify-end gap-12px">
|
||||
<AButton @click="reset">
|
||||
<div class="flex-y-center gap-8px">
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
<span>{{ $t('common.reset') }}</span>
|
||||
</div>
|
||||
</AButton>
|
||||
<AButton type="primary" ghost @click="search">
|
||||
<div class="flex-y-center gap-8px">
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
<span>{{ $t('common.search') }}</span>
|
||||
</div>
|
||||
</AButton>
|
||||
</div>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
</ARow>
|
||||
</AForm>
|
||||
</ACard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user