mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-24 12:26:38 +08:00
398 lines
13 KiB
Vue
398 lines
13 KiB
Vue
<script setup lang="tsx">
|
|
import type { Ref } from 'vue';
|
|
import { computed, reactive, ref, watch } from 'vue';
|
|
import type { SelectOption } from 'naive-ui';
|
|
import type { LastLevelRouteKey } from '@elegant-router/types';
|
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
|
import { $t } from '@/locales';
|
|
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
|
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
|
import { getLocalIcons } from '@/utils/icon';
|
|
import { fetchGetAllRoles } from '@/service/api';
|
|
import {
|
|
getLayoutAndPage,
|
|
transformLayoutAndPageToComponent,
|
|
transformToKeyValuePairs,
|
|
transformToQueryObject
|
|
} from './shared';
|
|
|
|
defineOptions({
|
|
name: 'MenuOperateDrawer'
|
|
});
|
|
|
|
export type OperateType = NaiveUI.TableOperateType | 'addChild';
|
|
|
|
interface Props {
|
|
/** the type of operation */
|
|
operateType: OperateType;
|
|
/** the edit menu data or the parent menu data when adding a child menu */
|
|
rowData?: Api.SystemManage.Menu | null;
|
|
/** all pages */
|
|
allPages: string[];
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
interface Emits {
|
|
(e: 'submitted'): void;
|
|
}
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
const visible = defineModel<boolean>('visible', {
|
|
default: false
|
|
});
|
|
|
|
const { formRef, validate, restoreValidation } = useNaiveForm();
|
|
const { defaultRequiredRule } = useFormRules();
|
|
|
|
const title = computed(() => {
|
|
const titles: Record<OperateType, string> = {
|
|
add: $t('page.manage.menu.addMenu'),
|
|
addChild: $t('page.manage.menu.addChildMenu'),
|
|
edit: $t('page.manage.menu.editMenu')
|
|
};
|
|
return titles[props.operateType];
|
|
});
|
|
|
|
type Model = Pick<
|
|
Api.SystemManage.Menu,
|
|
| 'menuType'
|
|
| 'menuName'
|
|
| 'i18nKey'
|
|
| 'icon'
|
|
| 'iconType'
|
|
| 'routeName'
|
|
| 'routePath'
|
|
| 'component'
|
|
| 'status'
|
|
| 'hideInMenu'
|
|
| 'activeMenu'
|
|
| 'order'
|
|
| 'parentId'
|
|
| 'constant'
|
|
| 'keepAlive'
|
|
| 'href'
|
|
| 'multiTab'
|
|
| 'fixedIndexInTab'
|
|
| 'roles'
|
|
| 'buttons'
|
|
| 'query'
|
|
> & {
|
|
layout: string;
|
|
page: string;
|
|
};
|
|
|
|
const model: Model = reactive(createDefaultModel());
|
|
|
|
function createDefaultModel(): Model {
|
|
return {
|
|
menuType: '1',
|
|
menuName: '',
|
|
i18nKey: '' as App.I18n.I18nKey,
|
|
icon: '',
|
|
iconType: '1',
|
|
routeName: '',
|
|
routePath: '',
|
|
layout: '',
|
|
page: '',
|
|
status: '1',
|
|
hideInMenu: false,
|
|
activeMenu: '' as LastLevelRouteKey,
|
|
order: 0,
|
|
parentId: 0,
|
|
constant: false,
|
|
keepAlive: false,
|
|
href: '',
|
|
multiTab: false,
|
|
fixedIndexInTab: 0,
|
|
roles: [],
|
|
buttons: [],
|
|
query: {}
|
|
};
|
|
}
|
|
|
|
type RuleKey = Extract<keyof Model, 'menuName' | 'status' | 'routeName' | 'routePath'>;
|
|
|
|
const rules: Record<RuleKey, App.Global.FormRule> = {
|
|
menuName: defaultRequiredRule,
|
|
status: defaultRequiredRule,
|
|
routeName: defaultRequiredRule,
|
|
routePath: defaultRequiredRule
|
|
};
|
|
|
|
const disabledMenuType = computed(() => props.operateType === 'edit');
|
|
|
|
const localIcons = getLocalIcons();
|
|
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
|
label: () => (
|
|
<div class="flex-y-center gap-16px">
|
|
<SvgIcon localIcon={item} class="text-icon" />
|
|
<span>{item}</span>
|
|
</div>
|
|
),
|
|
value: item
|
|
}));
|
|
|
|
const showLayout = computed(() => model.parentId === 0);
|
|
|
|
const showPage = computed(() => model.menuType === '2');
|
|
|
|
const pageOptions = computed(() => {
|
|
const allPages = [...props.allPages];
|
|
|
|
if (model.routeName && !allPages.includes(model.routeName)) {
|
|
allPages.unshift(model.routeName);
|
|
}
|
|
|
|
const opts: CommonType.Option[] = allPages.map(page => ({
|
|
label: page,
|
|
value: page
|
|
}));
|
|
|
|
return opts;
|
|
});
|
|
|
|
const layoutOptions: CommonType.Option[] = [
|
|
{
|
|
label: 'base',
|
|
value: 'base'
|
|
},
|
|
{
|
|
label: 'blank',
|
|
value: 'blank'
|
|
}
|
|
];
|
|
|
|
const dynamicQueryKeyValuePairs: Ref<Record<string, string>[]> = ref([
|
|
{
|
|
key: '',
|
|
value: ''
|
|
}
|
|
]);
|
|
|
|
/** the enabled role options */
|
|
const roleOptions = ref<CommonType.Option<string>[]>([]);
|
|
|
|
async function getRoleOptions() {
|
|
const { error, data } = await fetchGetAllRoles();
|
|
|
|
if (!error) {
|
|
const options = data.map(item => ({
|
|
label: item.roleName,
|
|
value: item.roleCode
|
|
}));
|
|
|
|
roleOptions.value = [...options];
|
|
}
|
|
}
|
|
|
|
function handleUpdateModel() {
|
|
if (props.operateType === 'add') {
|
|
Object.assign(model, createDefaultModel());
|
|
|
|
return;
|
|
}
|
|
|
|
if (props.operateType === 'addChild' && props.rowData) {
|
|
const { id } = props.rowData;
|
|
|
|
Object.assign(model, createDefaultModel(), { parentId: id });
|
|
}
|
|
|
|
if (props.operateType === 'edit' && props.rowData) {
|
|
const { component, query, ...rest } = props.rowData;
|
|
|
|
const { layout, page } = getLayoutAndPage(component);
|
|
|
|
Object.assign(model, rest, { layout, page });
|
|
dynamicQueryKeyValuePairs.value = transformToKeyValuePairs(query);
|
|
}
|
|
}
|
|
|
|
function closeDrawer() {
|
|
visible.value = false;
|
|
}
|
|
|
|
async function handleSubmit() {
|
|
await validate();
|
|
|
|
model.component = transformLayoutAndPageToComponent(model.layout, model.page);
|
|
model.query = transformToQueryObject(dynamicQueryKeyValuePairs.value);
|
|
|
|
// model.buttons = [];
|
|
// Need: Get buttons based on roles
|
|
|
|
console.log('model:', model);
|
|
|
|
// request
|
|
window.$message?.success($t('common.updateSuccess'));
|
|
closeDrawer();
|
|
emit('submitted');
|
|
}
|
|
|
|
watch(visible, () => {
|
|
if (visible.value) {
|
|
handleUpdateModel();
|
|
restoreValidation();
|
|
getRoleOptions();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<NDrawer v-model:show="visible" display-directive="show" :width="400">
|
|
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
|
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
|
|
<NGrid>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.menuType')" path="menuType">
|
|
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
|
|
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.order')" path="order">
|
|
<NInputNumber v-model:value="model.order" :placeholder="$t('page.manage.menu.form.order')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.menuName')" path="menuName">
|
|
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.i18nKey')" path="i18nKey">
|
|
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.menuStatus')" path="status">
|
|
<NRadioGroup v-model:value="model.status">
|
|
<NRadio
|
|
v-for="item in enableStatusOptions"
|
|
:key="item.value"
|
|
:value="item.value"
|
|
:label="$t(item.label)"
|
|
/>
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
|
|
<NRadioGroup v-model:value="model.hideInMenu">
|
|
<NRadio value :label="$t('common.yesOrNo.yes')" />
|
|
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive">
|
|
<NRadioGroup v-model:value="model.keepAlive">
|
|
<NRadio value :label="$t('common.yesOrNo.yes')" />
|
|
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.constant')" path="constant">
|
|
<NRadioGroup v-model:value="model.constant">
|
|
<NRadio value :label="$t('common.yesOrNo.yes')" />
|
|
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
|
|
<NRadioGroup v-model:value="model.multiTab">
|
|
<NRadio value :label="$t('common.yesOrNo.yes')" />
|
|
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
|
|
<NInputNumber
|
|
v-model:value="model.fixedIndexInTab"
|
|
:placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
|
|
/>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
|
|
<NRadioGroup v-model:value="model.iconType">
|
|
<NRadio
|
|
v-for="item in menuIconTypeOptions"
|
|
:key="item.value"
|
|
:value="item.value"
|
|
:label="$t(item.label)"
|
|
/>
|
|
</NRadioGroup>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.icon')" path="icon">
|
|
<template v-if="model.iconType === '1'">
|
|
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
|
|
<template #suffix>
|
|
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
|
|
</template>
|
|
</NInput>
|
|
</template>
|
|
<template v-if="model.iconType === '2'">
|
|
<NSelect
|
|
v-model:value="model.icon"
|
|
:placeholder="$t('page.manage.menu.form.localIcon')"
|
|
:options="localIconOptions"
|
|
/>
|
|
</template>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.routeName')" path="routeName">
|
|
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.routePath')" path="routePath">
|
|
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi v-if="showLayout" span="24" :label="$t('page.manage.menu.layout')" path="layout">
|
|
<NSelect
|
|
v-model:value="model.layout"
|
|
:options="layoutOptions"
|
|
:placeholder="$t('page.manage.menu.form.layout')"
|
|
/>
|
|
</NFormItemGi>
|
|
<NFormItemGi v-if="showPage" span="24" :label="$t('page.manage.menu.page')" path="page">
|
|
<NSelect
|
|
v-model:value="model.page"
|
|
:options="pageOptions"
|
|
:placeholder="$t('page.manage.menu.form.page')"
|
|
/>
|
|
</NFormItemGi>
|
|
<NFormItemGi v-if="showPage" span="24" :label="$t('page.manage.menu.activeMenu')" path="activeMenu">
|
|
<NSelect
|
|
v-model:value="model.activeMenu"
|
|
:options="pageOptions"
|
|
:placeholder="$t('page.manage.menu.form.activeMenu')"
|
|
/>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.href')" path="href">
|
|
<NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" />
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.roles')" path="roles">
|
|
<NSelect
|
|
v-model:value="model.roles"
|
|
multiple
|
|
:options="roleOptions"
|
|
:placeholder="$t('page.manage.menu.form.roles')"
|
|
/>
|
|
</NFormItemGi>
|
|
<NFormItemGi span="24" :label="$t('page.manage.menu.query')">
|
|
<NDynamicInput
|
|
v-model:value="dynamicQueryKeyValuePairs"
|
|
preset="pair"
|
|
:key-placeholder="$t('page.manage.menu.form.queryKey')"
|
|
:value-placeholder="$t('page.manage.menu.form.queryValue')"
|
|
>
|
|
<template #action="{ index, create, remove }">
|
|
<NSpace class="ml-2">
|
|
<NButton size="medium" @click="() => create(index)">
|
|
<icon-ic:round-plus class="text-icon" />
|
|
</NButton>
|
|
<NButton size="medium" @click="() => remove(index)">
|
|
<icon-ic-round-remove class="text-icon" />
|
|
</NButton>
|
|
</NSpace>
|
|
</template>
|
|
</NDynamicInput>
|
|
</NFormItemGi>
|
|
</NGrid>
|
|
</NForm>
|
|
<template #footer>
|
|
<NSpace :size="16">
|
|
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
|
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
|
</NSpace>
|
|
</template>
|
|
</NDrawerContent>
|
|
</NDrawer>
|
|
</template>
|
|
|
|
<style scoped></style>
|