mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat: enhance MultipleKeywordsInput with Excel import functionality and add tips for column selection
This commit is contained in:
parent
b870cc400f
commit
56bc058389
@ -11,7 +11,9 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"Multiple keywords"
|
"Multiple keywords"
|
||||||
],
|
],
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"xlsx": "0.18.5"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@nocobase/client": "1.x",
|
"@nocobase/client": "1.x",
|
||||||
"@nocobase/server": "1.x",
|
"@nocobase/server": "1.x",
|
||||||
|
@ -8,31 +8,194 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RemoteSelect, useCollection } from '@nocobase/client';
|
import { RemoteSelect, useCollection } from '@nocobase/client';
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useRef, useState } from 'react';
|
||||||
import { Button, Space } from 'antd';
|
import { Button, Space, Modal, message, Select, Alert } from 'antd';
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { useT } from './locale';
|
||||||
|
|
||||||
export const MultipleKeywordsInput: FC<any> = (props) => {
|
export const MultipleKeywordsInput: FC<any> = (props) => {
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [importLoading, setImportLoading] = useState(false);
|
||||||
|
const [columnModal, setColumnModal] = useState(false);
|
||||||
|
const [columns, setColumns] = useState<string[]>([]);
|
||||||
|
const [selectedColumns, setSelectedColumns] = useState<string[]>([]);
|
||||||
|
const [excelData, setExcelData] = useState<any[]>([]);
|
||||||
|
const t = useT();
|
||||||
|
|
||||||
|
const handleImportButtonClick = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
try {
|
||||||
|
setImportLoading(true);
|
||||||
|
|
||||||
|
// 读取 Excel 文件
|
||||||
|
const data = await readExcel(file);
|
||||||
|
if (data.length === 0) {
|
||||||
|
message.error('Excel 文件为空');
|
||||||
|
setImportLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有列名
|
||||||
|
const extractedColumns = Object.keys(data[0]);
|
||||||
|
setColumns(extractedColumns);
|
||||||
|
setExcelData(data);
|
||||||
|
|
||||||
|
// 如果只有一列,直接导入
|
||||||
|
if (extractedColumns.length === 1) {
|
||||||
|
const keywords = extractKeywordsFromColumn(data, extractedColumns[0]);
|
||||||
|
handleImportKeywords(keywords);
|
||||||
|
} else {
|
||||||
|
// 如果有多列,打开选择对话框
|
||||||
|
setColumnModal(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析 Excel 文件出错:', error);
|
||||||
|
message.error('解析 Excel 文件失败');
|
||||||
|
} finally {
|
||||||
|
setImportLoading(false);
|
||||||
|
// 清空文件输入,以便下次选择同一文件时仍能触发 change 事件
|
||||||
|
event.target.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 读取 Excel 文件内容
|
||||||
|
const readExcel = (file: File): Promise<any[]> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
const data = e.target?.result;
|
||||||
|
const workbook = XLSX.read(data, { type: 'binary' });
|
||||||
|
const firstSheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[firstSheetName];
|
||||||
|
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||||
|
resolve(jsonData);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从指定列提取关键词
|
||||||
|
const extractKeywordsFromColumn = (data: any[], columnName: string): string => {
|
||||||
|
return data
|
||||||
|
.map((row) => row[columnName])
|
||||||
|
.filter((value) => value !== undefined && value !== null && value !== '')
|
||||||
|
.join(',');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从多个列提取关键词
|
||||||
|
const extractKeywordsFromColumns = (data: any[], columnNames: string[]): string => {
|
||||||
|
const keywordSet = new Set<string>();
|
||||||
|
|
||||||
|
data.forEach((row) => {
|
||||||
|
columnNames.forEach((column) => {
|
||||||
|
const value = row[column];
|
||||||
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
keywordSet.add(value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(keywordSet).join(',');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理导入关键词到输入框
|
||||||
|
const handleImportKeywords = (keywords: string) => {
|
||||||
|
if (!keywords) {
|
||||||
|
message.warning('未找到有效关键词');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将关键词设置到输入框
|
||||||
|
if (props.onChange) {
|
||||||
|
const keywordArray = keywords.split(',').filter(Boolean);
|
||||||
|
props.onChange(keywordArray);
|
||||||
|
message.success(`成功导入 ${keywordArray.length} 个关键词`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理列选择确认
|
||||||
|
const handleColumnSelectConfirm = () => {
|
||||||
|
if (selectedColumns.length === 0) {
|
||||||
|
message.warning('请至少选择一列');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keywords = extractKeywordsFromColumns(excelData, selectedColumns);
|
||||||
|
handleImportKeywords(keywords);
|
||||||
|
setColumnModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space.Compact block>
|
<>
|
||||||
<RemoteSelect
|
<Space.Compact block>
|
||||||
mode="tags"
|
<RemoteSelect
|
||||||
placeholder="支持输入多个关键词,通过逗号或者换行符分割"
|
mode="tags"
|
||||||
tokenSeparators={[',', '\n', ',']}
|
placeholder="支持输入多个关键词,通过逗号或者换行符分割"
|
||||||
fieldNames={{
|
tokenSeparators={[',', '\n', ',']}
|
||||||
label: fieldSchema.name as string,
|
fieldNames={{
|
||||||
value: fieldSchema.name as string,
|
label: fieldSchema.name as string,
|
||||||
}}
|
value: fieldSchema.name as string,
|
||||||
service={{
|
}}
|
||||||
resource: collection.name,
|
service={{
|
||||||
action: 'list',
|
resource: collection.name,
|
||||||
}}
|
action: 'list',
|
||||||
{...props}
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<Button onClick={handleImportButtonClick} loading={importLoading}>
|
||||||
|
导入 Excel
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
<Button>导入 Excel</Button>
|
|
||||||
</Space.Compact>
|
{/* 列选择对话框 */}
|
||||||
|
<Modal
|
||||||
|
title="选择要导入的 Excel 列"
|
||||||
|
open={columnModal}
|
||||||
|
onOk={handleColumnSelectConfirm}
|
||||||
|
onCancel={() => setColumnModal(false)}
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
type="info"
|
||||||
|
style={{ marginBottom: '10px', whiteSpace: 'pre-line', padding: '4px 8px' }}
|
||||||
|
description={t('tips')}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
value={selectedColumns}
|
||||||
|
onChange={(values) => setSelectedColumns(values)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="请选择要导入的列"
|
||||||
|
>
|
||||||
|
{columns.map((column) => (
|
||||||
|
<Select.Option key={column} value={column}>
|
||||||
|
{column}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1 +1,3 @@
|
|||||||
{}
|
{
|
||||||
|
"tips": "选择单列的效果:将导入该列的所有非空值作为关键词\n选择多列的效果:将合并多个列的非空值作为关键词,重复值将被去除"
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user