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 { PageContainer } from '@ant-design/pro-components';
|
||||
import { ActionType, PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import { request } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -12,7 +12,7 @@ import {
|
|||
Upload,
|
||||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
|
|
@ -22,250 +22,168 @@ const DictPage: React.FC = () => {
|
|||
const [loadingDicts, setLoadingDicts] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||
|
||||
// 右侧字典项列表的状态
|
||||
const [dictItems, setDictItems] = useState<any[]>([]);
|
||||
const [loadingDictItems, setLoadingDictItems] = useState(false);
|
||||
|
||||
// 控制添加字典模态框的显示
|
||||
const [isAddDictModalVisible, setIsAddDictModalVisible] = useState(false);
|
||||
const [editingDict, setEditingDict] = useState<any>(null);
|
||||
const [newDictName, setNewDictName] = useState('');
|
||||
const [newDictTitle, setNewDictTitle] = useState('');
|
||||
const [editingDict, setEditingDict] = useState<any>(null);
|
||||
|
||||
// 控制字典项模态框的显示
|
||||
// 右侧字典项列表的状态
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
const [editingDictItem, setEditingDictItem] = useState<any>(null);
|
||||
const [dictItemForm] = Form.useForm();
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
// 获取字典列表
|
||||
const fetchDicts = async (title?: string) => {
|
||||
const fetchDicts = async (name = '') => {
|
||||
setLoadingDicts(true);
|
||||
try {
|
||||
const res = await request('/dict/list', { params: { title } });
|
||||
if (res) {
|
||||
setDicts(res);
|
||||
}
|
||||
const res = await request('/dict/list', { params: { name } });
|
||||
setDicts(res);
|
||||
} catch (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) => {
|
||||
fetchDicts(value);
|
||||
};
|
||||
|
||||
// 处理添加字典
|
||||
// 添加或编辑字典
|
||||
const handleAddDict = async () => {
|
||||
if (!newDictName || !newDictTitle) {
|
||||
message.warning('请输入字典名称和标题');
|
||||
return;
|
||||
}
|
||||
const values = { name: newDictName, title: newDictTitle };
|
||||
try {
|
||||
if (editingDict) {
|
||||
await request(`/dict/${editingDict.id}`, {
|
||||
method: 'PUT',
|
||||
data: { name: newDictName, title: newDictTitle },
|
||||
data: values,
|
||||
});
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
await request('/dict', {
|
||||
method: 'POST',
|
||||
data: { name: newDictName, title: newDictTitle },
|
||||
});
|
||||
await request('/dict', { method: 'POST', data: values });
|
||||
message.success('添加成功');
|
||||
}
|
||||
setIsAddDictModalVisible(false);
|
||||
setEditingDict(null);
|
||||
setNewDictName('');
|
||||
setNewDictTitle('');
|
||||
setEditingDict(null);
|
||||
fetchDicts(); // 重新获取字典列表
|
||||
fetchDicts(); // 重新获取列表
|
||||
} catch (error) {
|
||||
message.error(editingDict ? '更新失败' : '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditDict = (dict: any) => {
|
||||
setEditingDict(dict);
|
||||
setNewDictName(dict.name);
|
||||
setNewDictTitle(dict.title);
|
||||
// 删除字典
|
||||
const handleDeleteDict = async (id: number) => {
|
||||
try {
|
||||
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);
|
||||
};
|
||||
|
||||
// 打开添加字典项模态框
|
||||
// 下载字典导入模板
|
||||
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 = () => {
|
||||
setEditingDictItem(null);
|
||||
dictItemForm.resetFields();
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 打开编辑字典项模态框
|
||||
const handleEditDictItem = (item: any) => {
|
||||
setEditingDictItem(item);
|
||||
dictItemForm.setFieldsValue(item);
|
||||
// 编辑字典项
|
||||
const handleEditDictItem = (record: any) => {
|
||||
setEditingDictItem(record);
|
||||
dictItemForm.setFieldsValue(record);
|
||||
setIsDictItemModalVisible(true);
|
||||
};
|
||||
|
||||
// 处理字典项表单提交(添加/编辑)
|
||||
const handleDictItemFormSubmit = async (values: any) => {
|
||||
// 删除字典项
|
||||
const handleDeleteDictItem = async (id: number) => {
|
||||
try {
|
||||
if (editingDictItem) {
|
||||
// 编辑
|
||||
await request(`/dict/item/${editingDictItem.id}`, {
|
||||
method: 'PUT',
|
||||
data: values,
|
||||
});
|
||||
message.success('更新成功');
|
||||
} else {
|
||||
// 添加
|
||||
await request('/dict/item', {
|
||||
method: 'POST',
|
||||
data: { ...values, dictId: selectedDict.id },
|
||||
});
|
||||
message.success('添加成功');
|
||||
}
|
||||
await request(`/dict/item/${id}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
actionRef.current?.reload();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 字典项表单提交
|
||||
const handleDictItemFormSubmit = async (values: any) => {
|
||||
const url = editingDictItem
|
||||
? `/dict/item/${editingDictItem.id}`
|
||||
: '/dict/item';
|
||||
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);
|
||||
fetchDictItems(selectedDict.id);
|
||||
actionRef.current?.reload();
|
||||
} catch (error) {
|
||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 处理删除字典项
|
||||
const handleDeleteDictItem = async (itemId: number) => {
|
||||
try {
|
||||
await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchDictItems(selectedDict.id);
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
// 下载字典项导入模板
|
||||
const handleDownloadDictItemTemplate = () => {
|
||||
const link = document.createElement('a');
|
||||
link.href = '/dict/item/template';
|
||||
link.setAttribute('download', 'dict_item_template.xlsx');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// 处理删除字典
|
||||
const handleDeleteDict = async (dictId: number) => {
|
||||
try {
|
||||
await request(`/dict/${dictId}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchDicts(); // 重新获取字典列表
|
||||
// 如果删除的是当前选中的字典,则清空右侧列表
|
||||
if (selectedDict?.id === dictId) {
|
||||
setSelectedDict(null);
|
||||
setDictItems([]);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
fetchDicts();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* @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 = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
},
|
||||
{ title: '字典名称', dataIndex: 'name', key: 'name' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: any) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditDict(record);
|
||||
}}
|
||||
>
|
||||
<Button type="link" size="small" onClick={() => handleEditDict(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteDict(record.id);
|
||||
}}
|
||||
onClick={() => handleDeleteDict(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
|
|
@ -280,16 +198,19 @@ const DictPage: React.FC = () => {
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '中文标题',
|
||||
dataIndex: 'titleCN',
|
||||
key: 'titleCN',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
|
@ -324,7 +245,6 @@ const DictPage: React.FC = () => {
|
|||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Input.Search
|
||||
size="small"
|
||||
size="small"
|
||||
placeholder="搜索字典"
|
||||
onSearch={handleSearch}
|
||||
|
|
@ -411,7 +331,7 @@ const DictPage: React.FC = () => {
|
|||
onChange={(info) => {
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
fetchDictItems(selectedDict.id);
|
||||
actionRef.current?.reload();
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
|
|
@ -433,12 +353,37 @@ const DictPage: React.FC = () => {
|
|||
下载模板
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
dataSource={dictItems}
|
||||
<ProTable
|
||||
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"
|
||||
loading={loadingDictItems}
|
||||
search={{
|
||||
layout: 'vertical',
|
||||
}}
|
||||
pagination={false}
|
||||
options={false}
|
||||
size="small"
|
||||
key={selectedDict?.id}
|
||||
/>
|
||||
</Space>
|
||||
</Content>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
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 {
|
||||
Button,
|
||||
|
|
@ -12,7 +16,7 @@ import {
|
|||
Upload,
|
||||
message,
|
||||
} from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
|
|
@ -25,9 +29,8 @@ const AttributePage: React.FC = () => {
|
|||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedDict, setSelectedDict] = useState<any>(null);
|
||||
|
||||
// 中文注释:右侧字典项状态
|
||||
const [dictItems, setDictItems] = useState<any[]>([]);
|
||||
const [loadingDictItems, setLoadingDictItems] = useState(false);
|
||||
// 中文注释:右侧字典项 ProTable 的引用
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
// 中文注释:字典项新增/编辑模态框控制
|
||||
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
|
||||
|
|
@ -48,29 +51,11 @@ const AttributePage: React.FC = () => {
|
|||
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(() => {
|
||||
fetchDicts();
|
||||
fetchDictItems();
|
||||
}, []);
|
||||
|
||||
// 中文注释:当选择的字典发生变化时刷新右侧字典项
|
||||
useEffect(() => {
|
||||
fetchDictItems(selectedDict?.id);
|
||||
}, [selectedDict]);
|
||||
|
||||
// 中文注释:搜索触发过滤
|
||||
const handleSearch = (value: string) => {
|
||||
fetchDicts(value);
|
||||
|
|
@ -109,7 +94,7 @@ const AttributePage: React.FC = () => {
|
|||
message.success('添加成功');
|
||||
}
|
||||
setIsDictItemModalVisible(false);
|
||||
fetchDictItems(selectedDict.id);
|
||||
actionRef.current?.reload(); // 中文注释:刷新 ProTable
|
||||
} catch (error) {
|
||||
message.error(editingDictItem ? '更新失败' : '添加失败');
|
||||
}
|
||||
|
|
@ -120,7 +105,7 @@ const AttributePage: React.FC = () => {
|
|||
try {
|
||||
await request(`/dict/item/${itemId}`, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchDictItems(selectedDict.id);
|
||||
actionRef.current?.reload(); // 中文注释:刷新 ProTable
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
|
|
@ -133,13 +118,14 @@ const AttributePage: React.FC = () => {
|
|||
];
|
||||
|
||||
// 中文注释:右侧字典项列表列定义(紧凑样式)
|
||||
const dictItemColumns = [
|
||||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||||
{ title: '标题', dataIndex: 'title', key: 'title' },
|
||||
{ title: '中文标题', dataIndex: 'titleCN', key: 'titleCN' },
|
||||
const dictItemColumns: any[] = [
|
||||
{ title: '名称', dataIndex: 'name', key: 'name', copyable: true },
|
||||
{ title: '标题', dataIndex: 'title', key: 'title', copyable: true },
|
||||
{ title: '中文标题', dataIndex: 'titleCN', key: 'titleCN', copyable: true },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
valueType: 'option',
|
||||
render: (_: any, record: any) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
|
|
@ -206,56 +192,75 @@ const AttributePage: React.FC = () => {
|
|||
</Space>
|
||||
</Sider>
|
||||
<Content style={{ padding: '8px' }}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="small">
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={handleAddDictItem}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
添加字典项
|
||||
</Button>
|
||||
<Upload
|
||||
name="file"
|
||||
action={`/dict/item/import`}
|
||||
data={{ dictId: selectedDict?.id }}
|
||||
showUploadList={false}
|
||||
disabled={!selectedDict}
|
||||
onChange={(info) => {
|
||||
// 中文注释:条件判断,上传状态处理
|
||||
if (info.file.status === 'done') {
|
||||
message.success(`${info.file.name} 文件上传成功`);
|
||||
fetchDictItems(selectedDict.id);
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error(`${info.file.name} 文件上传失败`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ProTable
|
||||
columns={dictItemColumns}
|
||||
actionRef={actionRef}
|
||||
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"
|
||||
search={{
|
||||
layout: 'vertical',
|
||||
}}
|
||||
pagination={false}
|
||||
options={false}
|
||||
size="small"
|
||||
key={selectedDict?.id}
|
||||
headerTitle={
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<UploadOutlined />}
|
||||
onClick={handleAddDictItem}
|
||||
disabled={!selectedDict}
|
||||
>
|
||||
导入字典项
|
||||
添加字典项
|
||||
</Button>
|
||||
</Upload>
|
||||
</div>
|
||||
<Table
|
||||
dataSource={dictItems}
|
||||
columns={dictItemColumns}
|
||||
rowKey="id"
|
||||
loading={loadingDictItems}
|
||||
size="small"
|
||||
/>
|
||||
</Space>
|
||||
<Upload
|
||||
name="file"
|
||||
action={`/dict/item/import`}
|
||||
data={{ dictId: selectedDict?.id }}
|
||||
showUploadList={false}
|
||||
disabled={!selectedDict}
|
||||
onChange={(info) => {
|
||||
// 中文注释:条件判断,上传状态处理
|
||||
if (info.file.status === 'done') {
|
||||
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>
|
||||
</Layout>
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ const List: React.FC = () => {
|
|||
title: '操作',
|
||||
dataIndex: 'option',
|
||||
valueType: 'option',
|
||||
fixed: true,
|
||||
render: (_, record) => (
|
||||
<>
|
||||
<EditForm record={record} tableRef={actionRef} />
|
||||
|
|
|
|||
|
|
@ -31,15 +31,7 @@ import { useRef } from 'react';
|
|||
const List: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const columns: ProColumns<API.WpProductDTO>[] = [
|
||||
{
|
||||
title: 'sku',
|
||||
dataIndex: 'sku',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
{
|
||||
title: '站点',
|
||||
dataIndex: 'siteId',
|
||||
valueType: 'select',
|
||||
|
|
@ -54,6 +46,15 @@ const List: React.FC = () => {
|
|||
return record.site?.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'sku',
|
||||
dataIndex: 'sku',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
title: '产品状态',
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import {
|
|||
ProFormSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
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 { request } from '@umijs/max';
|
||||
|
||||
// 定义配置接口
|
||||
interface TagConfig {
|
||||
|
|
@ -163,31 +164,6 @@ const computeTags = (name: string, sku: string, config: TagConfig): string => {
|
|||
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
|
||||
*/
|
||||
|
|
@ -198,6 +174,40 @@ const WpToolPage: React.FC = () => {
|
|||
const [csvData, setCsvData] = useState<any[]>([]); // 解析后的 CSV 数据
|
||||
const [processedData, setProcessedData] = useState<any[]>([]); // 处理后待下载的数据
|
||||
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 处理文件上传
|
||||
|
|
@ -341,7 +351,7 @@ const WpToolPage: React.FC = () => {
|
|||
<Card title="1. 配置映射规则">
|
||||
<ProForm
|
||||
form={form}
|
||||
initialValues={DEFAULT_CONFIG}
|
||||
initialValues={config}
|
||||
onFinish={handleProcessData}
|
||||
submitter={false}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
// 站点数据项类型(前端不包含密钥字段,后端列表不返回密钥)
|
||||
interface SiteItem {
|
||||
id: number;
|
||||
siteName: string;
|
||||
name: string;
|
||||
apiUrl?: string;
|
||||
type?: 'woocommerce' | 'shopyy';
|
||||
skuPrefix?: string;
|
||||
|
|
@ -24,7 +24,7 @@ interface SiteItem {
|
|||
|
||||
// 创建/更新表单的值类型,包含可选的密钥字段
|
||||
interface SiteFormValues {
|
||||
siteName: string;
|
||||
name: string;
|
||||
apiUrl?: string;
|
||||
type?: 'woocommerce' | 'shopyy';
|
||||
isDisabled?: boolean;
|
||||
|
|
@ -43,7 +43,7 @@ const SiteList: React.FC = () => {
|
|||
if (!open) return;
|
||||
if (editing) {
|
||||
formRef.current?.setFieldsValue({
|
||||
siteName: editing.siteName,
|
||||
name: editing.name,
|
||||
apiUrl: editing.apiUrl,
|
||||
type: editing.type,
|
||||
skuPrefix: editing.skuPrefix,
|
||||
|
|
@ -53,7 +53,7 @@ const SiteList: React.FC = () => {
|
|||
});
|
||||
} else {
|
||||
formRef.current?.setFieldsValue({
|
||||
siteName: undefined,
|
||||
name: undefined,
|
||||
apiUrl: undefined,
|
||||
type: 'woocommerce',
|
||||
skuPrefix: undefined,
|
||||
|
|
@ -148,13 +148,13 @@ const SiteList: React.FC = () => {
|
|||
// 表格数据请求
|
||||
const tableRequest = async (params: Record<string, any>) => {
|
||||
try {
|
||||
const { current = 1, pageSize = 10, siteName, type } = params;
|
||||
const { current = 1, pageSize = 10, name, type } = params;
|
||||
const resp = await request('/site/list', {
|
||||
method: 'GET',
|
||||
params: {
|
||||
current,
|
||||
pageSize,
|
||||
keyword: siteName || undefined,
|
||||
keyword: name || undefined,
|
||||
type: type || undefined,
|
||||
},
|
||||
});
|
||||
|
|
@ -177,7 +177,7 @@ const SiteList: React.FC = () => {
|
|||
if (editing) {
|
||||
const payload: Record<string, any> = {
|
||||
// 仅提交存在的字段,避免覆盖为 null/空
|
||||
...(values.siteName ? { siteName: values.siteName } : {}),
|
||||
...(values.name ? { name: values.name } : {}),
|
||||
...(values.apiUrl ? { apiUrl: values.apiUrl } : {}),
|
||||
...(values.type ? { type: values.type } : {}),
|
||||
...(typeof values.isDisabled === 'boolean'
|
||||
|
|
@ -204,7 +204,7 @@ const SiteList: React.FC = () => {
|
|||
await request('/site/create', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
siteName: values.siteName,
|
||||
name: values.name,
|
||||
apiUrl: values.apiUrl,
|
||||
type: values.type || 'woocommerce',
|
||||
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