feat(字典): 重构字典管理页面使用ProTable并优化功能
refactor(站点): 统一站点名称字段从siteName改为name fix(产品列表): 修复操作列固定显示问题 refactor(产品工具): 动态加载字典配置并移除硬编码默认值 feat(属性管理): 使用ProTable重构字典项列表并添加复制功能
This commit is contained in:
parent
52e982ba42
commit
9c35ada7b1
|
|
@ -1,5 +1,5 @@
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
Upload,
|
Upload,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -22,250 +22,168 @@ const DictPage: React.FC = () => {
|
||||||
const [loadingDicts, setLoadingDicts] = useState(false);
|
const [loadingDicts, setLoadingDicts] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||||
|
|
||||||
// 右侧字典项列表的状态
|
|
||||||
const [dictItems, setDictItems] = useState<any[]>([]);
|
|
||||||
const [loadingDictItems, setLoadingDictItems] = useState(false);
|
|
||||||
|
|
||||||
// 控制添加字典模态框的显示
|
|
||||||
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
||||||
|
const [editingDict, setEditingDict] = useState<any>(null);
|
||||||
const [newDictName, setNewDictName] = useState('');
|
const [newDictName, setNewDictName] = useState('');
|
||||||
const [newDictTitle, setNewDictTitle] = useState('');
|
const [newDictTitle, setNewDictTitle] = useState('');
|
||||||
const [editingDict, setEditingDict] = useState<any>(null);
|
|
||||||
|
|
||||||
// 控制字典项模态框的显示
|
// 右侧字典项列表的状态
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
||||||
const [dictItemForm] = Form.useForm();
|
const [dictItemForm] = Form.useForm();
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
// 获取字典列表
|
// 获取字典列表
|
||||||
const fetchDicts = async (title?: string) => {
|
const fetchDicts = async (name = '') => {
|
||||||
setLoadingDicts(true);
|
setLoadingDicts(true);
|
||||||
try {
|
try {
|
||||||
const res = await request('/dict/list', { params: { title } });
|
const res = await request('/dict/list', { params: { name } });
|
||||||
if (res) {
|
setDicts(res);
|
||||||
setDicts(res);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取字典列表失败');
|
message.error('获取字典列表失败');
|
||||||
|
} finally {
|
||||||
|
setLoadingDicts(false);
|
||||||
}
|
}
|
||||||
setLoadingDicts(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取字典项列表
|
// 搜索字典
|
||||||
const fetchDictItems = async (dictId?: number) => {
|
|
||||||
setLoadingDictItems(true);
|
|
||||||
try {
|
|
||||||
const res = await request('/dict/items', { params: { dictId } });
|
|
||||||
if (res) {
|
|
||||||
setDictItems(res);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取字典项列表失败');
|
|
||||||
}
|
|
||||||
setLoadingDictItems(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件加载时获取数据
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDicts();
|
|
||||||
fetchDictItems();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 当选择的字典变化时,重新获取字典项
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDictItems(selectedDict?.id);
|
|
||||||
}, [selectedDict]);
|
|
||||||
|
|
||||||
// 处理字典搜索
|
|
||||||
const handleSearch = (value: string) => {
|
const handleSearch = (value: string) => {
|
||||||
fetchDicts(value);
|
fetchDicts(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理添加字典
|
// 添加或编辑字典
|
||||||
const handleAddDict = async () => {
|
const handleAddDict = async () => {
|
||||||
if (!newDictName || !newDictTitle) {
|
const values = { name: newDictName, title: newDictTitle };
|
||||||
message.warning('请输入字典名称和标题');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
if (editingDict) {
|
if (editingDict) {
|
||||||
await request(`/dict/${editingDict.id}`, {
|
await request(`/dict/${editingDict.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: { name: newDictName, title: newDictTitle },
|
data: values,
|
||||||
});
|
});
|
||||||
message.success('更新成功');
|
message.success('更新成功');
|
||||||
} else {
|
} else {
|
||||||
await request('/dict', {
|
await request('/dict', { method: 'POST', data: values });
|
||||||
method: 'POST',
|
|
||||||
data: { name: newDictName, title: newDictTitle },
|
|
||||||
});
|
|
||||||
message.success('添加成功');
|
message.success('添加成功');
|
||||||
}
|
}
|
||||||
setIsAddDictModalVisible(false);
|
setIsAddDictModalVisible(false);
|
||||||
|
setEditingDict(null);
|
||||||
setNewDictName('');
|
setNewDictName('');
|
||||||
setNewDictTitle('');
|
setNewDictTitle('');
|
||||||
setEditingDict(null);
|
fetchDicts(); // 重新获取列表
|
||||||
fetchDicts(); // 重新获取字典列表
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(editingDict ? '更新失败' : '添加失败');
|
message.error(editingDict ? '更新失败' : '添加失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditDict = (dict: any) => {
|
// 删除字典
|
||||||
setEditingDict(dict);
|
const handleDeleteDict = async (id: number) => {
|
||||||
setNewDictName(dict.name);
|
try {
|
||||||
setNewDictTitle(dict.title);
|
await request(`/dict/${id}`, { method: 'DELETE' });
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchDicts();
|
||||||
|
if (selectedDict?.id === id) {
|
||||||
|
setSelectedDict(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 编辑字典
|
||||||
|
const handleEditDict = (record: any) => {
|
||||||
|
setEditingDict(record);
|
||||||
|
setNewDictName(record.name);
|
||||||
|
setNewDictTitle(record.title);
|
||||||
setIsAddDictModalVisible(true);
|
setIsAddDictModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开添加字典项模态框
|
// 下载字典导入模板
|
||||||
|
const handleDownloadDictTemplate = () => {
|
||||||
|
// 创建一个空的 a 标签用于下载
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = '/dict/template'; // 指向后端的模板下载接口
|
||||||
|
link.setAttribute('download', 'dict_template.xlsx');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加字典项
|
||||||
const handleAddDictItem = () => {
|
const handleAddDictItem = () => {
|
||||||
setEditingDictItem(null);
|
setEditingDictItem(null);
|
||||||
dictItemForm.resetFields();
|
dictItemForm.resetFields();
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑字典项模态框
|
// 编辑字典项
|
||||||
const handleEditDictItem = (item: any) => {
|
const handleEditDictItem = (record: any) => {
|
||||||
setEditingDictItem(item);
|
setEditingDictItem(record);
|
||||||
dictItemForm.setFieldsValue(item);
|
dictItemForm.setFieldsValue(record);
|
||||||
setIsDictItemModalVisible(true);
|
setIsDictItemModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理字典项表单提交(添加/编辑)
|
// 删除字典项
|
||||||
const handleDictItemFormSubmit = async (values: any) => {
|
const handleDeleteDictItem = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
if (editingDictItem) {
|
await request(`/dict/item/${id}`, { method: 'DELETE' });
|
||||||
// 编辑
|
message.success('删除成功');
|
||||||
await request(`/dict/item/${editingDictItem.id}`, {
|
actionRef.current?.reload();
|
||||||
method: 'PUT',
|
} catch (error) {
|
||||||
data: values,
|
message.error('删除失败');
|
||||||
});
|
}
|
||||||
message.success('更新成功');
|
};
|
||||||
} else {
|
|
||||||
// 添加
|
// 字典项表单提交
|
||||||
await request('/dict/item', {
|
const handleDictItemFormSubmit = async (values: any) => {
|
||||||
method: 'POST',
|
const url = editingDictItem
|
||||||
data: { ...values, dictId: selectedDict.id },
|
? `/dict/item/${editingDictItem.id}`
|
||||||
});
|
: '/dict/item';
|
||||||
message.success('添加成功');
|
const method = editingDictItem ? 'PUT' : 'POST';
|
||||||
}
|
const data = editingDictItem
|
||||||
|
? { ...values }
|
||||||
|
: { ...values, dict: { id: selectedDict.id } };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request(url, { method, data });
|
||||||
|
message.success(editingDictItem ? '更新成功' : '添加成功');
|
||||||
setIsDictItemModalVisible(false);
|
setIsDictItemModalVisible(false);
|
||||||
fetchDictItems(selectedDict.id);
|
actionRef.current?.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理删除字典项
|
// 下载字典项导入模板
|
||||||
const handleDeleteDictItem = async (itemId: number) => {
|
const handleDownloadDictItemTemplate = () => {
|
||||||
try {
|
const link = document.createElement('a');
|
||||||
await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
link.href = '/dict/item/template';
|
||||||
message.success('删除成功');
|
link.setAttribute('download', 'dict_item_template.xlsx');
|
||||||
fetchDictItems(selectedDict.id);
|
document.body.appendChild(link);
|
||||||
} catch (error) {
|
link.click();
|
||||||
message.error('删除失败');
|
document.body.removeChild(link);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理删除字典
|
// Effects
|
||||||
const handleDeleteDict = async (dictId: number) => {
|
useEffect(() => {
|
||||||
try {
|
fetchDicts();
|
||||||
await request(`/dict/${dictId}`, { method: 'DELETE' });
|
}, []);
|
||||||
message.success('删除成功');
|
|
||||||
fetchDicts(); // 重新获取字典列表
|
|
||||||
// 如果删除的是当前选中的字典,则清空右侧列表
|
|
||||||
if (selectedDict?.id === dictId) {
|
|
||||||
setSelectedDict(null);
|
|
||||||
setDictItems([]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// 左侧字典表格的列定义
|
||||||
* @description 下载字典模板(走认证请求)
|
|
||||||
*/
|
|
||||||
const handleDownloadDictTemplate = async () => {
|
|
||||||
try {
|
|
||||||
// 使用带有认证拦截的 request 发起下载请求(后端鉴权通过)
|
|
||||||
const blob = await request('/dict/template', { responseType: 'blob' });
|
|
||||||
// 创建临时链接并触发下载
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'dict-template.xlsx';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
} catch (error) {
|
|
||||||
// 错误处理:认证失败或网络错误
|
|
||||||
message.error('下载模板失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 下载字典项模板(走认证请求)
|
|
||||||
*/
|
|
||||||
const handleDownloadDictItemTemplate = async () => {
|
|
||||||
try {
|
|
||||||
// 使用带有认证拦截的 request 发起下载请求(后端鉴权通过)
|
|
||||||
const blob = await request('/dict/item/template', {
|
|
||||||
responseType: 'blob',
|
|
||||||
});
|
|
||||||
// 创建临时链接并触发下载
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = 'dict-item-template.xlsx';
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
} catch (error) {
|
|
||||||
// 错误处理:认证失败或网络错误
|
|
||||||
message.error('下载模板失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 左侧字典列表的列定义
|
|
||||||
const dictColumns = [
|
const dictColumns = [
|
||||||
{
|
{ title: '字典名称', dataIndex: 'name', key: 'name' },
|
||||||
title: '名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '标题',
|
|
||||||
dataIndex: 'title',
|
|
||||||
key: 'title',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (_: any, record: any) => (
|
render: (_: any, record: any) => (
|
||||||
<Space size="small">
|
<Space size="small">
|
||||||
<Button
|
<Button type="link" size="small" onClick={() => handleEditDict(record)}>
|
||||||
size="small"
|
|
||||||
type="link"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleEditDict(record);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
|
||||||
type="link"
|
type="link"
|
||||||
|
size="small"
|
||||||
danger
|
danger
|
||||||
onClick={(e) => {
|
onClick={() => handleDeleteDict(record.id)}
|
||||||
e.stopPropagation();
|
|
||||||
handleDeleteDict(record.id);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -280,16 +198,19 @@ const DictPage: React.FC = () => {
|
||||||
title: '名称',
|
title: '名称',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标题',
|
title: '标题',
|
||||||
dataIndex: 'title',
|
dataIndex: 'title',
|
||||||
key: 'title',
|
key: 'title',
|
||||||
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '中文标题',
|
title: '中文标题',
|
||||||
dataIndex: 'titleCN',
|
dataIndex: 'titleCN',
|
||||||
key: 'titleCN',
|
key: 'titleCN',
|
||||||
|
copyable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
|
@ -324,7 +245,6 @@ const DictPage: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
size="small"
|
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="搜索字典"
|
placeholder="搜索字典"
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
|
|
@ -411,7 +331,7 @@ const DictPage: React.FC = () => {
|
||||||
onChange={(info) => {
|
onChange={(info) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
message.success(`${info.file.name} 文件上传成功`);
|
message.success(`${info.file.name} 文件上传成功`);
|
||||||
fetchDictItems(selectedDict.id);
|
actionRef.current?.reload();
|
||||||
} else if (info.file.status === 'error') {
|
} else if (info.file.status === 'error') {
|
||||||
message.error(`${info.file.name} 文件上传失败`);
|
message.error(`${info.file.name} 文件上传失败`);
|
||||||
}
|
}
|
||||||
|
|
@ -433,12 +353,37 @@ const DictPage: React.FC = () => {
|
||||||
下载模板
|
下载模板
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<ProTable
|
||||||
dataSource={dictItems}
|
|
||||||
columns={dictItemColumns}
|
columns={dictItemColumns}
|
||||||
|
request={async (params) => {
|
||||||
|
// 当没有选择字典时,不发起请求
|
||||||
|
if (!selectedDict?.id) {
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { name, title } = params;
|
||||||
|
const res = await request('/dict/items', {
|
||||||
|
params: {
|
||||||
|
dictId: selectedDict?.id,
|
||||||
|
name,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
data: res,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loadingDictItems}
|
search={{
|
||||||
|
layout: 'vertical',
|
||||||
|
}}
|
||||||
|
pagination={false}
|
||||||
|
options={false}
|
||||||
size="small"
|
size="small"
|
||||||
|
key={selectedDict?.id}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import {
|
||||||
|
ActionType,
|
||||||
|
PageContainer,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -12,7 +16,7 @@ import {
|
||||||
Upload,
|
Upload,
|
||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
|
|
@ -25,9 +29,8 @@ const AttributePage: React.FC = () => {
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||||
|
|
||||||
// 中文注释:右侧字典项状态
|
// 中文注释:右侧字典项 ProTable 的引用
|
||||||
const [dictItems, setDictItems] = useState<any[]>([]);
|
const actionRef = useRef<ActionType>();
|
||||||
const [loadingDictItems, setLoadingDictItems] = useState(false);
|
|
||||||
|
|
||||||
// 中文注释:字典项新增/编辑模态框控制
|
// 中文注释:字典项新增/编辑模态框控制
|
||||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||||
|
|
@ -48,29 +51,11 @@ const AttributePage: React.FC = () => {
|
||||||
setLoadingDicts(false);
|
setLoadingDicts(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 中文注释:获取字典项列表
|
|
||||||
const fetchDictItems = async (dictId?: number) => {
|
|
||||||
setLoadingDictItems(true);
|
|
||||||
try {
|
|
||||||
const res = await request('/dict/items', { params: { dictId } });
|
|
||||||
setDictItems(res || []);
|
|
||||||
} catch (error) {
|
|
||||||
message.error('获取字典项列表失败');
|
|
||||||
}
|
|
||||||
setLoadingDictItems(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 中文注释:组件挂载时初始化数据
|
// 中文注释:组件挂载时初始化数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDicts();
|
fetchDicts();
|
||||||
fetchDictItems();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 中文注释:当选择的字典发生变化时刷新右侧字典项
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDictItems(selectedDict?.id);
|
|
||||||
}, [selectedDict]);
|
|
||||||
|
|
||||||
// 中文注释:搜索触发过滤
|
// 中文注释:搜索触发过滤
|
||||||
const handleSearch = (value: string) => {
|
const handleSearch = (value: string) => {
|
||||||
fetchDicts(value);
|
fetchDicts(value);
|
||||||
|
|
@ -109,7 +94,7 @@ const AttributePage: React.FC = () => {
|
||||||
message.success('添加成功');
|
message.success('添加成功');
|
||||||
}
|
}
|
||||||
setIsDictItemModalVisible(false);
|
setIsDictItemModalVisible(false);
|
||||||
fetchDictItems(selectedDict.id);
|
actionRef.current?.reload(); // 中文注释:刷新 ProTable
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +105,7 @@ const AttributePage: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchDictItems(selectedDict.id);
|
actionRef.current?.reload(); // 中文注释:刷新 ProTable
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
}
|
}
|
||||||
|
|
@ -133,13 +118,14 @@ const AttributePage: React.FC = () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
// 中文注释:右侧字典项列表列定义(紧凑样式)
|
// 中文注释:右侧字典项列表列定义(紧凑样式)
|
||||||
const dictItemColumns = [
|
const dictItemColumns: any[] = [
|
||||||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
{ title: '名称', dataIndex: 'name', key: 'name', copyable: true },
|
||||||
{ title: '标题', dataIndex: 'title', key: 'title' },
|
{ title: '标题', dataIndex: 'title', key: 'title', copyable: true },
|
||||||
{ title: '中文标题', dataIndex: 'titleCN', key: 'titleCN' },
|
{ title: '中文标题', dataIndex: 'titleCN', key: 'titleCN', copyable: true },
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
valueType: 'option',
|
||||||
render: (_: any, record: any) => (
|
render: (_: any, record: any) => (
|
||||||
<Space size="small">
|
<Space size="small">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -206,56 +192,75 @@ const AttributePage: React.FC = () => {
|
||||||
</Space>
|
</Space>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Content style={{ padding: '8px' }}>
|
<Content style={{ padding: '8px' }}>
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
<ProTable
|
||||||
<div
|
columns={dictItemColumns}
|
||||||
style={{
|
actionRef={actionRef}
|
||||||
width: '100%',
|
request={async (params) => {
|
||||||
display: 'flex',
|
// 中文注释:当没有选择字典时,不发起请求
|
||||||
flexDirection: 'row',
|
if (!selectedDict?.id) {
|
||||||
gap: '4px',
|
return {
|
||||||
}}
|
data: [],
|
||||||
>
|
success: true,
|
||||||
<Button
|
};
|
||||||
type="primary"
|
}
|
||||||
size="small"
|
const { name, title } = params;
|
||||||
onClick={handleAddDictItem}
|
const res = await request('/dict/items', {
|
||||||
disabled={!selectedDict}
|
params: {
|
||||||
>
|
dictId: selectedDict.id,
|
||||||
添加字典项
|
name,
|
||||||
</Button>
|
title,
|
||||||
<Upload
|
},
|
||||||
name="file"
|
});
|
||||||
action={`/dict/item/import`}
|
return {
|
||||||
data={{ dictId: selectedDict?.id }}
|
data: res,
|
||||||
showUploadList={false}
|
success: true,
|
||||||
disabled={!selectedDict}
|
};
|
||||||
onChange={(info) => {
|
}}
|
||||||
// 中文注释:条件判断,上传状态处理
|
rowKey="id"
|
||||||
if (info.file.status === 'done') {
|
search={{
|
||||||
message.success(`${info.file.name} 文件上传成功`);
|
layout: 'vertical',
|
||||||
fetchDictItems(selectedDict.id);
|
}}
|
||||||
} else if (info.file.status === 'error') {
|
pagination={false}
|
||||||
message.error(`${info.file.name} 文件上传失败`);
|
options={false}
|
||||||
}
|
size="small"
|
||||||
}}
|
key={selectedDict?.id}
|
||||||
>
|
headerTitle={
|
||||||
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<UploadOutlined />}
|
onClick={handleAddDictItem}
|
||||||
disabled={!selectedDict}
|
disabled={!selectedDict}
|
||||||
>
|
>
|
||||||
导入字典项
|
添加字典项
|
||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
<Upload
|
||||||
</div>
|
name="file"
|
||||||
<Table
|
action={`/dict/item/import`}
|
||||||
dataSource={dictItems}
|
data={{ dictId: selectedDict?.id }}
|
||||||
columns={dictItemColumns}
|
showUploadList={false}
|
||||||
rowKey="id"
|
disabled={!selectedDict}
|
||||||
loading={loadingDictItems}
|
onChange={(info) => {
|
||||||
size="small"
|
// 中文注释:条件判断,上传状态处理
|
||||||
/>
|
if (info.file.status === 'done') {
|
||||||
</Space>
|
message.success(`${info.file.name} 文件上传成功`);
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} else if (info.file.status === 'error') {
|
||||||
|
message.error(`${info.file.name} 文件上传失败`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
disabled={!selectedDict}
|
||||||
|
>
|
||||||
|
导入字典项
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,7 @@ const List: React.FC = () => {
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'option',
|
dataIndex: 'option',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
|
fixed: true,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<>
|
<>
|
||||||
<EditForm record={record} tableRef={actionRef} />
|
<EditForm record={record} tableRef={actionRef} />
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,7 @@ import { useRef } from 'react';
|
||||||
const List: React.FC = () => {
|
const List: React.FC = () => {
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
const columns: ProColumns<API.WpProductDTO>[] = [
|
const columns: ProColumns<API.WpProductDTO>[] = [
|
||||||
{
|
{
|
||||||
title: 'sku',
|
|
||||||
dataIndex: 'sku',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '站点',
|
title: '站点',
|
||||||
dataIndex: 'siteId',
|
dataIndex: 'siteId',
|
||||||
valueType: 'select',
|
valueType: 'select',
|
||||||
|
|
@ -54,6 +46,15 @@ const List: React.FC = () => {
|
||||||
return record.site?.name;
|
return record.site?.name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'sku',
|
||||||
|
dataIndex: 'sku',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '产品状态',
|
title: '产品状态',
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import {
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Button, Card, Col, Input, message, Row, Upload } from 'antd';
|
import { Button, Card, Col, Input, message, Row, Upload } from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
// 定义配置接口
|
// 定义配置接口
|
||||||
interface TagConfig {
|
interface TagConfig {
|
||||||
|
|
@ -163,31 +164,6 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => {
|
||||||
return finalTags.join(', ');
|
return finalTags.join(', ');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从 Python 脚本中提取的默认配置
|
|
||||||
const DEFAULT_CONFIG = {
|
|
||||||
brands: ['YOONE', 'ZYN', 'ZEX', 'JUX', 'WHITE FOX'],
|
|
||||||
fruitKeys: [
|
|
||||||
'apple',
|
|
||||||
'blueberry',
|
|
||||||
'citrus',
|
|
||||||
'mango',
|
|
||||||
'peach',
|
|
||||||
'grape',
|
|
||||||
'cherry',
|
|
||||||
'strawberry',
|
|
||||||
'watermelon',
|
|
||||||
'orange',
|
|
||||||
'lemon',
|
|
||||||
'lemonade',
|
|
||||||
'razz',
|
|
||||||
'pineapple',
|
|
||||||
'berry',
|
|
||||||
'fruit',
|
|
||||||
],
|
|
||||||
mintKeys: ['mint', 'wintergreen', 'peppermint', 'spearmint', 'menthol'],
|
|
||||||
nonFlavorTokens: ['slim', 'pouches', 'pouch', 'mini', 'dry'],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description WordPress 产品工具页面,用于处理产品 CSV 并生成 Tags
|
* @description WordPress 产品工具页面,用于处理产品 CSV 并生成 Tags
|
||||||
*/
|
*/
|
||||||
|
|
@ -198,6 +174,40 @@ const WpToolPage: React.FC = () => {
|
||||||
const [csvData, setCsvData] = useState<any[]>([]); // 解析后的 CSV 数据
|
const [csvData, setCsvData] = useState<any[]>([]); // 解析后的 CSV 数据
|
||||||
const [processedData, setProcessedData] = useState<any[]>([]); // 处理后待下载的数据
|
const [processedData, setProcessedData] = useState<any[]>([]); // 处理后待下载的数据
|
||||||
const [isProcessing, setIsProcessing] = useState(false); // 是否正在处理中
|
const [isProcessing, setIsProcessing] = useState(false); // 是否正在处理中
|
||||||
|
const [config, setConfig] = useState<TagConfig>({ // 动态配置
|
||||||
|
brands: [],
|
||||||
|
fruitKeys: [],
|
||||||
|
mintKeys: [],
|
||||||
|
nonFlavorTokens: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在组件加载时获取字典数据
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDictItems = async (name: string) => {
|
||||||
|
try {
|
||||||
|
const response = await request('/api/dict/items-by-name', {
|
||||||
|
params: { name },
|
||||||
|
});
|
||||||
|
return response.data.map((item: any) => item.name);
|
||||||
|
} catch (error) {
|
||||||
|
message.error(`获取字典 ${name} 失败`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAllConfigs = async () => {
|
||||||
|
const [brands, fruitKeys, mintKeys, nonFlavorTokens] = await Promise.all([
|
||||||
|
fetchDictItems('brand'),
|
||||||
|
fetchDictItems('fruit'),
|
||||||
|
fetchDictItems('mint'),
|
||||||
|
fetchDictItems('non_flavor_tokens'),
|
||||||
|
]);
|
||||||
|
setConfig({ brands, fruitKeys, mintKeys, nonFlavorTokens });
|
||||||
|
form.setFieldsValue({ brands, fruitKeys, mintKeys, nonFlavorTokens });
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAllConfigs();
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 处理文件上传
|
* @description 处理文件上传
|
||||||
|
|
@ -341,7 +351,7 @@ const WpToolPage: React.FC = () => {
|
||||||
<Card title="1. 配置映射规则">
|
<Card title="1. 配置映射规则">
|
||||||
<ProForm
|
<ProForm
|
||||||
form={form}
|
form={form}
|
||||||
initialValues={DEFAULT_CONFIG}
|
initialValues={config}
|
||||||
onFinish={handleProcessData}
|
onFinish={handleProcessData}
|
||||||
submitter={false}
|
submitter={false}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥)
|
// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥)
|
||||||
interface SiteItem {
|
interface SiteItem {
|
||||||
id: number;
|
id: number;
|
||||||
siteName: string;
|
name: string;
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
type?: 'woocommerce' | 'shopyy';
|
type?: 'woocommerce' | 'shopyy';
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
|
|
@ -24,7 +24,7 @@ interface SiteItem {
|
||||||
|
|
||||||
// 创建/更新表单的值类型,包含可选的密钥字段
|
// 创建/更新表单的值类型,包含可选的密钥字段
|
||||||
interface SiteFormValues {
|
interface SiteFormValues {
|
||||||
siteName: string;
|
name: string;
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
type?: 'woocommerce' | 'shopyy';
|
type?: 'woocommerce' | 'shopyy';
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
|
@ -43,7 +43,7 @@ const SiteList: React.FC = () => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
if (editing) {
|
if (editing) {
|
||||||
formRef.current?.setFieldsValue({
|
formRef.current?.setFieldsValue({
|
||||||
siteName: editing.siteName,
|
name: editing.name,
|
||||||
apiUrl: editing.apiUrl,
|
apiUrl: editing.apiUrl,
|
||||||
type: editing.type,
|
type: editing.type,
|
||||||
skuPrefix: editing.skuPrefix,
|
skuPrefix: editing.skuPrefix,
|
||||||
|
|
@ -53,7 +53,7 @@ const SiteList: React.FC = () => {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
formRef.current?.setFieldsValue({
|
formRef.current?.setFieldsValue({
|
||||||
siteName: undefined,
|
name: undefined,
|
||||||
apiUrl: undefined,
|
apiUrl: undefined,
|
||||||
type: 'woocommerce',
|
type: 'woocommerce',
|
||||||
skuPrefix: undefined,
|
skuPrefix: undefined,
|
||||||
|
|
@ -148,13 +148,13 @@ const SiteList: React.FC = () => {
|
||||||
// 表格数据请求
|
// 表格数据请求
|
||||||
const tableRequest = async (params: Record<string, any>) => {
|
const tableRequest = async (params: Record<string, any>) => {
|
||||||
try {
|
try {
|
||||||
const { current = 1, pageSize = 10, siteName, type } = params;
|
const { current = 1, pageSize = 10, name, type } = params;
|
||||||
const resp = await request('/site/list', {
|
const resp = await request('/site/list', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
current,
|
current,
|
||||||
pageSize,
|
pageSize,
|
||||||
keyword: siteName || undefined,
|
keyword: name || undefined,
|
||||||
type: type || undefined,
|
type: type || undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -177,7 +177,7 @@ const SiteList: React.FC = () => {
|
||||||
if (editing) {
|
if (editing) {
|
||||||
const payload: Record<string, any> = {
|
const payload: Record<string, any> = {
|
||||||
// 仅提交存在的字段,避免覆盖为 null/空
|
// 仅提交存在的字段,避免覆盖为 null/空
|
||||||
...(values.siteName ? { siteName: values.siteName } : {}),
|
...(values.name ? { name: values.name } : {}),
|
||||||
...(values.apiUrl ? { apiUrl: values.apiUrl } : {}),
|
...(values.apiUrl ? { apiUrl: values.apiUrl } : {}),
|
||||||
...(values.type ? { type: values.type } : {}),
|
...(values.type ? { type: values.type } : {}),
|
||||||
...(typeof values.isDisabled === 'boolean'
|
...(typeof values.isDisabled === 'boolean'
|
||||||
|
|
@ -204,7 +204,7 @@ const SiteList: React.FC = () => {
|
||||||
await request('/site/create', {
|
await request('/site/create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
siteName: values.siteName,
|
name: values.name,
|
||||||
apiUrl: values.apiUrl,
|
apiUrl: values.apiUrl,
|
||||||
type: values.type || 'woocommerce',
|
type: values.type || 'woocommerce',
|
||||||
consumerKey: values.consumerKey,
|
consumerKey: values.consumerKey,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项列表
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
export async function getDictItems(params: { name: string }) {
|
||||||
|
return request('/api/dict/items-by-name', {
|
||||||
|
method: 'GET',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue