Skip to content

Commit 5558485

Browse files
committed
feat: chart & export Excel support large number data
#233
1 parent 5b97cf3 commit 5558485

File tree

9 files changed

+78
-15
lines changed

9 files changed

+78
-15
lines changed

backend/apps/chat/api/chat.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
import io
33
import traceback
44

5-
import numpy as np
65
import orjson
76
import pandas as pd
87
from fastapi import APIRouter, HTTPException
98
from fastapi.responses import StreamingResponse
109
from sqlalchemy import and_, select
1110

1211
from apps.chat.curd.chat import list_chats, get_chat_with_records, create_chat, rename_chat, \
13-
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id
12+
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id, \
13+
format_json_data
1414
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, ExcelData
1515
from apps.chat.task.llm import LLMService
1616
from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans
@@ -45,15 +45,17 @@ def inner():
4545
@router.get("/record/get/{chart_record_id}/data")
4646
async def chat_record_data(session: SessionDep, chart_record_id: int):
4747
def inner():
48-
return get_chat_chart_data(chart_record_id=chart_record_id, session=session)
48+
data = get_chat_chart_data(chart_record_id=chart_record_id, session=session)
49+
return format_json_data(data)
4950

5051
return await asyncio.to_thread(inner)
5152

5253

5354
@router.get("/record/get/{chart_record_id}/predict_data")
5455
async def chat_predict_data(session: SessionDep, chart_record_id: int):
5556
def inner():
56-
return get_chat_predict_data(chart_record_id=chart_record_id, session=session)
57+
data = get_chat_predict_data(chart_record_id=chart_record_id, session=session)
58+
return format_json_data(data)
5759

5860
return await asyncio.to_thread(inner)
5961

@@ -211,21 +213,53 @@ def inner():
211213
detail=trans("i18n_excel_export.data_is_empty")
212214
)
213215

216+
# 预处理数据并记录每列的格式类型
217+
col_formats = {} # 格式类型:'text'(文本)、'number'(数字)、'default'(默认)
218+
for field_idx, field in enumerate(excel_data.axis):
219+
_fields_list.append(field.name)
220+
col_formats[field_idx] = 'default' # 默认不特殊处理
221+
214222
for _data in excel_data.data:
215223
_row = []
216-
for field in excel_data.axis:
217-
_row.append(_data.get(field.value))
224+
for field_idx, field in enumerate(excel_data.axis):
225+
value = _data.get(field.value)
226+
if value is not None:
227+
# 检查是否为数字且需要特殊处理
228+
if isinstance(value, (int, float)):
229+
# 整数且超过15位 → 转字符串并标记为文本列
230+
if isinstance(value, int) and len(str(abs(value))) > 15:
231+
value = str(value)
232+
col_formats[field_idx] = 'text'
233+
# 小数且超过15位有效数字 → 转字符串并标记为文本列
234+
elif isinstance(value, float):
235+
decimal_str = format(value, '.16f').rstrip('0').rstrip('.')
236+
if len(decimal_str) > 15:
237+
value = str(value)
238+
col_formats[field_idx] = 'text'
239+
# 其他数字列标记为数字格式(避免科学记数法)
240+
elif col_formats[field_idx] != 'text':
241+
col_formats[field_idx] = 'number'
242+
_row.append(value)
218243
data.append(_row)
219-
for field in excel_data.axis:
220-
_fields_list.append(field.name)
221-
df = pd.DataFrame(np.array(data), columns=_fields_list)
244+
245+
df = pd.DataFrame(data, columns=_fields_list)
222246

223247
buffer = io.BytesIO()
224248

225249
with pd.ExcelWriter(buffer, engine='xlsxwriter',
226-
engine_kwargs={'options': {'strings_to_numbers': True}}) as writer:
250+
engine_kwargs={'options': {'strings_to_numbers': False}}) as writer:
227251
df.to_excel(writer, sheet_name='Sheet1', index=False)
228252

253+
# 获取 xlsxwriter 的工作簿和工作表对象
254+
workbook = writer.book
255+
worksheet = writer.sheets['Sheet1']
256+
257+
for col_idx, fmt_type in col_formats.items():
258+
if fmt_type == 'text':
259+
worksheet.set_column(col_idx, col_idx, None, workbook.add_format({'num_format': '@'}))
260+
elif fmt_type == 'number':
261+
worksheet.set_column(col_idx, col_idx, None, workbook.add_format({'num_format': '0'}))
262+
229263
buffer.seek(0)
230264
return io.BytesIO(buffer.getvalue())
231265

backend/apps/chat/curd/chat.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,30 @@ def get_last_execute_sql_error(session: SessionDep, chart_id: int):
8686
return None
8787

8888

89+
def format_json_data(origin_data: dict):
90+
result = {'fields': origin_data.get('fields') if origin_data.get('fields') else []}
91+
data = []
92+
for _data in origin_data.get('data') if origin_data.get('data') else []:
93+
_row = {}
94+
for key, value in _data.items():
95+
if value is not None:
96+
# 检查是否为数字且需要特殊处理
97+
if isinstance(value, (int, float)):
98+
# 整数且超过15位 → 转字符串并标记为文本列
99+
if isinstance(value, int) and len(str(abs(value))) > 15:
100+
value = str(value)
101+
# 小数且超过15位有效数字 → 转字符串并标记为文本列
102+
elif isinstance(value, float):
103+
decimal_str = format(value, '.16f').rstrip('0').rstrip('.')
104+
if len(decimal_str) > 15:
105+
value = str(value)
106+
_row[key] = value
107+
data.append(_row)
108+
result['data'] = data
109+
110+
return result
111+
112+
89113
def get_chat_chart_data(session: SessionDep, chart_record_id: int):
90114
stmt = select(ChatRecord.data).where(and_(ChatRecord.id == chart_record_id))
91115
res = session.execute(stmt)
@@ -94,7 +118,7 @@ def get_chat_chart_data(session: SessionDep, chart_record_id: int):
94118
return orjson.loads(row.data)
95119
except Exception:
96120
pass
97-
return []
121+
return {}
98122

99123

100124
def get_chat_predict_data(session: SessionDep, chart_record_id: int):
@@ -105,7 +129,7 @@ def get_chat_predict_data(session: SessionDep, chart_record_id: int):
105129
return orjson.loads(row.predict_data)
106130
except Exception:
107131
pass
108-
return []
132+
return {}
109133

110134

111135
def get_chat_with_records_with_data(session: SessionDep, chart_id: int, current_user: CurrentUser,

backend/apps/chat/task/llm.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from datetime import datetime
99
from typing import Any, List, Optional, Union, Dict, Iterator
1010

11-
import numpy as np
1211
import orjson
1312
import pandas as pd
1413
import requests
@@ -1078,7 +1077,7 @@ def run_task(self, in_chat: bool = True, stream: bool = True,
10781077
if not data or not _fields_list:
10791078
yield 'The SQL execution result is empty.\n\n'
10801079
else:
1081-
df = pd.DataFrame(np.array(data), columns=_fields_list)
1080+
df = pd.DataFrame(data, columns=_fields_list)
10821081
markdown_table = df.to_markdown(index=False)
10831082
yield markdown_table + '\n\n'
10841083
else:
@@ -1138,7 +1137,7 @@ def run_task(self, in_chat: bool = True, stream: bool = True,
11381137
if not data or not _fields_list:
11391138
yield 'The SQL execution result is empty.\n\n'
11401139
else:
1141-
df = pd.DataFrame(np.array(data), columns=_fields_list)
1140+
df = pd.DataFrame(data, columns=_fields_list)
11421141
markdown_table = df.to_markdown(index=False)
11431142
yield markdown_table + '\n\n'
11441143

frontend/src/views/chat/component/charts/Bar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export class Bar extends BaseG2Chart {
8888
},
8989
y: {
9090
nice: true,
91+
type: 'linear',
9192
},
9293
},
9394
interaction: {

frontend/src/views/chat/component/charts/Column.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export class Column extends BaseG2Chart {
7777
},
7878
y: {
7979
nice: true,
80+
type: 'linear',
8081
},
8182
},
8283
interaction: {

frontend/src/views/chat/component/charts/Line.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class Line extends BaseG2Chart {
5151
},
5252
y: {
5353
nice: true,
54+
type: 'linear',
5455
},
5556
},
5657
children: [

g2-ssr/charts/bar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function getBarOptions(baseOptions, axis, data) {
6969
},
7070
y: {
7171
nice: true,
72+
type: 'linear',
7273
},
7374
},
7475
interaction: {

g2-ssr/charts/column.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ function getColumnOptions(baseOptions, axis, data) {
6868
},
6969
y: {
7070
nice: true,
71+
type: 'linear',
7172
},
7273
},
7374
interaction: {

g2-ssr/charts/line.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function getLineOptions(baseOptions, axis, data) {
4242
},
4343
y: {
4444
nice: true,
45+
type: 'linear',
4546
},
4647
},
4748
children: [

0 commit comments

Comments
 (0)