提交 b8930cc2 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 2b798ac2
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect } from 'react'
import { Outlet, NavLink, useLocation } from 'react-router' import { Outlet, NavLink, useLocation } from 'react-router'
import { import {
CircleArrowRight, CircleArrowRight,
...@@ -25,6 +25,109 @@ type MyMenuItem = { ...@@ -25,6 +25,109 @@ type MyMenuItem = {
type MenuItem = Required<MenuProps>['items'][number] type MenuItem = Required<MenuProps>['items'][number]
const menus: MyMenuItem[] = [
{
icon: <Database />,
name: '数据采集',
path: '/data/write',
children: [
{ name: '我的数据集', path: '/data/write/my' },
{ name: '数据复制', path: '/data/write/copy' },
{ name: '数据导入', path: '/data/write/upload' },
{ name: '数据爬虫', path: '/data/write/crawler', disabled: true },
{ name: 'API数据采集', path: '/data/write/api', disabled: true },
{ name: '数据库对接', path: '/data/write/db', disabled: true },
{ name: '内置数据集管理', path: '/data/write/built' },
],
},
{
icon: <SearchCheck />,
name: '数据理解与探索',
path: '/data/read',
children: [
{ name: '数据理解', path: '/data/read/understanding' },
{ name: '数据探索', path: '/data/read/exploration' },
],
},
{
icon: <Filter />,
name: '数据预处理',
path: '/data/preprocess',
children: [
{ name: '缺失值处理', path: '/data/preprocess/null' },
{ name: '重复值处理', path: '/data/preprocess/repeat' },
{ name: '过大值处理', path: '/data/preprocess/max' },
{ name: '过小值处理', path: '/data/preprocess/min' },
{ name: '逻辑错误值处理', path: '/data/preprocess/error' },
{ name: '数据拆分', path: '/data/preprocess/split' },
{ name: '数据去空格', path: '/data/preprocess/space' },
{ name: '数据去标点', path: '/data/preprocess/symbol' },
{ name: '数据类型转换', path: '/data/preprocess/type' },
{ name: '数据排序', path: '/data/preprocess/sort' },
{ name: '数据拼接', path: '/data/preprocess/splice' },
],
},
{
icon: <Bolt />,
name: '数据加工',
path: '/data/process',
children: [
{ name: '值映射', path: '/data/process/mapping' },
{ name: '数据分箱', path: '/data/process/binning' },
{ name: '数据分组', path: '/data/process/group' },
{ name: '数据脱敏', path: '/data/process/desensitization' },
{ name: '日期计算', path: '/data/process/date' },
{ name: '文本计算', path: '/data/process/string' },
{ name: '数值计算', path: '/data/process/number' },
{ name: '逻辑计算', path: '/data/process/logic' },
{ name: '数据透视', path: '/data/process/perspective' },
],
},
{
icon: <Cone />,
name: '数据挖掘',
path: '/data/digging',
children: [
{ name: '线性回归', path: '/data/digging/linear' },
{ name: '逻辑回归', path: '/data/digging/logistic' },
{ name: '决策树', path: '/data/digging/tree' },
{ name: '随机森林', path: '/data/digging/forest' },
{ name: '支持向量机', path: '/data/digging/svm' },
{ name: 'K-Means', path: '/data/digging/kmeans' },
{ name: '层次聚类', path: '/data/digging/hierarchical' },
{ name: 'Apriori', path: '/data/digging/apriori' },
{ name: 'FP-Growth', path: '/data/digging/fpgrowth' },
{ name: 'Holt-Winters', path: '/data/digging/holtwinters' },
],
},
{
icon: <ChartArea />,
name: '数据可视化组件',
path: '/data/chart',
children: [
{ name: '柱状图', path: '/data/chart/bar' },
{ name: '折线图', path: '/data/chart/line' },
{ name: '饼状图', path: '/data/chart/pie' },
{ name: '雷达图', path: '/data/chart/radar' },
{ name: '散点图', path: '/data/chart/point' },
{ name: '气泡图', path: '/data/chart/bubble' },
{ name: '词云', path: '/data/chart/wordCloud' },
{ name: '地图', path: '/data/chart/map' },
{ name: '指标卡', path: '/data/chart/indicator' },
{ name: '漏斗图', path: '/data/chart/funnel' },
{ name: '直方图', path: '/data/chart/histogram' },
{ name: '表格', path: '/data/chart/table' },
{ name: '帕累托图', path: '/data/chart/pareto' },
{ name: '矩形树图', path: '/data/chart/treeMap' },
],
},
{
icon: <Wallpaper />,
name: '数据可视化大屏',
path: '/data/screen',
},
]
export default function DataLayout() { export default function DataLayout() {
const [collapsed, setCollapsed] = useState(false) const [collapsed, setCollapsed] = useState(false)
const [openKeys, setOpenKeys] = useState<string[]>([]) const [openKeys, setOpenKeys] = useState<string[]>([])
...@@ -36,111 +139,6 @@ export default function DataLayout() { ...@@ -36,111 +139,6 @@ export default function DataLayout() {
const location = useLocation() const location = useLocation()
const selectedKeys = [location.pathname] const selectedKeys = [location.pathname]
const menus: MyMenuItem[] = useMemo(() => {
return [
{
icon: <Database />,
name: '数据采集',
path: '/data/write',
children: [
{ name: '我的数据集', path: '/data/write/my' },
{ name: '数据复制', path: '/data/write/copy' },
{ name: '数据导入', path: '/data/write/upload' },
{ name: '数据爬虫', path: '/data/write/crawler', disabled: true },
{ name: 'API数据采集', path: '/data/write/api', disabled: true },
{ name: '数据库对接', path: '/data/write/db', disabled: true },
{ name: '内置数据集管理', path: '/data/write/built' },
],
},
{
icon: <SearchCheck />,
name: '数据理解与探索',
path: '/data/read',
children: [
{ name: '数据理解', path: '/data/read/understanding' },
{ name: '数据探索', path: '/data/read/exploration' },
],
},
{
icon: <Filter />,
name: '数据预处理',
path: '/data/preprocess',
children: [
{ name: '缺失值处理', path: '/data/preprocess/null' },
{ name: '重复值处理', path: '/data/preprocess/repeat' },
{ name: '过大值处理', path: '/data/preprocess/max' },
{ name: '过小值处理', path: '/data/preprocess/min' },
{ name: '逻辑错误值处理', path: '/data/preprocess/error' },
{ name: '数据拆分', path: '/data/preprocess/split' },
{ name: '数据去空格', path: '/data/preprocess/space' },
{ name: '数据去标点', path: '/data/preprocess/symbol' },
{ name: '数据类型转换', path: '/data/preprocess/type' },
{ name: '数据排序', path: '/data/preprocess/sort' },
{ name: '数据拼接', path: '/data/preprocess/splice' },
],
},
{
icon: <Bolt />,
name: '数据加工',
path: '/data/process',
children: [
{ name: '值映射', path: '/data/process/mapping' },
{ name: '数据分箱', path: '/data/process/binning' },
{ name: '数据分组', path: '/data/process/group' },
{ name: '数据脱敏', path: '/data/process/desensitization' },
{ name: '日期计算', path: '/data/process/date' },
{ name: '文本计算', path: '/data/process/string' },
{ name: '数值计算', path: '/data/process/number' },
{ name: '逻辑计算', path: '/data/process/logic' },
{ name: '数据透视', path: '/data/process/perspective' },
],
},
{
icon: <Cone />,
name: '数据挖掘',
path: '/data/digging',
children: [
{ name: '线性回归', path: '/data/digging/linear' },
{ name: '逻辑回归', path: '/data/digging/logistic' },
{ name: '决策树', path: '/data/digging/tree' },
{ name: '随机森林', path: '/data/digging/forest' },
{ name: '支持向量机', path: '/data/digging/svm' },
{ name: 'K-Means', path: '/data/digging/kmeans' },
{ name: '层次聚类', path: '/data/digging/hierarchical' },
{ name: 'Apriori', path: '/data/digging/apriori' },
{ name: 'FP-Growth', path: '/data/digging/fpgrowth' },
{ name: 'Holt-Winters', path: '/data/digging/holtwinters' },
],
},
{
icon: <ChartArea />,
name: '数据可视化组件',
path: '/data/chart',
children: [
{ name: '柱状图', path: '/data/chart/bar' },
{ name: '折线图', path: '/data/chart/line' },
{ name: '饼状图', path: '/data/chart/pie' },
{ name: '雷达图', path: '/data/chart/radar' },
{ name: '散点图', path: '/data/chart/point' },
{ name: '气泡图', path: '/data/chart/bubble' },
{ name: '词云', path: '/data/chart/wordCloud' },
{ name: '地图', path: '/data/chart/map' },
{ name: '指标卡', path: '/data/chart/indicator' },
{ name: '漏斗图', path: '/data/chart/funnel' },
{ name: '直方图', path: '/data/chart/histogram' },
{ name: '表格', path: '/data/chart/table' },
{ name: '帕累托图', path: '/data/chart/pareto' },
{ name: '矩形树图', path: '/data/chart/treeMap' },
],
},
{
icon: <Wallpaper />,
name: '数据可视化大屏',
path: '/data/screen',
},
]
}, [])
useEffect(() => { useEffect(() => {
// 根据当前路径计算需要展开的菜单项 // 根据当前路径计算需要展开的菜单项
const calculateOpenKeys = (menus: MyMenuItem[]): string[] => { const calculateOpenKeys = (menus: MyMenuItem[]): string[] => {
...@@ -158,7 +156,7 @@ export default function DataLayout() { ...@@ -158,7 +156,7 @@ export default function DataLayout() {
return [] return []
} }
setOpenKeys(calculateOpenKeys(menus)) setOpenKeys(calculateOpenKeys(menus))
}, [location.pathname, menus]) }, [location.pathname])
const menusToMenuItems = (menus: MyMenuItem[]): MenuItem[] => { const menusToMenuItems = (menus: MyMenuItem[]): MenuItem[] => {
return menus.map((item) => { return menus.map((item) => {
...@@ -180,6 +178,14 @@ export default function DataLayout() { ...@@ -180,6 +178,14 @@ export default function DataLayout() {
const items = menusToMenuItems(menus) const items = menusToMenuItems(menus)
const onOpenChange = (keys: string[]) => {
if (keys.length > 1) {
setOpenKeys([keys[keys.length - 1]])
} else {
setOpenKeys(keys)
}
}
return ( return (
<div className={`data-layout ${collapsed ? 'collapsed' : ''}`}> <div className={`data-layout ${collapsed ? 'collapsed' : ''}`}>
<div className="data-layout-sidebar"> <div className="data-layout-sidebar">
...@@ -194,7 +200,7 @@ export default function DataLayout() { ...@@ -194,7 +200,7 @@ export default function DataLayout() {
inlineCollapsed={collapsed} inlineCollapsed={collapsed}
selectedKeys={selectedKeys} selectedKeys={selectedKeys}
openKeys={openKeys} openKeys={openKeys}
onOpenChange={setOpenKeys} onOpenChange={onOpenChange}
/> />
</div> </div>
</div> </div>
......
...@@ -46,27 +46,45 @@ export function useDataQuery() { ...@@ -46,27 +46,45 @@ export function useDataQuery() {
return { data: { total: 0, list: [], title: [] } } return { data: { total: 0, list: [], title: [] } }
}, },
}) })
const file = query.data.info?.source || {}
useExcelQuery(file.url)
return query
}
export function useExcelQuery(url: string) {
const query = useQuery({
queryKey: ['excel', url],
queryFn: () => {
return axios(url, { responseType: 'arraybuffer' })
},
select: (res) => res.data,
enabled: !!url,
})
useEffect(() => { useEffect(() => {
if (query.data?.info) { if (query.data) {
const file = query.data.info.source const workbook = read(query.data)
axios(file.url, { responseType: 'arraybuffer' }).then((res) => { const sheetName = workbook.SheetNames[0]
const workbook = read(res.data) const worksheet = workbook.Sheets[sheetName]
const sheetName = workbook.SheetNames[0] const jsonData = utils.sheet_to_json(worksheet, { defval: '' })
const worksheet = workbook.Sheets[sheetName] localStorage.setItem('dataset', JSON.stringify(jsonData))
const jsonData = utils.sheet_to_json(worksheet, { defval: '' })
localStorage.setItem('dataset', JSON.stringify(jsonData))
})
} }
}, [query.data]) }, [query.data])
}
return query interface DataField {
name: string
english_name: string
type: string
} }
export function useDataFieldQuery() { export function useDataFieldQuery() {
const query = useQuery({ const query = useQuery({
queryKey: ['dataFiled'], queryKey: ['dataFiled'],
queryFn: getMyField, queryFn: getMyField,
select: (res) => res.data, select: (res): DataField[] => res.data,
placeholderData: (): any => { placeholderData: (): any => {
return { data: [] } return { data: [] }
}, },
......
...@@ -27,7 +27,7 @@ export default function ButtonModal() { ...@@ -27,7 +27,7 @@ export default function ButtonModal() {
</Form.Item> </Form.Item>
<Form.Item label="请选择值映射字段" name="checked"> <Form.Item label="请选择值映射字段" name="checked">
<Radio.Group> <Radio.Group>
<Row gutter={10}> <Row gutter={[10, 10]}>
{data.map((item) => ( {data.map((item) => (
<Col span={8} key={item.english_name}> <Col span={8} key={item.english_name}>
<Radio value={item.name}>{item.name}</Radio> <Radio value={item.name}>{item.name}</Radio>
......
...@@ -8,7 +8,7 @@ export default function ButtonModal() { ...@@ -8,7 +8,7 @@ export default function ButtonModal() {
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const results = searchParams.get('results')?.split(',') || [] const results = searchParams.get('results')?.split(',') || []
const { data } = useDataFieldQuery() const { data: fields = [] } = useDataFieldQuery()
const [open, setOpen] = useState(results.length > 0) const [open, setOpen] = useState(results.length > 0)
const [current, setCurrent] = useState(0) const [current, setCurrent] = useState(0)
...@@ -16,6 +16,7 @@ export default function ButtonModal() { ...@@ -16,6 +16,7 @@ export default function ButtonModal() {
const initialValues = { const initialValues = {
checkedList: results, checkedList: results,
method: 'unified', method: 'unified',
rule: '1',
} }
const checkedList = Form.useWatch('checkedList', form) || [] const checkedList = Form.useWatch('checkedList', form) || []
...@@ -38,7 +39,7 @@ export default function ButtonModal() { ...@@ -38,7 +39,7 @@ export default function ButtonModal() {
{ label: '热卡填充(下)', value: '7' }, { label: '热卡填充(下)', value: '7' },
] ]
const [step, setStep] = useState<number>(null) const [step, setStep] = useState<number>(-1)
const steps = [ const steps = [
{ {
...@@ -46,8 +47,8 @@ export default function ButtonModal() { ...@@ -46,8 +47,8 @@ export default function ButtonModal() {
content: ( content: (
<Form.Item name="checkedList"> <Form.Item name="checkedList">
<Checkbox.Group> <Checkbox.Group>
<Row gutter={10}> <Row gutter={[10, 10]}>
{data.map((item) => ( {fields.map((item) => (
<Col span={6} key={item.english_name}> <Col span={6} key={item.english_name}>
<Checkbox value={item.name}>{item.name}</Checkbox> <Checkbox value={item.name}>{item.name}</Checkbox>
</Col> </Col>
......
...@@ -38,7 +38,7 @@ export default function ButtonModal() { ...@@ -38,7 +38,7 @@ export default function ButtonModal() {
content: ( content: (
<Form.Item name="checkedList"> <Form.Item name="checkedList">
<Checkbox.Group> <Checkbox.Group>
<Row gutter={10}> <Row gutter={[10, 10]}>
{data.map((item) => ( {data.map((item) => (
<Col span={6} key={item.english_name}> <Col span={6} key={item.english_name}>
<Checkbox value={item.name}>{item.name}</Checkbox> <Checkbox value={item.name}>{item.name}</Checkbox>
......
import { useState } from 'react' import { useEffect, useState } from 'react'
import { Button, Flex, Modal, Radio, Input, Form, Row, Col, Table } from 'antd' import { Button, Flex, Modal, Radio, Input, Form, Row, Col, Table } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery' import { useDataQuery, useDataFieldQuery } from '@/hooks/useQuery'
import AppProgressSteps from '@/components/AppProgressSteps' import AppProgressSteps from '@/components/AppProgressSteps'
import { uniqBy } from 'lodash-es'
export default function ButtonModal() { export default function ButtonModal() {
const { data } = useDataFieldQuery() const { data = { list: [] } } = useDataQuery() // 添加默认值防止 data 为 undefined
const { data: fields = [] } = useDataFieldQuery()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0) const [current, setCurrent] = useState(0)
const [form] = Form.useForm() const [form] = Form.useForm()
const [dataSource, setDataSource] = useState<{ key: number; raw_value: string; mapping_value: string }[]>([])
const [step, setStep] = useState(-1)
// 使用 Form.useWatch 监听表单值变化
const checked = Form.useWatch('checked', form) const checked = Form.useWatch('checked', form)
const [step, setStep] = useState<number>(-1) // 当 checked 或 data 变化时更新数据源
useEffect(() => {
if (checked && data.list?.length) {
const uniqList = uniqBy(data.list, checked)
setDataSource(
uniqList.map((item: any, index) => ({
key: index + 1,
raw_value: item[checked] || '',
mapping_value: '',
}))
)
}
}, [checked, data.list])
// 处理映射值变化
const handleMappingValueChange = (value: string, key: number) => {
setDataSource((prevDataSource) =>
prevDataSource.map((item) => (item.key === key ? { ...item, mapping_value: value } : item))
)
}
// 步骤定义
const steps = [ const steps = [
{ {
title: '请选择值映射字段', title: '请选择值映射字段',
content: ( content: (
<> <>
<Form.Item label="值映射字段名称" name="name"> <Form.Item label="值映射字段名称" name="name" rules={[{ required: true, message: '请输入字段名称' }]}>
<Input placeholder="请输入" /> <Input placeholder="请输入" />
</Form.Item> </Form.Item>
<Form.Item label="值映射字段英文名称" name="english_name"> <Form.Item
label="值映射字段英文名称"
name="english_name"
rules={[{ required: true, message: '请输入英文名称' }]}>
<Input placeholder="请输入" /> <Input placeholder="请输入" />
</Form.Item> </Form.Item>
<Form.Item label="请选择值映射字段" name="checked"> <Form.Item label="请选择值映射字段" name="checked" rules={[{ required: true, message: '请选择值映射字段' }]}>
<Radio.Group> <Radio.Group>
<Row gutter={10}> <Row gutter={[10, 10]}>
{data.map((item) => ( {fields.map((item) => (
<Col span={8} key={item.english_name}> <Col span={8} key={item.english_name}>
<Radio value={item.name}>{item.name}</Radio> <Radio value={item.english_name}>{item.name}</Radio>
</Col> </Col>
))} ))}
</Row> </Row>
...@@ -47,8 +75,25 @@ export default function ButtonModal() { ...@@ -47,8 +75,25 @@ export default function ButtonModal() {
<Form.Item> <Form.Item>
<Table <Table
bordered bordered
columns={[{ title: '序号' }, { title: '原始值' }, { title: '映射值' }]} pagination={{ pageSize: 10 }}
dataSource={[]}></Table> columns={[
{ title: '序号', dataIndex: 'key', align: 'center', width: 80 },
{ title: '原始值', dataIndex: 'raw_value', align: 'center' },
{
title: '映射值',
dataIndex: 'mapping_value',
align: 'center',
render: (_, record) => (
<Input
value={record.mapping_value}
onChange={(e) => handleMappingValueChange(e.target.value, record.key)}
placeholder="请输入映射值"
/>
),
},
]}
dataSource={dataSource}
/>
</Form.Item> </Form.Item>
</> </>
), ),
...@@ -102,9 +147,9 @@ export default function ButtonModal() { ...@@ -102,9 +147,9 @@ export default function ButtonModal() {
), ),
description: ( description: (
<> <>
累计处理XX个字段 累计处理{dataSource.length}个字段
<br /> <br />
累计处理XX条记录 累计处理{data.list?.length || 0}条记录
</> </>
), ),
}, },
...@@ -116,25 +161,48 @@ export default function ButtonModal() { ...@@ -116,25 +161,48 @@ export default function ButtonModal() {
}, },
] ]
// 处理下一步按钮逻辑
const handleNext = async () => {
try {
if (current === 0) {
// 第一步验证表单
await form.validateFields(['name', 'english_name', 'checked'])
}
setCurrent(current + 1)
} catch (error) {
// 表单验证错误处理
console.error('表单验证失败', error)
}
}
// 重置状态
const handleClose = () => {
setOpen(false)
setCurrent(0)
setStep(-1)
form.resetFields()
setDataSource([])
}
return ( return (
<> <>
<Button type="primary" onClick={() => setOpen(true)}> <Button type="primary" onClick={() => setOpen(true)}>
值映射 值映射
</Button> </Button>
<Modal <Modal
title={steps[current].title} title={steps[current]?.title}
open={open} open={open}
footer={ footer={
<Flex justify="center" gap={20}> <Flex justify="center" gap={20}>
{current === 1 && <Button type="primary">AI智能建议</Button>} {current === 1 && <Button type="primary">AI智能建议</Button>}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>} {current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current < steps.length - 1 && ( {current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checked?.length}> <Button type="primary" onClick={handleNext}>
下一步 下一步
</Button> </Button>
)} )}
{current === steps.length - 1 && ( {current === steps.length - 1 && (
<Button type="primary" onClick={() => setOpen(false)}> <Button type="primary" onClick={handleClose}>
关闭 关闭
</Button> </Button>
)} )}
...@@ -142,11 +210,11 @@ export default function ButtonModal() { ...@@ -142,11 +210,11 @@ export default function ButtonModal() {
} }
destroyOnClose destroyOnClose
width={800} width={800}
onCancel={() => setOpen(false)}> onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}> <div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}> <Form form={form} labelCol={{ span: 5 }} preserve={false}>
{steps.map((item, index) => ( {steps.map((item, index) => (
<div key={index} hidden={current !== index} style={{ marginTop: '20px' }}> <div key={index} style={{ display: current === index ? 'block' : 'none', marginTop: '20px' }}>
{item.content} {item.content}
</div> </div>
))} ))}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论