Compare commits
1 Commits
origin-syn
...
backup/bac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9835831415 |
6
app/.env
6
app/.env
@@ -11,9 +11,9 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot
|
|||||||
|
|
||||||
client_name=ShuFuAI
|
client_name=ShuFuAI
|
||||||
|
|
||||||
# java_api_base=http://api.aishufu.top:18080/
|
# java_api_base=http://api.aishufu.top:18080/
|
||||||
java_api_base=http://api.aishufu.top:18080/
|
# java_api_base=http://api.aishufu.top:18080/
|
||||||
# java_api_base=http://api.aishufu.top:18080/
|
java_api_base=http://127.0.0.1:18080/
|
||||||
|
|
||||||
# 与 Java 后端共享的 JWT 签名密钥,必须与 backend-java 的 AIIMAGE_JWT_SECRET 完全一致
|
# 与 Java 后端共享的 JWT 签名密钥,必须与 backend-java 的 AIIMAGE_JWT_SECRET 完全一致
|
||||||
AIIMAGE_JWT_SECRET=please-change-this-secret-please-rotate-at-least-32-bytes
|
AIIMAGE_JWT_SECRET=please-change-this-secret-please-rotate-at-least-32-bytes
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ public class SimilarAsinProperties {
|
|||||||
* 不影响 AppearancePatentProperties 的同名值。
|
* 不影响 AppearancePatentProperties 的同名值。
|
||||||
*/
|
*/
|
||||||
private int cozeBatchSize = 3;
|
private int cozeBatchSize = 3;
|
||||||
|
/**
|
||||||
|
* img_switch=false 时单次提交 Coze 的 row 数。
|
||||||
|
* 不走图片检测时工作流压力小,恢复到 10 行一批以提高吞吐;开启图片检测时仍使用 cozeBatchSize。
|
||||||
|
*/
|
||||||
|
private int cozeTextOnlyBatchSize = 10;
|
||||||
private int cozeConnectTimeoutMillis = 10000;
|
private int cozeConnectTimeoutMillis = 10000;
|
||||||
private int cozeReadTimeoutMillis = 60000;
|
private int cozeReadTimeoutMillis = 60000;
|
||||||
private int cozePollIntervalMillis = 30000;
|
private int cozePollIntervalMillis = 30000;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.nanri.aiimage.modules.productcategory.controller;
|
package com.nanri.aiimage.modules.productcategory.controller;
|
||||||
|
|
||||||
import com.nanri.aiimage.common.api.ApiResponse;
|
import com.nanri.aiimage.common.api.ApiResponse;
|
||||||
|
import com.nanri.aiimage.common.util.DownloadHeaderUtil;
|
||||||
import com.nanri.aiimage.modules.productcategory.model.dto.ProductCategorySaveRequest;
|
import com.nanri.aiimage.modules.productcategory.model.dto.ProductCategorySaveRequest;
|
||||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryItemVo;
|
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryItemVo;
|
||||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryListVo;
|
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryListVo;
|
||||||
@@ -9,6 +10,9 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
@@ -19,12 +23,17 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/api/admin")
|
@RequestMapping("/api/admin")
|
||||||
@Tag(name = "商品类目管理", description = "维护商品类目树")
|
@Tag(name = "商品类目管理", description = "维护商品类目树")
|
||||||
public class ProductCategoryController {
|
public class ProductCategoryController {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter EXPORT_FILENAME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||||
|
|
||||||
private final ProductCategoryService productCategoryService;
|
private final ProductCategoryService productCategoryService;
|
||||||
|
|
||||||
@GetMapping("/product-categories")
|
@GetMapping("/product-categories")
|
||||||
@@ -51,6 +60,18 @@ public class ProductCategoryController {
|
|||||||
return ApiResponse.success(productCategoryService.search(keyword, page, pageSize));
|
return ApiResponse.success(productCategoryService.search(keyword, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/product-categories/export")
|
||||||
|
@Operation(summary = "导出商品类目")
|
||||||
|
public ResponseEntity<byte[]> export(@RequestParam(value = "keyword", required = false) String keyword) {
|
||||||
|
byte[] bytes = productCategoryService.export(keyword);
|
||||||
|
String filename = "product-categories-" + LocalDateTime.now().format(EXPORT_FILENAME_FORMATTER) + ".xlsx";
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, DownloadHeaderUtil.contentDisposition(filename))
|
||||||
|
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
|
||||||
|
.contentLength(bytes.length)
|
||||||
|
.body(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/product-category")
|
@PostMapping("/product-category")
|
||||||
@Operation(summary = "新增商品类目")
|
@Operation(summary = "新增商品类目")
|
||||||
public ApiResponse<ProductCategoryItemVo> create(@Valid @RequestBody ProductCategorySaveRequest request) {
|
public ApiResponse<ProductCategoryItemVo> create(@Valid @RequestBody ProductCategorySaveRequest request) {
|
||||||
|
|||||||
@@ -9,9 +9,15 @@ import com.nanri.aiimage.modules.productcategory.model.entity.ProductCategoryEnt
|
|||||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryItemVo;
|
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryItemVo;
|
||||||
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryListVo;
|
import com.nanri.aiimage.modules.productcategory.model.vo.ProductCategoryListVo;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -25,6 +31,8 @@ import java.util.Set;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class ProductCategoryService {
|
public class ProductCategoryService {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
private final ProductCategoryMapper productCategoryMapper;
|
private final ProductCategoryMapper productCategoryMapper;
|
||||||
|
|
||||||
public ProductCategoryListVo list() {
|
public ProductCategoryListVo list() {
|
||||||
@@ -169,6 +177,26 @@ public class ProductCategoryService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] export(String keyword) {
|
||||||
|
ProductCategoryListVo list = list(keyword);
|
||||||
|
List<ProductCategoryItemVo> rows = list.getItems() == null ? List.of() : list.getItems();
|
||||||
|
Map<Long, String> nameById = buildExportNameById(rows);
|
||||||
|
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
Sheet sheet = workbook.createSheet("商品类目");
|
||||||
|
writeExportHeader(sheet);
|
||||||
|
for (int index = 0; index < rows.size(); index++) {
|
||||||
|
writeExportRow(sheet, index + 1, rows.get(index), nameById);
|
||||||
|
}
|
||||||
|
applyExportColumnWidths(sheet);
|
||||||
|
workbook.write(outputStream);
|
||||||
|
workbook.dispose();
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("导出商品类目失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ProductCategoryListVo buildSearchPageVo(List<ProductCategoryEntity> rows, Long total, long page, long pageSize) {
|
private ProductCategoryListVo buildSearchPageVo(List<ProductCategoryEntity> rows, Long total, long page, long pageSize) {
|
||||||
Map<Long, Integer> childCountById = buildChildCountById(rows);
|
Map<Long, Integer> childCountById = buildChildCountById(rows);
|
||||||
List<ProductCategoryItemVo> items = rows.stream()
|
List<ProductCategoryItemVo> items = rows.stream()
|
||||||
@@ -200,6 +228,82 @@ public class ProductCategoryService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeExportHeader(Sheet sheet) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = {
|
||||||
|
"序号", "关系类型", "上级类目", "类目名称", "层级路径", "类目编码",
|
||||||
|
"层级", "排序", "来源", "备注", "子类目数", "类目ID", "父级ID", "创建时间", "更新时间"
|
||||||
|
};
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
header.createCell(i).setCellValue(headers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeExportRow(Sheet sheet, int rowNo, ProductCategoryItemVo item, Map<Long, String> nameById) {
|
||||||
|
Row row = sheet.createRow(rowNo);
|
||||||
|
int col = 0;
|
||||||
|
row.createCell(col++).setCellValue(rowNo);
|
||||||
|
row.createCell(col++).setCellValue(resolveRelationType(item));
|
||||||
|
row.createCell(col++).setCellValue(resolveParentName(item, nameById));
|
||||||
|
row.createCell(col++).setCellValue(indentName(item));
|
||||||
|
row.createCell(col++).setCellValue(blankToEmpty(item.getPath()));
|
||||||
|
row.createCell(col++).setCellValue(blankToEmpty(item.getCategoryKey()));
|
||||||
|
row.createCell(col++).setCellValue(item.getLevel() == null ? 0 : item.getLevel());
|
||||||
|
row.createCell(col++).setCellValue(item.getSortOrder() == null ? 0 : item.getSortOrder());
|
||||||
|
row.createCell(col++).setCellValue(Boolean.TRUE.equals(item.getIsBuiltin()) ? "内置" : "自定义");
|
||||||
|
row.createCell(col++).setCellValue(blankToEmpty(item.getDescription()));
|
||||||
|
row.createCell(col++).setCellValue(item.getChildCount() == null ? 0 : item.getChildCount());
|
||||||
|
row.createCell(col++).setCellValue(item.getId() == null ? "" : String.valueOf(item.getId()));
|
||||||
|
row.createCell(col++).setCellValue(item.getParentId() == null ? "" : String.valueOf(item.getParentId()));
|
||||||
|
row.createCell(col++).setCellValue(formatExportTime(item.getCreatedAt()));
|
||||||
|
row.createCell(col).setCellValue(formatExportTime(item.getUpdatedAt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyExportColumnWidths(Sheet sheet) {
|
||||||
|
int[] widths = {2400, 4200, 7200, 8600, 12000, 7200, 2400, 2400, 2800, 10000, 3200, 3600, 3600, 5200, 5200};
|
||||||
|
for (int i = 0; i < widths.length; i++) {
|
||||||
|
sheet.setColumnWidth(i, widths[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, String> buildExportNameById(List<ProductCategoryItemVo> rows) {
|
||||||
|
Map<Long, String> nameById = new HashMap<>();
|
||||||
|
for (ProductCategoryItemVo row : rows) {
|
||||||
|
if (row.getId() != null) {
|
||||||
|
nameById.put(row.getId(), row.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nameById;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveRelationType(ProductCategoryItemVo item) {
|
||||||
|
int level = item.getLevel() == null ? 0 : item.getLevel();
|
||||||
|
if (level <= 0) {
|
||||||
|
return "顶级类目";
|
||||||
|
}
|
||||||
|
return level + "级子类目";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveParentName(ProductCategoryItemVo item, Map<Long, String> nameById) {
|
||||||
|
if (item.getParentId() == null) {
|
||||||
|
return "无";
|
||||||
|
}
|
||||||
|
String parentName = nameById.get(item.getParentId());
|
||||||
|
if (parentName == null || parentName.isBlank()) {
|
||||||
|
return "上级ID: " + item.getParentId();
|
||||||
|
}
|
||||||
|
return parentName + "(ID: " + item.getParentId() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String indentName(ProductCategoryItemVo item) {
|
||||||
|
int level = Math.max(0, item.getLevel() == null ? 0 : item.getLevel());
|
||||||
|
return " ".repeat(level) + blankToEmpty(item.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatExportTime(LocalDateTime time) {
|
||||||
|
return time == null ? "" : time.format(EXPORT_TIME_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<Long, Integer> buildChildCountById(List<ProductCategoryEntity> rows) {
|
private Map<Long, Integer> buildChildCountById(List<ProductCategoryEntity> rows) {
|
||||||
Map<Long, Integer> childCountById = new HashMap<>();
|
Map<Long, Integer> childCountById = new HashMap<>();
|
||||||
if (rows.isEmpty()) {
|
if (rows.isEmpty()) {
|
||||||
@@ -450,6 +554,10 @@ public class ProductCategoryService {
|
|||||||
return normalized.length() > 512 ? normalized.substring(0, 512) : normalized;
|
return normalized.length() > 512 ? normalized.substring(0, 512) : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String blankToEmpty(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
|
||||||
private String generateCategoryKey(Long parentId, String name) {
|
private String generateCategoryKey(Long parentId, String name) {
|
||||||
String source = (parentId == null ? "root" : String.valueOf(parentId)) + ":" + name;
|
String source = (parentId == null ? "root" : String.valueOf(parentId)) + ":" + name;
|
||||||
return "custom_" + DigestUtil.sha1Hex(source).substring(0, 20);
|
return "custom_" + DigestUtil.sha1Hex(source).substring(0, 20);
|
||||||
|
|||||||
@@ -1231,7 +1231,7 @@ public class SimilarAsinTaskService {
|
|||||||
String prompt = readAiPrompt(task);
|
String prompt = readAiPrompt(task);
|
||||||
String apiKey = readApiKey(task);
|
String apiKey = readApiKey(task);
|
||||||
boolean imgSwitch = readImgSwitch(task);
|
boolean imgSwitch = readImgSwitch(task);
|
||||||
int batchSize = Math.max(1, properties.getCozeBatchSize());
|
int batchSize = resolveCozeBatchSize(imgSwitch);
|
||||||
List<SimilarAsinResultRowDto> result = new ArrayList<>();
|
List<SimilarAsinResultRowDto> result = new ArrayList<>();
|
||||||
for (int i = 0; i < items.size(); i += batchSize) {
|
for (int i = 0; i < items.size(); i += batchSize) {
|
||||||
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt, apiKey, imgSwitch));
|
result.addAll(cozeClient.inspect(items.subList(i, Math.min(i + batchSize, items.size())), prompt, apiKey, imgSwitch));
|
||||||
@@ -1553,6 +1553,11 @@ public class SimilarAsinTaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int resolveCozeBatchSize(boolean imgSwitch) {
|
||||||
|
int configured = imgSwitch ? properties.getCozeBatchSize() : properties.getCozeTextOnlyBatchSize();
|
||||||
|
return Math.max(1, configured);
|
||||||
|
}
|
||||||
|
|
||||||
private void finalizeTask(FileTaskEntity task, String error, int rowCount, boolean assembleWorkbook) {
|
private void finalizeTask(FileTaskEntity task, String error, int rowCount, boolean assembleWorkbook) {
|
||||||
String finalError = error;
|
String finalError = error;
|
||||||
FileResultEntity result = null;
|
FileResultEntity result = null;
|
||||||
@@ -1687,7 +1692,7 @@ public class SimilarAsinTaskService {
|
|||||||
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
|
.eq(TaskChunkEntity::getModuleType, MODULE_TYPE)
|
||||||
.orderByAsc(TaskChunkEntity::getChunkIndex));
|
.orderByAsc(TaskChunkEntity::getChunkIndex));
|
||||||
Map<String, List<SimilarAsinParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
|
Map<String, List<SimilarAsinParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
|
||||||
int batchSize = Math.max(1, properties.getCozeBatchSize());
|
int batchSize = resolveCozeBatchSize(readImgSwitch(task));
|
||||||
int cozeWorkUnits = countCozeWorkUnits(chunks, batchSize);
|
int cozeWorkUnits = countCozeWorkUnits(chunks, batchSize);
|
||||||
int totalProgressUnits = Math.max(3, cozeWorkUnits + 3);
|
int totalProgressUnits = Math.max(3, cozeWorkUnits + 3);
|
||||||
if (countPendingCozeStates(task.getId()) > 0) {
|
if (countPendingCozeStates(task.getId()) > 0) {
|
||||||
@@ -1921,7 +1926,7 @@ public class SimilarAsinTaskService {
|
|||||||
String prompt = readAiPrompt(task);
|
String prompt = readAiPrompt(task);
|
||||||
String apiKey = readApiKey(task);
|
String apiKey = readApiKey(task);
|
||||||
boolean imgSwitch = readImgSwitch(task);
|
boolean imgSwitch = readImgSwitch(task);
|
||||||
int batchSize = Math.max(1, properties.getCozeBatchSize());
|
int batchSize = resolveCozeBatchSize(imgSwitch);
|
||||||
// P1-1:检测到 720712008 风暴时强制把 batch 降到 1,隔离毒行;
|
// P1-1:检测到 720712008 风暴时强制把 batch 降到 1,隔离毒行;
|
||||||
// 持续 5+ 次提交命中率 ≥ 40% 才会触发,正常波动不影响吞吐。
|
// 持续 5+ 次提交命中率 ≥ 40% 才会触发,正常波动不影响吞吐。
|
||||||
if (isPoisonStormActive(task.getId())) {
|
if (isPoisonStormActive(task.getId())) {
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ aiimage:
|
|||||||
coze-workflow-id: ${AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID:7639708860686024756}
|
coze-workflow-id: ${AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID:7639708860686024756}
|
||||||
coze-token: ${AIIMAGE_SIMILAR_ASIN_COZE_TOKEN:}
|
coze-token: ${AIIMAGE_SIMILAR_ASIN_COZE_TOKEN:}
|
||||||
coze-batch-size: ${AIIMAGE_SIMILAR_ASIN_COZE_BATCH_SIZE:3}
|
coze-batch-size: ${AIIMAGE_SIMILAR_ASIN_COZE_BATCH_SIZE:3}
|
||||||
|
coze-text-only-batch-size: ${AIIMAGE_SIMILAR_ASIN_COZE_TEXT_ONLY_BATCH_SIZE:10}
|
||||||
coze-credential-stripe-size: ${AIIMAGE_SIMILAR_ASIN_COZE_CREDENTIAL_STRIPE_SIZE:0}
|
coze-credential-stripe-size: ${AIIMAGE_SIMILAR_ASIN_COZE_CREDENTIAL_STRIPE_SIZE:0}
|
||||||
coze-connect-timeout-millis: ${AIIMAGE_SIMILAR_ASIN_COZE_CONNECT_TIMEOUT_MILLIS:10000}
|
coze-connect-timeout-millis: ${AIIMAGE_SIMILAR_ASIN_COZE_CONNECT_TIMEOUT_MILLIS:10000}
|
||||||
coze-read-timeout-millis: ${AIIMAGE_SIMILAR_ASIN_COZE_READ_TIMEOUT_MILLIS:60000}
|
coze-read-timeout-millis: ${AIIMAGE_SIMILAR_ASIN_COZE_READ_TIMEOUT_MILLIS:60000}
|
||||||
|
|||||||
@@ -1306,6 +1306,46 @@ def search_product_categories():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api.route('/product-categories/export')
|
||||||
|
@login_required
|
||||||
|
def export_product_categories():
|
||||||
|
_ensure_product_category_schema()
|
||||||
|
_, _, denied = _ensure_product_category_access()
|
||||||
|
if denied:
|
||||||
|
return denied
|
||||||
|
keyword = (request.args.get('keyword') or '').strip()
|
||||||
|
params = {}
|
||||||
|
if keyword:
|
||||||
|
params['keyword'] = keyword
|
||||||
|
|
||||||
|
url = f"{backend_java_base_url}/api/admin/product-categories/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',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin_api.route('/product-category', methods=['POST'])
|
@admin_api.route('/product-category', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def create_product_category():
|
def create_product_category():
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ backend_java_base_url, backend_java_base_url_source = _get_env(
|
|||||||
"java_api_base",
|
"java_api_base",
|
||||||
"BACKEND_JAVA_BASE_URL",
|
"BACKEND_JAVA_BASE_URL",
|
||||||
default="http://api.aishufu.top:18080/",
|
default="http://api.aishufu.top:18080/",
|
||||||
# default="http://api.aishufu.top:18080/",
|
# default="http://127.0.0.1:18080/",
|
||||||
)
|
)
|
||||||
backend_java_base_url = backend_java_base_url.rstrip("/")
|
backend_java_base_url = backend_java_base_url.rstrip("/")
|
||||||
os.environ["OSS_ACCESS_KEY_ID"] = accessKeyId
|
os.environ["OSS_ACCESS_KEY_ID"] = accessKeyId
|
||||||
|
|||||||
@@ -3420,6 +3420,34 @@
|
|||||||
renderProductCategoryRows();
|
renderProductCategoryRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportProductCategories() {
|
||||||
|
var keyword = (document.getElementById('productCategoryKeyword').value || '').trim();
|
||||||
|
var url = '/api/admin/product-categories/export' + (keyword ? ('?keyword=' + encodeURIComponent(keyword)) : '');
|
||||||
|
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'), 'product-categories.xlsx')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function (payload) {
|
||||||
|
triggerBrowserDownload(payload.blob, payload.filename);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
alert(err.message || '导出失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('btnCancelProductCategoryEdit').onclick = function () {
|
document.getElementById('btnCancelProductCategoryEdit').onclick = function () {
|
||||||
resetProductCategoryForm();
|
resetProductCategoryForm();
|
||||||
};
|
};
|
||||||
@@ -3435,6 +3463,10 @@
|
|||||||
loadProductCategories();
|
loadProductCategories();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.getElementById('btnExportProductCategory').onclick = function () {
|
||||||
|
exportProductCategories();
|
||||||
|
};
|
||||||
|
|
||||||
document.getElementById('productCategoryKeyword').onkeydown = function (event) {
|
document.getElementById('productCategoryKeyword').onkeydown = function (event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -3976,4 +4008,3 @@
|
|||||||
loadAdminMenus();
|
loadAdminMenus();
|
||||||
});
|
});
|
||||||
}) ();
|
}) ();
|
||||||
|
|
||||||
|
|||||||
@@ -1426,6 +1426,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm" id="btnSearchProductCategory" type="button">搜索</button>
|
<button class="btn btn-sm" id="btnSearchProductCategory" type="button">搜索</button>
|
||||||
<button class="btn btn-sm btn-secondary" id="btnClearProductCategorySearch" type="button">清空</button>
|
<button class="btn btn-sm btn-secondary" id="btnClearProductCategorySearch" type="button">清空</button>
|
||||||
|
<button class="btn btn-sm btn-secondary" id="btnExportProductCategory" type="button">导出</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
|
|||||||
Reference in New Issue
Block a user