diff --git a/reallife_asset/init.py b/reallife_asset/init.py index 274c574..174a4bc 100644 --- a/reallife_asset/init.py +++ b/reallife_asset/init.py @@ -584,6 +584,52 @@ async def rl_sync_asset_status_user(org_id, asset_id, user_id): return {"success": True, "status": status, "url": url} +async def rl_list_assets_client(org_id, vendor_group_id): + """Client API: List assets for a vendor_group_id, sync from vendor first.""" + dbname = _get_dbname() + db = DBPools() + + # Validate org owns this group, get vendor + local_group_id + async with db.sqlorContext(dbname) as sor: + grp_recs = await sor.R("rl_org_group", { + "org_id": org_id, + "vendor_group_id": vendor_group_id, + }) + if not grp_recs: + return {"success": False, "message": "无效的认证组合或无权访问"} + vendor = grp_recs[0].vendor + local_group_id = grp_recs[0].local_group_id + + # Sync from vendor to get fresh status + try: + await rl_sync_assets_from_vendor(org_id, local_group_id) + except Exception as e: + debug(f"rl_list_assets_client: vendor sync failed: {e}") + + # Query local assets + async with db.sqlorContext(dbname) as sor: + recs = await sor.R("rl_asset", { + "org_id": org_id, + "group_id": local_group_id, + }) + if not recs: + return {"success": True, "assets": []} + + assets = [] + for r in recs: + assets.append({ + "id": r.id, + "name": getattr(r, "name", ""), + "status": getattr(r, "status", ""), + "url": getattr(r, "url", ""), + "asset_type": getattr(r, "asset_type", ""), + "vendor_asset_id": getattr(r, "vendor_asset_id", ""), + "create_time": getattr(r, "create_time", ""), + }) + + return {"success": True, "assets": assets} + + async def rl_handle_callback(byted_token, project_name="default"): """ Callback handler: vendor POSTs here after H5 auth completes. @@ -719,6 +765,7 @@ def load_reallife_asset(): g.rl_verify_user = rl_verify_user g.rl_upload_user = rl_upload_user g.rl_sync_asset_status_user = rl_sync_asset_status_user + g.rl_list_assets_client = rl_list_assets_client g.rl_handle_callback = rl_handle_callback g.rl_query_groups = rl_query_groups diff --git a/scripts/load_path.py b/scripts/load_path.py index a089220..8eb819a 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -56,6 +56,7 @@ PATHS_LOGINED = [ # 客户可用页面 f"/{MOD}/create_validate.ui", f"/{MOD}/upload_asset.ui", + f"/{MOD}/view_assets.ui", # API — 所有 api/ 下的 .dspy(脚本内部通过 get_user() 做权限校验) f"/{MOD}/api/%", diff --git a/wwwroot/api/submit_list_assets.dspy b/wwwroot/api/submit_list_assets.dspy new file mode 100644 index 0000000..2b1fa65 --- /dev/null +++ b/wwwroot/api/submit_list_assets.dspy @@ -0,0 +1,146 @@ + +vendor_group_id = params_kw.get('vendor_group_id', '') + +if not vendor_group_id: + return json.dumps({ + "widgettype": "Error", + "options": {"title": "错误", "message": "请选择认证组合"} + }) + +org_id = (await get_userorgid()) or '0' + +result = await rl_list_assets_client(org_id, vendor_group_id) + +if not result.get('success'): + return json.dumps({ + "widgettype": "Error", + "options": {"title": "查询失败", "message": result.get('message', '未知错误')} + }) + +assets = result.get('assets', []) + +if not assets: + return json.dumps({ + "widgettype": "VBox", + "id": "rl_asset_results", + "options": {"padding": "16px", "gap": "12px"}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "该认证组合下暂无素材,请先上传素材。"} + } + ] + }) + +# Build asset cards +cards = [] +for a in assets: + status = a.get('status', '') + name = a.get('name', '') + url = a.get('url', '') + atype = a.get('asset_type', '') + create_time = str(a.get('create_time', ''))[:16] + asset_id = a.get('id', '') + vendor_asset_id = a.get('vendor_asset_id', '') + + # Status icon + s_lower = status.lower() if status else '' + if s_lower in ('active', 'available', 'ready'): + status_icon = "✅" + elif s_lower in ('processing', 'pending', 'submitted'): + status_icon = "⏳" + elif s_lower in ('failed', 'error'): + status_icon = "❌" + else: + status_icon = "📋" + + # Build card subwidgets + card_subs = [ + { + "widgettype": "HBox", + "options": {"gap": "8px", "alignItems": "center", "marginBottom": "4px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": name or "未命名", "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": f"{status_icon} {status}", "cfontsize": 0.9}} + ] + }, + { + "widgettype": "Text", + "options": {"text": f"类型: {atype} 创建: {create_time}", "cfontsize": 0.9} + } + ] + + # Media preview + if url: + if atype == 'Image': + card_subs.append({ + "widgettype": "Image", + "options": {"url": url, "cwidth": 15, "cheight": 10, "objectFit": "cover", "borderRadius": "4px", "marginTop": "8px"} + }) + elif atype == 'Video': + card_subs.append({ + "widgettype": "VideoPlayer", + "options": {"url": url, "cwidth": 20, "cheight": 12, "marginTop": "8px"} + }) + elif atype == 'Audio': + card_subs.append({ + "widgettype": "AudioPlayer", + "options": {"url": url, "marginTop": "8px"} + }) + + # Refresh status button (always show for processing assets, optional for others) + check_url = request.path.rsplit('/', 1)[0] + '/submit_query_status.dspy' + btn_subs = [ + { + "widgettype": "Button", + "options": {"label": "刷新状态"}, + "binds": [{ + "wid": "self", "event": "click", + "actiontype": "script", "target": "self", + "script": "(async function(){" \ + "var url='" + check_url + "?_webbricks_=1&asset_id=" + asset_id + "';" \ + "var r=await fetch(url);" \ + "var j=await r.json();" \ + "await bricks.show_resp_message_or_error({json:async function(){return j}});" \ + "})()" + }] + } + ] + if url: + btn_subs.append({ + "widgettype": "Button", + "options": {"label": "下载"}, + "binds": [{ + "wid": "self", "event": "click", + "actiontype": "script", "target": "self", + "script": "window.open('" + url + "', '_blank')" + }] + }) + card_subs.append({ + "widgettype": "HBox", + "options": {"gap": "8px", "marginTop": "8px"}, + "subwidgets": btn_subs + }) + + cards.append({ + "widgettype": "VBox", + "options": {"css": "card", "padding": "12px", "borderRadius": "8px"}, + "subwidgets": card_subs + }) + +# Wrap cards in ResponsableBox for responsive grid +result_widget = { + "widgettype": "VBox", + "id": "rl_asset_results", + "options": {"padding": "16px", "gap": "12px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"text": f"共 {len(assets)} 个素材", "fontWeight": "bold"}}, + { + "widgettype": "ResponsableBox", + "options": {"gap": "12px", "minWidth": "300px"}, + "subwidgets": cards + } + ] +} + +return json.dumps(result_widget, ensure_ascii=False) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index c472a64..6825f12 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -121,6 +121,44 @@ } } ] + }, + { + "widgettype": "VBox", + "options": { + "css": "card", + "cheight": 12, + "cwidth": 23, + "padding": "20px", + "cursor": "pointer", + "borderRadius": "8px" + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.rl_content", + "options": { + "url": "{{entire_url('view_assets.ui')}}" + }, + "mode": "replace" + } + ], + "subwidgets": [ + { + "widgettype": "Title5", + "options": { + "text": "🖼️ 查看素材" + } + }, + { + "widgettype": "Text", + "options": { + "text": "按认证组合查看已上传的素材及状态", + "cfontsize": 1.2 + } + } + ] } {% endif %} {% if is_admin %} diff --git a/wwwroot/view_assets.ui b/wwwroot/view_assets.ui new file mode 100644 index 0000000..d28c0b2 --- /dev/null +++ b/wwwroot/view_assets.ui @@ -0,0 +1,54 @@ +{% set current_group = params_kw.get('vendor_group_id', '') %} +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Title4", + "options": { + "text": "查看素材", + "fontWeight": "600", + "marginBottom": "16px" + } + }, + { + "widgettype": "Form", + "id": "view_assets_form", + "options": { + "submit_url": "{{entire_url('api/submit_list_assets.dspy')}}", + "fields": [ + { + "uitype": "code", + "name": "vendor_group_id", + "label": "认证组合", + "dataurl": "{{entire_url('api/get_org_groups.dspy')}}", + "data_field": "value", + "text_field": "text", + "required": true, + "value": "{{ current_group }}" + } + ] + }, + "binds": [ + { + "wid": "self", + "event": "submited", + "actiontype": "urlwidget", + "target": "app.rl_asset_results", + "options": { + "method": "POST" + }, + "mode": "replace" + } + ] + }, + { + "widgettype": "VBox", + "id": "rl_asset_results", + "options": {"padding": "16px"} + } + ] +}