123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- <script lang="ts" setup>
- import type { ChangeEvent } from 'ant-design-vue/es/_util/EventInterface';
- import type { Recordable } from '@vben/types';
- import type { VbenFormSchema } from '#/adapter/form';
- import { computed, h, ref } from 'vue';
- import { useVbenDrawer } from '@vben/common-ui';
- import { IconifyIcon } from '@vben/icons';
- import { $te } from '@vben/locales';
- import { getPopupContainer } from '@vben/utils';
- import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
- import { useVbenForm, z } from '#/adapter/form';
- import {
- createMenu,
- getMenuList,
- isMenuNameExists,
- isMenuPathExists,
- SystemMenuApi,
- updateMenu,
- } from '#/api/system/menu';
- import { $t } from '#/locales';
- import { componentKeys } from '#/router/routes';
- import { getMenuTypeOptions } from '../data';
- const emit = defineEmits<{
- success: [];
- }>();
- const formData = ref<SystemMenuApi.SystemMenu>();
- const loading = ref(false);
- const titleSuffix = ref<string>();
- const schema: VbenFormSchema[] = [
- {
- component: 'RadioGroup',
- componentProps: {
- buttonStyle: 'solid',
- options: getMenuTypeOptions(),
- optionType: 'button',
- },
- defaultValue: 'menu',
- fieldName: 'type',
- formItemClass: 'col-span-2 md:col-span-2',
- label: $t('system.menu.type'),
- },
- {
- component: 'Input',
- fieldName: 'name',
- label: $t('system.menu.menuName'),
- rules: z
- .string()
- .min(2, $t('ui.formRules.minLength', [$t('system.menu.menuName'), 2]))
- .max(30, $t('ui.formRules.maxLength', [$t('system.menu.menuName'), 30]))
- .refine(
- async (value: string) => {
- return !(await isMenuNameExists(value, formData.value?.id));
- },
- (value) => ({
- message: $t('ui.formRules.alreadyExists', [
- $t('system.menu.menuName'),
- value,
- ]),
- }),
- ),
- },
- {
- component: 'ApiTreeSelect',
- componentProps: {
- api: getMenuList,
- class: 'w-full',
- filterTreeNode(input: string, node: Recordable<any>) {
- if (!input || input.length === 0) {
- return true;
- }
- const title: string = node.meta?.title ?? '';
- if (!title) return false;
- return title.includes(input) || $t(title).includes(input);
- },
- getPopupContainer,
- labelField: 'meta.title',
- showSearch: true,
- treeDefaultExpandAll: true,
- valueField: 'id',
- childrenField: 'children',
- },
- fieldName: 'pid',
- label: $t('system.menu.parent'),
- renderComponentContent() {
- return {
- title({ label, meta }: { label: string; meta: Recordable<any> }) {
- const coms = [];
- if (!label) return '';
- if (meta?.icon) {
- coms.push(h(IconifyIcon, { class: 'size-4', icon: meta.icon }));
- }
- coms.push(h('span', { class: '' }, $t(label || '')));
- return h('div', { class: 'flex items-center gap-1' }, coms);
- },
- };
- },
- },
- {
- component: 'Input',
- componentProps() {
- // 不需要处理多语言时就无需这么做
- return {
- addonAfter: titleSuffix.value,
- onChange({ target: { value } }: ChangeEvent) {
- titleSuffix.value = value && $te(value) ? $t(value) : undefined;
- },
- };
- },
- fieldName: 'meta.title',
- label: $t('system.menu.menuTitle'),
- rules: 'required',
- },
- {
- component: 'Input',
- dependencies: {
- show: (values) => {
- return ['catalog', 'embedded', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'path',
- label: $t('system.menu.path'),
- rules: z
- .string()
- .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
- .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
- .refine(
- (value: string) => {
- return value.startsWith('/');
- },
- $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
- )
- .refine(
- async (value: string) => {
- return !(await isMenuPathExists(value, formData.value?.id));
- },
- (value) => ({
- message: $t('ui.formRules.alreadyExists', [
- $t('system.menu.path'),
- value,
- ]),
- }),
- ),
- },
- {
- component: 'Input',
- dependencies: {
- show: (values) => {
- return ['embedded', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'activePath',
- help: $t('system.menu.activePathHelp'),
- label: $t('system.menu.activePath'),
- rules: z
- .string()
- .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
- .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
- .refine(
- (value: string) => {
- return value.startsWith('/');
- },
- $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
- )
- .refine(async (value: string) => {
- return await isMenuPathExists(value, formData.value?.id);
- }, $t('system.menu.activePathMustExist'))
- .optional(),
- },
- {
- component: 'IconPicker',
- componentProps: {
- prefix: 'carbon',
- },
- dependencies: {
- show: (values) => {
- return ['catalog', 'embedded', 'link', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.icon',
- label: $t('system.menu.icon'),
- },
- {
- component: 'IconPicker',
- componentProps: {
- prefix: 'carbon',
- },
- dependencies: {
- show: (values) => {
- return ['catalog', 'embedded', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.activeIcon',
- label: $t('system.menu.activeIcon'),
- },
- {
- component: 'AutoComplete',
- componentProps: {
- allowClear: true,
- class: 'w-full',
- filterOption(input: string, option: { value: string }) {
- return option.value.toLowerCase().includes(input.toLowerCase());
- },
- options: componentKeys.map((v) => ({ value: v })),
- },
- dependencies: {
- rules: (values) => {
- return values.type === 'menu' ? 'required' : null;
- },
- show: (values) => {
- return values.type === 'menu';
- },
- triggerFields: ['type'],
- },
- fieldName: 'component',
- label: $t('system.menu.component'),
- },
- {
- component: 'Input',
- dependencies: {
- show: (values) => {
- return ['embedded', 'link'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'linkSrc',
- label: $t('system.menu.linkSrc'),
- rules: z.string().url($t('ui.formRules.invalidURL')),
- },
- {
- component: 'Input',
- dependencies: {
- rules: (values) => {
- return values.type === 'button' ? 'required' : null;
- },
- show: (values) => {
- return ['button', 'catalog', 'embedded', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'authCode',
- label: $t('system.menu.authCode'),
- },
- {
- component: 'RadioGroup',
- componentProps: {
- buttonStyle: 'solid',
- options: [
- { label: $t('common.enabled'), value: 1 },
- { label: $t('common.disabled'), value: 0 },
- ],
- optionType: 'button',
- },
- defaultValue: 1,
- fieldName: 'status',
- label: $t('system.menu.status'),
- },
- {
- component: 'Select',
- componentProps: {
- allowClear: true,
- class: 'w-full',
- options: [
- { label: $t('system.menu.badgeType.dot'), value: 'dot' },
- { label: $t('system.menu.badgeType.normal'), value: 'normal' },
- ],
- },
- dependencies: {
- show: (values) => {
- return values.type !== 'button';
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.badgeType',
- label: $t('system.menu.badgeType.title'),
- },
- {
- component: 'Input',
- componentProps: (values) => {
- return {
- allowClear: true,
- class: 'w-full',
- disabled: values.meta?.badgeType !== 'normal',
- };
- },
- dependencies: {
- show: (values) => {
- return values.type !== 'button';
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.badge',
- label: $t('system.menu.badge'),
- },
- {
- component: 'Select',
- componentProps: {
- allowClear: true,
- class: 'w-full',
- options: SystemMenuApi.BadgeVariants.map((v) => ({
- label: v,
- value: v,
- })),
- },
- dependencies: {
- show: (values) => {
- return values.type !== 'button';
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.badgeVariants',
- label: $t('system.menu.badgeVariants'),
- },
- {
- component: 'Divider',
- dependencies: {
- show: (values) => {
- return !['button', 'link'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'divider1',
- formItemClass: 'col-span-2 md:col-span-2 pb-0',
- hideLabel: true,
- renderComponentContent() {
- return {
- default: () => $t('system.menu.advancedSettings'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return ['menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.keepAlive',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.keepAlive'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return ['embedded', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.affixTab',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.affixTab'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return !['button'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.hideInMenu',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.hideInMenu'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return ['catalog', 'menu'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.hideChildrenInMenu',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.hideChildrenInMenu'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return !['button', 'link'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.hideInBreadcrumb',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.hideInBreadcrumb'),
- };
- },
- },
- {
- component: 'Checkbox',
- dependencies: {
- show: (values) => {
- return !['button', 'link'].includes(values.type);
- },
- triggerFields: ['type'],
- },
- fieldName: 'meta.hideInTab',
- renderComponentContent() {
- return {
- default: () => $t('system.menu.hideInTab'),
- };
- },
- },
- ];
- const breakpoints = useBreakpoints(breakpointsTailwind);
- const isHorizontal = computed(() => breakpoints.greaterOrEqual('md').value);
- const [Form, formApi] = useVbenForm({
- commonConfig: {
- colon: true,
- formItemClass: 'col-span-2 md:col-span-1',
- },
- schema,
- showDefaultActions: false,
- wrapperClass: 'grid-cols-2 gap-x-4',
- });
- const [Drawer, drawerApi] = useVbenDrawer({
- onBeforeClose() {
- if (loading.value) return false;
- },
- onConfirm: onSubmit,
- onOpenChange(isOpen) {
- if (isOpen) {
- const data = drawerApi.getData<SystemMenuApi.SystemMenu>();
- if (data?.type === 'link') {
- data.linkSrc = data.meta?.link;
- } else if (data?.type === 'embedded') {
- data.linkSrc = data.meta?.iframeSrc;
- }
- if (data) {
- formData.value = data;
- formApi.setValues(formData.value);
- titleSuffix.value = formData.value.meta?.title
- ? $t(formData.value.meta.title)
- : '';
- } else {
- formApi.resetForm();
- titleSuffix.value = '';
- }
- }
- },
- });
- async function onSubmit() {
- const { valid } = await formApi.validate();
- if (valid) {
- loading.value = true;
- drawerApi.setState({
- closeOnClickModal: false,
- closeOnPressEscape: false,
- confirmLoading: true,
- loading: true,
- });
- const data =
- await formApi.getValues<
- Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>
- >();
- if (data.type === 'link') {
- data.meta = { ...data.meta, link: data.linkSrc };
- } else if (data.type === 'embedded') {
- data.meta = { ...data.meta, iframeSrc: data.linkSrc };
- }
- delete data.linkSrc;
- try {
- await (formData.value?.id
- ? updateMenu(formData.value.id, data)
- : createMenu(data));
- drawerApi.close();
- emit('success');
- } finally {
- loading.value = false;
- drawerApi.setState({
- closeOnClickModal: true,
- closeOnPressEscape: true,
- confirmLoading: false,
- loading: false,
- });
- }
- }
- }
- const getDrawerTitle = computed(() =>
- formData.value?.id
- ? $t('ui.actionTitle.edit', [$t('system.menu.name')])
- : $t('ui.actionTitle.create', [$t('system.menu.name')]),
- );
- </script>
- <template>
- <Drawer class="w-full max-w-[800px]" :title="getDrawerTitle">
- <Form class="mx-4" :layout="isHorizontal ? 'horizontal' : 'vertical'" />
- </Drawer>
- </template>
|