feat(workspace): xlsx在线查看/编辑接入(2026-09-18用户要求)

- office_read.dspy: 按扩展名分流——xlsx/xlsm调univer-office的xlsx-to-workbookdata
  返回workbookData;docx原链路不变(顺带f-string改拼接合规)
- office_save.dspy: workbookData/documentData双字段分流,xlsx调workbook-data-to-xlsx回写
- workspace_view/open.dspy: office_exts扩为['.docx','.xlsx','.xlsm'],
  xlsx与docx同享在线查看(mode=view只读)+在线编辑+下载按钮
- 配套: univer-office仓11d83bc(IWorkbookData双向转换器+sheets插件+44断言往返自测)
This commit is contained in:
ymq 2026-09-18 10:59:51 +08:00
parent 0438a9c4b5
commit 964f475067
4 changed files with 54 additions and 30 deletions

View File

@ -39,22 +39,37 @@ if not real_full.startswith(real_ws + os.sep):
if not os.path.isfile(full_path):
return {"error": "文件不存在", "markdown": ""}
# 读 docx bytes
with open(full_path, 'rb') as f:
docx_bytes = f.read()
# 按扩展名分流xlsx → IWorkbookData表格其余docx→ IDocumentData文档
# 2026-09-18 xlsx 在线查看/编辑接入)
_is_sheet = file_id.lower().endswith(('.xlsx', '.xlsm'))
# 调 univer-office 转 IDocumentData格式保真跳过 markdown
# 读文件 bytes
with open(full_path, 'rb') as f:
file_bytes = f.read()
# 调 univer-office 转换(格式保真,跳过 markdown
try:
timeout = aiohttp.ClientTimeout(total=60)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
'http://127.0.0.1:19091/convert/docx-to-docdata',
data=docx_bytes,
headers={'Content-Type': 'application/octet-stream'},
) as resp:
if resp.status != 200:
return {"error": f"转换失败 HTTP {resp.status}", "documentData": None}
doc_data = await resp.json()
return {"documentData": doc_data}
if _is_sheet:
async with session.post(
'http://127.0.0.1:19091/convert/xlsx-to-workbookdata',
data=file_bytes,
headers={'Content-Type': 'application/octet-stream'},
) as resp:
if resp.status != 200:
return {"error": "转换失败 HTTP " + str(resp.status), "workbookData": None}
workbook_data = await resp.json()
return {"workbookData": workbook_data}
else:
async with session.post(
'http://127.0.0.1:19091/convert/docx-to-docdata',
data=file_bytes,
headers={'Content-Type': 'application/octet-stream'},
) as resp:
if resp.status != 200:
return {"error": "转换失败 HTTP " + str(resp.status), "documentData": None}
doc_data = await resp.json()
return {"documentData": doc_data}
except Exception as e:
return {"error": str(e), "documentData": None}
return {"error": str(e), "documentData": None, "workbookData": None}

View File

@ -5,6 +5,8 @@ import aiohttp
file_id = (params_kw or {}).get('file', '').strip()
docdata_str = (params_kw or {}).get('documentData', '') or ''
# xlsx 保存:前端传 workbookData 字段2026-09-18 xlsx 在线编辑接入)
workbookdata_str = (params_kw or {}).get('workbookData', '') or ''
uid = await get_user()
if not uid:
@ -30,13 +32,16 @@ async with DBPools().sqlorContext(dbname) as sor:
if not file_id or not space_dir:
return {"success": False, "error": "未指定文件或无工作空间"}
if not docdata_str:
# 按扩展名分流xlsx 用 workbookData 字段docx 用 documentData 字段
_is_sheet = file_id.lower().endswith(('.xlsx', '.xlsm'))
_payload_str = workbookdata_str if _is_sheet else docdata_str
if not _payload_str:
return {"success": False, "error": "缺少文档数据"}
try:
documentData = _json.loads(docdata_str)
_payload = _json.loads(_payload_str)
except Exception as e:
return {"success": False, "error": f"文档数据解析失败: {e}"}
return {"success": False, "error": "文档数据解析失败: " + str(e)}
full_path = resolve_workspace_path(project_dir, space_dir, file_id)
@ -52,24 +57,28 @@ if not os.path.isfile(full_path):
title = os.path.basename(full_path)
# 调 univer-office 生成 docxIDocumentData -> docx保留格式
# 调 univer-office 生成 office 文件(按扩展名分流,保留格式)
# xlsx: IWorkbookData -> xlsxdocx: IDocumentData -> docx
try:
timeout = aiohttp.ClientTimeout(total=120)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
'http://127.0.0.1:19091/convert/doc-data-to-docx',
json={'documentData': documentData, 'title': title},
) as resp:
if _is_sheet:
_url = 'http://127.0.0.1:19091/convert/workbook-data-to-xlsx'
_req_json = {'workbookData': _payload}
else:
_url = 'http://127.0.0.1:19091/convert/doc-data-to-docx'
_req_json = {'documentData': _payload, 'title': title}
async with session.post(_url, json=_req_json) as resp:
if resp.status != 200:
return {"success": False, "error": f"导出失败 HTTP {resp.status}"}
docx_bytes = await resp.read()
return {"success": False, "error": "导出失败 HTTP " + str(resp.status)}
out_bytes = await resp.read()
except Exception as e:
return {"success": False, "error": str(e)}
# 写回工作空间
try:
with open(full_path, 'wb') as f:
f.write(docx_bytes)
return {"success": True, "bytes": len(docx_bytes)}
f.write(out_bytes)
return {"success": True, "bytes": len(out_bytes)}
except Exception as e:
return {"success": False, "error": str(e)}

View File

@ -54,7 +54,7 @@ text_exts = ['.md', '.py', '.js', '.html', '.css', '.json', '.txt', '.xml', '.ya
video_exts = ['.mp4', '.m3u8', '.mpd', '.webm', '.mov', '.avi', '.mkv']
audio_exts = ['.mp3', '.wav', '.aac', '.flac', '.m4a', '.ogg']
image_exts = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp', '.ico']
office_exts = ['.docx'] # 在线编辑univer-officexlsx/ppt 后续接入 Univer sheet/slide 插件
office_exts = ['.docx', '.xlsx', '.xlsm'] # 在线查看/编辑univer-office。ppt 后续接入 Univer slide 插件
# 二级 URL 双透传 session_id + pipeline_id2026-09-17 根因同 workspace_view/upload
# 通用助手pipeline_id=_generic、无 session_id下漏拼 pipeline_id 会让下游端点

View File

@ -139,9 +139,9 @@ if ext in image_exts:
}]
}
# office 文档docx→ univer-office 在线查看mode=view 只读,不显示保存按钮)+ 下载按钮
# 2026-09-18 用户要求:查看和编辑都在线,另提供下载按钮)
office_exts = ['.docx']
# office 文档docx/xlsx)→ univer-office 在线查看mode=view 只读,不显示保存按钮)+ 下载按钮
# 2026-09-18 用户要求:查看和编辑都在线,另提供下载按钮;同日 xlsx 接入
office_exts = ['.docx', '.xlsx', '.xlsm']
if ext in office_exts:
univer_url = entire_url("/univer-office/") + "?file=" + quote(file_id) + "&mode=view" + _fq_s
download_url_v = file_url + "&download=1"