- main.py admin_page 调用 _load_current_backend_menu_items 取当前用户权限菜单, 经 render_html 传入模板;Java 权限接口不可用时降级不渲染(避免展示越权菜单) - admin.html 删除写死的 16 个静态菜单按钮,改为 Jinja2 按权限渲染分组菜单, 渲染成功时 nav 带 data-server-rendered 标记,失败显示轻量占位 - admin.js loadAdminMenus 识别 data-server-rendered 后直接绑定事件并激活, 不再请求 /current-user/menus 重渲染(保留 API 兜底路径)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
主页面蓝图:首页、管理后台页、静态文件
|
主页面蓝图:首页、管理后台页、静态文件
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from flask import Blueprint, redirect, url_for, send_file, session
|
from flask import Blueprint, current_app, redirect, url_for, send_file, session
|
||||||
from werkzeug.utils import safe_join
|
from werkzeug.utils import safe_join
|
||||||
|
|
||||||
from utils.auth import login_required, is_session_user_valid
|
from utils.auth import login_required, is_session_user_valid
|
||||||
@@ -24,7 +24,25 @@ def index():
|
|||||||
@main.route('/admin')
|
@main.route('/admin')
|
||||||
@login_required
|
@login_required
|
||||||
def admin_page():
|
def admin_page():
|
||||||
return render_html('admin.html')
|
"""管理后台页:服务端直接渲染当前用户有权限的菜单,避免客户端二次渲染造成闪烁。"""
|
||||||
|
context = {}
|
||||||
|
try:
|
||||||
|
# 复用权限菜单加载逻辑;Java 权限接口不可用时降级为无菜单(JS 侧会走 API 兜底)
|
||||||
|
from blueprints.admin_api import _load_current_backend_menu_items
|
||||||
|
_, _, items, denied = _load_current_backend_menu_items()
|
||||||
|
if denied is None and items:
|
||||||
|
context['admin_menu_items'] = items
|
||||||
|
context['admin_menu_rendered'] = True
|
||||||
|
else:
|
||||||
|
# 权限接口失败:真实页面同样会失败,服务端不渲染菜单,避免展示越权菜单
|
||||||
|
context['admin_menu_rendered'] = False
|
||||||
|
current_app.logger.warning('[admin] 服务端菜单渲染失败,降级为客户端加载: %s',
|
||||||
|
(denied[0].get_json() if denied and len(denied) > 0 else None))
|
||||||
|
except Exception as exc:
|
||||||
|
# 兜底:任何异常都不得阻断管理页打开,JS 会自行请求菜单接口
|
||||||
|
current_app.logger.exception('[admin] 服务端渲染菜单异常: %s', exc)
|
||||||
|
context['admin_menu_rendered'] = False
|
||||||
|
return render_html('admin.html', **context)
|
||||||
|
|
||||||
|
|
||||||
@main.route('/static/<path:filename>')
|
@main.route('/static/<path:filename>')
|
||||||
|
|||||||
@@ -268,8 +268,43 @@
|
|||||||
group.style.display = visible ? '' : 'none';
|
group.style.display = visible ? '' : 'none';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function bindAdminMenuTabs(serverRendered) {
|
||||||
|
adminMenuByRoute = {};
|
||||||
|
var routes = [];
|
||||||
|
adminMenuEl.querySelectorAll('#adminMenu .tab').forEach(function (tab) {
|
||||||
|
var route = tab.dataset.tab;
|
||||||
|
if (!route) return;
|
||||||
|
routes.push(route);
|
||||||
|
if (!adminMenuByRoute[route]) {
|
||||||
|
adminMenuByRoute[route] = { route_path: route, name: tab.getAttribute('title') || route };
|
||||||
|
}
|
||||||
|
tab.onclick = function () {
|
||||||
|
activateAdminTab(tab.dataset.tab);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
function loadAdminMenus(preferredTab) {
|
function loadAdminMenus(preferredTab) {
|
||||||
if (preferredTab) activeAdminTabName = preferredTab;
|
if (preferredTab) activeAdminTabName = preferredTab;
|
||||||
|
// 服务端已按权限渲染菜单(data-server-rendered=true):直接绑定事件并激活,
|
||||||
|
// 不再请求接口重渲染,避免"全量菜单→折叠"的闪烁。
|
||||||
|
if (!adminMenuEl || adminMenuEl.getAttribute('data-server-rendered') !== 'true') {
|
||||||
|
renderAdminTabsFromApi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var serverRoutes = bindAdminMenuTabs(true);
|
||||||
|
if (!serverRoutes.length) {
|
||||||
|
activeAdminTabName = '';
|
||||||
|
hideAllAdminPanels();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (window.__initAdminMenuCollapse) window.__initAdminMenuCollapse();
|
||||||
|
var fallbackTab = activeAdminTabName && serverRoutes.indexOf(activeAdminTabName) >= 0
|
||||||
|
? activeAdminTabName
|
||||||
|
: serverRoutes[0];
|
||||||
|
activateAdminTab(fallbackTab);
|
||||||
|
}
|
||||||
|
function renderAdminTabsFromApi() {
|
||||||
fetch('/api/admin/current-user/menus')
|
fetch('/api/admin/current-user/menus')
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
|
|||||||
@@ -4756,56 +4756,60 @@
|
|||||||
<span class="admin-brand-sub">电商运营管理后台</span>
|
<span class="admin-brand-sub">电商运营管理后台</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<nav class="admin-menu" id="adminMenu" aria-label="管理菜单">
|
<nav class="admin-menu" id="adminMenu" aria-label="管理菜单"{% if admin_menu_rendered %} data-server-rendered="true"{% endif %}>
|
||||||
<!-- 静态兜底菜单:JS 加载后按后端权限重渲染 -->
|
{% if admin_menu_rendered %}
|
||||||
<div class="menu-group" data-menu-group="account">
|
{# 服务端按当前用户权限直接渲染菜单,避免客户端二次渲染造成"全量菜单→折叠"的闪烁 #}
|
||||||
<button class="menu-group-title" type="button" aria-expanded="true">
|
{% set __adm_chevron %}<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>{% endset %}
|
||||||
账号与权限
|
{% set __adm_icons = {
|
||||||
<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>
|
'users': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>',
|
||||||
</button>
|
'group-manage': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>',
|
||||||
|
'columns': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="7" height="7" x="3" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="14" rx="1"></rect><rect width="7" height="7" x="3" y="14" rx="1"></rect></svg>',
|
||||||
|
'dedupe-total-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path></svg>',
|
||||||
|
'invalid-asin-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>',
|
||||||
|
'shop-keys': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg>',
|
||||||
|
'shop-manage': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"></path><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"></path><path d="M2 7h20"></path><path d="M22 7v3a2 2 0 0 1-2 2 2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"></path></svg>',
|
||||||
|
'skip-price-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="m4.9 4.9 14.2 14.2"></path></svg>',
|
||||||
|
'query-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg>',
|
||||||
|
'product-categories': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg>',
|
||||||
|
'image-video-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 8-6 4 6 4V8Z"></path><rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect></svg>',
|
||||||
|
'shop-data-crawl-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg>',
|
||||||
|
'shop-data-duplicate-check': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.35 11.1h-9.17a2 2 0 0 1-1.75-2.98l1.67-2.79a2 2 0 0 0-1.75-2.98l-5.98-.01a2 2 0 0 0-2 2v1.5a2 2 0 0 0 2 2h3.36l-2.38 3.97a2 2 0 0 0 1.75 2.98h11.5a2 2 0 0 0 2-2v-1.5a2 2 0 0 0-2-2Z"></path><path d="M3 21h18"></path></svg>',
|
||||||
|
'history': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5"></path><path d="M12 7v5l4 2"></path></svg>',
|
||||||
|
'version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7.5 4.27 9 5.15"></path><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="M3.3 7 12 12l8.7-5"></path><path d="M12 22V12"></path></svg>',
|
||||||
|
'digital-human-version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>',
|
||||||
|
} %}
|
||||||
|
{% set __adm_fallback_icon %}<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="M8 12h8"></path></svg>{% endset %}
|
||||||
|
{% set __adm_groups = [
|
||||||
|
('account', '账号与权限', ['users', 'columns', 'group-manage']),
|
||||||
|
('data', '数据管理', ['dedupe-total-data', 'invalid-asin-data', 'query-asin', 'product-categories']),
|
||||||
|
('shop', '店铺管理', ['shop-keys', 'shop-manage', 'skip-price-asin', 'shop-data-crawl-tasks', 'shop-data-duplicate-check']),
|
||||||
|
('record', '记录与版本', ['history', 'version', 'digital-human-version', 'image-video-tasks']),
|
||||||
|
] %}
|
||||||
|
{% set __adm_allowed = admin_menu_items | map(attribute='route_path') | list %}
|
||||||
|
{% set __adm_has = namespace(v=False) %}
|
||||||
|
{% for __gk, __gt, __routes in __adm_groups %}
|
||||||
|
{% set __visible_routes = __routes | select('in', __adm_allowed) | list %}
|
||||||
|
{% if __visible_routes %}
|
||||||
|
{% set __adm_has.v = True %}
|
||||||
|
<div class="menu-group" data-menu-group="{{ __gk }}">
|
||||||
|
<button class="menu-group-title" type="button" aria-expanded="true">{{ __gt }}{{ __adm_chevron | safe }}</button>
|
||||||
<div class="menu-group-body">
|
<div class="menu-group-body">
|
||||||
<button class="tab active" type="button" data-tab="users" aria-current="page"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg></span><span class="adm-label">用户管理</span></button>
|
{% for __r in __visible_routes %}
|
||||||
<button class="tab" type="button" data-tab="columns" aria-controls="panel-columns"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="7" height="7" x="3" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="14" rx="1"></rect><rect width="7" height="7" x="3" y="14" rx="1"></rect></svg></span><span class="adm-label">菜单权限配置</span></button>
|
{% set __it = admin_menu_items | selectattr('route_path', 'equalto', __r) | first %}
|
||||||
<button class="tab" type="button" data-tab="group-manage" aria-controls="panel-group-manage"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg></span><span class="adm-label">分组管理</span></button>
|
{% set __label = ((__it.get('name') if __it and __it.get('name') else __r) | e) %}
|
||||||
</div>
|
<button class="tab" type="button" data-tab="{{ __r }}" aria-controls="panel-{{ __r }}" title="{{ __label }}"><span class="adm-icon">{{ __adm_icons.get(__r, __adm_fallback_icon) | safe }}</span><span class="adm-label">{{ __label }}</span></button>
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="menu-group" data-menu-group="data">
|
|
||||||
<button class="menu-group-title" type="button" aria-expanded="true">
|
|
||||||
数据管理
|
|
||||||
<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>
|
|
||||||
</button>
|
|
||||||
<div class="menu-group-body">
|
|
||||||
<button class="tab" type="button" data-tab="dedupe-total-data" aria-controls="panel-dedupe-total-data"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path></svg></span><span class="adm-label">去重数据汇总</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="invalid-asin-data" aria-controls="panel-invalid-asin-data"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg></span><span class="adm-label">品牌数据库</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="query-asin" aria-controls="panel-query-asin"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg></span><span class="adm-label">查询 ASIN</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="product-categories" aria-controls="panel-product-categories"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg></span><span class="adm-label">商品类目</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="menu-group" data-menu-group="shop">
|
|
||||||
<button class="menu-group-title" type="button" aria-expanded="true">
|
|
||||||
店铺管理
|
|
||||||
<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>
|
|
||||||
</button>
|
|
||||||
<div class="menu-group-body">
|
|
||||||
<button class="tab" type="button" data-tab="shop-keys" aria-controls="panel-shop-keys"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg></span><span class="adm-label">店铺密钥管理</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="shop-manage" aria-controls="panel-shop-manage"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"></path><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"></path><path d="M2 7h20"></path><path d="M22 7v3a2 2 0 0 1-2 2 2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"></path></svg></span><span class="adm-label">店铺管理</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="skip-price-asin" aria-controls="panel-skip-price-asin"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="m4.9 4.9 14.2 14.2"></path></svg></span><span class="adm-label">跳过跟价 ASIN</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="shop-data-crawl-tasks" aria-controls="panel-shop-data-crawl-tasks"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg></span><span class="adm-label">店铺数据记录</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="shop-data-duplicate-check" aria-controls="panel-shop-data-duplicate-check"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.35 11.1h-9.17a2 2 0 0 1-1.75-2.98l1.67-2.79a2 2 0 0 0-1.75-2.98l-5.98-.01a2 2 0 0 0-2 2v1.5a2 2 0 0 0 2 2h3.36l-2.38 3.97a2 2 0 0 0 1.75 2.98h11.5a2 2 0 0 0 2-2v-1.5a2 2 0 0 0-2-2Z"></path><path d="M3 21h18"></path></svg></span><span class="adm-label">店铺数据重复检查</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="menu-group" data-menu-group="record">
|
|
||||||
<button class="menu-group-title" type="button" aria-expanded="true">
|
|
||||||
记录与版本
|
|
||||||
<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>
|
|
||||||
</button>
|
|
||||||
<div class="menu-group-body">
|
|
||||||
<button class="tab" type="button" data-tab="history" aria-controls="panel-history"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5"></path><path d="M12 7v5l4 2"></path></svg></span><span class="adm-label">生成记录</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="version" aria-controls="panel-version"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7.5 4.27 9 5.15"></path><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="M3.3 7 12 12l8.7-5"></path><path d="M12 22V12"></path></svg></span><span class="adm-label">软件版本管理</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="digital-human-version" aria-controls="panel-digital-human-version"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg></span><span class="adm-label">数字人版本管理</span></button>
|
|
||||||
<button class="tab" type="button" data-tab="image-video-tasks" aria-controls="panel-image-video-tasks"><span class="adm-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 8-6 4 6 4V8Z"></path><rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect></svg></span><span class="adm-label">视频任务记录</span></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if not __adm_has.v %}
|
||||||
|
<div class="menu-empty">暂无可用菜单</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{# 服务端不可用时的轻量占位:JS 加载后走接口渲染,避免闪出全量菜单 #}
|
||||||
|
<div class="menu-empty">菜单加载中...</div>
|
||||||
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="admin-sidebar-foot">数富AI · 管理控制台</div>
|
<div class="admin-sidebar-foot">数富AI · 管理控制台</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Reference in New Issue
Block a user