feat: add view_assets page — browse media by group with bricks widgets
- init.py: add rl_list_assets_client() — validates org ownership, syncs from vendor, returns asset list with status/url/type - view_assets.ui: dropdown to select vendor_group_id, submit triggers asset list display - submit_list_assets.dspy: returns responsive card grid with Image/ VideoPlayer/AudioPlayer widgets, per-asset refresh status + download - index.ui: add '查看素材' card for customers - load_path.py: register view_assets.ui under logined RBAC
This commit is contained in:
parent
daf82107fb
commit
6491182249
@ -584,6 +584,52 @@ async def rl_sync_asset_status_user(org_id, asset_id, user_id):
|
|||||||
return {"success": True, "status": status, "url": url}
|
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"):
|
async def rl_handle_callback(byted_token, project_name="default"):
|
||||||
"""
|
"""
|
||||||
Callback handler: vendor POSTs here after H5 auth completes.
|
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_verify_user = rl_verify_user
|
||||||
g.rl_upload_user = rl_upload_user
|
g.rl_upload_user = rl_upload_user
|
||||||
g.rl_sync_asset_status_user = rl_sync_asset_status_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_handle_callback = rl_handle_callback
|
||||||
g.rl_query_groups = rl_query_groups
|
g.rl_query_groups = rl_query_groups
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,7 @@ PATHS_LOGINED = [
|
|||||||
# 客户可用页面
|
# 客户可用页面
|
||||||
f"/{MOD}/create_validate.ui",
|
f"/{MOD}/create_validate.ui",
|
||||||
f"/{MOD}/upload_asset.ui",
|
f"/{MOD}/upload_asset.ui",
|
||||||
|
f"/{MOD}/view_assets.ui",
|
||||||
|
|
||||||
# API — 所有 api/ 下的 .dspy(脚本内部通过 get_user() 做权限校验)
|
# API — 所有 api/ 下的 .dspy(脚本内部通过 get_user() 做权限校验)
|
||||||
f"/{MOD}/api/%",
|
f"/{MOD}/api/%",
|
||||||
|
|||||||
146
wwwroot/api/submit_list_assets.dspy
Normal file
146
wwwroot/api/submit_list_assets.dspy
Normal file
@ -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)
|
||||||
@ -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 %}
|
{% endif %}
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
|
|||||||
54
wwwroot/view_assets.ui
Normal file
54
wwwroot/view_assets.ui
Normal file
@ -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"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user