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

chore: update

上级 89430342
import { useDataQuery } from '@/hooks/useQuery'
import { Column, Line, Pie, Radar, Scatter, WordCloud } from '@ant-design/plots'
export default function Chart({ type = '1', ...props }) {
const { data } = useDataQuery()
switch (type) {
case '1':
return <Column data={data.list} {...props} />
case '2': {
const defaultConfig = {
data: data.list,
point: { shapeField: 'point', sizeField: 4 },
style: { lineWidth: 2 },
}
return <Line {...defaultConfig} {...props} />
}
case '3': {
const defaultConfig = {
data: data.list,
angleField: props.yField,
colorField: props.xField,
}
return <Pie {...defaultConfig} />
}
case '4':
return <Radar data={data.list} {...props} />
case '5':
return <Scatter data={data.list} {...props} />
case '7':
return <WordCloud data={data.list} {...props} />
default:
return <Column data={data.list} {...props} />
}
}
import { useState } from 'react' import { useEffect, useState } from 'react'
import { Button, Flex, Modal, Form, Divider, Select, Radio, Row, Col, Input } from 'antd' import { Button, Flex, Modal, Form, Divider, Select, Radio, Row, Col, Input } from 'antd'
import { useDataQuery, useDataFieldQuery } from '@/hooks/useQuery' import { useDataFieldQuery } from '@/hooks/useQuery'
import { useCreateChart } from '@/hooks/useChartQuery' import { useCreateChart, useUpdateChart, useViewChartQuery } from '@/hooks/useChartQuery'
import { useAI } from '@/hooks/useAI' import { useAI } from '@/hooks/useAI'
import { Column } from '@ant-design/plots' import Chart from './Chart'
interface Props { interface Props {
id?: string
type: string type: string
setOpen: (open: boolean) => void setOpen: (open: boolean) => void
} }
const ModalContent = ({ setOpen, type }: Props) => { const ModalContent = ({ setOpen, type, id = '' }: Props) => {
const { data } = useDataQuery()
const { fieldOptions } = useDataFieldQuery() const { fieldOptions } = useDataFieldQuery()
const { data: chartData } = useViewChartQuery(id)
const [form] = Form.useForm() const [form] = Form.useForm()
useEffect(() => {
if (id && chartData) {
try {
const parsedContent = JSON.parse(chartData.content)
form.setFieldsValue({ ...parsedContent.form })
} catch (e) {
console.error('解析图表数据失败:', e)
}
}
}, [id, chartData, form])
const [results, setResults] = useState({}) const [results, setResults] = useState({})
const xField = Form.useWatch('x', form) const xField = Form.useWatch('x', form)
const yField = Form.useWatch('y', form) const yField = Form.useWatch('y', form)
const colorField = Form.useWatch('colorField', form)
const title = Form.useWatch('title', form) const title = Form.useWatch('title', form)
const config = { data: data.list, xField, yField, colorField: xField, title, ...results } const config = { xField, yField, colorField, title, ...results }
const { post } = useAI({ const { post } = useAI({
onComplete: (message) => { onComplete: (message) => {
...@@ -59,17 +72,28 @@ const ModalContent = ({ setOpen, type }: Props) => { ...@@ -59,17 +72,28 @@ const ModalContent = ({ setOpen, type }: Props) => {
}) })
} }
const { mutate } = useCreateChart() const { mutate: mutateCreate } = useCreateChart()
const { mutate: mutateUpdate } = useUpdateChart()
// 保存 // 保存
const handleSubmit = () => { const handleSubmit = () => {
form.validateFields().then((values) => { form.validateFields().then((values) => {
const params = { ...values, type, content: JSON.stringify({ config }) } const params = { ...values, type, content: JSON.stringify({ form: values, config }) }
mutate(params, {
onSuccess: () => setOpen(false), if (id) {
}) mutateUpdate(
{ id, ...params },
{
onSuccess: () => setOpen(false),
}
)
} else {
mutateCreate(params, {
onSuccess: () => setOpen(false),
})
}
}) })
} }
return ( return (
<> <>
<Form <Form
...@@ -84,11 +108,11 @@ const ModalContent = ({ setOpen, type }: Props) => { ...@@ -84,11 +108,11 @@ const ModalContent = ({ setOpen, type }: Props) => {
has_title: '无', has_title: '无',
fill_image: '纯色', fill_image: '纯色',
}}> }}>
<Form.Item label="组件名称" name="name"> <Form.Item label="组件名称" name="name" rules={[{ required: true, message: '请输入组件名称' }]}>
<Input placeholder="请输入" /> <Input placeholder="请输入" />
</Form.Item> </Form.Item>
<Divider orientation="left" orientationMargin="0"> <Divider orientation="left" orientationMargin="0">
步骤1:数字字段设置 数字字段设置
</Divider> </Divider>
<Row gutter={20}> <Row gutter={20}>
<Col span={8}> <Col span={8}>
...@@ -127,17 +151,17 @@ const ModalContent = ({ setOpen, type }: Props) => { ...@@ -127,17 +151,17 @@ const ModalContent = ({ setOpen, type }: Props) => {
</Col> </Col>
</Row> </Row>
<Divider orientation="left" orientationMargin="0"> <Divider orientation="left" orientationMargin="0">
步骤2:辅助可视化设置 辅助可视化设置
</Divider> </Divider>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<Form.Item label="请选择“标签”字段" name="label"> <Form.Item label="请选择“标签”字段" name="colorField">
<Select options={fieldOptions} placeholder="请选择"></Select> <Select options={fieldOptions} placeholder="请选择"></Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label="请选择颜色规则" name="fill_color"> <Form.Item label="请选择颜色规则" name="fill_color">
<Radio.Group options={['自动颜色', '不同柱子颜色不同']}></Radio.Group> <Radio.Group options={['自动颜色']}></Radio.Group>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
...@@ -171,9 +195,9 @@ const ModalContent = ({ setOpen, type }: Props) => { ...@@ -171,9 +195,9 @@ const ModalContent = ({ setOpen, type }: Props) => {
</Col> </Col>
</Row> </Row>
<Divider orientation="left" orientationMargin="0"> <Divider orientation="left" orientationMargin="0">
步骤3:预览组件效果 预览组件效果
</Divider> </Divider>
{xField && yField && <Column {...config} />} <Chart type={type} {...config} />
</Form> </Form>
<Flex justify="center" gap={20}> <Flex justify="center" gap={20}>
...@@ -189,15 +213,16 @@ const ModalContent = ({ setOpen, type }: Props) => { ...@@ -189,15 +213,16 @@ const ModalContent = ({ setOpen, type }: Props) => {
) )
} }
export default function ButtonModal({ title = '新建柱状图', type = '1' }) { export default function ButtonModal({ type = '1', id = '' }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const title = id ? '编辑' : '新建'
return ( return (
<> <>
<Button type="primary" onClick={() => setOpen(true)}> <Button color="primary" variant={id ? 'text' : 'solid'} onClick={() => setOpen(true)}>
{title} {title}
</Button> </Button>
<Modal title={title} open={open} footer={null} destroyOnClose width={1000} onCancel={() => setOpen(false)}> <Modal title={title} open={open} footer={null} destroyOnClose width={1000} onCancel={() => setOpen(false)}>
<ModalContent type={type} setOpen={(open) => setOpen(open)} /> <ModalContent type={type} setOpen={(open) => setOpen(open)} id={id} />
</Modal> </Modal>
</> </>
) )
......
import { Button, Card, Flex } from 'antd' import { Button, Card, Flex } from 'antd'
import { ReactNode } from 'react' import { lazy, ReactNode } from 'react'
import AppList, { AppListProps } from '@/components/AppList' import AppList, { AppListProps } from '@/components/AppList'
import ViewDataButtonModal from '../data/ViewMyDataButtonModal' import ViewDataButtonModal from '../data/ViewMyDataButtonModal'
import ViewChartButtonModal from './ViewChartButtonModal' import ViewChartButtonModal from './ViewChartButtonModal'
import { getChartComponentList } from '@/api/data' import { getChartComponentList } from '@/api/data'
import { useDeleteChart } from '@/hooks/useChartQuery' import { useDeleteChart } from '@/hooks/useChartQuery'
import { CHART_TYPE } from '@/utils/constants'
const chartType: any = { const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
1: '柱状图',
2: '折线图',
3: '饼状图',
4: '雷达图',
5: '散点图',
6: '气泡图',
7: '词云',
8: '地图',
9: '指标卡',
10: '漏斗图',
11: '直方图',
12: '表格',
13: '帕累托图',
14: '矩形树图',
}
export default function DataWrap({ export default function DataWrap({
title, title,
buttons,
type, type,
}: { }: {
type: string type: string
title: string title: string
buttons: ReactNode buttons?: ReactNode
children?: ReactNode children?: ReactNode
}) { }) {
const { mutate } = useDeleteChart() const { mutate } = useDeleteChart()
...@@ -58,8 +42,8 @@ export default function DataWrap({ ...@@ -58,8 +42,8 @@ export default function DataWrap({
title: '组件类型', title: '组件类型',
dataIndex: 'type', dataIndex: 'type',
align: 'center', align: 'center',
render(value) { render(value: keyof typeof CHART_TYPE) {
return chartType[value] || value return CHART_TYPE[value] || value
}, },
}, },
{ title: '组件名称', dataIndex: 'name', align: 'center' }, { title: '组件名称', dataIndex: 'name', align: 'center' },
...@@ -75,9 +59,7 @@ export default function DataWrap({ ...@@ -75,9 +59,7 @@ export default function DataWrap({
return ( return (
<> <>
<ViewChartButtonModal id={record.id}></ViewChartButtonModal> <ViewChartButtonModal id={record.id}></ViewChartButtonModal>
<Button color="primary" variant="text"> <ButtonModal type={type} id={record.id}></ButtonModal>
编辑
</Button>
<Button color="danger" variant="text" onClick={() => handleRemove(record)}> <Button color="danger" variant="text" onClick={() => handleRemove(record)}>
删除 删除
</Button> </Button>
...@@ -92,7 +74,7 @@ export default function DataWrap({ ...@@ -92,7 +74,7 @@ export default function DataWrap({
<Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}> <Card className="app-card" title={title} style={{ flex: 1, overflowX: 'hidden' }}>
<Flex justify="space-between" style={{ marginBottom: '20px' }}> <Flex justify="space-between" style={{ marginBottom: '20px' }}>
<Flex wrap gap={10}> <Flex wrap gap={10}>
{buttons} <ButtonModal type={type}></ButtonModal>
</Flex> </Flex>
<ViewDataButtonModal></ViewDataButtonModal> <ViewDataButtonModal></ViewDataButtonModal>
</Flex> </Flex>
......
import { Button, Modal } from 'antd'
import AppList, { AppListProps } from '@/components/AppList'
import { getChartComponentList } from '@/api/data'
import { CHART_TYPE } from '@/utils/constants'
import { useState, useEffect } from 'react'
import ViewChartButtonModal from './ViewChartButtonModal'
interface SelectChartProps {
type?: string
value?: any[]
onChange?: (value: any[]) => void
}
export default function SelectChart({ type, value = [], onChange }: SelectChartProps) {
const [open, setOpen] = useState(false)
const [selectedRows, setSelectedRows] = useState<any[]>([])
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
const [dataSource, setDataSource] = useState<any[]>(value)
useEffect(() => {
setDataSource(value)
}, [value])
const listOptions: AppListProps = {
queryKey: ['chartComponentList', { type }],
fetchApi: async (params) => {
const { data } = await getChartComponentList({ ...params, type })
return { ...data }
},
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{
title: '组件类型',
dataIndex: 'type',
align: 'center',
render(value: keyof typeof CHART_TYPE) {
return CHART_TYPE[value] || value
},
},
{ title: '组件名称', dataIndex: 'name', align: 'center' },
{ title: '创建人', dataIndex: 'created_operator_name', align: 'center' },
{ title: '创建时间', dataIndex: 'created_time', align: 'center' },
{ title: '更新时间', dataIndex: 'updated_time', align: 'center' },
],
rowSelection: {
type: 'checkbox',
selectedRowKeys,
onChange: (keys: React.Key[], rows: any[]) => {
setSelectedRowKeys(keys)
setSelectedRows(rows)
},
getCheckboxProps: (record: any) => ({
disabled: dataSource.some((item) => item.id === record.id),
}),
},
}
const removeDuplicates = (originalArray: any[], newArray: any[]) => {
const uniqueArray = [...originalArray]
newArray.forEach((newItem) => {
const isDuplicate = uniqueArray.some((item) => item.id === newItem.id)
if (!isDuplicate) {
uniqueArray.push(newItem)
}
})
return uniqueArray
}
const handleRemove = (record: any) => {
const newDataSource = dataSource.filter((item) => item.id !== record.id)
setDataSource(newDataSource)
onChange?.(newDataSource)
}
const handleOk = () => {
const newDataSource = removeDuplicates(dataSource, selectedRows)
setDataSource(newDataSource)
onChange?.(newDataSource)
setSelectedRows([])
setSelectedRowKeys([])
setOpen(false)
}
const selectListOptions: AppListProps = {
dataSource,
columns: [
{
title: '序号',
key: 'index',
render(_value, _record, index) {
return index + 1
},
width: 62,
align: 'center',
},
{
title: '组件类型',
dataIndex: 'type',
align: 'center',
render(value: keyof typeof CHART_TYPE) {
return CHART_TYPE[value] || value
},
},
{ title: '组件名称', dataIndex: 'name', align: 'center' },
{
title: '操作',
key: 'x',
width: 220,
align: 'center',
render(_value, record) {
return (
<>
<ViewChartButtonModal id={record.id}></ViewChartButtonModal>
<Button color="danger" variant="text" onClick={() => handleRemove(record)}>
删除
</Button>
</>
)
},
},
],
}
return (
<>
<Button type="primary" onClick={() => setOpen(true)} style={{ marginBottom: 20 }}>
添加可视化组件
</Button>
<AppList {...selectListOptions} style={{ marginBottom: 20 }} />
<Modal title="添加可视化组件" width="1000px" open={open} onCancel={() => setOpen(false)} onOk={handleOk}>
<AppList {...listOptions} />
</Modal>
</>
)
}
import { useState } from 'react' import { useState } from 'react'
import { Button, Modal } from 'antd' import { Button, Modal } from 'antd'
import { Column } from '@ant-design/plots'
import { useViewChartQuery } from '@/hooks/useChartQuery' import { useViewChartQuery } from '@/hooks/useChartQuery'
import Chart from './Chart'
export default function ViewDataButtonModal({ id }: { id: string }) { export default function ViewDataButtonModal({ id }: { id: string }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
...@@ -34,7 +34,7 @@ function ModalContent({ id }: { id: string }) { ...@@ -34,7 +34,7 @@ function ModalContent({ id }: { id: string }) {
return ( return (
<div> <div>
<Column {...config} /> <Chart type={data.type} {...config} />
</div> </div>
) )
} }
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { createChartComponent, deleteChartComponent, getChartComponent } from '@/api/data' import { createChartComponent, updateChartComponent, deleteChartComponent, getChartComponent } from '@/api/data'
import { message } from 'antd' import { message } from 'antd'
// 创建 // 创建
...@@ -15,6 +15,19 @@ export function useCreateChart() { ...@@ -15,6 +15,19 @@ export function useCreateChart() {
}) })
} }
// 编辑
export function useUpdateChart() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { id: string; name: string; type: string; content: string }) => updateChartComponent(data),
onSuccess: () => {
message.success('编辑成功')
queryClient.invalidateQueries({ queryKey: ['chartComponentList'] })
},
})
}
// 删除 // 删除
export function useDeleteChart() { export function useDeleteChart() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
...@@ -36,5 +49,7 @@ export function useViewChartQuery(id: string) { ...@@ -36,5 +49,7 @@ export function useViewChartQuery(id: string) {
return getChartComponent({ id }) return getChartComponent({ id })
}, },
select: (res) => res.data, select: (res) => res.data,
enabled: !!id,
staleTime: 0,
}) })
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:柱状图" type="1" buttons={<ButtonModal title="新建柱状图" type="1" />}></ChartWrap> return <ChartWrap title="可视化:柱状图" type="1"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:气泡图" type="6" buttons={<ButtonModal title="新建气泡图" type="6" />}></ChartWrap> return <ChartWrap title="可视化:气泡图" type="6"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:漏斗图" type="10" buttons={<ButtonModal title="新建漏斗图" type="10" />}></ChartWrap> return <ChartWrap title="可视化:漏斗图" type="10"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:直方图" type="11" buttons={<ButtonModal title="新建直方图" type="11" />}></ChartWrap> return <ChartWrap title="可视化:直方图" type="11"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:指标卡" type="9" buttons={<ButtonModal title="新建指标卡" type="9" />}></ChartWrap> return <ChartWrap title="可视化:指标卡" type="9"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:折线图" type="2" buttons={<ButtonModal title="新建折线图" type="2" />}></ChartWrap> return <ChartWrap title="可视化:折线图" type="2"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:地图" type="8" buttons={<ButtonModal title="新建地图" type="8" />}></ChartWrap> return <ChartWrap title="可视化:地图" type="8"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return ( return <ChartWrap title="可视化:帕累托图" type="13"></ChartWrap>
<ChartWrap title="可视化:帕累托图" type="13" buttons={<ButtonModal title="新建帕累托图" type="13" />}></ChartWrap>
)
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:饼状图" type="3" buttons={<ButtonModal title="新建饼状图" type="3" />}></ChartWrap> return <ChartWrap title="可视化:饼状图" type="3"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:散点图" type="5" buttons={<ButtonModal title="新建散点图" type="5" />}></ChartWrap> return <ChartWrap title="可视化:散点图" type="5"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:雷达图" type="4" buttons={<ButtonModal title="新建雷达图" type="4" />}></ChartWrap> return <ChartWrap title="可视化:雷达图" type="4"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:表格" type="12" buttons={<ButtonModal title="新建表格" type="12" />}></ChartWrap> return <ChartWrap title="可视化:表格" type="12"></ChartWrap>
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return ( return <ChartWrap title="可视化:矩形树图" type="14"></ChartWrap>
<ChartWrap title="可视化:矩形树图" type="14" buttons={<ButtonModal title="新建矩形树图" type="14" />}></ChartWrap>
)
} }
import { lazy } from 'react'
import ChartWrap from '@/components/chart/ChartWrap' import ChartWrap from '@/components/chart/ChartWrap'
const ButtonModal = lazy(() => import('@/components/chart/ChartButtonModal'))
export default function DataProcess() { export default function DataProcess() {
return <ChartWrap title="可视化:词云" type="7" buttons={<ButtonModal title="新建词云" type="7" />}></ChartWrap> return <ChartWrap title="可视化:词云" type="7"></ChartWrap>
} }
import { useState } from 'react' import { useEffect, useState } from 'react'
import { Button, Flex, Modal, Form, Divider, Select, Radio, Input } from 'antd' import { Button, Flex, Modal, Form, Divider, Select, Radio, Input, message, ColorPicker, Space, Tooltip } from 'antd'
import { useCreateLargeScreen } from '../query' import { useCreateLargeScreen, useUpdateLargeScreen, useViewLargeScreen } from '../query'
import SelectChart from '@/components/chart/SelectChart'
import AppUpload from '@/components/AppUpload'
import {
BoldOutlined,
ItalicOutlined,
AlignLeftOutlined,
AlignCenterOutlined,
AlignRightOutlined,
} from '@ant-design/icons'
export default function ButtonModal({ title = '新建大屏' }) { interface Chart {
const [open, setOpen] = useState(false) id: string
type: string
config: any
}
interface Props {
id?: string
setOpen: (open: boolean) => void
}
const ModalContent = ({ setOpen, id = '' }: Props) => {
const [form] = Form.useForm() const [form] = Form.useForm()
const [selectedCharts, setSelectedCharts] = useState<Chart[]>([])
const { data } = useViewLargeScreen(id)
// Add form field watchers
const titleStyle = Form.useWatch('titleStyle', form)
useEffect(() => {
if (id && data) {
try {
const parsedContent = JSON.parse(data.content)
form.setFieldsValue({ ...parsedContent.form })
setSelectedCharts(parsedContent.charts)
} catch (e) {
console.error('解析数据失败:', e)
}
}
}, [id, data, form])
const { mutate: mutateCreate } = useCreateLargeScreen()
const { mutate: mutateUpdate } = useUpdateLargeScreen()
// 预览
const handlePreview = () => {
form.validateFields().then((values) => {
const ids = selectedCharts.map((item) => item.id)
if (!ids.length) {
message.warning('请至少选择一个图表')
return
}
const params = {
...values,
component_ids: ids.join(','),
content: JSON.stringify({ form: values, charts: selectedCharts }),
}
// 先保存,然后在新窗口打开预览
mutateUpdate(
{ id, ...params },
{
onSuccess: () => {
window.open(`/data/screen/view/${id}`, '_blank')
},
}
)
})
}
const { mutate } = useCreateLargeScreen()
// 保存 // 保存
const handleSubmit = () => { const handleSubmit = () => {
form.validateFields().then((values) => { form.validateFields().then((values) => {
const params = { ...values, content: JSON.stringify(values) } const ids = selectedCharts.map((item) => item.id)
mutate(params, { if (!ids.length) {
onSuccess: () => setOpen(false), message.warning('请至少选择一个图表')
}) return
}
const params = {
...values,
component_ids: ids.join(','),
content: JSON.stringify({ form: values, charts: selectedCharts }),
}
if (id) {
mutateUpdate(
{ id, ...params },
{
onSuccess: () => setOpen(false),
}
)
} else {
mutateCreate(params, {
onSuccess: () => setOpen(false),
})
}
}) })
} }
return ( return (
<> <>
<Button type="primary" onClick={() => setOpen(true)}> <Form
{title} form={form}
</Button> preserve={false}
<Modal initialValues={{
title={title} size: '1920*1080',
open={open} bg: 'default',
footer={ layout: 'auto',
<Flex justify="center" gap={20}> titleStyle: {
<Button autoInsertSpace onClick={() => setOpen(false)}> fontSize: 24,
取消 color: '#000000',
</Button> bold: false,
<Button type="primary">预览</Button> italic: false,
<Button type="primary" autoInsertSpace onClick={handleSubmit}> align: 'center',
保存 },
</Button> }}>
</Flex> <Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
} <Input placeholder="请输入名称" />
destroyOnClose </Form.Item>
width={1000} <Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]}>
onCancel={() => setOpen(false)}> <Space>
<div style={{ minHeight: 300, padding: '20px 0' }}> <Form.Item name="title" noStyle>
<Form form={form} preserve={false} initialValues={{ size: '1920*1080', bg: 'default', layout: 'auto' }}> <Input placeholder="请输入标题" />
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入' }]}>
<Input placeholder="请输入" />
</Form.Item>
<Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入' }]}>
<Input placeholder="请输入" />
</Form.Item> </Form.Item>
<Form.Item label="大小" name="size" rules={[{ required: true, message: '请选择' }]}> <Form.Item name={['titleStyle', 'fontSize']} noStyle>
<Select <Select
options={[ options={[
{ label: '1920*1080', value: '1920*1080' }, { label: '16px', value: 16 },
{ label: '1024*768', value: '1024*768' }, { label: '18px', value: 18 },
{ label: '20px', value: 20 },
{ label: '24px', value: 24 },
{ label: '28px', value: 28 },
{ label: '32px', value: 32 },
{ label: '36px', value: 36 },
{ label: '40px', value: 40 },
]} ]}
placeholder="请选择"></Select> style={{ width: 120 }}
/>
</Form.Item> </Form.Item>
<Form.Item label="背景" name="bg" rules={[{ required: true, message: '请选择' }]}> <Form.Item name={['titleStyle', 'color']} noStyle getValueFromEvent={(e) => e.toHexString()}>
<Radio.Group <ColorPicker />
options={[ </Form.Item>
{ label: '默认', value: 'default' }, <Form.Item name={['titleStyle', 'bold']} noStyle>
{ label: '纯色', value: 'color' }, <Tooltip title="加粗">
{ label: '图片', value: 'image' }, <Button
]}></Radio.Group> type={titleStyle?.bold ? 'primary' : 'default'}
icon={<BoldOutlined />}
onClick={() => form.setFieldValue(['titleStyle', 'bold'], !titleStyle?.bold)}
/>
</Tooltip>
</Form.Item> </Form.Item>
<Divider /> <Form.Item name={['titleStyle', 'italic']} noStyle>
<Button type="primary">添加可视化组件</Button> <Tooltip title="斜体">
<Form.Item label="组件布局方式" name="layout" rules={[{ required: true, message: '请选择' }]}> <Button
<Radio.Group options={[{ label: '自动布局', value: 'auto' }]}></Radio.Group> type={titleStyle?.italic ? 'primary' : 'default'}
icon={<ItalicOutlined />}
onClick={() => form.setFieldValue(['titleStyle', 'italic'], !titleStyle?.italic)}
/>
</Tooltip>
</Form.Item> </Form.Item>
</Form> <Form.Item name={['titleStyle', 'align']} noStyle>
</div> <Space>
<Tooltip title="左对齐">
<Button
type={titleStyle?.align === 'left' ? 'primary' : 'default'}
icon={<AlignLeftOutlined />}
onClick={() => form.setFieldValue(['titleStyle', 'align'], 'left')}
/>
</Tooltip>
<Tooltip title="居中">
<Button
type={titleStyle?.align === 'center' ? 'primary' : 'default'}
icon={<AlignCenterOutlined />}
onClick={() => form.setFieldValue(['titleStyle', 'align'], 'center')}
/>
</Tooltip>
<Tooltip title="右对齐">
<Button
type={titleStyle?.align === 'right' ? 'primary' : 'default'}
icon={<AlignRightOutlined />}
onClick={() => form.setFieldValue(['titleStyle', 'align'], 'right')}
/>
</Tooltip>
</Space>
</Form.Item>
</Space>
</Form.Item>
<Form.Item label="大小" name="size" rules={[{ required: true, message: '请选择大小' }]}>
<Select
options={[
{ label: '1920*1080', value: '1920*1080' },
{ label: '1024*768', value: '1024*768' },
]}
placeholder="请选择大小"
/>
</Form.Item>
<Form.Item label="背景" name="bg" rules={[{ required: true, message: '请选择背景' }]}>
<Radio.Group
options={[
{ label: '默认', value: 'default' },
{ label: '纯色', value: 'color' },
{ label: '图片', value: 'image' },
]}
/>
</Form.Item>
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.bg !== currentValues.bg}>
{({ getFieldValue }) => {
const bgType = getFieldValue('bg')
if (bgType === 'color') {
return (
<Form.Item label="背景颜色" name="bgColor" getValueFromEvent={(e) => e.toHexString()}>
<ColorPicker />
</Form.Item>
)
}
if (bgType === 'image') {
return (
<Form.Item label="背景图片" name="bgImage">
<AppUpload accept="image/*" />
</Form.Item>
)
}
return null
}}
</Form.Item>
<Divider />
<SelectChart value={selectedCharts} onChange={setSelectedCharts} />
<Form.Item label="组件布局方式" name="layout" rules={[{ required: true, message: '请选择布局方式' }]}>
<Radio.Group options={[{ label: '自动布局', value: 'auto' }]} />
</Form.Item>
</Form>
<Flex justify="center" gap={20}>
<Button autoInsertSpace onClick={() => setOpen(false)}>
取消
</Button>
{id && (
<Button type="primary" onClick={handlePreview}>
预览
</Button>
)}
<Button type="primary" autoInsertSpace onClick={handleSubmit}>
保存
</Button>
</Flex>
</>
)
}
export default function ButtonModal({ id = '' }) {
const [open, setOpen] = useState(false)
const title = id ? '编辑' : '新建'
return (
<>
<Button color="primary" variant={id ? 'text' : 'solid'} onClick={() => setOpen(true)}>
{title}
</Button>
<Modal title={title} open={open} footer={null} destroyOnClose width={1000} onCancel={() => setOpen(false)}>
<ModalContent setOpen={(open) => setOpen(open)} id={id} />
</Modal> </Modal>
</> </>
) )
......
import { useMutation, useQueryClient } from '@tanstack/react-query' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { createLargeScreen, deleteLargeScreen } from './api' import { createLargeScreen, deleteLargeScreen, getLargeScreen, updateLargeScreen } from './api'
import { message } from 'antd' import { message } from 'antd'
// 创建 // 创建
...@@ -15,6 +15,18 @@ export function useCreateLargeScreen() { ...@@ -15,6 +15,18 @@ export function useCreateLargeScreen() {
}) })
} }
// 更新
export function useUpdateLargeScreen() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: { id: string; name: string; type: string; content: string }) => updateLargeScreen(data),
onSuccess: () => {
message.success('更新成功')
queryClient.invalidateQueries({ queryKey: ['largeScreenList'] })
},
})
}
// 删除 // 删除
export function useDeleteLargeScreen() { export function useDeleteLargeScreen() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
...@@ -27,3 +39,13 @@ export function useDeleteLargeScreen() { ...@@ -27,3 +39,13 @@ export function useDeleteLargeScreen() {
}, },
}) })
} }
export function useViewLargeScreen(id: string) {
return useQuery({
queryKey: ['largeScreen', id],
queryFn: () => getLargeScreen({ id }),
select: (res) => res.data,
enabled: !!id,
staleTime: 0,
})
}
import { lazy } from 'react'
import type { RouteObject } from 'react-router'
export const routes: RouteObject[] = [
{
path: '/data/screen/view/:id',
Component: lazy(() => import('./views/View')),
},
]
...@@ -4,6 +4,7 @@ import ViewDataButtonModal from '@/components/data/ViewMyDataButtonModal' ...@@ -4,6 +4,7 @@ import ViewDataButtonModal from '@/components/data/ViewMyDataButtonModal'
import ButtonModal from '../components/ButtonModal' import ButtonModal from '../components/ButtonModal'
import { getLargeScreenList } from '../api' import { getLargeScreenList } from '../api'
import { useDeleteLargeScreen } from '../query' import { useDeleteLargeScreen } from '../query'
import { Link } from 'react-router'
export default function DataWrap() { export default function DataWrap() {
const { mutate } = useDeleteLargeScreen() const { mutate } = useDeleteLargeScreen()
...@@ -39,12 +40,12 @@ export default function DataWrap() { ...@@ -39,12 +40,12 @@ export default function DataWrap() {
render(_value, record) { render(_value, record) {
return ( return (
<> <>
<Button color="primary" variant="text"> <Link to={`/data/screen/view/${record.id}`} target="_blank">
查看 <Button color="primary" variant="text">
</Button> 查看
<Button color="primary" variant="text"> </Button>
编辑 </Link>
</Button> <ButtonModal id={record.id}></ButtonModal>
<Button color="danger" variant="text" onClick={() => handleRemove(record)}> <Button color="danger" variant="text" onClick={() => handleRemove(record)}>
删除 删除
</Button> </Button>
...@@ -56,7 +57,7 @@ export default function DataWrap() { ...@@ -56,7 +57,7 @@ export default function DataWrap() {
} }
return ( return (
<Flex gap={20} style={{ height: '100%' }}> <Flex gap={20} style={{ height: '100%' }}>
<Card className="app-card" style={{ flex: 1, overflowX: 'hidden' }}> <Card className="app-card" title="数据可视化大屏" style={{ flex: 1, overflowX: 'hidden' }}>
<Flex justify="space-between" style={{ marginBottom: '20px' }}> <Flex justify="space-between" style={{ marginBottom: '20px' }}>
<Flex wrap gap={10}> <Flex wrap gap={10}>
<ButtonModal></ButtonModal> <ButtonModal></ButtonModal>
......
import { useParams } from 'react-router'
import { useViewLargeScreen } from '../query'
import Chart from '@/components/chart/Chart'
import { Card } from 'antd'
export default function LargeScreenView() {
const { id } = useParams()
const { data } = useViewLargeScreen(id || '')
if (!data) return null
const components = data.component.map((item: any) => {
try {
const { config } = JSON.parse(item.content)
return { ...item, config }
} catch (error) {
console.log(error)
}
return item
})
try {
const { form } = JSON.parse(data.content)
const getBackground = () => {
switch (form.bg) {
case 'default':
return '#f0f2f5'
case 'color':
return form.bgColor || '#ffffff'
case 'image':
return form.bgImage ? `url(${form.bgImage?.url})` : '#ffffff'
default:
return '#ffffff'
}
}
return (
<div
style={{
width: '100vw',
minHeight: '100%',
background: getBackground(),
backgroundSize: form.bg === 'image' ? 'cover' : 'auto',
backgroundPosition: form.bg === 'image' ? 'center' : 'auto',
}}>
<div style={{ padding: 20 }}>
<h2
style={{
fontSize: `${form.titleStyle.fontSize}px`,
color: form.titleStyle.color,
fontWeight: form.titleStyle.bold ? 'bold' : 'normal',
fontStyle: form.titleStyle.italic ? 'italic' : 'normal',
textAlign: form.titleStyle.align,
marginBottom: '20px',
}}>
{form.title}
</h2>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: '20px' }}>
{components.map((item: any) => (
<Card title={item.name} key={item.id}>
<Chart type={item.type} {...item.config} />
</Card>
))}
</div>
</div>
</div>
)
} catch (e) {
console.error('解析数据失败:', e)
return null
}
}
export const CHART_TYPE = {
1: '柱状图',
2: '折线图',
3: '饼状图',
4: '雷达图',
5: '散点图',
6: '气泡图',
7: '词云',
8: '地图',
9: '指标卡',
10: '漏斗图',
11: '直方图',
12: '表格',
13: '帕累托图',
14: '矩形树图',
}
export const CHART_TYPE_OPTIONS = Object.entries(CHART_TYPE).map(([key, value]) => ({
label: value,
value: key,
}))
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论