商品列表、分类管理、客户等级

This commit is contained in:
liu
2026-08-05 19:28:53 +08:00
parent dfa471bf22
commit 258df92e6c
36 changed files with 472 additions and 516 deletions
@@ -0,0 +1,161 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { message, theme } from 'antd';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
import type { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
import '@wangeditor/editor/dist/css/style.css';
import { uploadFile } from '@/api/system/sysFile';
import type { ISysFileInfo } from '@/domain/iSysFile';
import { useTranslation } from 'react-i18next';
import type { RichTextEditorProps } from './typings';
/**
* 富文本编辑器(wangEditor v5
* - 自定义图片上传:复用系统文件上传接口,校验参照 ImageUploader
* - 隐藏视频上传、表情、附件等复杂功能,仅保留基础排版与图片
*/
const RichTextEditor: React.FC<RichTextEditorProps> = ({
value,
onChange,
disabled = false,
height = 400,
placeholder,
groupId = 0,
maxSize = 5,
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const [editor, setEditor] = useState<IDomEditor | null>(null);
// 自定义图片上传(校验逻辑参照 ImageUploader 组件)
const customUpload = useCallback(
async (file: File, insertFn: (src: string, alt: string, href: string) => void) => {
// 1. 文件类型校验
if (!file.type.startsWith('image/')) {
message.error(t('xin.form.richText.error.notImage'));
return;
}
// 2. 文件大小校验
if (file.size / 1024 / 1024 > maxSize) {
message.error(t('xin.form.richText.error.sizeExceeded', { maxSize }));
return;
}
// 3. 调用系统文件上传接口
try {
const res = await uploadFile(file, groupId);
const info = res.data.data as ISysFileInfo;
insertFn(info.preview_url || info.file_url || '', info.file_name || '', '');
} catch {
message.error(t('xin.form.richText.error.uploadFailed'));
}
},
[t, groupId, maxSize]
);
// 工具栏配置:只保留基础排版 + 图片,隐藏视频/表情/附件等复杂功能
const toolbarConfig: Partial<IToolbarConfig> = useMemo(
() => ({
toolbarKeys: [
'headerSelect',
'blockquote',
'|',
'bold',
'underline',
'italic',
'through',
'|',
'color',
'bgColor',
'|',
'bulletedList',
'numberedList',
'todo',
'|',
'justifyLeft',
'justifyCenter',
'justifyRight',
'|',
'insertLink',
'uploadImage',
'insertTable',
'|',
'undo',
'redo',
'fullScreen',
],
}),
[]
);
// 编辑器配置
const editorConfig: Partial<IEditorConfig> = useMemo(
() => ({
placeholder,
MENU_CONF: {
uploadImage: {
// 自定义上传:覆盖默认的服务端上传方式
customUpload,
allowedFileTypes: ['image/*'],
maxFileSize: maxSize * 1024 * 1024,
// 禁用 base64 插入,粘贴/拖拽图片一律走自定义上传
base64LimitSize: 0,
},
},
}),
[placeholder, customUpload, maxSize]
);
// 禁用/启用编辑器
useEffect(() => {
if (editor == null) return;
if (disabled) {
editor.disable();
} else {
editor.enable();
}
}, [editor, disabled]);
// 组件卸载时销毁编辑器实例
useEffect(
() => () => {
if (editor == null) return;
editor.destroy();
setEditor(null);
},
[editor]
);
return (
<div
style={{
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadius,
overflow: 'hidden',
}}
>
{!disabled && (
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: `1px solid ${token.colorBorder}` }}
/>
)}
<Editor
defaultConfig={editorConfig}
value={value}
onCreated={setEditor}
onChange={(ed) => onChange?.(ed.getHtml())}
mode="default"
style={{
height,
overflowY: 'hidden',
background: disabled ? token.colorBgLayout : undefined,
}}
/>
</div>
);
};
export default RichTextEditor;
@@ -0,0 +1,44 @@
/**
* 富文本编辑器组件属性
*/
export interface RichTextEditorProps {
/**
* 当前值(HTML 字符串)
*/
value?: string;
/**
* 值变化回调
* @param value HTML 字符串
*/
onChange?: (value?: string) => void;
/**
* 是否禁用
* @default false
*/
disabled?: boolean;
/**
* 编辑器高度 (px)
* @default 400
*/
height?: number;
/**
* 占位符
*/
placeholder?: string;
/**
* 上传文件分组 ID
* @default 0
*/
groupId?: number;
/**
* 图片大小限制 (MB)
* @default 5
*/
maxSize?: number;
}