Przeglądaj źródła

create、update、delete menu

dev
powersir 1 rok temu
rodzic
commit
4119587b16
7 zmienionych plików z 325 dodań i 40 usunięć
  1. +1
    -1
      src/models/user.data.ts
  2. +7
    -7
      src/pages/custom/product/finished/index.tsx
  3. +14
    -5
      src/pages/custom/product/sample/index.tsx
  4. +11
    -3
      src/pages/custom/product/shape/index.tsx
  5. +84
    -22
      src/pages/system/menu/index.tsx
  6. +186
    -0
      src/pages/system/menu/menu-editor.tsx
  7. +22
    -2
      src/request/service/menu.ts

+ 1
- 1
src/models/user.data.ts Wyświetl plik

@@ -71,7 +71,7 @@ export interface Menu {
sort?: number;
status?: number;
parentPaths?: string[];
createTime: number;
createTime?: number;
children?: Array<Menu>;
}



+ 7
- 7
src/pages/custom/product/finished/index.tsx Wyświetl plik

@@ -3,7 +3,7 @@ import type { TableColumnsType, DatePickerProps } from 'antd';
import { t } from '@/utils/i18n';
import React, { useState } from 'react';
import mData from '../../../../../mock/productListPage.json'
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined,UndoOutlined } from '@ant-design/icons';
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } from '@ant-design/icons';
import type { Product, ProductBase, ProductImage, ProductSku } from '@/models'
import { antdUtils } from '@/utils/antd';
import customParseFormat from 'dayjs/plugin/customParseFormat';
@@ -236,19 +236,19 @@ const FinishedProductPage: React.FC = () => {
<div>
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]' bodyStyle={{ paddingTop: 4, paddingBottom: 4 }}>
<div className='flex justify-normal items-center'>
<Cascader options={optionsList} style={{ width: '20%' }} loadData={loadData} onChange={onChange} placeholder="请选择商品类目" changeOnSelect/>
<Cascader options={optionsList} style={{ width: '20%' }} loadData={loadData} onChange={onChange} placeholder="请选择商品类目" changeOnSelect />
<Space.Compact className='ml-[5px]'>
<Select defaultValue="0" options={options} onChange={onKeywordsTypeChanged}/>
<Input allowClear/>
<Select defaultValue="0" options={options} onChange={onKeywordsTypeChanged} />
<Input allowClear />

</Space.Compact>
<RangePicker
className='ml-[5px]'
format={dateFormat}
/>
<Space.Compact>
<Button className="ml-5" type='primary' size='large' icon={<SearchOutlined />}> 搜索 </Button>
<Button type='primary' size='large' icon={<UndoOutlined />}> 重置 </Button>
<Space.Compact className="ml-5">
<Button type='primary' size='large' icon={<SearchOutlined />}> 搜索 </Button>
<Button type='primary' size='large' icon={<UndoOutlined />}> 重置 </Button>
</Space.Compact>
<div className="py-[4px]">
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />}> 批量操作 </Button>


+ 14
- 5
src/pages/custom/product/sample/index.tsx Wyświetl plik

@@ -8,7 +8,14 @@ import MaskPictureEditor from './components/mask-picture-editor';
import type { SampleAttribute } from './components/attr-editor'
import type { MaskPicture } from './components/mask-picture-editor';
import { useNavigate } from 'react-router-dom';
import { ExclamationCircleFilled, PlusOutlined, DeleteOutlined, CarryOutOutlined } from '@ant-design/icons';
import {
ExclamationCircleFilled,
PlusOutlined,
DeleteOutlined,
CarryOutOutlined,
SearchOutlined,
UndoOutlined
} from '@ant-design/icons';
import { antdUtils } from '@/utils/antd';

const { Search } = Input;
@@ -341,7 +348,7 @@ const TablePage: React.FC = () => {
return (
<div>
<div>
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]' bodyStyle={{paddingTop: 4, paddingBottom:4 }}>
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]' bodyStyle={{ paddingTop: 4, paddingBottom: 4 }}>
<div className='flex justify-between content-center'>
<div className='flex justify-normal items-center'>
<TreeSelect
@@ -355,9 +362,11 @@ const TablePage: React.FC = () => {
placeholder="请选择类目"
/>
<Input className='ml-[10px]'
placeholder='请输入样机名称'/>
<Button className="ml-5" type='primary' size='large'> 搜索 </Button>
<Button className="ml-2" danger size='large'> 重置 </Button>
placeholder='请输入样机名称' />
<Space.Compact className="ml-5">
<Button type='primary' size='large' icon={<SearchOutlined />}> 搜索 </Button>
<Button type='primary' size='large' icon={<UndoOutlined />}> 重置 </Button>
</Space.Compact>
</div>
<div className="py-[4px]">
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />}> 新增样机 </Button>


+ 11
- 3
src/pages/custom/product/shape/index.tsx Wyświetl plik

@@ -4,7 +4,13 @@ import { t } from '@/utils/i18n';
import React, { useState } from 'react';
import mData from '../../../../../mock/findDiySpecimenPage.json'
import shapeData from '../../../../../mock/findSpecimensByPrototypeId.json'
import { PlusOutlined, ExclamationCircleFilled, CarryOutOutlined } from '@ant-design/icons';
import {
PlusOutlined,
ExclamationCircleFilled,
CarryOutOutlined,
SearchOutlined,
UndoOutlined
} from '@ant-design/icons';
import type { ShapeProperty, Shape, ProductImage } from '@/models'
import { antdUtils } from '@/utils/antd';

@@ -196,8 +202,10 @@ const ShapePage: React.FC = () => {
/>
<Input className='ml-[10px]'
placeholder='请输入样机名称'/>
<Button className="ml-5" type='primary' size='large'> 搜索 </Button>
<Button className="ml-2" danger size='large'> 重置 </Button>
<Space.Compact className="ml-5">
<Button type='primary' size='large' icon={<SearchOutlined />}> 搜索 </Button>
<Button type='primary' size='large' icon={<UndoOutlined />}> 重置 </Button>
</Space.Compact>
</div>
<div className="py-[4px]">
<Button className="ml-5" type='primary' icon={<PlusOutlined />}> 新增款式 </Button>


+ 84
- 22
src/pages/system/menu/index.tsx Wyświetl plik

@@ -1,4 +1,4 @@
import { Space, Table, Button, Image, Divider, Tag, Card, Badge } from 'antd';
import { Space, Table, Button, Input, Select, Divider, Tag, Card, Badge, Form } from 'antd';
import type { TableColumnsType } from 'antd';
import { t } from '@/utils/i18n';
import React, { useState, useEffect } from 'react';
@@ -6,22 +6,38 @@ import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } f
import type { Menu } from '@/models'
import { antdUtils } from '@/utils/antd';
import { useRequest } from '@/hooks/use-request';
import menuService from '@/request/service/menu'
import menuService from '@/request/service/menu';
import { formatDate } from '@/utils/formatTime'
import MenuEditor from './menu-editor';

import * as Icon from "@ant-design/icons";
// 编写生成ReactNode的方法
const icon2Element = (name: string) =>
React.createElement(Icon && (Icon as any)[name], {
style: { fontSize: '16px' }
})

const MenuListPage: React.FC = () => {

const [menuEditorVisable, seMenuEditorVisable] = useState<boolean>(false);
const [editMenu, seEditMenu] = useState<Menu>();
const [dataSource, setDataSource] = useState<Menu[]>([]);
const [searchFrom] = Form.useForm();

console.log()

const { runAsync: getMenuList } = useRequest(menuService.getMenuList, { manual: true });
const { runAsync: deleteMenu } = useRequest(menuService.deleteMenu, { manual: true });

const loadMenus = async () => {
const [error, { data: menus }] = await getMenuList();
const [error, { data: menus }] = await getMenuList(searchFrom.getFieldsValue());
if (!error) {
const menuIds = menus.map(it => it.id)
const rootMenus = menus.filter((menu) => menuIds.indexOf(menu.parentId??0) < 0)
const rootMenus = menus.filter((menu) => menuIds.indexOf(menu.parentId ?? 0) < 0)
menus.forEach(menu => {
const parentMenu = menus.find(it => it.id === menu.parentId); //menu.parentId
if(parentMenu) {
if(!parentMenu.children) {
if (parentMenu) {
if (!parentMenu.children) {
parentMenu.children = []
}
parentMenu.children.push(menu)
@@ -32,7 +48,7 @@ const MenuListPage: React.FC = () => {
};


const showDeleteConfirm = () => {
const showDeleteConfirm = (menu: Menu) => {
antdUtils.modal?.confirm({
title: '确认要将该菜单删除吗?',
icon: <ExclamationCircleFilled />,
@@ -41,8 +57,15 @@ const MenuListPage: React.FC = () => {
okType: 'danger',
cancelText: '取消',
onOk() {
return new Promise((resolve, reject) => {

return new Promise(async (resolve) => {
const [error, { code, msg} ] = await deleteMenu({ id: menu.id });
if(error || code !== 0) {
antdUtils.message?.open({ type: 'error', content: msg??'操作失败'})
} else {
antdUtils.message?.open({ type: 'success', content: '删除成功'})
}
await loadMenus();
resolve('')
}).catch(() => antdUtils.message?.open({
type: 'error',
content: '操作失败',
@@ -67,7 +90,7 @@ const MenuListPage: React.FC = () => {
key: 'type',
width: 100,
render: (value: number, record, index) => {
return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ?<Tag color="blue">菜单</Tag>: <Tag color="green">按钮</Tag>))
return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ? <Tag color="blue">菜单</Tag> : <Tag color="green">按钮</Tag>))
}
},
{
@@ -99,7 +122,7 @@ const MenuListPage: React.FC = () => {
dataIndex: 'visible',
width: 100,
render: (value: boolean) => {
return (value? <Badge status="success" text="已显示" /> : <Badge status="warning" text="已隐藏" />)
return (value ? <Badge status="success" text="已显示" /> : <Badge status="warning" text="已隐藏" />)
}
},
{
@@ -116,26 +139,23 @@ const MenuListPage: React.FC = () => {
key: 'createTime',
dataIndex: 'createTime',
width: 200,
render:(value: number) => {
render: (value: number) => {
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS")
}
},
{
title: t("QkOmYwne" /* 操作 */),
key: 'action',
render: (_, record) => (
<Space size="small" split={(
<Divider type='vertical' />
)}>
<a
onClick={() => {

render: (menu: Menu, record) => (
<Space size="small" split={( <Divider type='vertical' />)}>
<a onClick={() => {
seEditMenu(menu);
seMenuEditorVisable(true);
}}>
编辑
</a>
<a
onClick={() => {
showDeleteConfirm()
<a onClick={() => {
showDeleteConfirm(menu)
}}>
删除
</a>
@@ -148,9 +168,42 @@ const MenuListPage: React.FC = () => {
loadMenus();
}, []);

const onReset = () => {
searchFrom.resetFields()
loadMenus()
}

return (
<>
<div>
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]' bodyStyle={{ paddingTop: 4, paddingBottom: 4 }}>
<div className='flex justify-between content-center'>
<div className='flex justify-normal items-center'>
<Form layout='inline' form={searchFrom}>
<Form.Item name="name" label="菜单名称">
<Input className='ml-[10px]' placeholder='请输入名称' allowClear/>
</Form.Item>
<Form.Item className='ml-2 w-[130px]' name="status" label="状态">
<Select placeholder="请选择" allowClear >
<Select.Option value="2">全部</Select.Option>
<Select.Option value="0">开启</Select.Option>
<Select.Option value="1">关闭</Select.Option>
</Select>
</Form.Item>
</Form>
<Space.Compact className="ml-5">
<Button type='primary' size='large' icon={<SearchOutlined />} onClick={loadMenus}> 搜索 </Button>
<Button type='primary' size='large' icon={<UndoOutlined />} onClick={onReset}> 重置 </Button>
</Space.Compact>
</div>
<div className="py-[4px]">
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={() => {
seEditMenu(undefined);
seMenuEditorVisable(true);
}}> 添加菜单 </Button>
</div>
</div>
</Card>
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]'>
<Table rowKey="id"
scroll={{ x: true }}
@@ -160,6 +213,15 @@ const MenuListPage: React.FC = () => {
pagination={{ position: ['bottomRight'] }} />
</Card>
</div>
<MenuEditor
parentMenus={dataSource}
onSave={() => {
loadMenus();
seMenuEditorVisable(false);
}}
onCancel={() => { seMenuEditorVisable(false) }}
visible={menuEditorVisable}
editData={editMenu} />
</>
);
};


+ 186
- 0
src/pages/system/menu/menu-editor.tsx Wyświetl plik

@@ -0,0 +1,186 @@
import React, { useEffect, useState } from 'react'
import { CloseOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { TreeSelect, Form, Input, InputNumber, Radio, Modal, Switch } from 'antd'
import menuService from '@/request/service/menu';
import { useRequest } from '@/hooks/use-request';
import type { DefaultOptionType } from 'antd/es/select';
import type { Menu } from '@/models'
import { antdUtils } from '@/utils/antd';

const layout = {
labelCol: { span: 4, },
wrapperCol: { span: 16 },
bordered: false,
};

interface MenuEditorProps {
visible: boolean;
onCancel: (flag?: boolean) => void;
onSave: (menu: Menu) => void;
editData?: Menu | null;
parentMenus: Menu[];
}


const MenuEditor: React.FC<MenuEditorProps> = (props) => {

const { visible, onCancel, onSave, editData, parentMenus } = props;

const { runAsync: updateMenu } = useRequest(menuService.updateMenu, { manual: true });
const { runAsync: createMenu } = useRequest(menuService.createMenu, { manual: true });

const isEdit = !!editData;

const menu2Tree = (menu: Menu) => {
const treeItem: DefaultOptionType = {
label: menu.name,
value: menu.id
}
if (menu.children && menu.children.length > 0) {
treeItem.children = menu.children.filter(item => item.type !== 3).map(menu2Tree)
}

return treeItem;
}
const menuTree: DefaultOptionType[] = [{
label: '主类目',
value: 0,
children: parentMenus.filter(item => item.type !== 3).map(item => {
return menu2Tree(item)
})
}]

const [saveLoading, setSaveLoading] = useState(false);
const [form] = Form.useForm();

useEffect(() => {
if (visible) {
if (editData) {
form.setFieldsValue(editData);
}
} else {
form.resetFields();
}
}, [visible]);

const save = async () => {
setSaveLoading(true);
const menuValues = form.getFieldsValue();
if(typeof(menuValues.status) === 'boolean') {
menuValues.status = menuValues.status? 0 : 1
}
const newValue = isEdit? {...editData, ...menuValues} : {
permission: '',
icon: '',
keepAlive:true,
createTime: new Date(),
...menuValues,
}
const [error, { msg, code }] = isEdit? await updateMenu(newValue): await createMenu(newValue);
if(!error && code === 0) {
onSave(newValue);
} else {
antdUtils.message?.open({
type: 'error',
content: msg??'操作失败',
});
}
setSaveLoading(false);
}

return (
<>
<Modal
open={visible}
title={isEdit ? "编辑" : "新建"}
width={640}
onOk={save}
onCancel={() => onCancel()}
destroyOnClose
>
<Form
form={form}
{...layout}
onFinish={save}
labelCol={{ flex: '0 0 100px' }}
wrapperCol={{ span: 16 }}
>
<Form.Item name="parentId" label="上级菜单" >
<TreeSelect treeData={menuTree} />
</Form.Item>

<Form.Item name="name" label="菜单名称"
rules={[
{
required: true,
message: '请输入菜单名称',
},
]}
>
<Input />
</Form.Item>

<Form.Item name="type" label="菜单类型" >
<Radio.Group options={[
{ value: 1, label: "目录" },
{ value: 2, label: "菜单" },
{ value: 3, label: "按钮" }
]} optionType="button">
</Radio.Group>
</Form.Item>

<Form.Item name="icon" label="菜单图标" >
<Input />
</Form.Item>

<Form.Item name="path" label="路由地址"
rules={[
{
required: true,
message: '请输入路由地址',
}
]}
>
<Input />
</Form.Item>

<Form.Item name="component" label="组件地址" >
<Input />
</Form.Item>

<Form.Item name="sort" label="显示排序" >
<InputNumber min={1} max={100} />
</Form.Item>

<Form.Item name="status" label="菜单状态"
// getValueProps={(value: number) => {
// const checked = value !== 0;
// console.log(checked)
// return { checked }
// }}
valuePropName="checked"
// normalize={(value: boolean) => {
// // prevValues.status = value ? 0 : 1
// return value ? 0 : 1
// }}
>
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</Form.Item>

<Form.Item name="visible" label="显示状态" valuePropName="checked">
<Switch checkedChildren="显示" unCheckedChildren="隐藏" />
</Form.Item>

{/* <Form.Item
name="cache"
label="缓存状态"
>
<Switch />
</Form.Item> */}
</Form>
</Modal>
</>
)
}

export default MenuEditor;

+ 22
- 2
src/request/service/menu.ts Wyświetl plik

@@ -1,8 +1,28 @@
import request from '@/request';
import { Menu } from '@/models';

const BASE_URL = '/admin-api/system/menu';

export default {
getMenuList: (params?: {name?: string, status?: number}) => {
return request.get<Menu[]>('/admin-api/system/menu/list', {params});
getMenuList: (params?: { name?: string, status?: number }) => {
return request.get<Menu[]>(`${BASE_URL}/list`, { params });
},
// 获取菜单详情
getMenu: (id: number) => {
return request.get(`${BASE_URL}//menu/get`, {params: { id}})
},

// 新增菜单
createMenu: (data: Menu) => {
return request.post(`${BASE_URL}/create`, data)
},

// 修改菜单
updateMenu: (data: Menu) => {
return request.put(`${BASE_URL}/update`, data)
},

deleteMenu: (params: { id: number }) => {
return request.delete(`${BASE_URL}/delete`, { params })
}
};

Ładowanie…
Anuluj
Zapisz