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

chore: update

上级 0012a77d
......@@ -92,3 +92,52 @@ body,
padding: 0;
margin: 0 20px;
} */
.app-steps {
.ant-steps-item-title {
--ant-steps-title-line-height: 100px;
}
.app-step-box {
width: 90px;
height: 90px;
border-radius: 50%;
background-color: #fff;
border: 1px solid #bbb;
display: flex;
align-items: center;
justify-content: center;
padding: 14px;
box-sizing: border-box;
white-space: normal;
overflow-wrap: break-word;
text-align: center;
line-height: 1.5;
font-size: 14px;
}
.ant-steps-item-finish .app-step-box,
.ant-steps-item-process .app-step-box {
border: 1px solid var(--main-color);
background-color: var(--main-color);
color: #fff;
}
}
.app-process-steps {
--ant-steps-dot-size: 100px !important;
--ant-steps-dot-current-size: 100px !important;
.app-progress-step-box {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
border: 1px solid #bbb;
border-radius: 50%;
}
.ant-steps-item-finish .app-progress-step-box {
border: 1px solid var(--main-color);
background-color: var(--main-color);
color: #fff;
}
}
......@@ -2,7 +2,7 @@ import { Suspense } from 'react'
import { Spin } from 'antd'
import { useRoutes } from 'react-router'
import routes from './router/routes'
import './App.css'
import './App.scss'
const App = () => {
const element = useRoutes(routes)
......
......@@ -1135,3 +1135,41 @@ export function getMyField() {
})
return httpRequest.get('/api/bi/v1/data/my/field-detail')
}
// 我的数据集列表
export function getChartList(params?: Partial<{ page: number; 'per-page': number }>) {
return Promise.resolve({
code: 0,
message: 'OK',
data: {
total: 10,
list: [
{
pk_id: '1',
type: '柱状图',
name: '销量统计',
create_user: 'admin',
create_time: '2023-07-18 15:00:00',
update_time: '2023-07-18 15:00:00',
},
{
pk_id: '2',
type: '折线图',
name: '销售额趋势',
create_user: 'admin',
create_time: '2023-07-18 15:00:00',
update_time: '2023-07-18 15:00:00',
},
{
pk_id: '3',
type: '饼图',
name: '品类分布',
create_user: 'admin',
create_time: '2023-07-18 15:00:00',
update_time: '2023-07-18 15:00:00',
},
],
},
})
return httpRequest.get('/api/bi/v1/data/my/list', { params })
}
......@@ -10,6 +10,7 @@ export interface QueryParams {
}
export interface AppListProps extends TableProps {
queryKey?: any[]
fetchApi?: (params?: QueryParams) => Promise<{ list: any[]; total: number }>
filters?: any[]
filterAside?: React.ReactNode
......@@ -21,15 +22,19 @@ export interface AppListRef {
}
const AppList = forwardRef<AppListRef, AppListProps>(
({ fetchApi, filters = [], filterAside, dataSource = [], ...props }, ref) => {
({ queryKey = [], fetchApi, filters = [], filterAside, dataSource = [], ...props }, ref) => {
const [form] = Form.useForm()
const [queryParams, setQueryParams] = useState<QueryParams>({ page: 1, 'per-page': 10 })
const { data, refetch, isLoading } = useQuery({
queryKey: ['appList', queryParams],
queryKey: [...queryKey, 'appList', queryParams],
queryFn: async () => {
return fetchApi ? await fetchApi(queryParams) : { list: dataSource || [], total: dataSource?.length || 0 }
},
initialData: (): any => {
return { list: dataSource || [], total: dataSource?.length || 0 }
},
enabled: !!fetchApi,
})
// 暴露方法
......
import type { StepsProps } from 'antd'
import { Spin, Steps } from 'antd'
export default function AppSteps({ ...props }: StepsProps) {
const customDot = (dot, { status }) => {
const statusMap: any = {
wait: '待处理',
process: '处理中',
finish: '已完成',
}
return (
<>
<div className="app-progress-step-box">
{status === 'process' ? <Spin size="large"></Spin> : statusMap[status]}
</div>
</>
)
}
return <Steps className="app-process-steps" progressDot={customDot} labelPlacement="vertical" {...props}></Steps>
}
import type { StepsProps } from 'antd'
import { Popover, Steps } from 'antd'
import { Steps } from 'antd'
export default function AppSteps(props: StepsProps) {
const customDot: StepsProps['progressDot'] = (dot, { status, index }) => <>{dot}</>
return <Steps {...props} progressDot={customDot} />
export default function AppSteps({ items = [], ...props }: StepsProps) {
return (
<Steps className="app-steps" {...props}>
{items.map((item, index) => (
<Steps.Step
key={index}
icon={<></>}
title={<div className="app-step-box">{item.title}</div>}
description={item.description}
/>
))}
</Steps>
)
}
import { useState, KeyboardEvent, useEffect, useRef } from 'react'
import { Button, Card, FloatButton, Select } from 'antd'
import { Button, Card, FloatButton, Input, Select } from 'antd'
import { CircleArrowLeft, CircleArrowRight } from 'lucide-react'
import './AIChat.scss'
import { OpenAIOutlined, ArrowUpOutlined } from '@ant-design/icons'
import TextArea from 'antd/es/input/TextArea'
import { useAI, AIMessage } from '@/hooks/useAI'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { useAIStore, AIMessage } from '@/stores/ai'
import './AIChat.scss'
export const MessageItem = ({ message }: { message: AIMessage }) => {
return (
......@@ -27,7 +26,7 @@ export default function AIChat() {
setCollapsed(!collapsed)
}
const { ai, setAI, options, post, messages, isLoading } = useAI()
const { ai, setAI, options, post, messages, isLoading } = useAIStore()
const [content, setContent] = useState('')
......@@ -65,7 +64,7 @@ export default function AIChat() {
<div className="input-container">
<div className="input-box">
<div className="edit-area">
<TextArea
<Input.TextArea
className="content"
autoSize
value={content}
......
import { Button, Card, Flex } from 'antd'
import { ReactNode } from 'react'
import AppList, { AppListProps } from '@/components/AppList'
import { getChartList } from '@/api/base'
export default function DataWrap({
title,
buttons,
children,
}: {
title: string
buttons: ReactNode
children?: ReactNode
}) {
export default function DataWrap({ title, buttons }: { title: string; buttons: ReactNode; children?: ReactNode }) {
const listOptions: AppListProps = {
fetchApi: async (params) => {
const { data } = await getChartList(params)
return { ...data }
},
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{ title: '组件类型', dataIndex: 'type', align: 'center' },
{ title: '组件名称', dataIndex: 'name', align: 'center' },
{ title: '创建人', dataIndex: 'create_user', align: 'center' },
{ title: '创建时间', dataIndex: 'create_time', align: 'center' },
{ title: '更新时间', dataIndex: 'update_time', align: 'center' },
{
title: '操作',
key: 'x',
width: 220,
align: 'center',
render(_value, record) {
return (
<>
<Button color="primary" variant="text">
查看
</Button>
<Button color="primary" variant="text">
编辑
</Button>
<Button color="danger" variant="text">
删除
</Button>
</>
)
},
},
],
}
return (
<Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
......@@ -19,7 +56,7 @@ export default function DataWrap({
</Flex>
<Button>查看我的数据集</Button>
</Flex>
{children}
<AppList {...listOptions} />
</Card>
</Flex>
)
......
......@@ -7,8 +7,8 @@ export default function ViewDataFiledButtonModal({ children }: { children?: stri
const [open, setOpen] = useState(false)
const { data } = useDataFieldQuery()
const listOptions: AppListProps = {
queryKey: ['dataField'],
columns: [
{
title: '序号',
......
......@@ -99,16 +99,16 @@ export default function DataLayout() {
name: '数据挖掘',
path: '/data/digging',
children: [
{ name: '线性回归', path: '/data/digging/1' },
{ name: '逻辑回归', path: '/data/digging/2' },
{ name: '决策树', path: '/data/digging/3' },
{ name: '随机森林', path: '/data/digging/4' },
{ name: '支持向量机', path: '/data/digging/5' },
{ name: 'K-Means', path: '/data/digging/6' },
{ name: '层次聚类', path: '/data/digging/7' },
{ name: 'Apriori', path: '/data/digging/8' },
{ name: 'FP-Growth', path: '/data/digging/9' },
{ name: 'Holt-Winters', path: '/data/digging/10' },
{ 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' },
],
},
{
......@@ -117,19 +117,19 @@ export default function DataLayout() {
path: '/data/chart',
children: [
{ name: '柱状图', path: '/data/chart/bar' },
{ name: '折线图', path: '/data/chart/2' },
{ name: '饼状图', path: '/data/chart/3' },
{ name: '雷达图', path: '/data/chart/4' },
{ name: '散点图', path: '/data/chart/5' },
{ name: '气泡图', path: '/data/chart/6' },
{ name: '词云', path: '/data/chart/7' },
{ name: '地图', path: '/data/chart/8' },
{ name: '指标卡', path: '/data/chart/9' },
{ name: '漏斗图', path: '/data/chart/10' },
{ name: '直方图', path: '/data/chart/11' },
{ name: '表格', path: '/data/chart/12' },
{ name: '帕累托图', path: '/data/chart/13' },
{ name: '矩形树图', path: '/data/chart/14' },
{ 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' },
],
},
{
......
......@@ -22,7 +22,7 @@ export function useAI() {
{ label: '天工', value: 'tiangong' },
]
const [ai, setAI] = useState<string>(localStorage.getItem('ai') || 'yiyan')
const [ai, setAI] = useState<string>(localStorage.getItem('ai') || 'qwen')
const [messages, setMessages] = useState<AIMessage[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false)
......
......@@ -51,7 +51,14 @@ export function useDataQuery(params?: Partial<{ page: number; 'per-page': number
// 查看字段详情
export function useDataFieldQuery() {
const query = useQuery({ queryKey: ['data-filed'], queryFn: getMyField, select: (res) => res.data })
const query = useQuery({
queryKey: ['dataFiled'],
queryFn: getMyField,
select: (res) => res.data,
initialData: () => {
return { data: [] }
},
})
return query
}
import ChartWrap from '@/components/data/ChartWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <ChartWrap title="可视化:柱状图" buttons={<Button type="primary">新建柱状图</Button>}></ChartWrap>
}
import ChartWrap from '@/components/data/ChartWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <ChartWrap title="可视化:柱状图" buttons={<Button type="primary">新建柱状图</Button>}></ChartWrap>
}
import ChartWrap from '@/components/data/ChartWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <ChartWrap title="可视化:柱状图" buttons={<Button type="primary">新建柱状图</Button>}></ChartWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据透视" buttons={<Button type="primary">数据透视</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据挖掘:随机森林" buttons={<Button type="primary">随机森林</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:逻辑计算" buttons={<Button type="primary">逻辑计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数值计算" buttons={<Button type="primary">数值计算</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据透视" buttons={<Button type="primary">数据透视</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据加工:数据分组" buttons={<Button type="primary">数据分组</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据挖掘:线性回归" buttons={<Button type="primary">线性回归</Button>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据挖掘:逻辑回归" buttons={<Button type="primary">逻辑回归</Button>}></DataWrap>
}
import { useState } from 'react'
import { Button, Flex, Modal, Radio, Input, Form, Row, Col, Table } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery'
import AppProgressSteps from '@/components/AppProgressSteps'
export default function ButtonModal() {
const { data } = useDataFieldQuery()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [form] = Form.useForm()
const checked = Form.useWatch('checked', form)
const [step, setStep] = useState<number>(null)
const steps = [
{
title: '请选择值映射字段',
content: (
<>
<Form.Item label="值映射字段名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="值映射字段英文名称" name="english_name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="请选择值映射字段" name="checked">
<Radio.Group>
<Row gutter={10}>
{data.map((item) => (
<Col span={8} key={item.english_name}>
<Radio value={item.name}>{item.name}</Radio>
</Col>
))}
</Row>
</Radio.Group>
</Form.Item>
</>
),
},
{
title: '配置值映射规则',
content: (
<>
<Form.Item label="值映射操作字段">{checked}</Form.Item>
<Form.Item>
<Table
bordered
columns={[{ title: '序号' }, { title: '原始值' }, { title: '映射值' }]}
dataSource={[]}></Table>
</Form.Item>
</>
),
},
{
title: '处理执行',
content: (
<>
<Flex vertical align="center" style={{ marginTop: '20px' }}>
<Button type="primary" onClick={() => setStep(2)}>
开始处理
</Button>
<AppProgressSteps
style={{ margin: '80px' }}
current={step}
items={[
{
title: (
<>
第一步
<br />
新建字段
</>
),
},
{
title: (
<>
第二步
<br />
复制数据
</>
),
},
{
title: (
<>
第三步
<br />
值映射处理
</>
),
},
{
title: (
<>
第四步
<br />
处理结果
</>
),
description: (
<>
累计处理XX个字段
<br />
累计处理XX条记录
</>
),
},
]}
/>
</Flex>
</>
),
},
]
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
值映射
</Button>
<Modal
title={steps[current].title}
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 1 && <Button type="primary">AI智能建议</Button>}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checked?.length}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => setOpen(false)}>
关闭
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
{steps.map((item, index) => (
<div key={index} hidden={current !== index} style={{ marginTop: '20px' }}>
{item.content}
</div>
))}
</Form>
</div>
</Modal>
</>
)
}
import { lazy } from 'react'
import DataWrap from '@/components/data/DataWrap'
const ButtonModal = lazy(() => import('../components/ButtonModal'))
export default function DataProcess() {
return <DataWrap title="数据挖掘:支持向量机" buttons={<ButtonModal />}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据挖掘:决策树" buttons={<Button type="primary">决策树(二分类)</Button>}></DataWrap>
}
import { useState } from 'react'
import { Button, Checkbox, Flex, Modal, Steps, Radio, Select, Input, Form } from 'antd'
import { Button, Checkbox, Flex, Modal, Radio, Select, Input, Form, Row, Col, Divider } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery'
import AppProgressSteps from '@/components/AppProgressSteps'
export default function ButtonModal() {
const { data } = useDataFieldQuery()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [checkedList, setCheckedList] = useState<string[]>([])
const [processingMethod, setProcessingMethod] = useState<'unified' | 'individual'>('unified')
const [processingRule, setProcessingRule] = useState<string>('不处理')
const [specialValue, setSpecialValue] = useState<string>('')
const plainOptions = data?.map((item) => {
return {
label: item.name,
value: item.name,
}
})
const onChange = (list: string[]) => {
setCheckedList(list)
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const [form] = Form.useForm()
const checkedList = Form.useWatch('checkedList', form) || []
const processingMethod = Form.useWatch('processingMethod', form)
const rule = Form.useWatch('rule', form)
const rules = Form.useWatch('rules', form) || {}
const methodOptions = [
{ label: '统一规则处理', value: 'unified' },
{ label: '逐个配置规则处理', value: 'individual' },
]
const ruleOptions = [
{ label: '不处理', value: '不处理' },
{ label: 'AI智能填充', value: 'AI智能填充' },
{ label: '删除', value: '删除' },
{ label: '平均值填充', value: '平均值填充' },
{ label: '特殊值填充', value: '特殊值填充' },
{ label: '热卡填充(上)', value: '热卡填充(上)' },
{ label: '热卡填充(下)', value: '热卡填充(下)' },
{ label: '不处理', value: '1' },
{ label: 'AI智能填充', value: '2' },
{ label: '删除', value: '3' },
{ label: '平均值填充', value: '4' },
{ label: '特殊值填充', value: '5' },
{ label: '热卡填充(上)', value: '6' },
{ label: '热卡填充(下)', value: '7' },
]
const [step, setStep] = useState<number>(null)
const steps = [
{
title: '请选择缺失值字段',
content: <Checkbox.Group options={plainOptions} value={checkedList} onChange={onChange} />,
content: (
<Form.Item name="checkedList">
<Checkbox.Group>
<Row gutter={10}>
{data.map((item) => (
<Col span={6} key={item.english_name}>
<Checkbox value={item.name}>{item.name}</Checkbox>
</Col>
))}
</Row>
</Checkbox.Group>
</Form.Item>
),
},
{
title: '配置处理规则',
content: (
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
<Form.Item label="缺失值处理字段">是否会员、会员等级</Form.Item>
<Form.Item label="缺失值字段处理方法">
<Radio.Group
onChange={(e) => setProcessingMethod(e.target.value)}
value={processingMethod}
options={methodOptions}></Radio.Group>
</Form.Item>
<Form.Item label="缺失值字段处理规则">
<Select
style={{ width: 200 }}
value={processingRule}
onChange={(value) => setProcessingRule(value)}
options={ruleOptions}
/>
</Form.Item>
<Form.Item label="请填写特殊值">
<Input
style={{ width: 200 }}
value={specialValue}
onChange={(e) => setSpecialValue(e.target.value)}
placeholder="请输入"
/>
<>
<Form.Item label="缺失值字段处理方法" name="processingMethod">
<Radio.Group options={methodOptions} />
</Form.Item>
</Form>
{/* 统一规则处理 */}
{processingMethod === 'unified' && (
<>
<Form.Item label="缺失值字段处理规则" name="rule">
<Select options={ruleOptions} />
</Form.Item>
{rule === '5' && (
<Form.Item label="请填写特殊值" name="special">
<Input placeholder="请输入" />
</Form.Item>
)}
</>
)}
{/* 逐个配置规则处理 */}
{processingMethod === 'individual' &&
checkedList.map((field) => (
<div key={field} style={{ marginBottom: 10 }}>
<Form.Item label="字段">{field}</Form.Item>
<Form.Item label="缺失值字段处理规则" name={['rules', field, 'rule']}>
<Select options={ruleOptions} />
</Form.Item>
{rules[field]?.rule === '5' && (
<Form.Item label="请填写特殊值" name={['rules', field, 'special']}>
<Input placeholder="请输入" />
</Form.Item>
)}
<Divider />
</div>
))}
</>
),
},
{
title: '处理执行',
content: (
<>
<p>缺失值处理字段:XXXXX, XXXX, XXXX, XXXX, XXXX, XXXX</p>
<p>缺失值处理字段:{checkedList.join(', ')}</p>
<Flex vertical align="center" style={{ marginTop: '20px' }}>
<Button type="primary">开始处理</Button>
<Steps
labelPlacement="vertical"
<Button type="primary" onClick={() => setStep(1)}>
开始处理
</Button>
<AppProgressSteps
style={{ margin: '80px' }}
current={step}
items={[
{
title: '检查字段处理规则',
},
{
title: '缺失值处理',
},
{ title: '检查字段处理规则' },
{ title: '缺失值处理' },
{
title: '处理结果',
description: (
<>
累计处理XX个字段
<br />
累计处理XX条记录
</>
),
},
]}></Steps>
]}
/>
</Flex>
</>
),
},
]
const title = steps[current].title
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
处理缺失值
</Button>
<Modal
title={title}
title={steps[current].title}
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">检查缺失值字段</Button>}
{current > 0 && <Button onClick={() => prev()}>上一步</Button>}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checkedList.length}>
下一步
</Button>
)}
......@@ -137,7 +152,13 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<div style={{ marginTop: '20px' }}>{steps[current].content}</div>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
{steps.map((item, index) => (
<div key={index} hidden={current !== index} style={{ marginTop: '20px' }}>
{item.content}
</div>
))}
</Form>
</div>
</Modal>
</>
......
import { useState } from 'react'
import { Button, Checkbox, Flex, Modal, Steps, Radio, Select, Input, Form } from 'antd'
import { Button, Checkbox, Flex, Modal, Radio, Select, Input, Form, Row, Col, Divider } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery'
import AppProgressSteps from '@/components/AppProgressSteps'
export default function ButtonModal() {
const { data } = useDataFieldQuery()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [checkedList, setCheckedList] = useState<string[]>([])
const [processingMethod, setProcessingMethod] = useState<'unified' | 'individual'>('unified')
const [processingRule, setProcessingRule] = useState<string>('不处理')
const [specialValue, setSpecialValue] = useState<string>('')
const plainOptions = data?.map((item) => {
return {
label: item.name,
value: item.name,
}
})
const onChange = (list: string[]) => {
setCheckedList(list)
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const [form] = Form.useForm()
const checkedList = Form.useWatch('checkedList', form) || []
const processingMethod = Form.useWatch('processingMethod', form)
const rule = Form.useWatch('rule', form)
const rules = Form.useWatch('rules', form) || {}
const methodOptions = [
{ label: '统一规则处理', value: 'unified' },
{ label: '逐个配置规则处理', value: 'individual' },
]
const ruleOptions = [
{ label: '不处理', value: '不处理' },
{ label: 'AI智能填充', value: 'AI智能填充' },
{ label: '删除', value: '删除' },
{ label: '平均值填充', value: '平均值填充' },
{ label: '特殊值填充', value: '特殊值填充' },
{ label: '热卡填充(上)', value: '热卡填充(上)' },
{ label: '热卡填充(下)', value: '热卡填充(下)' },
{ label: '不处理', value: '1' },
{ label: 'AI智能填充', value: '2' },
{ label: '删除', value: '3' },
{ label: '平均值填充', value: '4' },
{ label: '特殊值填充', value: '5' },
{ label: '热卡填充(上)', value: '6' },
{ label: '热卡填充(下)', value: '7' },
]
const [step, setStep] = useState<number>(null)
const steps = [
{
title: '请选择重复值数据',
content: <Checkbox.Group options={plainOptions} value={checkedList} onChange={onChange} />,
title: '请选择缺失值字段',
content: (
<Form.Item name="checkedList">
<Checkbox.Group>
<Row gutter={10}>
{data.map((item) => (
<Col span={6} key={item.english_name}>
<Checkbox value={item.name}>{item.name}</Checkbox>
</Col>
))}
</Row>
</Checkbox.Group>
</Form.Item>
),
},
{
title: '配置处理规则',
content: (
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
<Form.Item label="重复值处理字段">是否会员、会员等级</Form.Item>
<Form.Item label="重复值字段处理方法">
<Radio.Group
onChange={(e) => setProcessingMethod(e.target.value)}
value={processingMethod}
options={methodOptions}></Radio.Group>
</Form.Item>
<Form.Item label="重复值字段处理规则">
<Select
style={{ width: 200 }}
value={processingRule}
onChange={(value) => setProcessingRule(value)}
options={ruleOptions}
/>
</Form.Item>
<Form.Item label="请填写特殊值">
<Input
style={{ width: 200 }}
value={specialValue}
onChange={(e) => setSpecialValue(e.target.value)}
placeholder="请输入"
/>
<>
<Form.Item label="缺失值字段处理方法" name="processingMethod">
<Radio.Group options={methodOptions} />
</Form.Item>
</Form>
{/* 统一规则处理 */}
{processingMethod === 'unified' && (
<>
<Form.Item label="缺失值字段处理规则" name="rule">
<Select options={ruleOptions} />
</Form.Item>
{rule === '5' && (
<Form.Item label="请填写特殊值" name="special">
<Input placeholder="请输入" />
</Form.Item>
)}
</>
)}
{/* 逐个配置规则处理 */}
{processingMethod === 'individual' &&
checkedList.map((field) => (
<div key={field} style={{ marginBottom: 10 }}>
<Form.Item label="字段">{field}</Form.Item>
<Form.Item label="缺失值字段处理规则" name={['rules', field, 'rule']}>
<Select options={ruleOptions} />
</Form.Item>
{rules[field]?.rule === '5' && (
<Form.Item label="请填写特殊值" name={['rules', field, 'special']}>
<Input placeholder="请输入" />
</Form.Item>
)}
<Divider />
</div>
))}
</>
),
},
{
title: '处理执行',
content: (
<>
<p>重复值处理字段:XXXXX, XXXX, XXXX, XXXX, XXXX, XXXX</p>
<p>缺失值处理字段:{checkedList.join(', ')}</p>
<Flex vertical align="center" style={{ marginTop: '20px' }}>
<Button type="primary">开始处理</Button>
<Steps
labelPlacement="vertical"
<Button type="primary" onClick={() => setStep(1)}>
开始处理
</Button>
<AppProgressSteps
style={{ margin: '80px' }}
current={step}
items={[
{
title: '检查字段处理规则',
},
{
title: '重复值处理',
},
{ title: '检查字段处理规则' },
{ title: '缺失值处理' },
{
title: '处理结果',
description: (
<>
累计处理XX个字段
<br />
累计处理XX条记录
</>
),
},
]}></Steps>
]}
/>
</Flex>
</>
),
},
]
const title = steps[current].title
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
处理重复
处理缺失
</Button>
<Modal
title={title}
title={steps[current].title}
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">检查重复值字段</Button>}
{current > 0 && <Button onClick={() => prev()}>上一步</Button>}
{current === 0 && <Button type="primary">检查缺失值字段</Button>}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checkedList.length}>
下一步
</Button>
)}
......@@ -137,7 +152,13 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<div style={{ marginTop: '20px' }}>{steps[current].content}</div>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
{steps.map((item, index) => (
<div key={index} hidden={current !== index} style={{ marginTop: '20px' }}>
{item.content}
</div>
))}
</Form>
</div>
</Modal>
</>
......
import { useState } from 'react'
import { Button, Flex, Modal, Radio, Input, Form, Row, Col, Table } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery'
import AppProgressSteps from '@/components/AppProgressSteps'
export default function ButtonModal() {
const { data } = useDataFieldQuery()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [form] = Form.useForm()
const checked = Form.useWatch('checked', form)
const [step, setStep] = useState<number>(null)
const steps = [
{
title: '请选择值映射字段',
content: (
<>
<Form.Item label="值映射字段名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="值映射字段英文名称" name="english_name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="请选择值映射字段" name="checked">
<Radio.Group>
<Row gutter={10}>
{data.map((item) => (
<Col span={8} key={item.english_name}>
<Radio value={item.name}>{item.name}</Radio>
</Col>
))}
</Row>
</Radio.Group>
</Form.Item>
</>
),
},
{
title: '配置值映射规则',
content: (
<>
<Form.Item label="值映射操作字段">{checked}</Form.Item>
<Form.Item>
<Table
bordered
columns={[{ title: '序号' }, { title: '原始值' }, { title: '映射值' }]}
dataSource={[]}></Table>
</Form.Item>
</>
),
},
{
title: '处理执行',
content: (
<>
<Flex vertical align="center" style={{ marginTop: '20px' }}>
<Button type="primary" onClick={() => setStep(2)}>
开始处理
</Button>
<AppProgressSteps
style={{ margin: '80px' }}
current={step}
items={[
{
title: (
<>
第一步
<br />
新建字段
</>
),
},
{
title: (
<>
第二步
<br />
复制数据
</>
),
},
{
title: (
<>
第三步
<br />
值映射处理
</>
),
},
{
title: (
<>
第四步
<br />
处理结果
</>
),
description: (
<>
累计处理XX个字段
<br />
累计处理XX条记录
</>
),
},
]}
/>
</Flex>
</>
),
},
]
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
值映射
</Button>
<Modal
title={steps[current].title}
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 1 && <Button type="primary">AI智能建议</Button>}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checked?.length}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => setOpen(false)}>
关闭
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
{steps.map((item, index) => (
<div key={index} hidden={current !== index} style={{ marginTop: '20px' }}>
{item.content}
</div>
))}
</Form>
</div>
</Modal>
</>
)
}
import { lazy } from 'react'
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
const ButtonModal = lazy(() => import('../components/ButtonModal'))
export default function DataProcess() {
return <DataWrap title="数据加工:值映射" buttons={<Button type="primary">值映射</Button>}></DataWrap>
return <DataWrap title="数据加工:值映射" buttons={<ButtonModal />}></DataWrap>
}
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import { Button, Radio, Flex, Modal } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
import AppSteps from '@/components/AppSteps'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
......@@ -23,14 +24,7 @@ export default function ButtonModal() {
setCurrent(current - 1)
}
const steps = [
{
title: '探索逻辑错误字段',
},
{
title: '处理逻辑错误',
},
]
const steps = [{ title: '探索逻辑错误字段' }, { title: '处理逻辑错误' }]
const style: React.CSSProperties = {
display: 'flex',
......@@ -66,10 +60,10 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<AppSteps current={current} items={steps} style={{ marginBottom: 40 }} />
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
</Flex>
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
......
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import { Button, Radio, Flex, Modal } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
import AppSteps from '@/components/AppSteps'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
......@@ -66,10 +67,10 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<AppSteps current={current} items={steps} style={{ marginBottom: 40 }} />
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
</Flex>
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
......
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import { Button, Radio, Flex, Modal } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
import AppSteps from '@/components/AppSteps'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
......@@ -66,10 +67,10 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<AppSteps current={current} items={steps} style={{ marginBottom: 40 }} />
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
</Flex>
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
......
import { useState } from 'react'
import { Button, Checkbox, Divider, Flex, Modal, Steps } from 'antd'
import { Button, Checkbox, Flex, Modal } from 'antd'
import type { CheckboxProps } from 'antd'
import { useNavigate } from 'react-router'
import AppSteps from '@/components/AppSteps'
const CheckboxGroup = Checkbox.Group
......@@ -40,13 +41,12 @@ export default function ButtonModal() {
title: '探索缺失值字段',
content: (
<>
<Flex justify="space-between">
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
<Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
全选
</Checkbox>
</Flex>
<Divider dashed />
<CheckboxGroup options={plainOptions} value={checkedList} onChange={onChange} />
</>
),
......@@ -55,8 +55,9 @@ export default function ButtonModal() {
title: '探索字段缺失值',
content: (
<>
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
</Flex>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
......@@ -69,13 +70,12 @@ export default function ButtonModal() {
title: '填充字段缺失值',
content: (
<>
<Flex justify="space-between">
<Flex justify="space-between" style={{ marginBottom: 20 }}>
请选择需要填充的字段:
<Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
全选
</Checkbox>
</Flex>
<Divider dashed />
<CheckboxGroup options={plainOptions} value={checkedList} onChange={onChange} />
</>
),
......@@ -116,8 +116,7 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<AppSteps current={current} items={steps} style={{ marginBottom: 40 }} />
<div>{steps[current].content}</div>
</div>
</Modal>
......
......@@ -32,7 +32,7 @@ export default function ButtonModal() {
const dataSource = [
{
key: '1',
name: '段1',
name: '段1',
max: 99,
min: '--',
mean: 50,
......@@ -46,7 +46,7 @@ export default function ButtonModal() {
},
{
key: '2',
name: '段2',
name: '段2',
max: 102586,
min: '--',
mean: 51293,
......@@ -60,7 +60,7 @@ export default function ButtonModal() {
},
{
key: '3',
name: '段3',
name: '段3',
max: 859,
min: '--',
mean: 430,
......@@ -74,7 +74,7 @@ export default function ButtonModal() {
},
{
key: '4',
name: '段4',
name: '段4',
max: 1568,
min: '--',
mean: 784,
......
import { useState } from 'react'
import { Button, Divider, Flex, Modal, Steps } from 'antd'
import { Button, Flex, Modal } from 'antd'
import { useNavigate } from 'react-router'
import AppSteps from '@/components/AppSteps'
export default function ButtonModal() {
const navigate = useNavigate()
......@@ -53,10 +54,10 @@ export default function ButtonModal() {
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<AppSteps current={current} items={steps} style={{ marginBottom: 40 }} />
<Flex justify="space-between" style={{ marginBottom: 20 }}>
探索结果:
</Flex>
<p>1、第XX行、第XX行的数据完全一样</p>
<p>2、第XX行、第XX行、第XX行的数据完全一样</p>
</div>
......
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
import { useAI } from '@/hooks/useAI'
import { useDataQuery } from '@/hooks/useQuery'
import { useAIStore } from '@/stores/ai'
export default function DataWriteUpload() {
const { post } = useAI()
const { post, isLoading } = useAIStore()
const { data } = useDataQuery()
function handleClick(type: number) {
......@@ -26,10 +26,10 @@ export default function DataWriteUpload() {
title="数据理解"
buttons={
<>
<Button type="primary" onClick={() => handleClick(1)}>
<Button type="primary" onClick={() => handleClick(1)} loading={isLoading}>
一键理解数据字段
</Button>
<Button type="primary" onClick={() => handleClick(2)}>
<Button type="primary" onClick={() => handleClick(2)} loading={isLoading}>
一键梳理字段关系
</Button>
</>
......
......@@ -26,30 +26,56 @@ export const routes: RouteObject[] = [
// 数据预处理
{ path: 'preprocess', element: <Navigate to="null" /> },
{ path: 'preprocess/null', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/repeat', Component: lazy(() => import('./preprocess/repeat/views/Index')) },
{ path: 'preprocess/max', Component: lazy(() => import('./preprocess/max/views/Index')) },
{ path: 'preprocess/min', Component: lazy(() => import('./preprocess/min/views/Index')) },
{ path: 'preprocess/error', Component: lazy(() => import('./preprocess/error/views/Index')) },
{ path: 'preprocess/split', Component: lazy(() => import('./preprocess/split/views/Index')) },
{ path: 'preprocess/space', Component: lazy(() => import('./preprocess/space/views/Index')) },
{ path: 'preprocess/symbol', Component: lazy(() => import('./preprocess/symbol/views/Index')) },
{ path: 'preprocess/type', Component: lazy(() => import('./preprocess/type/views/Index')) },
{ path: 'preprocess/sort', Component: lazy(() => import('./preprocess/sort/views/Index')) },
{ path: 'preprocess/splice', Component: lazy(() => import('./preprocess/splice/views/Index')) },
{ path: 'preprocess/repeat', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/max', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/min', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/error', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/split', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/space', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/symbol', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/type', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/sort', Component: lazy(() => import('./preprocess/null/views/Index')) },
{ path: 'preprocess/splice', Component: lazy(() => import('./preprocess/null/views/Index')) },
// 数据预处理
{ path: 'process', element: <Navigate to="binning" /> },
{ path: 'process/binning', Component: lazy(() => import('./process/binning/views/Index')) },
{ path: 'process', element: <Navigate to="mapping" /> },
{ path: 'process/mapping', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/group', Component: lazy(() => import('./process/group/views/Index')) },
{ path: 'process/desensitization', Component: lazy(() => import('./process/desensitization/views/Index')) },
{ path: 'process/date', Component: lazy(() => import('./process/date/views/Index')) },
{ path: 'process/string', Component: lazy(() => import('./process/string/views/Index')) },
{ path: 'process/number', Component: lazy(() => import('./process/number/views/Index')) },
{ path: 'process/logic', Component: lazy(() => import('./process/logic/views/Index')) },
{ path: 'process/perspective', Component: lazy(() => import('./process/perspective/views/Index')) },
{ path: 'process/binning', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/group', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/desensitization', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/date', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/string', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/number', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/logic', Component: lazy(() => import('./process/mapping/views/Index')) },
{ path: 'process/perspective', Component: lazy(() => import('./process/mapping/views/Index')) },
// 数据挖掘
{ path: 'digging', element: <Navigate to="linear" /> },
{ path: 'digging/linear', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/logistic', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/tree', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/forest', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/svm', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/kmeans', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/hierarchical', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/apriori', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/fpgrowth', Component: lazy(() => import('./digging/linear/views/Index')) },
{ path: 'digging/holtwinters', Component: lazy(() => import('./digging/linear/views/Index')) },
// 数据可视化组件
{ path: 'chart', element: <Navigate to="bar" /> },
{ path: 'chart/bar', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/line', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/pie', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/radar', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/point', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/bubble', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/wordCloud', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/map', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/indicator', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/funnel', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/histogram', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/table', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/pareto', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'chart/treeMap', Component: lazy(() => import('./chart/bar/views/Index')) },
{ path: 'screen', Component: lazy(() => import('./screen/views/Index')) },
],
},
],
......
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
export default function DataProcess() {
return <DataWrap title="数据可视化大屏" buttons={<Button type="primary">数据透视</Button>}></DataWrap>
}
......@@ -11,7 +11,7 @@ export function useCreateDataset() {
mutationFn: (data: CreateDatasetParams) => createDataset(data),
onSuccess: () => {
message.success('创建成功')
queryClient.invalidateQueries({ queryKey: ['appList'] })
queryClient.invalidateQueries({ queryKey: ['builtDataset'] })
},
})
}
......@@ -24,7 +24,7 @@ export function useDeleteDataset() {
mutationFn: (data: { id: string }) => deleteDataset(data),
onSuccess: () => {
message.success('删除成功')
queryClient.invalidateQueries({ queryKey: ['appList'] })
queryClient.invalidateQueries({ queryKey: ['builtDataset'] })
},
})
}
......@@ -18,6 +18,7 @@ export default function DataWriteBuilt() {
}
const listOptions: AppListProps = {
queryKey: ['builtDataset'],
fetchApi: async (params) => {
const { data } = await getDatasetList(params)
return { ...data }
......@@ -72,7 +73,7 @@ export default function DataWriteBuilt() {
}
return (
<Card className="app-card" title="内置数据集管理">
<AppList bordered {...listOptions} filterAside={<FormButtonModal title="添加数据集"></FormButtonModal>}></AppList>
<AppList {...listOptions} filterAside={<FormButtonModal title="添加数据集"></FormButtonModal>}></AppList>
</Card>
)
}
......@@ -12,6 +12,7 @@ export default function DataWriteCopy() {
const industryList = getMapValuesByKey('bi_data_industry')
const listOptions: AppListProps = {
queryKey: ['dataWriteCopy'],
fetchApi: async (params) => {
const { data } = await getDatasetList({ ...params, access_permissions: '1' })
return { ...data }
......
......@@ -11,7 +11,7 @@ export function useImportDataset() {
onSuccess: () => {
message.success('导入成功')
queryClient.invalidateQueries({ queryKey: ['data'] })
queryClient.invalidateQueries({ queryKey: ['data-filed'] })
queryClient.invalidateQueries({ queryKey: ['dataFiled'] })
},
})
}
import { create } from 'zustand'
import md5 from 'blueimp-md5'
import axios from 'axios'
import { fetchEventSource } from '@fortaine/fetch-event-source'
export interface Message {
export interface AIOption {
label: string
value: string
}
export interface AIMessage {
id?: string
role: 'user' | 'assistant'
content: string
}
interface State {
interface AIState {
ai: string
setAI: (ai: string) => void
messages: Message[]
options: AIOption[]
messages: AIMessage[]
isLoading: boolean
setAI: (ai: string) => void
post: (data: { content: string }) => Promise<void>
}
// Zustand 状态管理
export const useAIStore = create<State>((set, get) => ({
ai: localStorage.getItem('ai') || 'yiyan',
export const useAIStore = create<AIState>((set, get) => ({
ai: localStorage.getItem('ai') || 'qwen',
options: [
{ label: '文心一言', value: 'yiyan' },
{ label: 'DeepSeek', value: 'deepseek' },
{ label: '通义千问', value: 'qwen' },
{ label: '天工', value: 'tiangong' },
],
messages: [],
isLoading: false,
setAI: (ai) => {
localStorage.setItem('ai', ai)
set({ ai })
},
messages: [],
isLoading: false,
post: async (data) => {
const { ai, messages } = get()
set({ isLoading: true, messages: [...messages, { role: 'user', content: data.content }] })
try {
let response = ''
switch (ai) {
case 'yiyan':
response = await yiyan(data)
await yiyan(data)
break
case 'deepseek':
response = await deepseek(data)
await deepseek(data)
break
case 'qwen':
response = await qwen(data)
await qwen(data)
break
case 'tiangong':
response = await tiangong(data)
await tiangong(data)
break
default:
throw new Error('未找到对应的 AI 配置')
}
set((state) => ({
messages: [...state.messages, { role: 'assistant', content: response }],
isLoading: false,
}))
} catch (err) {
console.error('AI 请求失败:', err)
} finally {
set({ isLoading: false })
}
},
}))
// AI API 调用函数
async function yiyan(data: any) {
const resp = await axios.post('/api/qianfan/chat', { messages: [{ role: 'user', content: data.content }] })
return resp.data.result
const getAccessToken = async () => {
const AK = 'wY7bvMpkWeZbDVq9w3EDvpjU'
const SK = 'XJwpiJWxs5HXkOtbo6tQrvYPZFJAWdAy'
const resp = await axios.post(
`/api/qianfan/oauth/2.0/token?grant_type=client_credentials&client_id=${AK}&client_secret=${SK}`
)
return resp.data.access_token
}
const resp = await axios.post(
`/api/qianfan/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=${await getAccessToken()}`,
{
messages: [{ role: 'user', content: data.content }],
}
)
useAIStore.setState((state) => ({
messages: [...state.messages, { role: 'assistant', content: resp.data.result }],
}))
}
async function deepseek(data: any) {
const resp = await axios.post('/api/deepseek/chat/completions', {
model: 'deepseek-chat',
messages: [{ role: 'user', content: data.content }],
})
return resp.data.choices[0]?.message?.content || ''
const apiKey = 'sk-f1a6f0a7013241de8393cb2cb108e777'
const resp = await axios.post(
'/api/deepseek/chat/completions',
{
model: 'deepseek-chat',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
useAIStore.setState((state) => ({
messages: [...state.messages, { role: 'assistant', content: choice.message.content }],
}))
}
}
async function qwen(data: any) {
const resp = await axios.post('/api/qwen/chat/completions', {
model: 'qwen-max',
messages: [{ role: 'user', content: data.content }],
})
return resp.data.choices[0]?.message?.content || ''
const apiKey = 'sk-afd0fcdb53bf4058b2068b8548820150'
const resp = await axios.post(
'/api/qwen/compatible-mode/v1/chat/completions',
{
model: 'qwen-max',
messages: [{ role: 'user', content: data.content }],
},
{
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
}
)
if (resp.data) {
const [choice = {}] = resp.data.choices
useAIStore.setState((state) => ({
messages: [...state.messages, { role: 'assistant', content: choice.message.content }],
}))
}
}
async function tiangong(data: any) {
const resp = await axios.post('/api/tiangong/chat', {
chat_history: [{ role: 'user', content: data.content }],
const appKey = 'a8701b73637562d33a53c668a90ee3be'
const appSecret = 'e191593f486bb88a39c634f46926762dddc97b9082e192af'
const timestamp = Math.floor(Date.now() / 1000).toString()
const sign = md5(`${appKey}${appSecret}${timestamp}`)
return await fetchEventSource('/api/tiangong/sky-saas-writing/api/v1/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json', app_key: appKey, sign, timestamp, stream: 'true' },
body: JSON.stringify({
chat_history: [{ role: 'user', content: data.content }],
stream_resp_type: 'update',
}),
onmessage(res) {
console.log(res.data)
const message = JSON.parse(res.data)
if (message.type !== 1) return
useAIStore.setState((state) => {
const messageId = message.conversation_id
const messageIndex = state.messages.findIndex((msg) => msg.id === messageId)
const content = message?.arguments?.[0]?.messages?.[0]?.text || ''
if (messageIndex === -1) {
return { messages: [...state.messages, { id: messageId, role: 'assistant', content }] }
} else {
return {
messages: state.messages.map((msg) => (msg.id === messageId ? { ...msg, content } : msg)),
}
}
})
},
onerror(err) {
useAIStore.setState({ isLoading: false })
throw err
},
})
return resp.data.result
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论