pipeline-sdlc/wwwroot/api/workspace_open.dspy
ymq 0438a9c4b5 feat(workspace): office文件查看/编辑均univer在线+弹窗顶部下载按钮(2026-09-18用户要求)
- workspace_view.dspy(查看): docx从'需下载后查看'提示弹窗改为univer在线查看
  (mode=view只读,无保存按钮)+顶部'⬇下载'按钮+只读提示文案
- workspace_open.dspy(编辑): 保留univer在线编辑,顶部加'⬇下载'按钮+保存引导文案
- 布局: VBox filler→工具行HBox+Iframe filler(高度链完整)
- univer_url/file_url均沿用_fq_s双透传(session_id+pipeline_id)
- 配套: univer-office前端main.ts mode=view不渲染浮动保存按钮(独立部署)
2026-09-18 08:59:34 +08:00

160 lines
6.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# workspace_open.dspy - 打开文件:文本/源码→vi编辑,office/pdf→下载,媒体→播放
import os
import json as _json
from urllib.parse import quote, unquote
def _decode_id(s):
for _ in range(4):
if '%' not in s:
break
s2 = unquote(s)
if s2 == s:
break
s = s2
return s
file_id = _decode_id((params_kw or {}).get('id', '').strip())
uid = await get_user()
if not uid:
uid = 'user-01'
session_id = (params_kw or {}).get('session_id', '') or ''
pipeline_id = (params_kw or {}).get('pipeline_id', '') or ''
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
# 产线隔离:跨产线项目视为无项目(与弹窗入口一致,防绕过)
project_dir, _ = await get_project_dir_pl(sor, uid, session_id, pipeline_id)
space_dir, _ = await get_space_dir(sor, uid, session_id)
# 通用会话(pipeline_id=_generic):锁用户专属目录 _general/{uid},
# 不进入任何项目的工作空间(2026-09-08 用户要求)
if pipeline_id == '_generic':
_gd = generic_workspace_dir(uid)
os.makedirs(_gd, exist_ok=True)
project_dir = _gd
space_dir = _gd
if not file_id:
return {"widgettype": "Message", "options": {"title": "打开失败", "message": "未指定文件"}}
full_path = resolve_workspace_path(project_dir, space_dir, file_id)
if not os.path.isfile(full_path):
return {"widgettype": "Message", "options": {"title": "打开失败", "message": "文件不存在: " + file_id}}
name = os.path.basename(full_path)
ext = os.path.splitext(name)[1].lower()
text_exts = ['.md', '.py', '.js', '.html', '.css', '.json', '.txt', '.xml', '.yaml', '.yml',
'.toml', '.cfg', '.sh', '.sql', '.dspy', '.ui', '.csv', '.ts', '.tsx', '.jsx',
'.java', '.c', '.cpp', '.h', '.go', '.rs', '.rb', '.php', '.vue', '.scss', '.less', '.ini']
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-office)。xlsx/ppt 后续接入 Univer sheet/slide 插件
# 二级 URL 双透传 session_id + pipeline_id(2026-09-17 根因同 workspace_view/upload):
# 通用助手(pipeline_id=_generic、无 session_id)下漏拼 pipeline_id 会让下游端点
# (workspace_file/workspace_edit.xterm/univer-office)回退全局项目指针 → 「文件不存在」。
_fq = []
if session_id:
_fq.append("session_id=" + session_id)
if pipeline_id:
_fq.append("pipeline_id=" + pipeline_id)
_fq_s = ("&" + "&".join(_fq)) if _fq else ""
file_url = entire_url("/pipeline-sdlc/api/workspace_file.dspy") + "?id=" + quote(file_id) + _fq_s
# 1. 文本/源码 → Wterm + vi 编辑
if ext in text_exts:
ws_url = entire_url("/wss/pipeline-sdlc/workspace_edit.xterm") + "?id=" + quote(file_id) + _fq_s
return {
"widgettype": "PopupWindow",
"options": {"title": name + " (vi 编辑)", "width": "85%", "height": "85%", "auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "Wterm",
"options": {
"width": "100%",
"height": "100%",
"term_options": {"fontSize": 14, "cursorBlink": True},
"ws_url": ws_url
}
}]
}
# 2. 媒体 → 播放
if ext in video_exts:
return {
"widgettype": "PopupWindow",
"options": {"title": name, "width": "70%", "height": "70%", "auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "VideoPlayer",
"options": {"url": file_url, "width": "100%", "height": "100%"}
}]
}
if ext in audio_exts:
return {
"widgettype": "PopupWindow",
"options": {"title": name, "width": "50%", "height": "30%", "auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "AudioPlayer",
"options": {"url": file_url, "width": "100%", "height": "100%"}
}]
}
# 3. 图片 → 预览
if ext in image_exts:
return {
"widgettype": "PopupWindow",
"options": {"title": name, "width": "70%", "height": "70%", "auto_open": True, "resizable": True},
"subwidgets": [
{"widgettype": "Image",
"options": {"url": file_url, "width": "100%", "height": "100%"}}
]
}
# 4. office 文档 → iframe 嵌入 univer-office 在线编辑 + 顶部下载按钮
# (2026-09-18 用户要求:查看和编辑都在线,另提供下载按钮)
if ext in office_exts:
univer_url = entire_url("/univer-office/") + "?file=" + quote(file_id) + _fq_s
download_url_e = file_url + "&download=1"
download_script_e = (
"window.open(" + _json.dumps(download_url_e) + ",'_blank');"
)
return {
"widgettype": "PopupWindow",
"options": {"title": name + " (在线编辑)", "width": "85%", "height": "85%", "auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "height": "100%", "width": "100%", "gap": "6px", "padding": "6px"},
"subwidgets": [
{"widgettype": "HBox",
"options": {"width": "100%", "gap": "8px", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Button", "options": {"label": "⬇ 下载", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": download_script_e}]},
{"widgettype": "Text", "options": {"text": "编辑后点右上角「💾 保存」写回工作空间", "cfontsize": 0.8, "color": "#94a3b8"}},
]},
{"widgettype": "Iframe", "options": {"url": univer_url, "css": "filler", "width": "100%", "height": "100%"}},
]
}]
}
# 5. pdf 等 → 下载后用本地应用打开
download_url = file_url + "&download=1"
download_script = (
"window.open(" + _json.dumps(download_url) + ",'_blank');"
)
return {
"widgettype": "PopupWindow",
"options": {"title": name, "cwidth": 34, "cheight": 12, "auto_open": True, "resizable": True},
"subwidgets": [
{"widgettype": "Text", "options": {"otext": "该文件类型需下载后用本地应用打开", "text": "该文件类型需下载后用本地应用打开", "i18n": True, "cfontsize": 1, "padding": "16px", "halign": "middle"}},
{"widgettype": "Button", "options": {"label": "下载文件", "css": "primary"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": download_script}]}
]
}