后台店铺数据记录页新增 Tab 切换与重复 ASIN 分析;task-169/170 HTTP 客户端配置接入;dedupe/brand 相关改造
Build Backend JAR / build (push) Has been cancelled
Build Backend JAR / build (push) Has been cancelled
- 管理后台:店铺数据记录改为列表分页展示(去任务号/文件列/累计标题,'最新'改'更新时间'),新增'重复 ASIN' Tab 按跨店聚合展示 ASIN/店铺/分组/国家/日期/价格;修复失败/进行中任务被过滤不显示的问题 - task-169: BrandCheckHttpConfigResolver 从 aiimage.http-client.* 读取品牌检查超时/重试(默认同现状、钳制),附 8 条测试 - task-170: surefire 内存调整为 1536m - dedupe: DedupeRunService 重构(事务边界/进度)、新增 DedupeRunProgressVo、Controller 与结果 VO 调整,PermissionMenuService 相应适配 - brand/dedupe 前端:BrandDedupeTab/BrandPatrolDeleteTab 改造、新增 CountrySelector 组件与 country-options、类型与接口端对齐、测试更新 - 移除无引用文件:backend/static/logo.jpg、prompts/
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<div class="section-title">店铺输入</div>
|
||||
<div class="input-zone">
|
||||
<div class="hint">
|
||||
左侧负责录入店铺并加入备选区,点击“匹配店铺”只会生成匹配结果;确认后需手动点击“推送到 Python 队列”才会创建并执行任务。
|
||||
左侧负责录入店铺并加入备选区,点击“匹配店铺”只会生成匹配结果;确认后需手动点击“推送到 Python 队列”才会创建并执行任务。任务只会跑下方勾选的国家。
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<el-input
|
||||
@@ -113,6 +113,12 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<CountrySelector
|
||||
v-model="selectedCountryCodes"
|
||||
title="巡店国家与顺序"
|
||||
min-selected-warning="至少保留 1 个国家,否则任务没有可巡查的站点"
|
||||
/>
|
||||
|
||||
<ZiniaoVersionSetting v-model="ziniaoVersion" />
|
||||
|
||||
<div class="run-row">
|
||||
@@ -363,7 +369,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import BrandTopBar from "@/pages/brand/components/BrandTopBar.vue";
|
||||
import {
|
||||
@@ -399,8 +405,13 @@ import { checkQueuePayload } from "@/shared/dispatch-guard";
|
||||
import { passGuard } from "@/shared/dispatch-guard-ui";
|
||||
import ZiniaoVersionSetting from "@/shared/components/ZiniaoVersionSetting.vue";
|
||||
import { useZiniaoVersion } from "@/shared/utils/ziniao-version";
|
||||
import CountrySelector from "@/shared/components/CountrySelector.vue";
|
||||
import {
|
||||
EU_COUNTRY_CODES,
|
||||
countryLabel,
|
||||
sanitizeCountryCodes,
|
||||
} from "@/shared/country-options";
|
||||
|
||||
const COUNTRY_TEMPLATE = ["德国", "英国", "法国", "意大利", "西班牙"] as const;
|
||||
const MAX_TRANSIENT_ERRORS = 30;
|
||||
const ziniaoVersion = useZiniaoVersion();
|
||||
|
||||
@@ -410,6 +421,7 @@ const candidates = ref<PatrolDeleteCandidateVo[]>([]);
|
||||
const selectedCandidates = ref<PatrolDeleteCandidateVo[]>([]);
|
||||
const deleteConditions = ref<PatrolDeleteConditionVo[]>([]);
|
||||
const selectedConditionIds = ref<number[]>([]);
|
||||
const selectedCountryCodes = ref<string[]>([...EU_COUNTRY_CODES]);
|
||||
const matchedItems = ref<PatrolDeleteShopQueueItem[]>([]);
|
||||
const historyItems = ref<PatrolDeleteHistoryItem[]>([]);
|
||||
const dashboard = ref<PatrolDeleteDashboardVo>({
|
||||
@@ -432,6 +444,10 @@ const timers = createCategorizedTimers("patrol-delete");
|
||||
const matchedRunnableItems = computed(() =>
|
||||
matchedItems.value.filter((item) => item.matched),
|
||||
);
|
||||
// 模板结构以中文国家名为 key(Java 的 Excel 列、Python 的站点切换都按中文名匹配)
|
||||
const selectedCountryNames = computed(() =>
|
||||
selectedCountryCodes.value.map((code) => countryLabel(code)),
|
||||
);
|
||||
const taskRecordItems = computed(() => groupHistoryItemsByTask(historyItems.value));
|
||||
const currentSectionItems = computed(() =>
|
||||
taskRecordItems.value.filter((item) => !isTaskTerminal(item.taskStatus)),
|
||||
@@ -459,6 +475,10 @@ function queueStateStorageKey() {
|
||||
return `patrol-delete:queue-state:${uidForStorage()}`;
|
||||
}
|
||||
|
||||
function countryCodesStorageKey() {
|
||||
return `patrol-delete:country-codes:${uidForStorage()}`;
|
||||
}
|
||||
|
||||
function rowKeyForMatch(row: PatrolDeleteShopQueueItem) {
|
||||
return `${(row.shopName || "").trim()}::${row.shopId || ""}`;
|
||||
}
|
||||
@@ -559,7 +579,7 @@ function formatMatchRemark(row: PatrolDeleteShopQueueItem) {
|
||||
}
|
||||
|
||||
function buildTemplateCountrySections(): PatrolDeleteCountrySection[] {
|
||||
return COUNTRY_TEMPLATE.map((country) => ({
|
||||
return selectedCountryNames.value.map((country) => ({
|
||||
country,
|
||||
rows: [
|
||||
{
|
||||
@@ -573,7 +593,7 @@ function buildTemplateCountrySections(): PatrolDeleteCountrySection[] {
|
||||
}
|
||||
|
||||
function buildTemplateCartRatios(): PatrolDeleteCartRatio[] {
|
||||
return COUNTRY_TEMPLATE.map((country) => ({
|
||||
return selectedCountryNames.value.map((country) => ({
|
||||
country,
|
||||
ratio: "",
|
||||
}));
|
||||
@@ -745,6 +765,29 @@ function loadQueueState() {
|
||||
}
|
||||
}
|
||||
|
||||
function saveCountryCodes() {
|
||||
if (typeof window === "undefined") return;
|
||||
window.localStorage.setItem(
|
||||
countryCodesStorageKey(),
|
||||
JSON.stringify(selectedCountryCodes.value),
|
||||
);
|
||||
}
|
||||
|
||||
function loadCountryCodes() {
|
||||
try {
|
||||
const raw =
|
||||
typeof window !== "undefined"
|
||||
? window.localStorage.getItem(countryCodesStorageKey())
|
||||
: null;
|
||||
// 没存过(首次进入)时保持默认的五国全选,与改动前的行为一致
|
||||
selectedCountryCodes.value = raw
|
||||
? sanitizeCountryCodes(JSON.parse(raw))
|
||||
: [...EU_COUNTRY_CODES];
|
||||
} catch {
|
||||
selectedCountryCodes.value = [...EU_COUNTRY_CODES];
|
||||
}
|
||||
}
|
||||
|
||||
function clearActiveQueueTask() {
|
||||
activeTaskId.value = null;
|
||||
saveQueueState();
|
||||
@@ -1011,6 +1054,8 @@ function buildQueuePayload(taskId: number, items: PatrolDeleteHistoryItem[]) {
|
||||
source: "frontend-vue-patrol-delete",
|
||||
delete_conditions: deleteConditionsForTask,
|
||||
deleteConditions: deleteConditionsForTask,
|
||||
// 与店铺数据抓取 / 跟价 / 商品风险等页面统一:国家一律用 country_codes 传代码
|
||||
country_codes: [...selectedCountryCodes.value],
|
||||
items: items.map((item) => ({
|
||||
shopName: item.shopName,
|
||||
shopId: item.shopId,
|
||||
@@ -1122,10 +1167,11 @@ async function processQueue() {
|
||||
queuePayloadText.value = JSON.stringify(payload, null, 2);
|
||||
|
||||
// 删除条件为空时 Python 端没有可执行的判定规则,任务会一直停在执行中
|
||||
// country_codes 为空时 Python 端没有可遍历的站点,同样会空转
|
||||
const guard = checkQueuePayload(payload, {
|
||||
expectedType: "patrol-delete-run",
|
||||
requiredDataKeys: ["taskId"],
|
||||
nonEmptyArrayKeys: ["items", "delete_conditions"],
|
||||
nonEmptyArrayKeys: ["items", "delete_conditions", "country_codes"],
|
||||
});
|
||||
if (!(await passGuard(guard))) {
|
||||
await submitPatrolDeleteTaskResult(created.taskId, {
|
||||
@@ -1171,7 +1217,7 @@ async function processQueue() {
|
||||
}
|
||||
|
||||
for (const item of runnable) removeMatchedRowLocally(item);
|
||||
queuePushResult.value = `任务 ${created.taskId} 已入队,共 ${createdItems.length} 个店铺,等待执行完成`;
|
||||
queuePushResult.value = `任务 ${created.taskId} 已入队,共 ${createdItems.length} 个店铺、${selectedCountryNames.value.length} 个国家,等待执行完成`;
|
||||
|
||||
const finalStatus = await waitForTaskTerminal(created.taskId);
|
||||
queuePushResult.value = `任务 ${created.taskId} ${finalStatus === "SUCCESS" ? "已完成" : finalStatus === "DELETED" ? "已删除" : "执行失败"}`;
|
||||
@@ -1198,7 +1244,11 @@ async function pushToPythonQueue() {
|
||||
ElMessage.warning("请先匹配可用店铺");
|
||||
return;
|
||||
}
|
||||
queuePushResult.value = `开始创建任务,共 ${runnable.length} 个店铺`;
|
||||
if (!selectedCountryCodes.value.length) {
|
||||
ElMessage.warning("请至少勾选 1 个巡店国家");
|
||||
return;
|
||||
}
|
||||
queuePushResult.value = `开始创建任务,共 ${runnable.length} 个店铺、${selectedCountryNames.value.length} 个国家(${selectedCountryNames.value.join("、")})`;
|
||||
await processQueue();
|
||||
}
|
||||
|
||||
@@ -1250,6 +1300,8 @@ async function deleteTaskRecord(item: PatrolDeleteHistoryItem) {
|
||||
onMounted(async () => {
|
||||
loadMatchedItems();
|
||||
loadQueueState();
|
||||
loadCountryCodes();
|
||||
watch(selectedCountryCodes, saveCountryCodes, { deep: true });
|
||||
await Promise.all([loadCandidates(), loadConditions(), loadDashboard(), loadHistory()]);
|
||||
|
||||
if (activeTaskId.value) {
|
||||
|
||||
Reference in New Issue
Block a user