WEB/src/pages/Product/Attribute/index.tsx

434 lines
12 KiB
TypeScript

import * as dictApi from '@/servers/api/dict';
import {
ActionType,
PageContainer,
ProColumns,
ProTable,
} from '@ant-design/pro-components';
import { request } from '@umijs/max';
import { Button, Input, Layout, Space, Table, message } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
import DictItemActions from '../../Dict/components/DictItemActions';
import DictItemModal from '../../Dict/components/DictItemModal';
const { Sider, Content } = Layout;
import { notAttributes } from './consts';
const AttributePage: React.FC = () => {
// 左侧字典列表状态
const [dicts, setDicts] = useState<any[]>([]);
const [loadingDicts, setLoadingDicts] = useState(false);
const [searchText, setSearchText] = useState('');
const [selectedDict, setSelectedDict] = useState<any>(null);
// 右侧字典项 ProTable 的引用
const actionRef = useRef<ActionType>();
// 字典项模态框状态(由 DictItemModal 组件管理)
const [isDictItemModalVisible, setIsDictItemModalVisible] = useState(false);
const [isEditDictItem, setIsEditDictItem] = useState(false);
const [editingDictItemData, setEditingDictItemData] = useState<any>(null);
// 导出字典项数据
const handleExportDictItems = async () => {
// 条件判断,确保已选择字典
if (!selectedDict) {
message.warning('请先选择字典');
return;
}
try {
// 获取当前字典的所有数据
const response = await request('/dict/items', {
params: {
dictId: selectedDict.id,
},
});
// 确保返回的是数组
const data = Array.isArray(response) ? response : response?.data || [];
// 条件判断,检查是否有数据可导出
if (data.length === 0) {
message.warning('当前字典没有数据可导出');
return;
}
// 将数据转换为CSV格式
const headers = [
'name',
'title',
'titleCN',
'value',
'sort',
'image',
'shortName',
];
const csvContent = [
headers.join(','),
...data.map((item: any) =>
headers
.map((header) => {
const value = item[header] || '';
// 条件判断,如果值包含逗号或引号,需要转义
if (
typeof value === 'string' &&
(value.includes(',') || value.includes('"'))
) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
})
.join(','),
),
].join('\n');
// 创建blob并下载
const blob = new Blob(['\ufeff' + csvContent], {
// 添加BOM以支持中文
type: 'text/csv;charset=utf-8',
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${selectedDict.name}_dict_items.csv`);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
message.success(`成功导出 ${data.length} 条数据`);
} catch (error: any) {
message.error('导出字典项失败:' + (error.message || '未知错误'));
}
};
const fetchDicts = async (title?: string) => {
setLoadingDicts(true);
try {
const res = await request('/dict/list', { params: { title } });
// 条件判断,确保res是数组再进行过滤
const dataList = Array.isArray(res) ? res : res?.data || [];
const filtered = dataList.filter((d: any) => !notAttributes.has(d?.name));
setDicts(filtered);
} catch (error) {
console.error('获取字典列表失败:', error);
message.error('获取字典列表失败');
setDicts([]);
}
setLoadingDicts(false);
};
// 组件挂载时初始化数据
useEffect(() => {
fetchDicts();
}, []);
// 搜索触发过滤
const handleSearch = (value: string) => {
fetchDicts(value);
};
// 打开添加字典项模态框
const handleAddDictItem = () => {
setIsEditDictItem(false);
setEditingDictItemData(null);
setIsDictItemModalVisible(true);
};
// 打开编辑字典项模态框
const handleEditDictItem = (item: any) => {
setIsEditDictItem(true);
setEditingDictItemData(item);
setIsDictItemModalVisible(true);
};
// 字典项表单提交(新增或编辑)
const handleDictItemFormSubmit = async (values: any) => {
try {
if (isEditDictItem && editingDictItemData) {
// 条件判断,存在编辑项则执行更新
await request(`/dict/item/${editingDictItemData.id}`, {
method: 'PUT',
data: values,
});
message.success('更新成功');
} else {
// 否则执行新增,绑定到当前选择的字典
await request('/dict/item', {
method: 'POST',
data: { ...values, dictId: selectedDict.id },
});
message.success('添加成功');
}
setIsDictItemModalVisible(false);
actionRef.current?.reload(); // 刷新 ProTable
} catch (error) {
message.error(isEditDictItem ? '更新失败' : '添加失败');
}
};
// 删除字典项
const handleDeleteDictItem = async (itemId: number) => {
try {
const res = await request(`/dict/item/${itemId}`, { method: 'DELETE' });
const isOk =
typeof res === 'boolean'
? res
: res && res.code === 0
? res.data === true || res.data === null
: false;
if (!isOk) {
message.error('删除失败');
return;
}
if (selectedDict?.id) {
try {
const list = await request('/dict/items', {
params: {
dictId: selectedDict.id,
},
});
// 确保list是数组再进行some操作
const dataList = Array.isArray(list) ? list : list?.data || [];
const exists = dataList.some((it: any) => it.id === itemId);
if (exists) {
message.error('删除失败');
} else {
message.success('删除成功');
actionRef.current?.reload();
}
} catch (error) {
console.error('验证删除结果失败:', error);
message.success('删除成功');
actionRef.current?.reload();
}
} else {
message.success('删除成功');
actionRef.current?.reload();
}
} catch (error) {
message.error('删除失败');
}
};
// 左侧字典列表列定义(紧凑样式)
const dictColumns = [
{ title: '名称', dataIndex: 'name', key: 'name' },
{ title: '标题', dataIndex: 'title', key: 'title' },
];
// 右侧字典项列表列定义(紧凑样式)
const dictItemColumns: ProColumns<any>[] = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
copyable: true,
sorter: true,
},
{
title: '标题',
dataIndex: 'title',
key: 'title',
copyable: true,
sorter: true,
},
{
title: '中文标题',
dataIndex: 'titleCN',
key: 'titleCN',
copyable: true,
sorter: true,
},
{
title: '简称',
dataIndex: 'shortName',
key: 'shortName',
copyable: true,
sorter: true,
},
{
title: '图片',
dataIndex: 'image',
key: 'image',
valueType: 'image',
width: 80,
},
{
title: '操作',
key: 'action',
valueType: 'option',
render: (_: any, record: any) => (
<Space size="small">
<Button
size="small"
type="link"
onClick={() => handleEditDictItem(record)}
>
</Button>
<Button
size="small"
type="link"
danger
onClick={() => handleDeleteDictItem(record.id)}
>
</Button>
</Space>
),
},
];
return (
<PageContainer>
<Layout style={{ background: '#fff' }}>
<Sider
width={240}
style={{
background: '#fff',
padding: '8px',
borderRight: '1px solid #f0f0f0',
}}
>
<Space direction="vertical" style={{ width: '100%' }} size="small">
<Input.Search
placeholder="搜索字典"
onSearch={handleSearch}
onChange={(e) => setSearchText(e.target.value)}
enterButton
allowClear
size="small"
/>
</Space>
<div
style={{
marginTop: '8px',
overflowY: 'auto',
height: 'calc(100vh - 150px)',
}}
>
<Table
dataSource={dicts}
columns={dictColumns}
rowKey="id"
loading={loadingDicts}
size="small"
onRow={(record) => ({
onClick: () => {
// 条件判断,重复点击同一行则取消选择
if (selectedDict?.id === record.id) {
setSelectedDict(null);
} else {
setSelectedDict(record);
}
},
})}
rowClassName={(record) =>
selectedDict?.id === record.id ? 'ant-table-row-selected' : ''
}
pagination={false}
/>
</div>
</Sider>
<Content style={{ padding: '8px' }}>
<ProTable
columns={dictItemColumns}
actionRef={actionRef}
request={async (params) => {
// 当没有选择字典时,不发起请求
if (!selectedDict?.id) {
return {
data: [],
success: true,
};
}
const { name, title } = params;
try {
const res = await request('/dict/items', {
params: {
dictId: selectedDict.id,
name,
title,
},
});
// 确保返回的是数组
const data = Array.isArray(res) ? res : res?.data || [];
return {
data: data,
success: true,
};
} catch (error) {
console.error('获取字典项失败:', error);
return {
data: [],
success: false,
};
}
}}
rowKey="id"
search={{
layout: 'vertical',
}}
pagination={false}
options={{
reload: true,
density: false,
setting: {
draggable: true,
checkable: true,
checkedReset: false,
},
search: false,
fullScreen: false,
}}
size="small"
key={selectedDict?.id}
headerTitle={
<DictItemActions
selectedDict={selectedDict}
actionRef={actionRef}
showExport={true}
onImport={async (file: File, dictId: number) => {
// 创建 FormData 对象
const formData = new FormData();
// 添加文件到 FormData
formData.append('file', file);
// 添加字典 ID 到 FormData
formData.append('dictId', String(dictId));
// 调用导入字典项的 API
const response = await dictApi.dictcontrollerImportdictitems(
formData,
);
// 返回 JSON 响应
return await response.json();
}}
onExport={handleExportDictItems}
onAdd={handleAddDictItem}
onRefreshDicts={fetchDicts}
/>
}
/>
</Content>
</Layout>
{/* 字典项 Modal(添加或编辑) */}
<DictItemModal
visible={isDictItemModalVisible}
isEdit={isEditDictItem}
editingData={editingDictItemData}
selectedDict={selectedDict}
onCancel={() => {
setIsDictItemModalVisible(false);
setEditingDictItemData(null);
}}
onOk={handleDictItemFormSubmit}
/>
</PageContainer>
);
};
export default AttributePage;