提交货源采集更新
This commit is contained in:
547002
2026_05_06.log
547002
2026_05_06.log
File diff suppressed because one or more lines are too long
82604
2026_05_07.log
82604
2026_05_07.log
File diff suppressed because one or more lines are too long
4
app/.env
4
app/.env
@@ -13,8 +13,8 @@ client_name=ShuFuAI
|
||||
|
||||
|
||||
# java_api_base=http://47.111.163.154:18080
|
||||
# java_api_base=http://127.0.0.1:18080
|
||||
java_api_base=http://127.0.0.1:18080
|
||||
# java_api_base=http://8.136.19.173:18080
|
||||
java_api_base=http://121.196.149.225:18080
|
||||
# java_api_base=http://121.196.149.225:18080
|
||||
|
||||
|
||||
|
||||
1
app/assets/appearance-patent-BbMI4oev.css
Normal file
1
app/assets/appearance-patent-BbMI4oev.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{bb as r}from"./pywebview-BPTq9Myk.js";const n="";function s(e){return r(`${n}/api/brand/expand-folder-recursive`,{folder:e})}export{s as e};
|
||||
import{be as r}from"./pywebview-DNfEzGOK.js";const n="";function s(e){return r(`${n}/api/brand/expand-folder-recursive`,{folder:e})}export{s as e};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
app/assets/similar-asin-0mttxPNV.css
Normal file
1
app/assets/similar-asin-0mttxPNV.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>格式转换 - 数富AI</title>
|
||||
<script type="module" crossorigin src="/assets/convert.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-BPTq9Myk.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-DqzUfBy1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-DNfEzGOK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-D4E3c93-.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-DNJAt1gR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/convert-BKSNvX8i.css">
|
||||
</head>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>数据去重 - 数富AI</title>
|
||||
<script type="module" crossorigin src="/assets/dedupe.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-BPTq9Myk.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-DqzUfBy1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-DNfEzGOK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-D4E3c93-.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-DNJAt1gR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/dedupe-DNlVfFj-.css">
|
||||
</head>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>删除品牌 - 数富AI</title>
|
||||
<script type="module" crossorigin src="/assets/delete-brand.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-BPTq9Myk.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-DqzUfBy1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-DNfEzGOK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-D4E3c93-.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/categorized-timers-JPA-olTr.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-DNJAt1gR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/delete-brand-BJod9K65.css">
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>数据拆分 - 数富AI</title>
|
||||
<script type="module" crossorigin src="/assets/split.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-BPTq9Myk.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-DqzUfBy1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-DNfEzGOK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-D4E3c93-.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-DNJAt1gR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/split-BoBVrLdC.css">
|
||||
</head>
|
||||
|
||||
@@ -9,7 +9,7 @@ public class SimilarAsinProperties {
|
||||
private String cozeBaseUrl = "https://api.coze.cn";
|
||||
private String cozeWorkflowPath = "/v1/workflow/run";
|
||||
private String cozeWorkflowHistoryPath = "/v1/workflows/{workflow_id}/run_histories/{execute_id}";
|
||||
private String cozeWorkflowId = "7632683471312355338";
|
||||
private String cozeWorkflowId = "7635328462404583478";
|
||||
private String cozeToken = "";
|
||||
private int cozeBatchSize = 50;
|
||||
private int cozeConnectTimeoutMillis = 10000;
|
||||
|
||||
@@ -556,7 +556,6 @@ public class AppearancePatentTaskService {
|
||||
: row.getResultFilename();
|
||||
}
|
||||
|
||||
@Scheduled(cron = "${aiimage.appearance-patent.stale-finalize-cron:0 */2 * * * *}")
|
||||
public void finalizeStaleTasks() {
|
||||
if (transactionManager != null) {
|
||||
LocalDateTime threshold = LocalDateTime.now().minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
|
||||
|
||||
@@ -1209,7 +1209,6 @@ public class BrandTaskService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@org.springframework.scheduling.annotation.Scheduled(cron = "${aiimage.brand-progress.stale-check-cron:0 */2 * * * *}")
|
||||
public void failStaleRunningTasks() {
|
||||
DistributedJobLockService.LockHandle lockHandle = distributedJobLockService.tryLock("brand:stale-check", STALE_CHECK_LOCK_TTL);
|
||||
if (lockHandle == null) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.nanri.aiimage.common.service.DistributedJobLockService;
|
||||
import com.nanri.aiimage.config.DeleteBrandProgressProperties;
|
||||
import com.nanri.aiimage.modules.appearancepatent.service.AppearancePatentTaskService;
|
||||
import com.nanri.aiimage.modules.brand.service.BrandTaskService;
|
||||
import com.nanri.aiimage.modules.productrisk.service.ProductRiskTaskCacheService;
|
||||
import com.nanri.aiimage.modules.queryasin.service.QueryAsinTaskCacheService;
|
||||
import com.nanri.aiimage.modules.queryasin.service.QueryAsinTaskService;
|
||||
@@ -12,6 +14,7 @@ import com.nanri.aiimage.modules.patroldelete.service.PatrolDeleteTaskCacheServi
|
||||
import com.nanri.aiimage.modules.patroldelete.service.PatrolDeleteTaskService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskCacheService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskService;
|
||||
import com.nanri.aiimage.modules.similarasin.service.SimilarAsinTaskService;
|
||||
import com.nanri.aiimage.modules.shopmatch.service.ShopMatchTaskCacheService;
|
||||
import com.nanri.aiimage.modules.shopmatch.service.ShopMatchTaskService;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
@@ -63,6 +66,9 @@ public class DeleteBrandStaleTaskService {
|
||||
private final PatrolDeleteTaskCacheService patrolDeleteTaskCacheService;
|
||||
private final QueryAsinTaskService queryAsinTaskService;
|
||||
private final QueryAsinTaskCacheService queryAsinTaskCacheService;
|
||||
private final BrandTaskService brandTaskService;
|
||||
private final AppearancePatentTaskService appearancePatentTaskService;
|
||||
private final SimilarAsinTaskService similarAsinTaskService;
|
||||
private final DeleteBrandProgressProperties deleteBrandProgressProperties;
|
||||
private final DistributedJobLockService distributedJobLockService;
|
||||
private final TaskDistributedLockService taskDistributedLockService;
|
||||
@@ -88,6 +94,9 @@ public class DeleteBrandStaleTaskService {
|
||||
ShopMatchStaleCheckStats shopMatchStats = failStaleShopMatchTasks();
|
||||
ShopMatchStaleCheckStats patrolDeleteStats = failStalePatrolDeleteTasks();
|
||||
ShopMatchStaleCheckStats queryAsinStats = failStaleQueryAsinTasks();
|
||||
runModuleStaleCheck("brand", brandTaskService::failStaleRunningTasks);
|
||||
runModuleStaleCheck("appearance-patent", appearancePatentTaskService::finalizeStaleTasks);
|
||||
runModuleStaleCheck("similar-asin", similarAsinTaskService::finalizeStaleTasks);
|
||||
log.info("[stale-check] product-risk summary scanned={} finalized={} failed={} skipped={} elapsedMs={} thread={}",
|
||||
stats.scannedTaskCount,
|
||||
stats.finalizedTaskCount,
|
||||
@@ -126,6 +135,23 @@ public class DeleteBrandStaleTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
private void runModuleStaleCheck(String moduleName, Runnable action) {
|
||||
long startedAt = System.currentTimeMillis();
|
||||
try {
|
||||
action.run();
|
||||
log.info("[stale-check] {} delegated stale check completed elapsedMs={} thread={}",
|
||||
moduleName,
|
||||
System.currentTimeMillis() - startedAt,
|
||||
Thread.currentThread().getName());
|
||||
} catch (Exception ex) {
|
||||
log.warn("[stale-check] {} delegated stale check failed elapsedMs={} msg={}",
|
||||
moduleName,
|
||||
System.currentTimeMillis() - startedAt,
|
||||
ex.getMessage(),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void failStaleDeleteBrandTasks() {
|
||||
long minutes = Math.max(1L, deleteBrandProgressProperties.getHeartbeatTimeoutMinutes());
|
||||
long initialMinutes = Math.max(minutes, deleteBrandProgressProperties.getDeleteBrandInitialTimeoutMinutes());
|
||||
|
||||
@@ -210,6 +210,9 @@ public class SimilarAsinCozeClient {
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("workflow_id", properties.getCozeWorkflowId());
|
||||
body.put("parameters", parameters);
|
||||
if (apiKey != null && !apiKey.isBlank()) {
|
||||
body.put("api_key", apiKey.trim());
|
||||
}
|
||||
body.put("is_async", Boolean.TRUE);
|
||||
log.info("[similar-asin] coze request url={} body={}",
|
||||
joinUrl(properties.getCozeBaseUrl(), properties.getCozeWorkflowPath()),
|
||||
@@ -256,20 +259,12 @@ public class SimilarAsinCozeClient {
|
||||
}
|
||||
|
||||
private Map<String, Object> buildParameters(List<SimilarAsinResultRowDto> rows, String prompt, String apiKey) {
|
||||
List<String> groupKeys = rows.stream().map(row -> nonBlank(row.getGroupKey(), rowKey(row))).toList();
|
||||
List<String> rowIds = rows.stream().map(row -> nonBlank(row.getId(), "")).toList();
|
||||
List<String> asins = rows.stream().map(row -> nonBlank(row.getAsin(), "")).toList();
|
||||
List<String> countries = rows.stream().map(row -> nonBlank(row.getCountry(), "")).toList();
|
||||
List<String> prices = rows.stream().map(row -> nonBlank(row.getPrice(), "")).toList();
|
||||
List<String> titles = rows.stream().map(row -> nonBlank(row.getTitle(), row.getAsin())).toList();
|
||||
List<String> urls = rows.stream().map(row -> nonBlank(row.getUrl(), "")).toList();
|
||||
List<List<String>> urlLists = rows.stream().map(SimilarAsinResultRowDto::getUrls).toList();
|
||||
|
||||
Map<String, Object> parameters = new LinkedHashMap<>();
|
||||
parameters.put("title_list", titles);
|
||||
parameters.put("url_list", urls);
|
||||
parameters.put("url_lists", urlLists);
|
||||
parameters.put("items", buildItemObjects(rows, groupKeys, rowIds, asins, countries, prices, titles, urls, urlLists));
|
||||
parameters.put("items", buildItemObjects(rows, asins, urls, urlLists));
|
||||
parameters.put("prompt", prompt == null ? "" : prompt);
|
||||
if (apiKey != null && !apiKey.isBlank()) {
|
||||
parameters.put("api_key", apiKey.trim());
|
||||
@@ -280,6 +275,10 @@ public class SimilarAsinCozeClient {
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> maskCozeRequestBody(Map<String, Object> body) {
|
||||
Map<String, Object> masked = new LinkedHashMap<>(body);
|
||||
Object topLevelApiKey = masked.get("api_key");
|
||||
if (topLevelApiKey instanceof String apiKeyText && !apiKeyText.isBlank()) {
|
||||
masked.put("api_key", maskSecret(apiKeyText));
|
||||
}
|
||||
Object parametersObj = masked.get("parameters");
|
||||
if (parametersObj instanceof Map<?, ?> parameters) {
|
||||
Map<String, Object> maskedParameters = new LinkedHashMap<>((Map<String, Object>) parameters);
|
||||
@@ -304,25 +303,14 @@ public class SimilarAsinCozeClient {
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildItemObjects(List<SimilarAsinResultRowDto> rows,
|
||||
List<String> groupKeys,
|
||||
List<String> rowIds,
|
||||
List<String> asins,
|
||||
List<String> countries,
|
||||
List<String> prices,
|
||||
List<String> titles,
|
||||
List<String> urls,
|
||||
List<List<String>> urlLists) {
|
||||
List<Map<String, Object>> items = new ArrayList<>(rows.size());
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("group_key", groupKeys.get(i));
|
||||
item.put("row_id", rowIds.get(i));
|
||||
item.put("asin", asins.get(i));
|
||||
item.put("country", countries.get(i));
|
||||
item.put("price", prices.get(i));
|
||||
item.put("title", titles.get(i));
|
||||
item.put("url", urls.get(i));
|
||||
item.put("urls", urlLists.get(i));
|
||||
item.put("target_urls", urlLists.get(i));
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
@@ -616,7 +616,6 @@ public class SimilarAsinTaskService {
|
||||
: row.getResultFilename();
|
||||
}
|
||||
|
||||
@Scheduled(cron = "${aiimage.similar-asin.stale-finalize-cron:0 */2 * * * *}")
|
||||
public void finalizeStaleTasks() {
|
||||
if (transactionManager != null) {
|
||||
LocalDateTime threshold = LocalDateTime.now().minusMinutes(Math.max(1, properties.getStaleTimeoutMinutes()));
|
||||
@@ -1429,6 +1428,10 @@ public class SimilarAsinTaskService {
|
||||
SimilarAsinCozeClient.CozeSubmitResponse submit = submitCozeWorkflowThrottled(batchRows, prompt, apiKey);
|
||||
if (submit.immediateData() != null && !submit.immediateData().isBlank()) {
|
||||
List<SimilarAsinResultRowDto> cozeRows = cozeClient.mergeRowsFromDataText(batchRows, submit.immediateData());
|
||||
String emptyResultMessage = emptyCozeResultMessage(cozeRows, batchRows.size());
|
||||
if (!emptyResultMessage.isBlank()) {
|
||||
throw new IllegalStateException(emptyResultMessage);
|
||||
}
|
||||
mergeCozeRowsIntoChunk(task, chunk.getScopeHash(), chunk.getChunkIndex(), cozeRows, allRowsByBaseId);
|
||||
return false;
|
||||
}
|
||||
@@ -1570,15 +1573,22 @@ public class SimilarAsinTaskService {
|
||||
if (batchRows.isEmpty() && failureMessage.isBlank()) {
|
||||
failureMessage = "Coze batch payload missing";
|
||||
}
|
||||
List<SimilarAsinResultRowDto> cozeRows;
|
||||
if (failureMessage.isBlank()) {
|
||||
cozeRows = cozeClient.mergeRowsFromDataText(batchRows, poll.resolvedPayloadText());
|
||||
failureMessage = emptyCozeResultMessage(cozeRows, batchRows.size());
|
||||
} else {
|
||||
cozeRows = List.of();
|
||||
}
|
||||
if (!failureMessage.isBlank() && splitRetryFailedCozeBatchState(state, context, batchRows, failureMessage)) {
|
||||
return;
|
||||
}
|
||||
if (!failureMessage.isBlank() && retryFailedCozeBatchState(state, context, batchRows, failureMessage)) {
|
||||
return;
|
||||
}
|
||||
List<SimilarAsinResultRowDto> cozeRows = failureMessage.isBlank()
|
||||
? cozeClient.mergeRowsFromDataText(batchRows, poll.resolvedPayloadText())
|
||||
: cozeClient.markRowsFailed(batchRows, failureMessage);
|
||||
if (!failureMessage.isBlank()) {
|
||||
cozeRows = cozeClient.markRowsFailed(batchRows, failureMessage);
|
||||
}
|
||||
FileTaskEntity task = fileTaskMapper.selectById(state.getTaskId());
|
||||
if (task != null) {
|
||||
Map<String, List<SimilarAsinParsedRowVo>> allRowsByBaseId = loadAllRowsByBaseId(task);
|
||||
@@ -1819,6 +1829,7 @@ public class SimilarAsinTaskService {
|
||||
String normalized = normalize(failureMessage).toLowerCase(Locale.ROOT);
|
||||
return normalized.contains("timeout")
|
||||
|| normalized.contains("timed out")
|
||||
|| normalized.contains("empty result rows")
|
||||
|| normalized.contains("out of limit")
|
||||
|| normalized.contains("execution limit")
|
||||
|| normalized.contains("720712008")
|
||||
@@ -1834,6 +1845,7 @@ public class SimilarAsinTaskService {
|
||||
|| normalized.contains("retry later")
|
||||
|| normalized.contains("timeout")
|
||||
|| normalized.contains("timed out")
|
||||
|| normalized.contains("empty result rows")
|
||||
|| normalized.contains("out of limit")
|
||||
|| normalized.contains("execution limit")
|
||||
|| normalized.contains("702093018")
|
||||
@@ -1846,6 +1858,21 @@ public class SimilarAsinTaskService {
|
||||
|| normalized.contains("\u8c03\u7528\u8d85\u65f6");
|
||||
}
|
||||
|
||||
private String emptyCozeResultMessage(List<SimilarAsinResultRowDto> cozeRows, int expectedRows) {
|
||||
if (cozeRows == null || cozeRows.isEmpty()) {
|
||||
return expectedRows > 0 ? "Coze async workflow returned empty result rows" : "";
|
||||
}
|
||||
long unresolved = cozeRows.stream()
|
||||
.filter(row -> row != null
|
||||
&& !hasResolvedCozeFields(row)
|
||||
&& !isTechnicalCozeFailure(row.getError()))
|
||||
.count();
|
||||
if (unresolved <= 0) {
|
||||
return "";
|
||||
}
|
||||
return "Coze async workflow returned empty result rows: " + unresolved + "/" + Math.max(expectedRows, cozeRows.size());
|
||||
}
|
||||
|
||||
private void updateCozeStateRunning(TaskScopeStateEntity state, String error) {
|
||||
int updated = taskScopeStateMapper.update(null, new LambdaUpdateWrapper<TaskScopeStateEntity>()
|
||||
.eq(TaskScopeStateEntity::getId, state.getId())
|
||||
|
||||
@@ -36,7 +36,7 @@ AIIMAGE_APPEARANCE_PATENT_STALE_TIMEOUT_MINUTES=20
|
||||
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_BASE_URL=https://api.coze.cn
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_PATH=/v1/workflow/run
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID=7632683471312355338
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID=7635328462404583478
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_TOKEN=
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_BATCH_SIZE=50
|
||||
AIIMAGE_SIMILAR_ASIN_COZE_READ_TIMEOUT_MILLIS=60000
|
||||
|
||||
@@ -157,7 +157,7 @@ aiimage:
|
||||
similar-asin:
|
||||
coze-base-url: ${AIIMAGE_SIMILAR_ASIN_COZE_BASE_URL:https://api.coze.cn}
|
||||
coze-workflow-path: ${AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_PATH:/v1/workflow/run}
|
||||
coze-workflow-id: ${AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID:7632683471312355338}
|
||||
coze-workflow-id: ${AIIMAGE_SIMILAR_ASIN_COZE_WORKFLOW_ID:7635328462404583478}
|
||||
coze-token: ${AIIMAGE_SIMILAR_ASIN_COZE_TOKEN:Bearer sat_gsInckdsN0qqhnZSK7K4Zevw6neJCHmYHyIbm85oGgJIGEmSdkH5OFctSKLsAHvT}
|
||||
coze-batch-size: ${AIIMAGE_SIMILAR_ASIN_COZE_BATCH_SIZE:50}
|
||||
coze-connect-timeout-millis: ${AIIMAGE_SIMILAR_ASIN_COZE_CONNECT_TIMEOUT_MILLIS:10000}
|
||||
|
||||
@@ -44,6 +44,29 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="category-card">
|
||||
<div class="category-card-head">
|
||||
<div class="section-title">商品类目</div>
|
||||
<div class="category-actions">
|
||||
<button type="button" class="opt-btn" @click="openCategoryDialog">选择类目</button>
|
||||
<button
|
||||
v-if="selectedProductCategoryIds.length"
|
||||
type="button"
|
||||
class="link-danger"
|
||||
@click="clearSelectedProductCategories"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!selectedProductCategories.length" class="empty-conditions">暂未选择商品类目</div>
|
||||
<div v-else class="selected-category-list">
|
||||
<span v-for="category in selectedProductCategories" :key="category.id" class="category-chip">
|
||||
{{ category.path || category.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="run-row">
|
||||
<button type="button" class="btn-run" :disabled="parsing || !uploadedFiles.length" @click="parseFiles">
|
||||
{{ parsing ? '解析中...' : '解析并创建任务' }}
|
||||
@@ -159,6 +182,41 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="categoryDialogVisible"
|
||||
title="选择商品类目"
|
||||
width="560px"
|
||||
class="category-dialog"
|
||||
>
|
||||
<div class="category-dialog-toolbar">
|
||||
<span class="muted">可多选,确认后会追加到筛选条件 prompt 后面。</span>
|
||||
<button type="button" class="link-danger" @click="clearCategoryTreeSelection">清空选择</button>
|
||||
</div>
|
||||
<div v-if="loadingProductCategories" class="empty-tasks">正在加载商品类目...</div>
|
||||
<div v-else-if="!productCategoryTree.length" class="empty-tasks">暂无商品类目</div>
|
||||
<el-tree
|
||||
v-else
|
||||
ref="categoryTreeRef"
|
||||
class="category-tree"
|
||||
:data="productCategoryTree"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
default-expand-all
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span class="category-tree-node">
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="data.description" class="category-description">{{ data.description }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
<template #footer>
|
||||
<button type="button" class="opt-btn" @click="categoryDialogVisible = false">取消</button>
|
||||
<button type="button" class="btn-run category-confirm" @click="confirmCategorySelection">确定</button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -176,8 +234,10 @@ import {
|
||||
getSimilarAsinHistory,
|
||||
getSimilarAsinResultDownloadUrl,
|
||||
getSimilarAsinTaskProgressBatch,
|
||||
listProductCategories,
|
||||
listSimilarAsinFilterConditions,
|
||||
parseSimilarAsin,
|
||||
type ProductCategoryItemVo,
|
||||
type SimilarAsinFilterConditionVo,
|
||||
type SimilarAsinParsedGroup,
|
||||
type SimilarAsinDashboardVo,
|
||||
@@ -203,6 +263,12 @@ const queuedTaskSummary = ref<TaskSummary | null>(null)
|
||||
const filterConditionInput = ref('')
|
||||
const filterConditions = ref<SimilarAsinFilterConditionVo[]>([])
|
||||
const selectedFilterConditionId = ref<number | null>(null)
|
||||
const productCategoryTree = ref<ProductCategoryItemVo[]>([])
|
||||
const productCategoryItems = ref<ProductCategoryItemVo[]>([])
|
||||
const selectedProductCategoryIds = ref<number[]>([])
|
||||
const categoryDialogVisible = ref(false)
|
||||
const loadingProductCategories = ref(false)
|
||||
const categoryTreeRef = ref<any>(null)
|
||||
const parsing = ref(false)
|
||||
const pushing = ref(false)
|
||||
const savingCondition = ref(false)
|
||||
@@ -243,13 +309,80 @@ function selectedFilterConditionText() {
|
||||
}
|
||||
|
||||
function effectiveFilterCondition() {
|
||||
return filterConditionInput.value.trim() || selectedFilterConditionText()
|
||||
return appendProductCategoriesToPrompt(filterConditionInput.value.trim() || selectedFilterConditionText())
|
||||
}
|
||||
|
||||
function effectiveCozeApiKey() {
|
||||
return getStoredApiSecret('similar-asin').trim()
|
||||
}
|
||||
|
||||
const selectedProductCategories = computed(() => {
|
||||
const selected = new Set(selectedProductCategoryIds.value)
|
||||
return productCategoryItems.value.filter((item) => selected.has(item.id))
|
||||
})
|
||||
|
||||
function appendProductCategoriesToPrompt(prompt: string) {
|
||||
const lines = selectedProductCategories.value
|
||||
.map((item) => {
|
||||
const path = item.path || item.name
|
||||
const description = item.description?.trim()
|
||||
return description ? `${path}(${description})` : path
|
||||
})
|
||||
.filter(Boolean)
|
||||
if (!lines.length) return prompt
|
||||
const categoryBlock = [
|
||||
'商品类目要求:',
|
||||
...lines.map((line) => `- ${line}`),
|
||||
].join('\n')
|
||||
return prompt ? `${prompt}\n\n${categoryBlock}` : categoryBlock
|
||||
}
|
||||
|
||||
async function loadProductCategories() {
|
||||
if (loadingProductCategories.value) return
|
||||
loadingProductCategories.value = true
|
||||
try {
|
||||
const res = await listProductCategories()
|
||||
productCategoryTree.value = res.tree || []
|
||||
productCategoryItems.value = res.items || []
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '加载商品类目失败')
|
||||
} finally {
|
||||
loadingProductCategories.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openCategoryDialog() {
|
||||
categoryDialogVisible.value = true
|
||||
if (!productCategoryTree.value.length) {
|
||||
await loadProductCategories()
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
categoryTreeRef.value?.setCheckedKeys?.(selectedProductCategoryIds.value)
|
||||
})
|
||||
}
|
||||
|
||||
function hasCheckedDescendant(item: ProductCategoryItemVo, checkedIds: Set<number>): boolean {
|
||||
return Boolean(item.children?.some((child) => checkedIds.has(child.id) || hasCheckedDescendant(child, checkedIds)))
|
||||
}
|
||||
|
||||
function confirmCategorySelection() {
|
||||
const checkedNodes = (categoryTreeRef.value?.getCheckedNodes?.(false, false) || []) as ProductCategoryItemVo[]
|
||||
const checkedIds = new Set(checkedNodes.map((item) => item.id))
|
||||
selectedProductCategoryIds.value = checkedNodes
|
||||
.filter((item) => !hasCheckedDescendant(item, checkedIds))
|
||||
.map((item) => item.id)
|
||||
categoryDialogVisible.value = false
|
||||
}
|
||||
|
||||
function clearCategoryTreeSelection() {
|
||||
categoryTreeRef.value?.setCheckedKeys?.([])
|
||||
}
|
||||
|
||||
function clearSelectedProductCategories() {
|
||||
selectedProductCategoryIds.value = []
|
||||
categoryTreeRef.value?.setCheckedKeys?.([])
|
||||
}
|
||||
|
||||
function maskSecret(secret: string) {
|
||||
if (!secret) return ''
|
||||
if (secret.length <= 10) return '***'
|
||||
@@ -781,6 +914,7 @@ onMounted(async () => {
|
||||
loadPollingIds()
|
||||
await Promise.all([
|
||||
loadFilterConditions().catch(() => undefined),
|
||||
loadProductCategories().catch(() => undefined),
|
||||
loadDashboard().catch(() => undefined),
|
||||
loadHistory().catch(() => undefined),
|
||||
])
|
||||
@@ -827,6 +961,18 @@ onUnmounted(() => {
|
||||
.condition-check input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
|
||||
.condition-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.link-danger { border: none; background: transparent; color: #ff8f8f; cursor: pointer; font-size: 12px; padding: 2px 0; white-space: nowrap; }
|
||||
.category-card { margin: 0 0 14px; padding: 12px; border: 1px solid #2a2a2a; border-radius: 8px; background: #202020; }
|
||||
.category-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.category-card-head .section-title { margin-bottom: 0; }
|
||||
.category-actions { display: flex; align-items: center; gap: 10px; }
|
||||
.category-actions .opt-btn { padding: 6px 12px; }
|
||||
.selected-category-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; max-height: 96px; overflow: auto; }
|
||||
.category-chip { max-width: 100%; padding: 4px 8px; border-radius: 6px; background: rgba(52, 152, 219, .16); color: #cfe7ff; font-size: 12px; line-height: 1.4; }
|
||||
.category-dialog-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
|
||||
.category-tree { max-height: 420px; overflow: auto; padding: 8px; border: 1px solid #2f2f2f; border-radius: 8px; background: #202020; --el-tree-bg-color: #202020; --el-tree-text-color: #d8d8d8; --el-tree-node-hover-bg-color: #2a2a2a; }
|
||||
.category-tree-node { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
||||
.category-description { color: #888; font-size: 12px; }
|
||||
.category-confirm { padding: 8px 16px; }
|
||||
.parse-card, .queue-payload { margin-top: 14px; padding: 12px; border: 1px solid #2a2a2a; border-radius: 8px; background: #202020; color: #b8c1cc; font-size: 12px; }
|
||||
.queue-payload { max-height: 220px; overflow: auto; color: #8fd3ff; white-space: pre-wrap; }
|
||||
.panel-header { padding: 16px 20px; border-bottom: 1px solid #2a2a2a; font-size: 15px; font-weight: 600; color: #ddd; }
|
||||
|
||||
@@ -1680,6 +1680,31 @@ export function getAppearancePatentResultDownloadUrl(resultId: number) {
|
||||
|
||||
// ========== 货源查询 ==========
|
||||
|
||||
export interface ProductCategoryItemVo {
|
||||
id: number;
|
||||
parentId?: number | null;
|
||||
name: string;
|
||||
categoryKey?: string;
|
||||
sortOrder?: number;
|
||||
description?: string;
|
||||
isBuiltin?: boolean;
|
||||
childCount?: number;
|
||||
level?: number;
|
||||
path?: string;
|
||||
children?: ProductCategoryItemVo[];
|
||||
}
|
||||
|
||||
export interface ProductCategoryListVo {
|
||||
tree: ProductCategoryItemVo[];
|
||||
items: ProductCategoryItemVo[];
|
||||
}
|
||||
|
||||
export function listProductCategories() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<ProductCategoryListVo>>(`${JAVA_API_PREFIX}/admin/product-categories`),
|
||||
);
|
||||
}
|
||||
|
||||
export interface SimilarAsinFilterConditionVo {
|
||||
id: number;
|
||||
conditionText: string;
|
||||
|
||||
1
new_web_source/assets/brand-D4E3c93-.js
Normal file
1
new_web_source/assets/brand-D4E3c93-.js
Normal file
@@ -0,0 +1 @@
|
||||
import{be as r}from"./pywebview-DNfEzGOK.js";const n="";function s(e){return r(`${n}/api/brand/expand-folder-recursive`,{folder:e})}export{s as e};
|
||||
1
new_web_source/assets/el-table-column-CZbviO2X.css
Normal file
1
new_web_source/assets/el-table-column-CZbviO2X.css
Normal file
File diff suppressed because one or more lines are too long
55
new_web_source/assets/pywebview-DNfEzGOK.js
Normal file
55
new_web_source/assets/pywebview-DNfEzGOK.js
Normal file
File diff suppressed because one or more lines are too long
1
new_web_source/assets/similar-asin-0mttxPNV.css
Normal file
1
new_web_source/assets/similar-asin-0mttxPNV.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,12 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>货源查询</title>
|
||||
<script type="module" crossorigin src="/assets/similar-asin.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-CcblbV5J.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-CEi7uRFN.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-DNfEzGOK.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/brand-D4E3c93-.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/categorized-timers-JPA-olTr.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-DNJAt1gR.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/similar-asin-CVTb4S1E.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/el-table-column-BoPoyBFZ.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/similar-asin-0mttxPNV.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/el-table-column-CZbviO2X.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
BIN
tmp/repair-7686/7686-current.xlsx
Normal file
BIN
tmp/repair-7686/7686-current.xlsx
Normal file
Binary file not shown.
BIN
tmp/repair-7686/7686-redownload-full.xlsx
Normal file
BIN
tmp/repair-7686/7686-redownload-full.xlsx
Normal file
Binary file not shown.
BIN
tmp/repair-7686/7686-redownload.xlsx
Normal file
BIN
tmp/repair-7686/7686-redownload.xlsx
Normal file
Binary file not shown.
BIN
tmp/repair-7686/魏振峰查询专利-7686-repaired-full.xlsx
Normal file
BIN
tmp/repair-7686/魏振峰查询专利-7686-repaired-full.xlsx
Normal file
Binary file not shown.
BIN
tmp/repair-7686/魏振峰查询专利-7686-repaired.xlsx
Normal file
BIN
tmp/repair-7686/魏振峰查询专利-7686-repaired.xlsx
Normal file
Binary file not shown.
BIN
tmp/repair-7686/魏振峰查询专利-7686-supply-repaired.xlsx
Normal file
BIN
tmp/repair-7686/魏振峰查询专利-7686-supply-repaired.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user