Skip to content

Commit

Permalink
feat(menu-manage): 完成《菜单管理》模块的功能交互开发
Browse files Browse the repository at this point in the history
  • Loading branch information
baiwumm committed Jan 15, 2025
1 parent c5f696a commit 77ca95a
Show file tree
Hide file tree
Showing 12 changed files with 645 additions and 87 deletions.
13 changes: 11 additions & 2 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"viewer":"Viewer",
"user-manage":"User Manage",
"personal-center":"Personal Center",
"/_not-found":"404"
"/_not-found":"404",
"menu-manage":"Menu Manage"
},
"Pages":{
"internationalization":{
Expand Down Expand Up @@ -57,7 +58,15 @@
"verifySuccess": "Congratulations, the answer is correct!",
"verify": "Verify",
"graphicCode": "Graphic verification code"
}
},
"menu-manage":{
"title":"Title",
"name":"Menu Name",
"nameTip":"Please fill in the internationalization field",
"path":"Path",
"icon":"Icon",
"redirect":"Redirect"
}
},
"Global":{
"createdAt":"CreatedAt",
Expand Down
13 changes: 11 additions & 2 deletions messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"viewer":"图片预览",
"user-manage":"用户管理",
"personal-center":"个人中心",
"/_not-found":"404"
"/_not-found":"404",
"menu-manage":"菜单管理"
},
"Pages":{
"internationalization":{
Expand Down Expand Up @@ -57,7 +58,15 @@
"verifySuccess": "恭喜您,答案正确!",
"verify": "验证",
"graphicCode": "图形验证码"
}
},
"menu-manage":{
"title":"菜单",
"name":"菜单名称",
"nameTip":"请填写国际化字段",
"path":"路由地址",
"icon":"图标",
"redirect":"重定向地址"
}
},
"Global":{
"createdAt":"创建时间",
Expand Down
86 changes: 86 additions & 0 deletions src/app/system-manage/menu-manage/components/HeaderSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* @Author: 白雾茫茫丶<baiwumm.com>
* @Date: 2024-12-26 11:31:29
* @LastEditors: 白雾茫茫丶<baiwumm.com>
* @LastEditTime: 2025-01-14 16:02:12
* @Description: 顶部搜索
*/
import { parseDate } from '@internationalized/date';
import { Button, DateRangePicker, Input, Spinner } from '@nextui-org/react';
import { RiAddLine, RiResetLeftLine, RiSearchLine } from '@remixicon/react';
import { SetState } from 'ahooks/es/useSetState';
import dayjs from 'dayjs';
import { useTranslations } from 'next-intl';

export type HeaderSearchProps = {
loading: boolean;
refresh: () => void;
searchParams: App.SystemManage.MenuSearchParams;
setSearchParams: SetState<App.SystemManage.MenuSearchParams>;
onOpen: VoidFunction;
};

export default function HeaderSearch({
loading = false,
refresh,
searchParams,
setSearchParams,
onOpen,
}: HeaderSearchProps) {
const t = useTranslations('Pages');
const tGlobal = useTranslations('Global');

// 表单重置
const resetForm = () => {
setSearchParams({
name: '',
startTime: undefined,
endTime: undefined,
});
setTimeout(() => {
refresh();
});
};
return (
<div className="flex items-center gap-2 flex-wrap">
<Input
value={searchParams.name}
isClearable
placeholder={`${tGlobal('enter')}${t('menu-manage.name')}`}
className="w-[150px] lg:w-[250px]"
size="sm"
onValueChange={(value) => setSearchParams({ name: value })}
/>
<DateRangePicker
aria-label="Date Range Picker"
value={{
start: searchParams.startTime ? parseDate(dayjs(searchParams.startTime).format('YYYY-MM-DD')) : undefined,
end: searchParams.endTime ? parseDate(dayjs(searchParams.endTime).format('YYYY-MM-DD')) : undefined,
}}
size="sm"
className="w-[150px] lg:w-[250px]"
visibleMonths={2}
onChange={({ start, end }) => {
if (start && end) {
setSearchParams({
startTime: dayjs(start).startOf('day').valueOf(),
endTime: dayjs(end).endOf('day').valueOf(),
});
}
}}
/>
<Button variant="ghost" size="sm" disabled={loading} onPress={refresh} className="border">
{loading ? <Spinner size="sm" /> : <RiSearchLine size={18} />}
{tGlobal('search')}
</Button>
<Button variant="ghost" size="sm" onPress={resetForm} className="border">
<RiResetLeftLine size={18} />
{tGlobal('reset')}
</Button>
<Button variant="ghost" size="sm" onPress={onOpen} className="border">
<RiAddLine size={18} />
{tGlobal('add')}
</Button>
</div>
);
}
193 changes: 193 additions & 0 deletions src/app/system-manage/menu-manage/components/SaveModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* @Author: 白雾茫茫丶<baiwumm.com>
* @Date: 2025-01-14 14:52:45
* @LastEditors: 白雾茫茫丶<baiwumm.com>
* @LastEditTime: 2025-01-15 09:42:37
* @Description: 新增编辑弹窗
*/
import {
Button,
Form,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Select,
SelectItem,
} from '@nextui-org/react';
import { useRequest } from 'ahooks';
import { SetState } from 'ahooks/es/useSetState';
import { useTranslations } from 'next-intl';
import { FormEvent } from 'react';
import { toast } from 'sonner';

import { isSuccess } from '@/lib/utils';
import { addMenu, updateMenu } from '@/services/system-manage/menu-manage';

type SaveModalProps = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
refresh: VoidFunction;
onClose: VoidFunction;
menuId: string;
formData: App.SystemManage.MenuSaveParams;
setFormData: SetState<App.SystemManage.MenuSaveParams>;
handleCancel: VoidFunction;
menuList: App.SystemManage.Menu[];
};
export default function SaveModal({
isOpen = false,
onOpenChange,
refresh,
onClose,
menuId,
formData,
setFormData,
handleCancel,
menuList = [],
}: SaveModalProps) {
const t = useTranslations('Pages.menu-manage');
const tGlobal = useTranslations('Global');
const tRoute = useTranslations('Route');

// 新增/编辑曹丹
const { loading: saveLoading, run: runSave } = useRequest(menuId ? updateMenu : addMenu, {
manual: true,
onSuccess: ({ code, msg }) => {
if (isSuccess(code)) {
toast.success(msg);
onClose();
handleCancel();
refresh();
}
},
});

// 表单提交
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { sort, parentId, ...data } = Object.fromEntries(new FormData(e.currentTarget));
const params = {
id: menuId || undefined,
...data,
parentId: parentId || undefined,
sort: Number(sort),
};
runSave(params as App.SystemManage.MenuSaveParams);
};

return (
<Modal
isOpen={isOpen}
onOpenChange={onOpenChange}
size="2xl"
onClose={handleCancel}
backdrop="blur"
isDismissable={false}
isKeyboardDismissDisabled
>
<Form validationBehavior="native" onSubmit={onSubmit}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">
{`${menuId ? tGlobal('edit') : tGlobal('add')}`}
{t('title')}
</ModalHeader>
<ModalBody>
<div className="grid grid-cols-2 gap-4">
<Select
name="parentId"
label={tGlobal('parent')}
labelPlacement="outside"
description={tGlobal('parentTip')}
placeholder={tGlobal('select') + tGlobal('parent')}
size="sm"
selectedKeys={formData.parentId ? [formData.parentId] : undefined}
onChange={(e) => setFormData({ parentId: e.target.value })}
aria-label="parentId"
>
{menuList.map((menu) => (
<SelectItem key={menu.id}>{tRoute(menu.name)}</SelectItem>
))}
</Select>
<Input
value={formData.name}
name="name"
isClearable
isRequired
label={t('name')}
labelPlacement="outside"
placeholder={tGlobal('enter') + t('name')}
size="sm"
maxLength={32}
description={t('nameTip')}
onValueChange={(value) => setFormData({ name: value })}
/>
<Input
value={formData.path}
name="path"
isClearable
isRequired
label={t('path')}
labelPlacement="outside"
placeholder={tGlobal('enter') + t('path')}
size="sm"
maxLength={200}
onValueChange={(value) => setFormData({ path: value })}
/>
<Input
value={formData.icon}
name="icon"
isClearable
isRequired
label={t('icon')}
labelPlacement="outside"
placeholder={tGlobal('enter') + t('icon')}
size="sm"
maxLength={200}
onValueChange={(value) => setFormData({ icon: value })}
/>
<Input
value={formData.redirect || ''}
name="redirect"
isClearable
label={t('redirect')}
labelPlacement="outside"
placeholder={tGlobal('enter') + t('redirect')}
size="sm"
maxLength={200}
onValueChange={(value) => setFormData({ redirect: value })}
/>
<Input
value={String(formData.sort)}
name="sort"
isRequired
errorMessage={tGlobal('enter') + tGlobal('sort')}
label={tGlobal('sort')}
labelPlacement="outside"
size="sm"
type="number"
min={1}
max={99}
onValueChange={(value) => setFormData({ sort: Number(value) })}
/>
</div>
</ModalBody>
<ModalFooter>
<Button color="primary" type="submit" isLoading={saveLoading} size="sm">
{tGlobal('submit')}
</Button>
<Button color="danger" variant="light" onPress={onClose} isDisabled={saveLoading} size="sm">
{tGlobal('close')}
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Form>
</Modal>
);
}
Loading

0 comments on commit 77ca95a

Please sign in to comment.