修改完善这个专利部分
This commit is contained in:
@@ -5,6 +5,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
from urllib.parse import quote
|
||||
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
@@ -1217,7 +1218,9 @@ def list_product_categories():
|
||||
_, _, denied = _ensure_product_category_access()
|
||||
if denied:
|
||||
return denied
|
||||
result, error_response, status = _proxy_backend_java('GET', '/api/admin/product-categories')
|
||||
keyword = (request.args.get('keyword') or '').strip()
|
||||
query = f'?keyword={quote(keyword)}' if keyword else ''
|
||||
result, error_response, status = _proxy_backend_java('GET', f'/api/admin/product-categories{query}')
|
||||
if error_response is not None:
|
||||
return error_response, status
|
||||
payload = result.get('data') or {}
|
||||
@@ -1225,6 +1228,76 @@ def list_product_categories():
|
||||
'success': True,
|
||||
'tree': [_format_product_category_item(item) for item in (payload.get('tree') or [])],
|
||||
'items': [_format_product_category_item(item, include_children=False) for item in (payload.get('items') or [])],
|
||||
'total': payload.get('total') or 0,
|
||||
'page': payload.get('page') or 1,
|
||||
'page_size': payload.get('pageSize') or 0,
|
||||
'has_more': bool(payload.get('hasMore')),
|
||||
})
|
||||
|
||||
|
||||
@admin_api.route('/product-categories/children')
|
||||
@login_required
|
||||
def list_product_category_children():
|
||||
_ensure_product_category_schema()
|
||||
_, _, denied = _ensure_product_category_access()
|
||||
if denied:
|
||||
return denied
|
||||
parent_id_raw = (request.args.get('parent_id') or request.args.get('parentId') or '').strip()
|
||||
page = max(1, int(request.args.get('page', 1)))
|
||||
page_size = min(100, max(1, int(request.args.get('page_size', request.args.get('pageSize', 20)))))
|
||||
params = {
|
||||
'page': page,
|
||||
'pageSize': page_size,
|
||||
}
|
||||
if parent_id_raw:
|
||||
params['parentId'] = parent_id_raw
|
||||
result, error_response, status = _proxy_backend_java(
|
||||
'GET',
|
||||
'/api/admin/product-categories/children',
|
||||
params=params,
|
||||
)
|
||||
if error_response is not None:
|
||||
return error_response, status
|
||||
payload = result.get('data') or {}
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'items': [_format_product_category_item(item, include_children=False) for item in (payload.get('items') or [])],
|
||||
'total': payload.get('total') or 0,
|
||||
'page': payload.get('page') or page,
|
||||
'page_size': payload.get('pageSize') or page_size,
|
||||
'has_more': bool(payload.get('hasMore')),
|
||||
})
|
||||
|
||||
|
||||
@admin_api.route('/product-categories/search')
|
||||
@login_required
|
||||
def search_product_categories():
|
||||
_ensure_product_category_schema()
|
||||
_, _, denied = _ensure_product_category_access()
|
||||
if denied:
|
||||
return denied
|
||||
keyword = (request.args.get('keyword') or '').strip()
|
||||
page = max(1, int(request.args.get('page', 1)))
|
||||
page_size = min(100, max(1, int(request.args.get('page_size', request.args.get('pageSize', 20)))))
|
||||
result, error_response, status = _proxy_backend_java(
|
||||
'GET',
|
||||
'/api/admin/product-categories/search',
|
||||
params={
|
||||
'keyword': keyword,
|
||||
'page': page,
|
||||
'pageSize': page_size,
|
||||
},
|
||||
)
|
||||
if error_response is not None:
|
||||
return error_response, status
|
||||
payload = result.get('data') or {}
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'items': [_format_product_category_item(item, include_children=False) for item in (payload.get('items') or [])],
|
||||
'total': payload.get('total') or 0,
|
||||
'page': payload.get('page') or page,
|
||||
'page_size': payload.get('pageSize') or page_size,
|
||||
'has_more': bool(payload.get('hasMore')),
|
||||
})
|
||||
|
||||
|
||||
@@ -2415,6 +2488,54 @@ def list_query_asins():
|
||||
})
|
||||
|
||||
|
||||
@admin_api.route('/query-asins/export')
|
||||
@login_required
|
||||
def export_query_asins():
|
||||
role, current_row, denied = _ensure_backend_menu_access('query-asin')
|
||||
if denied:
|
||||
return denied
|
||||
params = {
|
||||
'operatorId': current_row.get('id') if current_row else None,
|
||||
'superAdmin': 'true' if role == 'super_admin' else 'false',
|
||||
}
|
||||
group_id_raw = (request.args.get('group_id') or request.args.get('groupId') or '').strip()
|
||||
shop_name = (request.args.get('shop_name') or request.args.get('shopName') or '').strip()
|
||||
asin = (request.args.get('asin') or '').strip()
|
||||
if group_id_raw:
|
||||
params['groupId'] = group_id_raw
|
||||
if shop_name:
|
||||
params['shopName'] = shop_name
|
||||
if asin:
|
||||
params['asin'] = asin
|
||||
|
||||
url = f"{backend_java_base_url}/api/admin/query-asins/export"
|
||||
try:
|
||||
resp = _get_backend_java_session().get(url, params=params, timeout=60)
|
||||
except requests.RequestException:
|
||||
return jsonify({'success': False, 'error': 'backend-java 服务不可用'}), 502
|
||||
if resp.status_code >= 400:
|
||||
try:
|
||||
data = resp.json()
|
||||
error = data.get('message') or data.get('error') or '导出失败'
|
||||
except ValueError:
|
||||
error = '导出失败'
|
||||
return jsonify({'success': False, 'error': error}), resp.status_code
|
||||
|
||||
headers = {}
|
||||
disposition = resp.headers.get('Content-Disposition')
|
||||
if disposition:
|
||||
headers['Content-Disposition'] = disposition
|
||||
return Response(
|
||||
resp.content,
|
||||
status=resp.status_code,
|
||||
headers=headers,
|
||||
content_type=resp.headers.get(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _format_query_asin_import_progress(progress):
|
||||
return {
|
||||
'status': progress.get('status') or 'pending',
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
def load_dotenv(*args, **kwargs):
|
||||
return False
|
||||
|
||||
base_url = "https://api.coze.cn/v1"
|
||||
coze_token = "sat_12nW40INoJxArrDXbY4lSCoudbqkOYTcphC99BP2efWyzxmsk4q81WDX3ezWgqZ5"
|
||||
workflow_id = "7608812635877900322"
|
||||
STITCH_WORKFLOW_ID = "7608813873483300907"
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
LOADED_ENV_FILES = []
|
||||
|
||||
|
||||
def _load_env_files():
|
||||
candidates = (
|
||||
BASE_DIR / ".env",
|
||||
BASE_DIR.parent / "app" / ".env",
|
||||
BASE_DIR.parent / ".env",
|
||||
)
|
||||
for env_path in candidates:
|
||||
if env_path.is_file():
|
||||
load_dotenv(dotenv_path=env_path, override=False)
|
||||
LOADED_ENV_FILES.append(str(env_path))
|
||||
|
||||
|
||||
def _get_env(*names, default=None):
|
||||
for name in names:
|
||||
value = os.getenv(name)
|
||||
if value not in (None, ""):
|
||||
return value, name
|
||||
return default, "config.default"
|
||||
|
||||
|
||||
_load_env_files()
|
||||
|
||||
# MySQL 配置
|
||||
mysql_host = "8.136.19.173"
|
||||
mysql_user = "aiimage"
|
||||
mysql_password = "WTFrb5y6hNLz6hNy" # 请修改为您的数据库密码
|
||||
mysql_database = "aiimage"
|
||||
mysql_host, mysql_host_source = _get_env("mysql_host", "MYSQL_HOST", default="47.110.241.161")
|
||||
mysql_user, mysql_user_source = _get_env("mysql_user", "MYSQL_USER", default="aiimage")
|
||||
mysql_password, mysql_password_source = _get_env("mysql_password", "MYSQL_PASSWORD", default="WTFrb5y6hNLz6hNy")
|
||||
mysql_database, mysql_database_source = _get_env("mysql_database", "MYSQL_DATABASE", default="aiimage")
|
||||
|
||||
|
||||
cache_path = "./user_data"
|
||||
@@ -22,20 +56,20 @@ bucket_path = "nanri-image/"
|
||||
|
||||
file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/"
|
||||
|
||||
import os
|
||||
backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/')
|
||||
# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://8.136.19.173:18080').rstrip('/')
|
||||
# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://47.111.163.154:18080').rstrip('/')
|
||||
# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://121.196.149.225:18080').rstrip('/')
|
||||
os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId
|
||||
os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret
|
||||
os.environ['SECRET_KEY'] = "ddffc7c1d02121d9554d7b080b2511b6"
|
||||
backend_java_base_url, backend_java_base_url_source = _get_env(
|
||||
"java_api_base",
|
||||
"BACKEND_JAVA_BASE_URL",
|
||||
default="http://127.0.0.1:18080",
|
||||
# default="http://47.111.163.154:18080",
|
||||
)
|
||||
backend_java_base_url = backend_java_base_url.rstrip("/")
|
||||
os.environ["OSS_ACCESS_KEY_ID"] = accessKeyId
|
||||
os.environ["OSS_ACCESS_KEY_SECRET"] = accessKeySecret
|
||||
os.environ["SECRET_KEY"] = "ddffc7c1d02121d9554d7b080b2511b6"
|
||||
|
||||
|
||||
debug = True
|
||||
version = "1.0.0"
|
||||
APP_UPDATE_URL = ""
|
||||
os.environ['APP_VERSION'] = version
|
||||
os.environ['APP_UPDATE_URL'] = version
|
||||
|
||||
|
||||
os.environ["APP_VERSION"] = version
|
||||
os.environ["APP_UPDATE_URL"] = version
|
||||
|
||||
@@ -44,6 +44,7 @@ PyQt5==5.15.11
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5_sip==12.17.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
pythonnet==3.0.5
|
||||
pytz==2026.1.post1
|
||||
pywebview==6.1
|
||||
|
||||
@@ -2563,14 +2563,47 @@
|
||||
}
|
||||
function buildQueryAsinQuery(page) {
|
||||
var query = 'page=' + (page || 1) + '&page_size=' + queryAsinPageSize;
|
||||
var filterQuery = buildQueryAsinFilterQuery();
|
||||
if (filterQuery) query += '&' + filterQuery;
|
||||
return query;
|
||||
}
|
||||
function buildQueryAsinFilterQuery() {
|
||||
var query = '';
|
||||
var groupId = (document.getElementById('queryAsinFilterGroupId').value || '').trim();
|
||||
var shopName = (document.getElementById('queryAsinFilterShopName').value || '').trim();
|
||||
var asin = (document.getElementById('queryAsinFilterAsin').value || '').trim();
|
||||
if (groupId) query += '&group_id=' + encodeURIComponent(groupId);
|
||||
if (shopName) query += '&shop_name=' + encodeURIComponent(shopName);
|
||||
if (asin) query += '&asin=' + encodeURIComponent(asin);
|
||||
if (groupId) query += (query ? '&' : '') + 'group_id=' + encodeURIComponent(groupId);
|
||||
if (shopName) query += (query ? '&' : '') + 'shop_name=' + encodeURIComponent(shopName);
|
||||
if (asin) query += (query ? '&' : '') + 'asin=' + encodeURIComponent(asin);
|
||||
return query;
|
||||
}
|
||||
function exportQueryAsin() {
|
||||
var query = buildQueryAsinFilterQuery();
|
||||
var url = '/api/admin/query-asins/export' + (query ? ('?' + query) : '');
|
||||
fetch(url)
|
||||
.then(function (response) {
|
||||
var contentType = response.headers.get('content-type') || '';
|
||||
if (!response.ok || contentType.indexOf('application/json') >= 0) {
|
||||
return response.json().then(function (res) {
|
||||
throw new Error((res && (res.error || res.msg)) || '导出失败');
|
||||
}).catch(function (err) {
|
||||
throw err instanceof Error ? err : new Error('导出失败');
|
||||
});
|
||||
}
|
||||
return response.blob().then(function (blob) {
|
||||
return {
|
||||
blob: blob,
|
||||
filename: extractDownloadFilename(response.headers.get('content-disposition'), 'query-asin.xlsx')
|
||||
};
|
||||
});
|
||||
})
|
||||
.then(function (payload) {
|
||||
triggerBrowserDownload(payload.blob, payload.filename);
|
||||
})
|
||||
.catch(function (err) {
|
||||
alert((err && err.message) || '导出失败');
|
||||
});
|
||||
}
|
||||
function renderQueryAsinCell(item, country) {
|
||||
var asinValue = item[country.field] || '';
|
||||
var infoHtml = asinValue ? '<span>' + asinValue + '</span>' : '<span style="color:#999;">-</span>';
|
||||
@@ -2883,6 +2916,9 @@
|
||||
document.getElementById('btnSearchQueryAsin').onclick = function () {
|
||||
loadQueryAsin(1);
|
||||
};
|
||||
document.getElementById('btnExportQueryAsin').onclick = function () {
|
||||
exportQueryAsin();
|
||||
};
|
||||
document.getElementById('queryAsinFilterShopName').addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -2964,6 +3000,11 @@
|
||||
renderQueryAsinInputs();
|
||||
|
||||
var productCategoryItems = [];
|
||||
var productCategoryNodeMap = {};
|
||||
var productCategoryPageSize = 20;
|
||||
var productCategoryRootState = { children: [], page: 0, total: 0, hasMore: true, loaded: false, loading: false };
|
||||
var productCategorySearchState = { items: [], page: 0, total: 0, hasMore: false, loading: false };
|
||||
var productCategoryKeyword = '';
|
||||
var editingProductCategoryId = null;
|
||||
|
||||
function resetProductCategoryForm() {
|
||||
@@ -2983,27 +3024,102 @@
|
||||
var selected = selectedValue == null ? select.value : String(selectedValue || '');
|
||||
var excluded = excludedId == null ? editingProductCategoryId : excludedId;
|
||||
var options = ['<option value="">顶级类目</option>'];
|
||||
productCategoryItems.forEach(function (item) {
|
||||
productCategoryItems.slice().sort(function (a, b) {
|
||||
return String(a.path || a.name || '').localeCompare(String(b.path || b.name || ''), 'zh-CN');
|
||||
}).forEach(function (item) {
|
||||
if (excluded && String(item.id) === String(excluded)) return;
|
||||
var prefix = new Array((item.level || 0) + 1).join(' ');
|
||||
options.push('<option value="' + escapeHtml(item.id) + '">' + prefix + escapeHtml(item.name || '') + '</option>');
|
||||
});
|
||||
if (selected && !productCategoryItems.some(function (item) { return String(item.id) === selected; })) {
|
||||
options.push('<option value="' + escapeHtml(selected) + '">当前父级 #' + escapeHtml(selected) + '</option>');
|
||||
}
|
||||
select.innerHTML = options.join('');
|
||||
select.value = selected;
|
||||
}
|
||||
|
||||
function renderProductCategoryRows(items) {
|
||||
function normalizeProductCategoryItem(item, parent) {
|
||||
var id = String(item.id || '');
|
||||
var existing = productCategoryNodeMap[id] || {};
|
||||
var level = item.level != null ? item.level : (parent ? (parent.level || 0) + 1 : 0);
|
||||
var path = item.path || (parent && parent.path ? parent.path + ' / ' + (item.name || '') : (item.name || ''));
|
||||
var normalized = Object.assign(existing, item, {
|
||||
id: item.id,
|
||||
parent_id: item.parent_id == null ? null : item.parent_id,
|
||||
child_count: item.child_count || 0,
|
||||
level: level,
|
||||
path: path,
|
||||
children: existing.children || [],
|
||||
page: existing.page || 0,
|
||||
total: existing.total || 0,
|
||||
hasMore: existing.hasMore !== false,
|
||||
loaded: existing.loaded || false,
|
||||
loading: false,
|
||||
expanded: existing.expanded || false
|
||||
});
|
||||
productCategoryNodeMap[id] = normalized;
|
||||
productCategoryItems = Object.keys(productCategoryNodeMap).map(function (key) { return productCategoryNodeMap[key]; });
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function flattenVisibleProductCategories(items, output) {
|
||||
(items || []).forEach(function (item) {
|
||||
output.push({ type: 'item', item: item });
|
||||
if ((item.child_count || 0) > 0 && item.expanded) {
|
||||
flattenVisibleProductCategories(item.children || [], output);
|
||||
if (item.hasMore) {
|
||||
output.push({ type: 'more', parent: item });
|
||||
}
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function renderProductCategoryRows() {
|
||||
var tbody = document.getElementById('productCategoryListBody');
|
||||
if (!tbody) return;
|
||||
if (!items.length) {
|
||||
var rows = productCategoryKeyword
|
||||
? productCategorySearchState.items.map(function (item) { return { type: 'item', item: item, search: true }; })
|
||||
: flattenVisibleProductCategories(productCategoryRootState.children, []);
|
||||
if (!productCategoryKeyword && productCategoryRootState.hasMore) {
|
||||
rows.push({ type: 'more', parent: null });
|
||||
}
|
||||
if (productCategoryKeyword && productCategorySearchState.hasMore) {
|
||||
rows.push({ type: 'searchMore' });
|
||||
}
|
||||
if (!rows.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无商品类目</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = items.map(function (item) {
|
||||
tbody.innerHTML = rows.map(function (row) {
|
||||
if (row.type === 'more') {
|
||||
var parent = row.parent;
|
||||
var level = parent ? (parent.level || 0) + 1 : 0;
|
||||
var indentMore = Math.max(0, level) * 24;
|
||||
var loading = parent ? parent.loading : productCategoryRootState.loading;
|
||||
var loadedCount = parent ? (parent.children || []).length : (productCategoryRootState.children || []).length;
|
||||
var totalCount = parent ? (parent.total || 0) : (productCategoryRootState.total || 0);
|
||||
return '<tr><td colspan="6"><div class="tree-name-cell"><span class="tree-indent" style="--indent:' + indentMore + 'px;"></span>' +
|
||||
'<button class="btn btn-sm btn-secondary" type="button" data-product-category-load-more="' + escapeHtml(parent ? parent.id : '') + '"' + (loading ? ' disabled' : '') + '>' +
|
||||
(loading ? '加载中...' : '加载更多') + '</button>' +
|
||||
'<span class="empty-tip" style="padding:0;">已加载 ' + escapeHtml(loadedCount) + ' / ' + escapeHtml(totalCount) + '</span></div></td></tr>';
|
||||
}
|
||||
if (row.type === 'searchMore') {
|
||||
var searchLoadedCount = (productCategorySearchState.items || []).length;
|
||||
var searchTotalCount = productCategorySearchState.total || 0;
|
||||
return '<tr><td colspan="6"><button class="btn btn-sm btn-secondary" type="button" data-product-category-search-more' + (productCategorySearchState.loading ? ' disabled' : '') + '>' +
|
||||
(productCategorySearchState.loading ? '加载中...' : '加载更多搜索结果') + '</button> ' +
|
||||
'<span class="empty-tip" style="padding:0;">已加载 ' + escapeHtml(searchLoadedCount) + ' / ' + escapeHtml(searchTotalCount) + '</span></td></tr>';
|
||||
}
|
||||
var item = row.item;
|
||||
var indent = Math.max(0, item.level || 0) * 24;
|
||||
var mark = item.child_count > 0 ? '+' : '-';
|
||||
var hasChildren = (item.child_count || 0) > 0;
|
||||
var expanded = !!item.expanded;
|
||||
var mark = hasChildren ? (expanded ? '-' : '+') : '';
|
||||
return '<tr>' +
|
||||
'<td><div class="tree-name-cell"><span class="tree-indent" style="--indent:' + indent + 'px;"></span><span class="tree-node-mark">' + mark + '</span><strong>' + escapeHtml(item.name || '') + '</strong></div></td>' +
|
||||
'<td><div class="tree-name-cell"><span class="tree-indent" style="--indent:' + indent + 'px;"></span>' +
|
||||
'<button type="button" class="tree-node-mark' + (hasChildren ? '' : ' is-leaf') + '" data-product-category-toggle="' + escapeHtml(item.id) + '"' + (hasChildren ? '' : ' disabled') + '>' + mark + '</button>' +
|
||||
'<strong>' + escapeHtml(item.name || '') + '</strong></div></td>' +
|
||||
'<td><div>' + escapeHtml(item.path || item.name || '') + '</div><div class="category-path">' + escapeHtml(item.category_key || '') + '</div></td>' +
|
||||
'<td>' + escapeHtml(item.sort_order || 0) + '</td>' +
|
||||
'<td><span class="category-tag">' + (item.is_builtin ? '内置' : '自定义') + '</span></td>' +
|
||||
@@ -3012,6 +3128,24 @@
|
||||
'<button class="btn btn-sm btn-danger" data-product-category-delete="' + escapeHtml(item.id) + '" data-product-category-name="' + escapeHtml(item.name || '') + '"' + (item.child_count > 0 ? ' disabled' : '') + '>删除</button></td>' +
|
||||
'</tr>';
|
||||
}).join('');
|
||||
document.querySelectorAll('[data-product-category-toggle]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
var id = String(btn.dataset.productCategoryToggle || '');
|
||||
if (!id || btn.disabled) return;
|
||||
toggleProductCategoryNode(id);
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('[data-product-category-load-more]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
var parentId = (btn.dataset.productCategoryLoadMore || '').trim();
|
||||
loadProductCategoryChildren(parentId || null, true);
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('[data-product-category-search-more]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
loadProductCategorySearch(true);
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('[data-product-category-edit]').forEach(function (btn) {
|
||||
btn.onclick = function () {
|
||||
var item = productCategoryItems.find(function (row) { return String(row.id) === String(btn.dataset.productCategoryEdit); });
|
||||
@@ -3047,28 +3181,136 @@
|
||||
}
|
||||
|
||||
function loadProductCategories() {
|
||||
fetch('/api/admin/product-categories')
|
||||
if (productCategoryKeyword) {
|
||||
productCategorySearchState = { items: [], page: 0, total: 0, hasMore: false, loading: false };
|
||||
loadProductCategorySearch(false);
|
||||
return;
|
||||
}
|
||||
productCategoryRootState = { children: [], page: 0, total: 0, hasMore: true, loaded: false, loading: false };
|
||||
productCategoryNodeMap = {};
|
||||
productCategoryItems = [];
|
||||
renderProductCategoryParentOptions();
|
||||
loadProductCategoryChildren(null, false);
|
||||
}
|
||||
|
||||
function loadProductCategoryChildren(parentId, append) {
|
||||
var parent = parentId ? productCategoryNodeMap[String(parentId)] : null;
|
||||
var state = parent || productCategoryRootState;
|
||||
if (state.loading) return;
|
||||
state.loading = true;
|
||||
var failed = false;
|
||||
renderProductCategoryRows();
|
||||
var nextPage = append ? (state.page || 0) + 1 : 1;
|
||||
var query = '?page=' + nextPage + '&page_size=' + productCategoryPageSize;
|
||||
if (parentId) query += '&parent_id=' + encodeURIComponent(parentId);
|
||||
fetch('/api/admin/product-categories/children' + query)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
var tbody = document.getElementById('productCategoryListBody');
|
||||
if (!res.success) {
|
||||
failed = true;
|
||||
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + escapeHtml(res.error || '') + '</td></tr>';
|
||||
return;
|
||||
}
|
||||
productCategoryItems = res.items || [];
|
||||
var children = (res.items || []).map(function (item) {
|
||||
return normalizeProductCategoryItem(item, parent);
|
||||
});
|
||||
if (!append) {
|
||||
state.children = [];
|
||||
}
|
||||
state.children = state.children.concat(children);
|
||||
state.page = res.page || nextPage;
|
||||
state.total = res.total || 0;
|
||||
state.hasMore = !!res.has_more;
|
||||
state.loaded = true;
|
||||
renderProductCategoryParentOptions();
|
||||
renderProductCategoryRows(productCategoryItems);
|
||||
renderProductCategoryRows();
|
||||
})
|
||||
.catch(function () {
|
||||
failed = true;
|
||||
var tbody = document.getElementById('productCategoryListBody');
|
||||
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">请求失败</td></tr>';
|
||||
})
|
||||
.finally(function () {
|
||||
state.loading = false;
|
||||
if (!failed) renderProductCategoryRows();
|
||||
});
|
||||
}
|
||||
|
||||
function loadProductCategorySearch(append) {
|
||||
if (productCategorySearchState.loading) return;
|
||||
productCategorySearchState.loading = true;
|
||||
var failed = false;
|
||||
renderProductCategoryRows();
|
||||
var nextPage = append ? (productCategorySearchState.page || 0) + 1 : 1;
|
||||
fetch('/api/admin/product-categories/search?keyword=' + encodeURIComponent(productCategoryKeyword) +
|
||||
'&page=' + nextPage + '&page_size=' + productCategoryPageSize)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
var tbody = document.getElementById('productCategoryListBody');
|
||||
if (!res.success) {
|
||||
failed = true;
|
||||
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + escapeHtml(res.error || '') + '</td></tr>';
|
||||
return;
|
||||
}
|
||||
var items = (res.items || []).map(function (item) {
|
||||
var normalized = normalizeProductCategoryItem(item, null);
|
||||
normalized.expanded = false;
|
||||
return normalized;
|
||||
});
|
||||
productCategorySearchState.items = append
|
||||
? productCategorySearchState.items.concat(items)
|
||||
: items;
|
||||
productCategorySearchState.page = res.page || nextPage;
|
||||
productCategorySearchState.total = res.total || 0;
|
||||
productCategorySearchState.hasMore = !!res.has_more;
|
||||
renderProductCategoryParentOptions();
|
||||
renderProductCategoryRows();
|
||||
})
|
||||
.catch(function () {
|
||||
failed = true;
|
||||
var tbody = document.getElementById('productCategoryListBody');
|
||||
if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">请求失败</td></tr>';
|
||||
})
|
||||
.finally(function () {
|
||||
productCategorySearchState.loading = false;
|
||||
if (!failed) renderProductCategoryRows();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleProductCategoryNode(id) {
|
||||
var item = productCategoryNodeMap[String(id)];
|
||||
if (!item) return;
|
||||
item.expanded = !item.expanded;
|
||||
if (item.expanded && !item.loaded) {
|
||||
loadProductCategoryChildren(id, false);
|
||||
return;
|
||||
}
|
||||
renderProductCategoryRows();
|
||||
}
|
||||
|
||||
document.getElementById('btnCancelProductCategoryEdit').onclick = function () {
|
||||
resetProductCategoryForm();
|
||||
};
|
||||
|
||||
document.getElementById('btnSearchProductCategory').onclick = function () {
|
||||
productCategoryKeyword = (document.getElementById('productCategoryKeyword').value || '').trim();
|
||||
loadProductCategories();
|
||||
};
|
||||
|
||||
document.getElementById('btnClearProductCategorySearch').onclick = function () {
|
||||
productCategoryKeyword = '';
|
||||
document.getElementById('productCategoryKeyword').value = '';
|
||||
loadProductCategories();
|
||||
};
|
||||
|
||||
document.getElementById('productCategoryKeyword').onkeydown = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
document.getElementById('btnSearchProductCategory').click();
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('btnCreateProductCategory').onclick = function () {
|
||||
var msgEl = document.getElementById('msgProductCategory');
|
||||
var name = (document.getElementById('productCategoryName').value || '').trim();
|
||||
|
||||
@@ -10,16 +10,32 @@ try:
|
||||
from config import mysql_user as config_mysql_user
|
||||
from config import mysql_password as config_mysql_password
|
||||
from config import mysql_database as config_mysql_database
|
||||
from config import mysql_host_source as config_mysql_host_source
|
||||
from config import mysql_user_source as config_mysql_user_source
|
||||
from config import mysql_database_source as config_mysql_database_source
|
||||
except ImportError:
|
||||
config_mysql_host = 'localhost'
|
||||
config_mysql_user = 'root'
|
||||
config_mysql_password = ''
|
||||
config_mysql_database = 'maixiang_ai'
|
||||
config_mysql_host_source = 'fallback.default'
|
||||
config_mysql_user_source = 'fallback.default'
|
||||
config_mysql_database_source = 'fallback.default'
|
||||
|
||||
mysql_host = os.environ.get('MYSQL_HOST', config_mysql_host)
|
||||
mysql_user = os.environ.get('MYSQL_USER', config_mysql_user)
|
||||
mysql_password = os.environ.get('MYSQL_PASSWORD', config_mysql_password)
|
||||
mysql_database = os.environ.get('MYSQL_DATABASE', config_mysql_database)
|
||||
mysql_host = config_mysql_host
|
||||
mysql_user = config_mysql_user
|
||||
mysql_password = config_mysql_password
|
||||
mysql_database = config_mysql_database
|
||||
mysql_host_source = config_mysql_host_source
|
||||
mysql_user_source = config_mysql_user_source
|
||||
mysql_database_source = config_mysql_database_source
|
||||
|
||||
|
||||
def describe_db_target():
|
||||
return (
|
||||
f"{mysql_user}@{mysql_host}/{mysql_database} "
|
||||
f"(host={mysql_host_source}, user={mysql_user_source}, database={mysql_database_source})"
|
||||
)
|
||||
|
||||
|
||||
def get_db():
|
||||
|
||||
@@ -371,6 +371,7 @@
|
||||
.tree-node-mark {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: #eef1ff;
|
||||
color: #667eea;
|
||||
@@ -379,6 +380,14 @@
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tree-node-mark.is-leaf {
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.category-path {
|
||||
@@ -1317,6 +1326,7 @@
|
||||
<input type="text" id="queryAsinFilterAsin" placeholder="请输入 ASIN">
|
||||
</div>
|
||||
<button class="btn" id="btnSearchQueryAsin">查询</button>
|
||||
<button class="btn btn-secondary" id="btnExportQueryAsin" type="button">导出 XLSX</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -1365,7 +1375,16 @@
|
||||
<p class="msg" id="msgProductCategory"></p>
|
||||
</div>
|
||||
<div class="panel-box">
|
||||
<h3 style="margin-bottom:16px;font-size:15px;">类目树</h3>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;">
|
||||
<h3 style="font-size:15px;">类目树</h3>
|
||||
<div class="form-row" style="gap:8px;margin:0;flex:0 1 420px;">
|
||||
<div class="form-group" style="min-width:220px;flex:1;margin:0;">
|
||||
<input type="text" id="productCategoryKeyword" placeholder="搜索类目名称、编码或备注">
|
||||
</div>
|
||||
<button class="btn btn-sm" id="btnSearchProductCategory" type="button">搜索</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btnClearProductCategorySearch" type="button">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
Reference in New Issue
Block a user