feat(mobile): 手机端「更多」产线入口+功能按钮收缩(2026-09-08用户要求:给会话留最大空间)——my_pipelines.dspy 加 fmt=more 返回 MobileSheet 项[{name,label,icon,desc,url}],url 直达 mobile_agent.ui?pipeline_id=X&embed=1 单产线会话页(已购即出现在底部「更多」,无需改 index.ui);mobile_agent.ui 加 embed 分支(隐藏 ChipBar 胶囊条+门禁遮罩,锁定产线,AgentIO 自带 pipeline_id/session_id=m_<pid>,会话区最大化,服务端 agent_chat 硬门禁兜底);agent_menus.dspy 按 _is_mobile 端形态分流(桌面一排 Button 原样不变,手机收缩为单个「⋯ 功能」Button→MobileSheet 竖排,项 popup:true 点击走 agent_menu_open/workspace_popup 的 PopupWindow 自开链路,与桌面完全一致)

This commit is contained in:
yumoqing 2026-09-08 19:33:59 +08:00
parent 2cfbc55dc3
commit 5ad85f1b60
3 changed files with 110 additions and 53 deletions

View File

@ -54,75 +54,93 @@ try:
except Exception: except Exception:
menus = [] menus = []
# 3. 构建菜单按钮(actiontype=urlwidget 打开通用弹窗 agent_menu_open,控制弹窗大小) # 3. 构建功能入口(actiontype=urlwidget 打开通用弹窗 agent_menu_open,控制弹窗大小)
# 注意:这里不预先 urlencode 查询参数。前端 bricks_fetch 的 url_parse 会把 url 里的 # 注意:这里不预先 urlencode 查询参数。前端 bricks_fetch 的 url_parse 会把 url 里的
# 查询参数提取后再 encodeURIComponent 编码一次,若此处已 urlencode 会导致双重编码 # 查询参数提取后再 encodeURIComponent 编码一次,若此处已 urlencode 会导致双重编码
# (title 的 emoji/中文会变成 %F0%9F... 明文显示)。 # (title 的 emoji/中文会变成 %F0%9F... 明文显示)。
# #
# 「需要项目」判断在【点击时】由 agent_menu_open 服务端做(传 require_project/session_id/pipeline_id): # 「需要项目」判断在【点击时】由 agent_menu_open 服务端做(传 require_project/session_id/pipeline_id):
# 渲染时固化判断是老 bug——页面加载时还没切项目 → 按钮写死报错脚本,之后切了项目仍报错。 # 渲染时固化判断是老 bug——页面加载时还没切项目 → 按钮写死报错脚本,之后切了项目仍报错。
#
# 端形态(2026-09-08 用户要求:产线上方的按钮收缩为一个菜单项,给会话留最大空间):
# 桌面:一排 Button(原样)
# 手机(jsoncall 自动带 _is_mobile=1):单个「⋯ 功能」Button → 点击弹
# MobileSheet 竖向菜单,项点击走同一 open_url(popup:true,agent_menu_open
# 返回 PopupWindow 自开,与桌面链路完全一致)
buttons = [] _is_mobile = str((params_kw or {}).get('_is_mobile', '') or '') == '1'
# 通用会话(pipeline_id=_generic):只给"工作空间"按钮,锁用户专属目录 actions = [] # [{label, url}] 统一收集,最后按端形态渲染
# 通用会话(pipeline_id=_generic):只给"工作空间"入口,锁用户专属目录
if pipeline_id == '_generic': if pipeline_id == '_generic':
buttons.append({ actions.append({
"widgettype": "Button", "label": "🗂 工作空间",
"options": {"label": "🗂 工作空间", "css": "small", "id": "menu_btn_ws_generic"}, "url": entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
"binds": [{ + "?pipeline_id=_generic",
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self",
"options": {"url": entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
+ "?pipeline_id=_generic"},
}],
}) })
else:
# 固定「工作空间」入口(通用能力,所有 agent 都有;依赖当前项目)
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断)
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
_ws_q = []
if session_id:
_ws_q.append("session_id=" + session_id)
if pipeline_id:
_ws_q.append("pipeline_id=" + pipeline_id)
if _ws_q:
_ws_url += "?" + "&".join(_ws_q)
actions.append({"label": "🗂 工作空间", "url": _ws_url})
for i, m in enumerate(menus):
label = str(m.get("label", "") or "")
url = str(m.get("url", "") or "")
if not url:
continue
require_project = bool(m.get("require_project", False))
width = str(m.get("width", "85%") or "85%")
height = str(m.get("height", "80%") or "80%")
open_url = (entire_url("/pipeline_core/api/agent_menu_open.dspy")
+ "?url=" + url + "&title=" + label
+ "&width=" + width + "&height=" + height
+ ("&require_project=1" if require_project else ""))
if session_id:
open_url += "&session_id=" + session_id
if pipeline_id:
open_url += "&pipeline_id=" + pipeline_id
actions.append({"label": label, "url": open_url})
if _is_mobile:
# 手机端:收缩为单个「⋯ 功能」按钮 → MobileSheet 竖向菜单。
# items 内联进脚本(JSON 字面量);popup:true → 点击 widgetBuild urlwidget,
# 服务端返回 PopupWindow 自开(agent_menu_open / workspace_popup 同链路)。
_items = [{"name": "act_%d" % i, "label": a["label"], "url": a["url"], "popup": True}
for i, a in enumerate(actions)]
_script = (
"var its=" + _json.dumps(_items, ensure_ascii=False) + ";"
"var sh=new bricks.MobileSheet({title:'功能',items:its});sh.open();"
)
return _json.dumps({ return _json.dumps({
"widgettype": "HBox", "widgettype": "HBox",
"options": {"width": "100%", "padding": "0 12px 8px 12px", "gap": "8px", "alignItems": "center"}, "options": {"width": "100%", "padding": "0 12px 6px 12px", "gap": "8px", "alignItems": "center"},
"subwidgets": buttons, "subwidgets": [{
"widgettype": "Button",
"options": {"label": "⋯ 功能", "css": "small", "id": "menu_btn_mobile_more"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": _script,
}],
}],
}, ensure_ascii=False) }, ensure_ascii=False)
# 固定「工作空间」按钮(通用能力,所有 agent 都有;依赖当前项目) buttons = []
# 带产线参数:入口产线与会话项目跨产线时视为无项目(产线隔离,弹窗端点自身在点击时判断) for i, a in enumerate(actions):
_ws_url = entire_url("/pipeline-sdlc/api/workspace_popup.dspy")
_ws_q = []
if session_id:
_ws_q.append("session_id=" + session_id)
if pipeline_id:
_ws_q.append("pipeline_id=" + pipeline_id)
if _ws_q:
_ws_url += "?" + "&".join(_ws_q)
buttons.append({
"widgettype": "Button",
"options": {"label": "🗂 工作空间", "css": "small", "id": "menu_btn_ws"},
"binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self", "options": {"url": _ws_url},
}],
})
for i, m in enumerate(menus):
label = str(m.get("label", "") or "")
url = str(m.get("url", "") or "")
if not url:
continue
require_project = bool(m.get("require_project", False))
width = str(m.get("width", "85%") or "85%")
height = str(m.get("height", "80%") or "80%")
open_url = (entire_url("/pipeline_core/api/agent_menu_open.dspy")
+ "?url=" + url + "&title=" + label
+ "&width=" + width + "&height=" + height
+ ("&require_project=1" if require_project else ""))
if session_id:
open_url += "&session_id=" + session_id
if pipeline_id:
open_url += "&pipeline_id=" + pipeline_id
buttons.append({ buttons.append({
"widgettype": "Button", "widgettype": "Button",
"options": {"label": label, "css": "small", "id": "menu_btn_%d" % i}, "options": {"label": a["label"], "css": "small", "id": "menu_btn_%d" % i},
"binds": [{ "binds": [{
"wid": "self", "event": "click", "actiontype": "urlwidget", "wid": "self", "event": "click", "actiontype": "urlwidget",
"target": "self", "options": {"url": open_url}, "target": "self", "options": {"url": a["url"]},
}], }],
}) })

View File

@ -17,6 +17,10 @@ dbname = get_module_dbname('pipeline_core')
# 默认(手机端 ChipBar):客户只见已购未到期产线(2026-09-07 用户定夺) # 默认(手机端 ChipBar):客户只见已购未到期产线(2026-09-07 用户定夺)
_mode = ((params_kw or {}).get('mode') or '').strip() _mode = ((params_kw or {}).get('mode') or '').strip()
# fmt=more(底部 TabBar「更多」菜单):返回 MobileSheet 项格式
# [{name,label,icon,desc,url}],url 直达该产线会话页(2026-09-08)
_fmt = ((params_kw or {}).get('fmt') or '').strip()
items = [] items = []
try: try:
# 平台侧角色(owner./reseller.)看全部 published 产线;客户只看已购未到期。 # 平台侧角色(owner./reseller.)看全部 published 产线;客户只看已购未到期。
@ -69,8 +73,17 @@ try:
ids = [(getattr(r, 'id', ''), getattr(r, 'name', '')) for r in (prec or [])] ids = [(getattr(r, 'id', ''), getattr(r, 'name', '')) for r in (prec or [])]
for pid, pname in ids: for pid, pname in ids:
items.append({"id": pid, "pipeline_id": pid, if _fmt == 'more':
"name": pname or pid, "session_id": "m_" + pid}) # 底部 TabBar「更多」菜单项(2026-09-08):点击 → 内容区直达该产线
# 会话页(embed=1:隐藏胶囊条/门禁,锁定产线,会话区最大化)。
# url 不预 urlencode(前端 url_parse 会再编码一次,双重编码坑)
items.append({"name": "m_pl_" + pid, "label": pname or pid,
"icon": "⚡", "desc": "已购产线",
"url": entire_url("/pipeline_core/mobile_agent.ui")
+ "?pipeline_id=" + pid + "&embed=1"})
else:
items.append({"id": pid, "pipeline_id": pid,
"name": pname or pid, "session_id": "m_" + pid})
except Exception: except Exception:
items = [] items = []

View File

@ -1,3 +1,28 @@
{% set embed = params_kw.get('embed','') == '1' %}
{% set pl_id = params_kw.get('pipeline_id','') or '' %}
{% if embed %}
{
"widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "0"},
"subwidgets": [
{
"widgettype": "urlwidget",
"options": {"url": "/pipeline_core/api/agent_menus.dspy?pipeline_id={{pl_id}}&session_id=m_{{pl_id}}"}
},
{
"widgettype": "AgentIO",
"options": {
"css": "filler",
"margin": "0 8px 8px 8px",
"url": "/pipeline_core/api/agent_chat.dspy?pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_dataurl": "/pipeline_core/api/agent_model_options.dspy?capabilities=chat&pipeline_id={{pl_id}}&session_id=m_{{pl_id}}",
"model_cwidth": 14,
"placeholder": "输入你的需求..."
}
}
]
}
{% else %}
{ {
"widgettype": "VBox", "widgettype": "VBox",
"options": {"width": "100%", "height": "100%", "padding": "0", "css": "pl-gate-root"}, "options": {"width": "100%", "height": "100%", "padding": "0", "css": "pl-gate-root"},
@ -91,3 +116,4 @@
} }
] ]
} }
{% endif %}