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

chore: update

上级 d0308fdc
......@@ -14,6 +14,7 @@
"antd": "^5.24.3",
"axios": "^1.8.1",
"blueimp-md5": "^2.19.0",
"lodash-es": "^4.17.21",
"lucide-react": "^0.477.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
......@@ -25,6 +26,7 @@
},
"devDependencies": {
"@types/blueimp-md5": "^2.18.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.9",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
......@@ -1468,6 +1470,23 @@
"@types/unist": "*"
}
},
"node_modules/@types/lodash": {
"version": "4.17.16",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
"integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
......@@ -3284,6 +3303,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
......
......@@ -16,6 +16,7 @@
"antd": "^5.24.3",
"axios": "^1.8.1",
"blueimp-md5": "^2.19.0",
"lodash-es": "^4.17.21",
"lucide-react": "^0.477.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
......@@ -27,6 +28,7 @@
},
"devDependencies": {
"@types/blueimp-md5": "^2.18.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.9",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
......
......@@ -33,3 +33,13 @@ export async function uploadFile(data: Record<string, any>) {
export function getMapList() {
return httpRequest.get('/api/resource/v1/util/get-data-dictionary-list')
}
// 我的数据集列表
export function getMyList(params?: Partial<{ page: number; 'per-page': number }>) {
return httpRequest.get('/api/bi/v1/data/my/list', { params })
}
// 查看字段详情
export function getMyField() {
return httpRequest.get('/api/bi/v1/data/my/field-detail')
}
......@@ -20,9 +20,8 @@ export interface AppListRef {
reset: () => void
}
const AppList = forwardRef<AppListRef, AppListProps>((props, ref) => {
const { fetchApi, filters = [], filterAside, dataSource = [], ...rest } = props
const AppList = forwardRef<AppListRef, AppListProps>(
({ fetchApi, filters = [], filterAside, dataSource = [], ...props }, ref) => {
const [form] = Form.useForm()
const [queryParams, setQueryParams] = useState<QueryParams>({ page: 1, 'per-page': 10 })
......@@ -90,10 +89,11 @@ const AppList = forwardRef<AppListRef, AppListProps>((props, ref) => {
<div className="app-list">
{filters.length > 0 && filterElement}
<div className="app-list-table">
<Table rowKey="id" dataSource={data?.list} loading={isLoading} pagination={pagination} {...rest} />
<Table bordered rowKey="id" dataSource={data?.list} loading={isLoading} pagination={pagination} {...props} />
</div>
</div>
)
})
}
)
export default AppList
import { Table, TableProps } from 'antd'
export default function DataRender(props: TableProps) {
const pagination = {
showSizeChanger: true,
pageSize: 100,
pageSizeOptions: [100, 200, 500],
showTotal: (total: number) => `共${total}条数据`,
}
export default function DataRender({ pagination = {}, ...props }: TableProps) {
return (
<Table
bordered
scroll={{ x: 'max-content', y: 600 }}
scroll={{ x: 'max-content', y: 800 }}
tableLayout="auto"
size="middle"
pagination={pagination}
pagination={{
hideOnSinglePage: true,
showSizeChanger: true,
defaultPageSize: 100,
pageSizeOptions: [100, 200, 500],
showTotal: (total: number) => `共${total}条数据`,
...pagination,
}}
{...props}
/>
)
......
import { Button, Card, Flex } from 'antd'
import { lazy, ReactNode, useState } from 'react'
import { Card, Flex } from 'antd'
import AIChat from '@/components/ai/AIChat'
import ViewData from './ViewData'
import { ReactNode } from 'react'
import DataRender from './DataRender'
import { useDataQuery } from '@/hooks/useQuery'
export default function DataWrap({ title, buttons }: { title: string; buttons: ReactNode }) {
const ViewDataFiledButtonModal = lazy(() => import('./ViewDataFiledButtonModal'))
export default function DataWrap({
hasAI = true,
title,
headerRender,
buttons,
empty,
}: {
title: string
buttons?: ReactNode
headerRender?: (data: any) => ReactNode
empty?: ReactNode
hasAI: boolean
}) {
const [pagination, setPagination] = useState({ page: 1, 'per-page': 100 })
const { data, isPending } = useDataQuery(pagination)
const columns: any = data.title.map((item: any) => {
return {
title: item.name,
dataIndex: item.english_name,
align: 'center',
minWidth: 120,
}
})
const handleTableChange = (pagination: any) => {
setPagination({ page: pagination.current, 'per-page': pagination.pageSize })
}
const Header = () => {
if (headerRender) return headerRender(data)
return (
<Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
<Flex justify="space-between" style={{ marginBottom: '20px' }}>
<Flex wrap gap={10}>
{buttons}
</Flex>
<Button>查看字段详细信息</Button>
<ViewDataFiledButtonModal></ViewDataFiledButtonModal>
</Flex>
<ViewData />
)
}
return (
<Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
{empty && !data.info ? (
empty
) : (
<>
<Header />
<DataRender
rowKey={'pk_id'}
loading={isPending}
dataSource={data.list}
pagination={{ total: data.total, current: pagination.page, pageSize: pagination['per-page'] }}
columns={columns}
onChange={handleTableChange}
/>
</>
)}
</Card>
<AIChat></AIChat>
{hasAI && <AIChat />}
</Flex>
)
}
差异被折叠。
import { useEffect, useState } from 'react'
import { Button, Flex, Modal } from 'antd'
import axios from 'axios'
import DataRender from './DataRender'
import { read, utils } from 'xlsx'
import { uniqueId } from 'lodash-es'
export default function ViewDataButtonModal({ data }: { data: any }) {
const [open, setOpen] = useState(false)
const [dataset, setDataset] = useState<any[]>([])
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const workbook = async () => {
setIsLoading(true)
try {
const file = JSON.parse(data.file)
const res = await axios(file.url, { responseType: 'arraybuffer' })
const workbook = read(res.data)
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
const jsonData = utils.sheet_to_json(worksheet)
setDataset(jsonData)
} catch (error) {
console.error(error)
}
setIsLoading(false)
}
if (open) workbook()
}, [open, data])
const columns: any =
dataset.length > 0
? Object.keys(dataset[0]).map((key) => ({
title: key,
dataIndex: key,
align: 'center',
}))
: []
const dataSource = dataset.map((item) => ({ ...item, pk_id: uniqueId() }))
return (
<>
<Button color="primary" variant="text" onClick={() => setOpen(true)}>
查阅
</Button>
<Modal title="查阅数据集" width={'80%'} open={open} footer={null} onCancel={() => setOpen(false)}>
<Flex justify="space-between" style={{ marginBottom: 20 }}>
<div>数据集名称:{data.name}</div>
<div>共计:{data.number}条数据</div>
</Flex>
<DataRender rowKey={'pk_id'} dataSource={dataSource} columns={columns} loading={isLoading}></DataRender>
</Modal>
</>
)
}
import { useState } from 'react'
import { Button, Modal } from 'antd'
import { useDataFieldQuery } from '@/hooks/useQuery'
import AppList, { AppListProps } from '@/components/AppList'
export default function ViewDataFiledButtonModal({ children }: { children?: string }) {
const [open, setOpen] = useState(false)
const { data } = useDataFieldQuery()
const listOptions: AppListProps = {
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{ title: '字段名称', dataIndex: 'english_name', align: 'center' },
{ title: '字段类型', dataIndex: 'type_name', align: 'center' },
{ title: '长度', dataIndex: 'length', align: 'center' },
{ title: '小数点', dataIndex: 'point', align: 'center' },
{ title: '是否允许为空', dataIndex: 'null', align: 'center' },
{ title: '默认值', dataIndex: 'default', align: 'center' },
{ title: '备注', dataIndex: 'name', align: 'center' },
],
dataSource: data || [],
}
return (
<>
<Button onClick={() => setOpen(true)}>{children ? children : '查看字段详细信息'}</Button>
<Modal title="查看字段详细信息" width={1000} open={open} footer={null} onCancel={() => setOpen(false)}>
<AppList rowKey="english_name" pagination={false} {...listOptions}></AppList>
</Modal>
</>
)
}
差异被折叠。
.app-header {
position: sticky;
top: 0;
z-index: 2001;
z-index: 100;
padding: 0 20px;
display: flex;
align-items: center;
......
import { useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { getUser, getMapList } from '@/api/base'
import { useQuery, keepPreviousData } from '@tanstack/react-query'
import { getUser, getMapList, getMyList, getMyField } from '@/api/base'
import { useUserStore } from '@/stores/user'
import { useMapStore } from '@/stores/map'
......@@ -31,3 +31,27 @@ export function useMapQuery() {
return query
}
// 我的数据集
export function useDataQuery(params?: Partial<{ page: number; 'per-page': number }>) {
const query = useQuery({
queryKey: ['data', params],
queryFn: () => {
return getMyList(params)
},
select: (res) => res.data,
initialData: (): any => {
return { data: { total: 0, list: [], title: [] } }
},
placeholderData: keepPreviousData,
})
return query
}
// 查看字段详情
export function useDataFieldQuery() {
const query = useQuery({ queryKey: ['data-filed'], queryFn: getMyField, select: (res) => res.data })
return query
}
......@@ -4,7 +4,7 @@ import AppUpload from '@/components/AppUpload'
import { useCreateDataset } from '../query'
import { useState } from 'react'
export default function FormButtonModal({ title, children }: { title: string; children?: React.ReactNode }) {
export default function FormButtonModal({ title = '添加数据集' }: { title?: string }) {
const [open, setOpen] = useState(false)
const [form] = Form.useForm()
......@@ -27,13 +27,9 @@ export default function FormButtonModal({ title, children }: { title: string; ch
return (
<>
{children ? (
children
) : (
<Button type="primary" onClick={() => setOpen(true)}>
{title}
</Button>
)}
<Modal
title={title}
open={open}
......
import { lazy, useState } from 'react'
import { lazy } from 'react'
import { Button, Card, Input, Select } from 'antd'
import AppList, { AppListProps } from '@/components/AppList'
import { useMapStore } from '@/stores/map'
......@@ -6,19 +6,12 @@ import { getDatasetList } from '../api'
import { useDeleteDataset } from '../query'
const FormButtonModal = lazy(() => import('../components/FormButtonModal'))
const ViewDataModal = lazy(() => import('@/components/data/ViewDataModal'))
const ViewDataButtonModal = lazy(() => import('@/components/data/ViewDataButtonModal'))
export default function DataWriteBuilt() {
const getMapValuesByKey = useMapStore((state) => state.getMapValuesByKey)
const industryList = getMapValuesByKey('bi_data_industry')
const [viewModalIsOpen, setViewModalIsOpen] = useState(false)
const handleView = (record: any) => {
console.log(record)
setViewModalIsOpen(true)
}
const { mutate } = useDeleteDataset()
const handleRemove = (record: any) => {
mutate({ id: record.id })
......@@ -67,9 +60,7 @@ export default function DataWriteBuilt() {
render(_value, record) {
return (
<>
<Button color="primary" variant="text" onClick={() => handleView(record)}>
查阅
</Button>
<ViewDataButtonModal data={record}></ViewDataButtonModal>
<Button color="danger" variant="text" onClick={() => handleRemove(record)}>
删除
</Button>
......@@ -82,8 +73,6 @@ export default function DataWriteBuilt() {
return (
<Card className="app-card" title="内置数据集管理">
<AppList bordered {...listOptions} filterAside={<FormButtonModal title="添加数据集"></FormButtonModal>}></AppList>
<ViewDataModal open={viewModalIsOpen} onCancel={() => setViewModalIsOpen(false)}></ViewDataModal>
</Card>
)
}
......@@ -14,3 +14,8 @@ export function getDatasetList(
) {
return httpRequest.get('/api/bi/v1/data/built-in/list', { params })
}
// 复制数据集
export function copyDataset(data: { id: string; name: string; force: string }) {
return httpRequest.post('/api/bi/v1/data/my/copy-built-in', data)
}
import { Button, Form, Input, Modal } from 'antd'
import { useState } from 'react'
import { useCopyDataset } from '../query'
export default function CopyButtonModal({ children, data }: { children?: string; data: any }) {
const [open, setOpen] = useState(false)
const [form] = Form.useForm()
const { mutate, isPending } = useCopyDataset()
const handleOk = () => {
form.validateFields().then((values) => {
const params = { id: data.id, force: '1', ...values }
mutate(params, {
onSuccess: () => setOpen(false),
})
})
}
return (
<>
<Button color="primary" variant="text" onClick={() => setOpen(true)}>
{children ? children : '复制'}
</Button>
<Modal
title="复制数据集"
open={open}
onOk={handleOk}
onCancel={() => setOpen(false)}
confirmLoading={isPending}
okText="复制"
destroyOnClose>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
<Form.Item label="数据集名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="原始数据集">{data.name}</Form.Item>
<Form.Item label="数据量">{data.number}</Form.Item>
<Form.Item label="所属行业">{data.industry_name}</Form.Item>
<Form.Item label="数据来源">{data.source_name}</Form.Item>
<Form.Item label="敏感等级">{data.sensitivity_level_name}</Form.Item>
<Form.Item label="访问权限">{data.access_permissions_name}</Form.Item>
<Form.Item label="创建人">{data.created_operator_name}</Form.Item>
<Form.Item label="创建时间">{data.created_time}</Form.Item>
<Form.Item label="更新时间">{data.updated_time}</Form.Item>
</Form>
</Modal>
</>
)
}
import { Form, Input, Modal } from 'antd'
export default function FormModal(props) {
return (
<Modal title="复制数据集" destroyOnClose {...props}>
<Form labelCol={{ span: 4 }} preserve={false}>
<Form.Item label="数据集名称" name="name">
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="原始数据集">电子商务案例数据集</Form.Item>
<Form.Item label="数据量">2000</Form.Item>
<Form.Item label="所属行业">电子商务</Form.Item>
<Form.Item label="数据来源">案例数据</Form.Item>
<Form.Item label="敏感等级">L1一般敏感</Form.Item>
<Form.Item label="访问权限">完全开放</Form.Item>
<Form.Item label="创建人">张三疯</Form.Item>
<Form.Item label="创建时间">2024-12-12 13:13:13</Form.Item>
<Form.Item label="更新时间">2024-12-12 13:13:13</Form.Item>
</Form>
</Modal>
)
}
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { copyDataset } from './api'
import { message } from 'antd'
// 复制
export function useCopyDataset() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { id: string; name: string; force: string }) => copyDataset(data),
onSuccess: () => {
message.success('复制成功')
queryClient.invalidateQueries({ queryKey: ['data'] })
},
})
}
import { Button, Card, Input, Select } from 'antd'
import { lazy } from 'react'
import { Card, Input, Select } from 'antd'
import AppList, { AppListProps } from '@/components/AppList'
import { useMapStore } from '@/stores/map'
import { lazy, useState } from 'react'
import { getDatasetList } from '../api'
const CopyModal = lazy(() => import('../components/CopyModal'))
const ViewDataModal = lazy(() => import('@/components/data/ViewDataModal'))
const CopyButtonModal = lazy(() => import('../components/CopyButtonModal'))
const ViewDataButtonModal = lazy(() => import('@/components/data/ViewDataButtonModal'))
export default function DataWriteCopy() {
const getMapValuesByKey = useMapStore((state) => state.getMapValuesByKey)
const industryList = getMapValuesByKey('bi_data_industry')
const [viewModalIsOpen, setViewModalIsOpen] = useState(false)
const handleView = (record: any) => {
console.log(record)
setViewModalIsOpen(true)
}
const [copyModalIsOpen, setCopyModalIsOpen] = useState(false)
const handleCopy = (record: any) => {
console.log(record)
setCopyModalIsOpen(true)
}
const listOptions: AppListProps = {
fetchApi: async (params) => {
const { data } = await getDatasetList({ ...params, access_permissions: '1' })
......@@ -67,12 +54,8 @@ export default function DataWriteCopy() {
render(_value, record) {
return (
<>
<Button color="primary" variant="text" onClick={() => handleView(record)}>
查阅
</Button>
<Button color="primary" variant="text" onClick={() => handleCopy(record)}>
复制
</Button>
<ViewDataButtonModal data={record}></ViewDataButtonModal>
<CopyButtonModal data={record}></CopyButtonModal>
</>
)
},
......@@ -82,8 +65,6 @@ export default function DataWriteCopy() {
return (
<Card className="app-card" title="数据复制">
<AppList bordered {...listOptions}></AppList>
<CopyModal open={copyModalIsOpen} onCancel={() => setCopyModalIsOpen(false)}></CopyModal>
<ViewDataModal open={viewModalIsOpen} onCancel={() => setViewModalIsOpen(false)}></ViewDataModal>
</Card>
)
}
import { Button, Card, Empty, Flex, Space } from 'antd'
import { Link } from 'react-router'
import { Button, Empty, Flex, Space } from 'antd'
import DataWrap from '@/components/data/DataWrap'
export default function DataWriteMy() {
const isEmpty = true
// 无数据渲染
const emptyRender = () => {
// 无数据渲染
const EmptyRender = () => {
return (
<>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="无数据"></Empty>
......@@ -30,23 +28,19 @@ export default function DataWriteMy() {
</Flex>
</>
)
}
}
export default function DataWriteMy() {
return (
<Card className="app-card" title="我的数据集">
<Flex justify="space-between" align="middle">
<h4>数据集名称:电子商务案例分析数据集</h4>
<Button type="primary" disabled={isEmpty}>
数据质量分析报告
</Button>
</Flex>
{!isEmpty && (
<Flex justify="space-between" align="middle">
<p>《商务数据分析基础》数据集-电子商务2025.xlsx</p>
<p>共计:1000条数据</p>
<DataWrap
title="我的数据集"
hasAI={false}
headerRender={(data) => (
<Flex justify="space-between" align="middle" style={{ marginBottom: '20px' }}>
<h4>数据集名称:{data.info.name}</h4>
<Button type="primary">数据质量分析报告</Button>
</Flex>
)}
{isEmpty && emptyRender()}
</Card>
empty={<EmptyRender />}></DataWrap>
)
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论