From 12495b6d85d7223dac448672de750169b399523f Mon Sep 17 00:00:00 2001 From: wuxichen <17301714657@163.com> Date: Wed, 24 Sep 2025 11:09:02 +0800 Subject: [PATCH] feat: tagEditor --- .env.local | 4 +- src/components/EnhancedProTable/index.tsx | 67 +++----- src/components/EnhancedProTable/types.ts | 27 ++- src/components/TagEditor/index.tsx | 105 ++++++++++++ src/constants/index.ts | 6 +- src/pages/prod/category/config.tsx | 155 ++++++++++++++++++ src/pages/prod/category/index.tsx | 132 +++++++++++++++ src/pages/prod/list/index.tsx | 5 + .../system/messages/mail/account/config.tsx | 3 +- .../system/messages/mail/account/index.tsx | 21 ++- src/services/prodApi/category.ts | 23 ++- 11 files changed, 461 insertions(+), 87 deletions(-) create mode 100644 src/components/TagEditor/index.tsx create mode 100644 src/pages/prod/category/config.tsx create mode 100644 src/pages/prod/category/index.tsx create mode 100644 src/pages/prod/list/index.tsx diff --git a/.env.local b/.env.local index c2c5dac..9b994ea 100644 --- a/.env.local +++ b/.env.local @@ -4,8 +4,8 @@ NODE_ENV=development VITE_DEV=true # 请求路径 -# VITE_BASE_URL='http://114.132.60.20:48080' -VITE_BASE_URL='http://192.168.1.114:48080' # 理君 +VITE_BASE_URL='http://114.132.60.20:48080' +# VITE_BASE_URL='http://192.168.1.114:48080' # 理君 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 diff --git a/src/components/EnhancedProTable/index.tsx b/src/components/EnhancedProTable/index.tsx index a601df2..dfefa53 100644 --- a/src/components/EnhancedProTable/index.tsx +++ b/src/components/EnhancedProTable/index.tsx @@ -4,30 +4,12 @@ import { PlusOutlined } from '@ant-design/icons'; import { type ActionType, type ParamsType, - ProColumns, ProTable, - TableDropdown, } from '@ant-design/pro-components'; import { Button, Space } from 'antd'; -import React, { - act, - forwardRef, - useCallback, - useMemo, - useRef, - useState, -} from 'react'; -import { - buildTableDropdownMenuItems, - formatPaginationTotal, - handleTableDropdownSelect, -} from '@/utils/antd/tableHelpers'; -import { - type BaseRecord, - type EnhancedProTableProps, - TableAction, - ToolbarAction, -} from './types'; +import React, { forwardRef, useCallback, useMemo, useState } from 'react'; +import { formatPaginationTotal } from '@/utils/antd/tableHelpers'; +import type { BaseRecord, EnhancedProTableProps } from './types'; function EnhancedProTable( props: EnhancedProTableProps, @@ -36,21 +18,22 @@ function EnhancedProTable( const { columns, request, - actions = [], - permissions = [], + // actions = [], + // permissions = [], checkPermission = () => true, toolbarActions, showIndex = true, showSelection = true, - showActions = true, - maxActionCount = 2, - onAdd, - onEdit, - onDelete, - onView, - onExport, - customToolbarRender, + // showActions = true, + // maxActionCount = 2, + // onAdd, + // onEdit, + // onDelete, + // onView, + // onExport, + // customToolbarRender, customActionRender, + rowKey, ...restProps } = props; @@ -91,14 +74,13 @@ function EnhancedProTable( [showSelection], ); - const toolBarRender = useCallback( - ( - action: ActionType | undefined, - rows: { - selectedRowKeys?: (string | number)[] | undefined; - selectedRows?: T[] | undefined; - }, - ) => { + const toolBarRender = useCallback(() => + // action: ActionType | undefined, + // rows: { + // selectedRowKeys?: (string | number)[] | undefined; + // selectedRows?: T[] | undefined; + // } + { const toolbarElements = toolbarActions?.map((action) => { return ( @@ -125,22 +107,19 @@ function EnhancedProTable( // , // ]; return toolbarElements; - }, - [toolbarActions], - ); + }, [toolbarActions]); return ( {...restProps} columns={columns} actionRef={ref} request={request} - rowKey="id" + rowKey={rowKey || 'id'} rowSelection={rowSelection} toolBarRender={toolBarRender} manualRequest={false} showSorterTooltip tableAlertRender={tableAlertRender} - // tableAlertOptionRender={tableAlertOptionRender} scroll={{ x: 'max-content' }} search={{ labelWidth: 'auto', diff --git a/src/components/EnhancedProTable/types.ts b/src/components/EnhancedProTable/types.ts index 99fc9a0..25586b1 100644 --- a/src/components/EnhancedProTable/types.ts +++ b/src/components/EnhancedProTable/types.ts @@ -1,20 +1,19 @@ // components/EnhancedProTable/types.ts -import { - ProTableProps, - ProColumns, +import type { ActionType, -} from "@ant-design/pro-components"; -import { ButtonProps } from "antd"; -import React from "react"; + ProColumns, + ProTableProps, +} from '@ant-design/pro-components'; +import type { ButtonProps } from 'antd'; +import React from 'react'; export interface BaseRecord { - id: number; [key: string]: any; } export interface ApiResponse { data: T[]; - total: number; + total?: number; success: boolean; message?: string; } @@ -32,7 +31,7 @@ export interface TableAction { key: string; label: string; icon?: React.ReactNode; - type?: ButtonProps["type"]; + type?: ButtonProps['type']; danger?: boolean; disabled?: (record: T) => boolean; visible?: (record: T) => boolean; @@ -44,7 +43,7 @@ export interface ToolbarAction { key: string; label: string; icon?: React.ReactNode; - type?: ButtonProps["type"]; + type?: ButtonProps['type']; danger?: boolean; disabled?: boolean; onClick: (selectedKeys?: React.Key[], selectedRows?: any[]) => void; @@ -53,10 +52,10 @@ export interface ToolbarAction { } export interface EnhancedProTableProps - extends Omit, "columns" | "request" | "toolBarRender"> { + extends Omit, 'columns' | 'request' | 'toolBarRender'> { columns: ProColumns[]; request?: ( - params: U & { current?: number; pageSize?: number } + params: U & { current?: number; pageSize?: number }, ) => Promise>; loading?: boolean; // 添加 loading 属性 actions?: TableAction[]; @@ -73,11 +72,11 @@ export interface EnhancedProTableProps onView?: (record: T) => void; onExport?: (selectedRows: T[]) => void; customToolbarRender?: ( - defaultActions: React.ReactNode[] + defaultActions: React.ReactNode[], ) => React.ReactNode[]; customActionRender?: ( record: T, - defaultActions: React.ReactNode[] + defaultActions: React.ReactNode[], ) => React.ReactNode[]; } diff --git a/src/components/TagEditor/index.tsx b/src/components/TagEditor/index.tsx new file mode 100644 index 0000000..512dcec --- /dev/null +++ b/src/components/TagEditor/index.tsx @@ -0,0 +1,105 @@ +// src/components/TagEditor/index.tsx + +import { PlusOutlined } from '@ant-design/icons'; +import type { TagProps } from 'antd'; +import { Input, Space, Tag } from 'antd'; +import React, { useMemo, useState } from 'react'; + +interface TagEditorProps { + value?: string[]; + onChange?: (value: string[]) => void; + placeholder?: string; + maxCount?: number; + tagProps?: TagProps; + disabled?: boolean; +} + +const TagEditor: React.FC = ({ + value = [], + onChange, + placeholder = '请输入标签', + maxCount, + tagProps, + disabled = false, +}) => { + // const [tags, setTags] = useState(value); + const [inputVisible, setInputVisible] = useState(false); + const [inputValue, setInputValue] = useState(''); + + const tags = useMemo(() => { + return value || []; + }, [value]); + + const handleClose = (removedTag: string) => { + const newTags = tags.filter((tag) => tag !== removedTag); + onChange?.(newTags); + }; + + const showInput = () => { + setInputVisible(true); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleInputConfirm = () => { + if (inputValue && !tags.includes(inputValue)) { + const newTags = [...tags, inputValue]; + onChange?.(newTags); + } + setInputVisible(false); + setInputValue(''); + }; + + const handleInputBlur = () => { + handleInputConfirm(); + }; + + const canAddMore = !maxCount || tags.length < maxCount; + + return ( + + {tags.map((tag) => ( + handleClose(tag)} + {...tagProps} + > + {tag} + + ))} + + {inputVisible ? ( + + ) : ( + canAddMore && + !disabled && ( + + 添加标签 + + ) + )} + + ); +}; + +export default TagEditor; diff --git a/src/constants/index.ts b/src/constants/index.ts index 468683e..3b7d7fd 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1 +1,5 @@ -export const formStatusType = { create: '创建', update: '编辑', test: '测试' }; +export const formStatusType: { [key: string]: string } = { + create: '创建', + update: '编辑', + test: '测试', +}; diff --git a/src/pages/prod/category/config.tsx b/src/pages/prod/category/config.tsx new file mode 100644 index 0000000..cb243ad --- /dev/null +++ b/src/pages/prod/category/config.tsx @@ -0,0 +1,155 @@ +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import dayjs from 'dayjs'; +import TagEditor from '@/components/TagEditor'; +export const baseTenantColumns: ProColumns[] = [ + { + title: '类目名称', + dataIndex: 'categoryName', + width: 100, + }, + { + title: '类目ID', + dataIndex: 'categoryId', + }, + { + title: '类目层级', + dataIndex: 'grade', + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '父级类目', + dataIndex: 'parentName', + + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '排序权重', + dataIndex: 'sort', + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'switch', + hideInSearch: true, + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + hideInSearch: true, // 在搜索表单中隐藏 + render: (_, record: API.CategoryDO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (data: { + type: string; + grade: number; +}): ProFormColumnsType[] => [ + { + title: '类目', + dataIndex: 'grade', + valueType: 'radio', + fieldProps: { + value: data.grade || 1, + options: [ + { label: '一级类目', value: 1 }, + { label: '二级类目', value: 2 }, + { label: '三级类目', value: 3 }, + ], + disabled: data.type === 'create', + }, + }, + { + title: '类目名称', + dataIndex: 'username', + formItemProps: { + rules: [ + { + required: true, + message: '请输入用户名', + }, + ], + }, + }, + { + title: '排序权重', + dataIndex: 'sort', + valueType: 'digit', + }, + { + title: '类目描述', + dataIndex: 'description', + valueType: 'textarea', + renderFormItem: () => { + return 1; + }, + }, + { + title: '关联父级', + dataIndex: 'parentId', + valueType: 'select', + hideInForm: data.grade - 1 === 0, + }, + { + title: '类目icon', + dataIndex: 'icon', + }, + { + title: '类目标签', + dataIndex: 'tages', + renderFormItem: () => { + return ( + + ); + }, + }, + { + title: '类目状态', + dataIndex: 'status', + hideInForm: data.type === 'create', + }, + { + title: '类目ID', + dataIndex: 'categoryId', + hideInForm: data.type === 'create', + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateTime', + hideInForm: data.type === 'create', + }, + { + title: '创建人', + dataIndex: 'creator', + hideInForm: data.type === 'create', + }, + { + title: '更新时间', + dataIndex: 'updateTime', + valueType: 'dateTime', + hideInForm: data.type === 'create', + }, + { + title: '更新人', + dataIndex: 'updator', + hideInForm: data.type === 'create', + }, +]; + +// { +// title: "模板内容", +// dataIndex: "content", +// valueType: "textarea", +// }, diff --git a/src/pages/prod/category/index.tsx b/src/pages/prod/category/index.tsx new file mode 100644 index 0000000..27372ad --- /dev/null +++ b/src/pages/prod/category/index.tsx @@ -0,0 +1,132 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { + type ActionType, + type ProColumns, + ProCoreActionType, +} from '@ant-design/pro-components'; +import type { TabsProps } from 'antd'; +import { Tabs } from 'antd'; +import { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { getCategoryList } from '@/services/prodApi/category'; +import { baseTenantColumns, formColumns } from './config'; + +const ProdCategory = () => { + const tableRef = useRef(null); + const [type, setType] = useState<'create' | 'update' | 'test'>('create'); + const [grade, setGrade] = useState(''); + const configurableDrawerRef = useRef(null); + const onChange = useCallback( + (key: string) => { + setGrade(key); + }, + [grade], + ); + + const onFetch = async (params: API.getProductCategoryCategoryListParams) => { + const data = await getCategoryList({ + ...params, + grade: grade ? Number(grade) : undefined, + }); + return { + data: data, + success: true, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open(); + }; + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + const handleEdit = async (row: API.CategoryDO) => { + setType('update'); + configurableDrawerRef.current?.open(row); + }; + const handleSubmit = async (values: API.CategoryDO) => { + console.log('values', values); + // const success = await handleAdd(values as API.CategoryDO); + // if (success) { + // handleClose(); + // } + return true; + }; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: API.CategoryDO, _: number) => [ + handleEdit(record)}> + 编辑 + , + 详情, + ], + }; + + const columns = [...baseTenantColumns, actionColumns]; + const renderChildren = () => { + return ( + <> + + ref={tableRef} + columns={columns} + rowKey="categoryId" + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="短信渠道" + showIndex={false} + showSelection={false} + /> + + + ); + }; + + const items: TabsProps['items'] = [ + { + key: '', + label: '全部分类', + children: renderChildren(), + }, + { + key: '3', + label: '三级分类', + children: renderChildren(), + }, + { + key: '2', + label: '二级分类', + children: renderChildren(), + }, + { + key: '1', + label: '一级分类', + children: renderChildren(), + }, + ]; + return ; +}; + +export default ProdCategory; diff --git a/src/pages/prod/list/index.tsx b/src/pages/prod/list/index.tsx new file mode 100644 index 0000000..2d19c04 --- /dev/null +++ b/src/pages/prod/list/index.tsx @@ -0,0 +1,5 @@ +const ProdList = () => { + return
ProdList
; +}; + +export default ProdList; diff --git a/src/pages/system/messages/mail/account/config.tsx b/src/pages/system/messages/mail/account/config.tsx index 76a71db..32e4140 100644 --- a/src/pages/system/messages/mail/account/config.tsx +++ b/src/pages/system/messages/mail/account/config.tsx @@ -1,6 +1,5 @@ import type { ProColumns, - ProDescriptionsItemProps, ProFormColumnsType, } from '@ant-design/pro-components'; import dayjs from 'dayjs'; @@ -160,7 +159,7 @@ export const formColumns = (): ProFormColumnsType[] => [ }, ]; -export const formTestColumns = (type: string): ProFormColumnsType[] => [ +export const formTestColumns = (): ProFormColumnsType[] => [ { title: '模板内容', dataIndex: 'content', diff --git a/src/pages/system/messages/mail/account/index.tsx b/src/pages/system/messages/mail/account/index.tsx index 55386b4..346efec 100644 --- a/src/pages/system/messages/mail/account/index.tsx +++ b/src/pages/system/messages/mail/account/index.tsx @@ -15,18 +15,17 @@ import { formStatusType } from '@/constants'; import { createMailAccount, deleteMailAccount, - getMailAccount, getMailAccountPage, type MailAccountVO, updateMailAccount, } from '@/services/system/message/mail/account'; -import { baseTenantColumns, formColumns, formTestColumns } from './config'; +import { baseTenantColumns, formColumns } from './config'; const SyStemMessageSmsTemplate = () => { const tableRef = useRef(null); const configurableDrawerRef = useRef(null); const [type, setType] = useState<'create' | 'update' | 'test'>('create'); - const testRef = useRef(null); + // const testRef = useRef(null); const [currentItem, setCurrentItem] = useState(); const onFetch = async ( params: MailAccountVO & { @@ -46,11 +45,11 @@ const SyStemMessageSmsTemplate = () => { }; }; - const handleSend = async (record: MailAccountVO) => { - setType('test'); - setCurrentItem(record); - testRef.current?.open(record); - }; + // const handleSend = async (record: MailAccountVO) => { + // setType("test"); + // setCurrentItem(record); + // testRef.current?.open(record); + // }; const handleEdit = (record: MailAccountVO) => { setType('update'); @@ -68,10 +67,10 @@ const SyStemMessageSmsTemplate = () => { if (type === 'create') { await createMailAccount(values); message.success('创建成功'); - } else { + } else if (currentItem?.id) { await updateMailAccount({ ...values, - id: currentItem!.id, + id: currentItem.id, }); message.success('编辑成功'); } @@ -97,7 +96,7 @@ const SyStemMessageSmsTemplate = () => { fixed: 'right', width: 120, render: ( - text: React.ReactNode, + _text: React.ReactNode, record: MailAccountVO, _: number, action: ProCoreActionType | undefined, diff --git a/src/services/prodApi/category.ts b/src/services/prodApi/category.ts index 14dec40..5ccd2c6 100644 --- a/src/services/prodApi/category.ts +++ b/src/services/prodApi/category.ts @@ -3,25 +3,22 @@ import { request } from "@umijs/max"; /** 获取菜单页面的表 GET /product/category/categoryList */ -export async function getProductCategoryCategoryList( +export async function getCategoryList( // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) params: API.getProductCategoryCategoryListParams, options?: { [key: string]: any } ) { - return request( - "/product/category/categoryList", - { - method: "GET", - params: { - ...params, - }, - ...(options || {}), - } - ); + return request("/product/category/categoryList", { + method: "GET", + params: { + ...params, + }, + ...(options || {}), + }); } /** 创建产品类目 创建产品类目 POST /product/category/create */ -export async function postProductCategoryCreate( +export async function postCategoryCreate( body: API.CategorySaveReqVO, options?: { [key: string]: any } ) { @@ -36,7 +33,7 @@ export async function postProductCategoryCreate( } /** 更新产品类目 更新产品类目 PUT /product/category/update */ -export async function putProductCategoryUpdate( +export async function putCategoryUpdate( body: API.CategorySaveReqVO, options?: { [key: string]: any } ) {