feat(patrol-delete): 删除条件改为添加式交互——输入后回车/点添加逐个加入chips列表(可单项移除),去掉原'保存到条件库+勾选'两步式与已知状态下拉,启动时随任务携带条件
This commit is contained in:
@@ -63,51 +63,43 @@
|
|||||||
<div class="section-title condition-title">
|
<div class="section-title condition-title">
|
||||||
<span>删除条件</span>
|
<span>删除条件</span>
|
||||||
<span class="condition-title-hint">
|
<span class="condition-title-hint">
|
||||||
注意删除条件的描述需要和页面描述一致
|
输入删除条件后点击添加(或回车);启动时作为本次任务的删除条件携带
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="condition-panel">
|
<div class="condition-panel">
|
||||||
<div class="condition-input-row">
|
<div class="condition-input-row">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="conditionInput"
|
v-model="conditionSelectValue"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入删除条件"
|
placeholder="请输入删除条件,回车或点击添加"
|
||||||
@keyup.enter="confirmAddCondition"
|
@keyup.enter="addCurrentCondition"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="opt-btn"
|
class="opt-btn"
|
||||||
:disabled="addingCondition"
|
@click="addCurrentCondition"
|
||||||
@click="confirmAddCondition"
|
|
||||||
>
|
>
|
||||||
{{ addingCondition ? "保存中..." : "保存" }}
|
添加
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!deleteConditions.length" class="empty-conditions">
|
<div v-if="!taskConditionTexts.length" class="empty-conditions">
|
||||||
暂无删除条件,保存后可在启动任务时多选携带。
|
暂无删除条件,输入后点击添加。
|
||||||
</div>
|
</div>
|
||||||
<ul v-else class="condition-list">
|
<ul v-else class="condition-list condition-list--chips">
|
||||||
<li
|
<li
|
||||||
v-for="condition in deleteConditions"
|
v-for="text in taskConditionTexts"
|
||||||
:key="condition.id"
|
:key="text"
|
||||||
class="condition-item"
|
class="condition-chip"
|
||||||
|
:title="text"
|
||||||
>
|
>
|
||||||
<label class="condition-check">
|
<span class="condition-chip-text">{{ text }}</span>
|
||||||
<input
|
|
||||||
v-model="selectedConditionIds"
|
|
||||||
type="checkbox"
|
|
||||||
:value="condition.id"
|
|
||||||
/>
|
|
||||||
<span :title="condition.conditionText">
|
|
||||||
{{ condition.conditionText }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="link-danger"
|
class="condition-chip-remove"
|
||||||
@click="removeCondition(condition.id)"
|
title="移除"
|
||||||
|
@click="removeTaskConditionText(text)"
|
||||||
>
|
>
|
||||||
删除
|
×
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -247,22 +239,18 @@ import TaskCenterPanel from "@/shared/components/tasks/TaskCenterPanel.vue";
|
|||||||
import type { TaskItemView, TaskStatCard } from "@/shared/components/tasks/types";
|
import type { TaskItemView, TaskStatCard } from "@/shared/components/tasks/types";
|
||||||
import {
|
import {
|
||||||
addPatrolDeleteCandidate,
|
addPatrolDeleteCandidate,
|
||||||
addPatrolDeleteCondition,
|
|
||||||
createPatrolDeleteTask,
|
createPatrolDeleteTask,
|
||||||
deletePatrolDeleteCandidate,
|
deletePatrolDeleteCandidate,
|
||||||
deletePatrolDeleteCondition,
|
|
||||||
deletePatrolDeleteHistory,
|
deletePatrolDeleteHistory,
|
||||||
deletePatrolDeleteTask,
|
deletePatrolDeleteTask,
|
||||||
getPatrolDeleteDashboard,
|
getPatrolDeleteDashboard,
|
||||||
getPatrolDeleteHistory,
|
getPatrolDeleteHistory,
|
||||||
getPatrolDeleteTaskProgressBatch,
|
getPatrolDeleteTaskProgressBatch,
|
||||||
getPatrolDeleteResultDownloadUrl,
|
getPatrolDeleteResultDownloadUrl,
|
||||||
listPatrolDeleteConditions,
|
|
||||||
listPatrolDeleteCandidates,
|
listPatrolDeleteCandidates,
|
||||||
matchPatrolDeleteShops,
|
matchPatrolDeleteShops,
|
||||||
submitPatrolDeleteTaskResult,
|
submitPatrolDeleteTaskResult,
|
||||||
type PatrolDeleteCandidateVo,
|
type PatrolDeleteCandidateVo,
|
||||||
type PatrolDeleteConditionVo,
|
|
||||||
type PatrolDeleteCartRatio,
|
type PatrolDeleteCartRatio,
|
||||||
type PatrolDeleteCountrySection,
|
type PatrolDeleteCountrySection,
|
||||||
type PatrolDeleteDashboardVo,
|
type PatrolDeleteDashboardVo,
|
||||||
@@ -289,11 +277,12 @@ const MAX_TRANSIENT_ERRORS = 30;
|
|||||||
const ziniaoVersion = useZiniaoVersion();
|
const ziniaoVersion = useZiniaoVersion();
|
||||||
|
|
||||||
const shopInput = ref("");
|
const shopInput = ref("");
|
||||||
const conditionInput = ref("");
|
|
||||||
const candidates = ref<PatrolDeleteCandidateVo[]>([]);
|
const candidates = ref<PatrolDeleteCandidateVo[]>([]);
|
||||||
const selectedCandidates = ref<PatrolDeleteCandidateVo[]>([]);
|
const selectedCandidates = ref<PatrolDeleteCandidateVo[]>([]);
|
||||||
const deleteConditions = ref<PatrolDeleteConditionVo[]>([]);
|
/** 本次任务已添加的删除条件(文本;输入后回车/点添加逐个加入,启动时随任务携带) */
|
||||||
const selectedConditionIds = ref<number[]>([]);
|
const taskConditionTexts = ref<string[]>([]);
|
||||||
|
/** 删除条件输入框当前值 */
|
||||||
|
const conditionSelectValue = ref("");
|
||||||
const selectedCountryCodes = ref<string[]>([...EU_COUNTRY_CODES]);
|
const selectedCountryCodes = ref<string[]>([...EU_COUNTRY_CODES]);
|
||||||
const matchedItems = ref<PatrolDeleteShopQueueItem[]>([]);
|
const matchedItems = ref<PatrolDeleteShopQueueItem[]>([]);
|
||||||
const historyItems = ref<PatrolDeleteHistoryItem[]>([]);
|
const historyItems = ref<PatrolDeleteHistoryItem[]>([]);
|
||||||
@@ -304,7 +293,6 @@ const dashboard = ref<PatrolDeleteDashboardVo>({
|
|||||||
failedTaskCount: 0,
|
failedTaskCount: 0,
|
||||||
});
|
});
|
||||||
const adding = ref(false);
|
const adding = ref(false);
|
||||||
const addingCondition = ref(false);
|
|
||||||
const matching = ref(false);
|
const matching = ref(false);
|
||||||
const pushing = ref(false);
|
const pushing = ref(false);
|
||||||
const queuePushResult = ref("");
|
const queuePushResult = ref("");
|
||||||
@@ -731,14 +719,6 @@ async function loadCandidates() {
|
|||||||
candidates.value = await listPatrolDeleteCandidates();
|
candidates.value = await listPatrolDeleteCandidates();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadConditions() {
|
|
||||||
deleteConditions.value = await listPatrolDeleteConditions();
|
|
||||||
const existingIds = new Set(deleteConditions.value.map((item) => item.id));
|
|
||||||
selectedConditionIds.value = selectedConditionIds.value.filter((id) =>
|
|
||||||
existingIds.has(id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDashboard() {
|
async function loadDashboard() {
|
||||||
dashboard.value = await getPatrolDeleteDashboard();
|
dashboard.value = await getPatrolDeleteDashboard();
|
||||||
}
|
}
|
||||||
@@ -848,39 +828,27 @@ async function confirmAdd() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmAddCondition() {
|
function addConditionText(text: string) {
|
||||||
const text = conditionInput.value.trim();
|
const trimmed = String(text || "").trim();
|
||||||
if (!text) {
|
if (!trimmed) {
|
||||||
ElMessage.warning("请输入删除条件");
|
ElMessage.warning("请输入删除条件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addingCondition.value = true;
|
if (taskConditionTexts.value.includes(trimmed)) {
|
||||||
try {
|
ElMessage.warning(`删除条件「${trimmed}」已添加`);
|
||||||
const saved = await addPatrolDeleteCondition(text);
|
return;
|
||||||
conditionInput.value = "";
|
|
||||||
await loadConditions();
|
|
||||||
if (saved?.id && !selectedConditionIds.value.includes(saved.id)) {
|
|
||||||
selectedConditionIds.value = [...selectedConditionIds.value, saved.id];
|
|
||||||
}
|
|
||||||
ElMessage.success("删除条件已保存");
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(error instanceof Error ? error.message : "保存失败");
|
|
||||||
} finally {
|
|
||||||
addingCondition.value = false;
|
|
||||||
}
|
}
|
||||||
|
taskConditionTexts.value = [...taskConditionTexts.value, trimmed];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeCondition(id: number) {
|
/** 「添加」按钮 / 回车:把输入文本加入本次任务条件列表 */
|
||||||
try {
|
function addCurrentCondition() {
|
||||||
await deletePatrolDeleteCondition(id);
|
addConditionText(conditionSelectValue.value);
|
||||||
selectedConditionIds.value = selectedConditionIds.value.filter(
|
conditionSelectValue.value = "";
|
||||||
(item) => item !== id,
|
}
|
||||||
);
|
|
||||||
await loadConditions();
|
function removeTaskConditionText(text: string) {
|
||||||
ElMessage.success("删除条件已删除");
|
taskConditionTexts.value = taskConditionTexts.value.filter((item) => item !== text);
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(error instanceof Error ? error.message : "删除失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeCandidate(id: number) {
|
async function removeCandidate(id: number) {
|
||||||
@@ -947,13 +915,10 @@ function buildTaskItem(item: PatrolDeleteShopQueueItem): PatrolDeleteTaskItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectedDeleteConditions() {
|
function selectedDeleteConditions() {
|
||||||
const selected = new Set(selectedConditionIds.value);
|
return taskConditionTexts.value.map((text) => ({
|
||||||
return deleteConditions.value
|
id: text,
|
||||||
.filter((condition) => selected.has(condition.id))
|
conditionText: text,
|
||||||
.map((condition) => ({
|
}));
|
||||||
id: condition.id,
|
|
||||||
conditionText: condition.conditionText,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildQueuePayload(taskId: number, items: PatrolDeleteHistoryItem[]) {
|
function buildQueuePayload(taskId: number, items: PatrolDeleteHistoryItem[]) {
|
||||||
@@ -1223,7 +1188,7 @@ onMounted(async () => {
|
|||||||
loadQueueState();
|
loadQueueState();
|
||||||
loadCountryCodes();
|
loadCountryCodes();
|
||||||
watch(selectedCountryCodes, saveCountryCodes, { deep: true });
|
watch(selectedCountryCodes, saveCountryCodes, { deep: true });
|
||||||
await Promise.all([loadCandidates(), loadConditions(), loadDashboard(), loadHistory()]);
|
await Promise.all([loadCandidates(), loadDashboard(), loadHistory()]);
|
||||||
|
|
||||||
if (activeTaskId.value) {
|
if (activeTaskId.value) {
|
||||||
queuePushResult.value =
|
queuePushResult.value =
|
||||||
@@ -1285,11 +1250,11 @@ async function batchDeleteHistory(views: TaskItemView[]) {
|
|||||||
.condition-input-row :deep(.el-input) { flex: 1; }
|
.condition-input-row :deep(.el-input) { flex: 1; }
|
||||||
.empty-conditions { color: #5e6878; font-size: 12px; padding: 10px 4px; }
|
.empty-conditions { color: #5e6878; font-size: 12px; padding: 10px 4px; }
|
||||||
.condition-list { list-style: none; margin: 0; padding: 0; max-height: 150px; overflow: auto; display: flex; flex-direction: column; gap: 8px; }
|
.condition-list { list-style: none; margin: 0; padding: 0; max-height: 150px; overflow: auto; display: flex; flex-direction: column; gap: 8px; }
|
||||||
.condition-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 6px; border-bottom: 1px solid #333f55; }
|
.condition-list--chips { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px; max-height: none; }
|
||||||
.condition-item:last-child { border-bottom: none; }
|
.condition-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 6px 4px 10px; border: 1px solid #3e6a8f; border-radius: 999px; background: #232a3b; color: #cfd6df; font-size: 12px; }
|
||||||
.condition-check { display: flex; align-items: center; gap: 8px; min-width: 0; color: #cfd6df; font-size: 12px; cursor: pointer; }
|
.condition-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
|
||||||
.condition-check input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
|
.condition-chip-remove { border: none; background: transparent; color: #8ba3c0; cursor: pointer; font-size: 14px; line-height: 1; padding: 0 4px; }
|
||||||
.condition-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.condition-chip-remove:hover { color: #f87171; }
|
||||||
.link-danger { background: none; border: none; color: #e74c3c; cursor: pointer; font-size: 12px; padding: 0; }
|
.link-danger { background: none; border: none; color: #e74c3c; cursor: pointer; font-size: 12px; padding: 0; }
|
||||||
.link-danger:hover { text-decoration: underline; }
|
.link-danger:hover { text-decoration: underline; }
|
||||||
.run-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
|
.run-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user