feat(web): 前端 SPA 化 + 工具页任务面板统一与历史批量删除
- SPA 化:22 个 MPA html 入口与 *-main.ts 合并为 index.html + vue-router(URL 无 .html 后缀), 页面跳转全部 router-link,/new_web_source/xxx.html 旧路径归一为 /xxx - 任务面板统一:共享 TaskCenterPanel/TaskItemCard/TaskStatCards/HistoryTaskLayer, 16 个工具页右侧统一为统计卡 + 当前任务 + 历史任务弹层(任务ID/开始/结束/状态必展示) - 历史记录支持单条删除 + 批量勾选删除(确认框/全选/失败提示) - Java 7 模块(dedupe/convert/split/productrisk/shopmatch/pricetrack/deletebrand) history 接口补齐任务时间字段(VO+Service,复用 biz_file_task 列) - 图片工作台/API 层(brand/permission/user)既有未提交改动一并提交
This commit is contained in:
@@ -91,110 +91,91 @@
|
||||
</aside>
|
||||
|
||||
<section class="right-panel">
|
||||
<div class="panel-header">上架结果</div>
|
||||
<div class="task-list-wrap">
|
||||
<div class="clean-result-summary">
|
||||
<div class="summary-card">
|
||||
<span class="summary-label">任务文件</span>
|
||||
<strong>{{ summary.total }}</strong>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<span class="summary-label">已处理文件</span>
|
||||
<strong>{{ summary.completed }}</strong>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<span class="summary-label">成功结果</span>
|
||||
<strong>{{ summary.successCount }}</strong>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<span class="summary-label">失败文件</span>
|
||||
<strong>{{ summary.failedCount }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-list-wrap">
|
||||
<div class="result-list-header">
|
||||
<span>上架任务列表</span>
|
||||
<span class="history-count">历史任务 {{ historyItems.length }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="!visibleTasks.length" class="empty-tasks">
|
||||
暂无上架任务,完成文件选择后点击“开始上架”创建任务。
|
||||
</div>
|
||||
|
||||
<section
|
||||
v-for="detail in visibleTasks"
|
||||
v-else
|
||||
:key="detail.task.id"
|
||||
class="task-section"
|
||||
>
|
||||
<div class="task-section-header">
|
||||
<div class="task-title-wrap">
|
||||
<strong>上架任务 #{{ detail.task.id }}</strong>
|
||||
<span class="task-meta">创建时间:{{ formatDateTime(detail.task.createdAt) }}</span>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<span class="status" :class="statusClass(detail.task.status)">
|
||||
{{ statusText(detail.task.status) }}
|
||||
<TaskCenterPanel
|
||||
:on-batch-delete="batchDeleteHistory"
|
||||
title="上架结果"
|
||||
:cards="publishCards"
|
||||
:current-items="currentTaskViews"
|
||||
:history-items="historyTaskViews"
|
||||
current-title="当前任务"
|
||||
current-empty-text="暂无上架任务,完成文件选择后点击“开始上架”创建任务。"
|
||||
history-empty-text="暂无历史记录"
|
||||
>
|
||||
<template #item-extra="{ item }">
|
||||
<ul class="publish-file-list">
|
||||
<li v-for="file in itemSource(item).files" :key="fileKey(file)" class="publish-file-row">
|
||||
<div class="publish-file-row-head">
|
||||
<span class="publish-file-row-name" :title="file.sourceFilename || ''">
|
||||
{{ file.sourceFilename || `文件 ${file.fileId}` }}
|
||||
</span>
|
||||
<button
|
||||
v-if="canDownload(detail)"
|
||||
type="button"
|
||||
class="download"
|
||||
@click="downloadResult(detail)"
|
||||
>
|
||||
下载结果
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-delete"
|
||||
:disabled="isDeletingTask(detail.task.id)"
|
||||
@click="deleteTaskRecord(detail)"
|
||||
>
|
||||
{{ isDeletingTask(detail.task.id) ? '删除中...' : '删除' }}
|
||||
</button>
|
||||
<span class="publish-file-row-chip" :class="statusClass(file.status)">{{ statusText(file.status) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detail.task.errorMessage" class="task-error">
|
||||
{{ detail.task.errorMessage }}
|
||||
</div>
|
||||
|
||||
<ul class="file-list">
|
||||
<li v-for="file in detail.files" :key="fileKey(file)" class="file-item">
|
||||
<div class="file-main">
|
||||
<div class="file-title" :title="file.sourceFilename || ''">
|
||||
{{ file.sourceFilename || `文件 ${file.fileId}` }}
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<span>店铺:{{ file.shopName || '-' }}</span>
|
||||
<span>匹配:{{ matchText(file) }}</span>
|
||||
<span v-if="file.platform">平台:{{ file.platform }}</span>
|
||||
<span>数据:{{ fileProgressCurrent(file) }}/{{ fileProgressTotal(file) }}</span>
|
||||
</div>
|
||||
<template v-if="shouldShowFileProgress(file)">
|
||||
<div class="file-progress-header">
|
||||
<span>{{ file.progressMessage || statusText(file.status) }}</span>
|
||||
<span>{{ fileProgressPercent(file) }}%</span>
|
||||
</div>
|
||||
<div class="file-progress-track">
|
||||
<div
|
||||
class="file-progress-fill"
|
||||
:class="statusClass(file.status)"
|
||||
:style="{ width: `${fileProgressPercent(file)}%` }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="fileErrorText(file)" class="file-error">{{ fileErrorText(file) }}</div>
|
||||
<div class="publish-file-row-info">
|
||||
<span>店铺:{{ file.shopName || '-' }}</span>
|
||||
<span>匹配:{{ matchText(file) }}</span>
|
||||
<span v-if="file.platform">平台:{{ file.platform }}</span>
|
||||
<span>数据:{{ fileProgressCurrent(file) }}/{{ fileProgressTotal(file) }}</span>
|
||||
</div>
|
||||
<template v-if="shouldShowFileProgress(file)">
|
||||
<div class="publish-file-row-progress-meta">
|
||||
<span>{{ file.progressMessage || statusText(file.status) }}</span>
|
||||
<span>{{ fileProgressPercent(file) }}%</span>
|
||||
</div>
|
||||
<span class="status file-status" :class="statusClass(file.status)">
|
||||
{{ statusText(file.status) }}
|
||||
<div class="publish-file-row-progress-track">
|
||||
<div class="publish-file-row-progress-fill" :class="statusClass(file.status)"
|
||||
:style="{ width: `${fileProgressPercent(file)}%` }"></div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="fileErrorText(file)" class="publish-file-row-error file-error">{{ fileErrorText(file) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template #history-item-extra="{ item }">
|
||||
<ul class="publish-file-list">
|
||||
<li v-for="file in itemSource(item).files" :key="fileKey(file)" class="publish-file-row">
|
||||
<div class="publish-file-row-head">
|
||||
<span class="publish-file-row-name" :title="file.sourceFilename || ''">
|
||||
{{ file.sourceFilename || `文件 ${file.fileId}` }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<span class="publish-file-row-chip" :class="statusClass(file.status)">{{ statusText(file.status) }}</span>
|
||||
</div>
|
||||
<div class="publish-file-row-info">
|
||||
<span>店铺:{{ file.shopName || '-' }}</span>
|
||||
<span>匹配:{{ matchText(file) }}</span>
|
||||
<span v-if="file.platform">平台:{{ file.platform }}</span>
|
||||
<span>数据:{{ fileProgressCurrent(file) }}/{{ fileProgressTotal(file) }}</span>
|
||||
</div>
|
||||
<template v-if="shouldShowFileProgress(file)">
|
||||
<div class="publish-file-row-progress-meta">
|
||||
<span>{{ file.progressMessage || statusText(file.status) }}</span>
|
||||
<span>{{ fileProgressPercent(file) }}%</span>
|
||||
</div>
|
||||
<div class="publish-file-row-progress-track">
|
||||
<div class="publish-file-row-progress-fill" :class="statusClass(file.status)"
|
||||
:style="{ width: `${fileProgressPercent(file)}%` }"></div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="fileErrorText(file)" class="publish-file-row-error file-error">{{ fileErrorText(file) }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template #item-actions="{ item }">
|
||||
<button v-if="canDownload(itemSource(item))" type="button" class="download"
|
||||
@click="downloadResult(itemSource(item))">下载结果</button>
|
||||
<button type="button" class="btn-delete" :disabled="isDeletingTask(itemSource(item).task.id)"
|
||||
@click="deleteTaskRecord(itemSource(item))">
|
||||
{{ isDeletingTask(itemSource(item).task.id) ? '删除中...' : '删除' }}
|
||||
</button>
|
||||
</template>
|
||||
<template #history-item-actions="{ item }">
|
||||
<button v-if="canDownload(itemSource(item))" type="button" class="download"
|
||||
@click="downloadResult(itemSource(item))">下载结果</button>
|
||||
<button type="button" class="btn-delete" :disabled="isDeletingTask(itemSource(item).task.id)"
|
||||
@click="deleteTaskRecord(itemSource(item))">
|
||||
{{ isDeletingTask(itemSource(item).task.id) ? '删除中...' : '删除' }}
|
||||
</button>
|
||||
</template>
|
||||
</TaskCenterPanel>
|
||||
</section>
|
||||
</div>
|
||||
</AmazonToolPageShell>
|
||||
@@ -207,6 +188,8 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.vue';
|
||||
import ModuleTemplateDownload from '@/shared/components/ModuleTemplateDownload.vue'
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import ZiniaoVersionSetting from '@/shared/components/ZiniaoVersionSetting.vue'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import {
|
||||
@@ -410,6 +393,65 @@ const visibleTasks = computed(() => {
|
||||
return Array.from(tasks.values()).sort((left, right) => right.task.id - left.task.id)
|
||||
})
|
||||
|
||||
function isPublishTaskTerminal(detail: PublishTaskDetailVo) {
|
||||
return isTerminalStatus(detail.task?.status)
|
||||
}
|
||||
|
||||
const publishCards = computed<TaskStatCard[]>(() => {
|
||||
const visible = visibleTasks.value
|
||||
const terminal = visible.filter((detail) => isPublishTaskTerminal(detail))
|
||||
let successCount = 0
|
||||
for (const detail of terminal) {
|
||||
const value = normalizeStatus(detail.task?.status)
|
||||
if (value === 'SUCCESS' || value === 'COMPLETED') successCount += 1
|
||||
}
|
||||
return [
|
||||
{ label: '运行中任务', value: visible.length - terminal.length },
|
||||
{ label: '已结束任务', value: terminal.length },
|
||||
{ label: '成功任务', value: successCount },
|
||||
{ label: '失败任务', value: terminal.length - successCount },
|
||||
]
|
||||
})
|
||||
|
||||
const currentTaskViews = computed<TaskItemView[]>(() =>
|
||||
visibleTasks.value
|
||||
.filter((detail) => !isPublishTaskTerminal(detail))
|
||||
.map(toPublishTaskView),
|
||||
)
|
||||
|
||||
const historyTaskViews = computed<TaskItemView[]>(() =>
|
||||
historyItems.value
|
||||
.filter((detail) => isPublishTaskTerminal(detail))
|
||||
.map(toPublishTaskView),
|
||||
)
|
||||
|
||||
function itemSource(item: TaskItemView): PublishTaskDetailVo {
|
||||
return item.source as PublishTaskDetailVo
|
||||
}
|
||||
|
||||
function toPublishTaskView(detail: PublishTaskDetailVo): TaskItemView {
|
||||
const task = detail.task
|
||||
const taskStatus = task?.status
|
||||
const terminal = isTerminalStatus(taskStatus)
|
||||
const rawPercent = Number(task?.percent || 0)
|
||||
const percent = Number.isFinite(rawPercent) ? Math.max(0, Math.min(100, Math.round(rawPercent))) : 0
|
||||
const errorMessage = task?.errorMessage || ''
|
||||
const extraLines: string[] = []
|
||||
if (errorMessage) extraLines.push(`错误:${errorMessage}`)
|
||||
return {
|
||||
key: `publish-${task?.id ?? 'unknown'}`,
|
||||
title: `上架任务 #${task?.id ?? '-'}`,
|
||||
taskId: task?.id ?? '-',
|
||||
startedAt: formatDateTime(task?.startedAt || task?.createdAt),
|
||||
finishedAt: task?.finishedAt ? formatDateTime(task.finishedAt) : terminal ? '-' : '进行中',
|
||||
statusText: statusText(taskStatus),
|
||||
statusClass: statusClass(taskStatus),
|
||||
extraLines,
|
||||
progress: !terminal && percent > 0 ? { percent, stage: '任务进度' } : null,
|
||||
source: detail,
|
||||
}
|
||||
}
|
||||
|
||||
const summary = computed(() => {
|
||||
const files = visibleTasks.value.flatMap((detail) => detail.files || [])
|
||||
const successCount = files.filter((file) => ['SUCCESS', 'COMPLETED'].includes(normalizeStatus(file.status))).length
|
||||
@@ -870,7 +912,7 @@ async function processQueue() {
|
||||
saveQueueState()
|
||||
try {
|
||||
await activatePublishFile(taskId, nextFileId)
|
||||
updateCurrentFile(nextFileId, { status: 'RUNNING', progressMessage: '已派发到 Python 队列' })
|
||||
updateCurrentFile(nextFileId, { status: 'RUNNING', progressMessage: '已启动任务' })
|
||||
const payload = buildQueuePayload(taskId, file)
|
||||
// totalRows/totalPages 为 0 时 Python 端翻不到任何明细,会一直停在执行中
|
||||
const guard = checkQueuePayload(payload, {
|
||||
@@ -882,7 +924,7 @@ async function processQueue() {
|
||||
throw new Error(`文件 ${file.sourceFilename || nextFileId} 数据校验未通过,已阻止推送`)
|
||||
}
|
||||
const result = await api.enqueue_json(payload)
|
||||
if (!result?.success) throw new Error(result?.error || 'Python 队列拒绝接收任务')
|
||||
if (!result?.success) throw new Error(result?.error || '任务队列拒绝接收任务')
|
||||
queueMessage.value = pendingFileIds.value.length
|
||||
? `文件 ${file.sourceFilename || nextFileId} 已入队,完成后继续剩余 ${pendingFileIds.value.length} 个文件。`
|
||||
: `文件 ${file.sourceFilename || nextFileId} 已入队,等待执行完成。`
|
||||
@@ -1158,6 +1200,26 @@ onBeforeUnmount(() => {
|
||||
disposed = true
|
||||
timers.clearScope()
|
||||
})
|
||||
|
||||
/**
|
||||
* 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表)
|
||||
*/
|
||||
async function batchDeleteHistory(views: TaskItemView[]) {
|
||||
if (!views.length) return
|
||||
let failed = 0
|
||||
for (const view of views) {
|
||||
try {
|
||||
await deleteTaskRecord(itemSource(view))
|
||||
} catch {
|
||||
failed += 1
|
||||
}
|
||||
}
|
||||
if (failed > 0) {
|
||||
ElMessage.warning(`删除完成,${failed} 条失败`)
|
||||
} else {
|
||||
ElMessage.success(`已删除 ${views.length} 条历史记录`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -1167,8 +1229,8 @@ onBeforeUnmount(() => {
|
||||
.right-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #151a25; }
|
||||
.section-title { margin-bottom: 10px; color: #a0acbe; font-size: 13px; }
|
||||
.upload-zone { margin-bottom: 20px; padding: 20px; border: 1px dashed #3e4a62; border-radius: 8px; background: #2e3a52; text-align: center; }
|
||||
.hint, .loading-msg, .task-meta, .file-info, .history-count { color: #5e6878; font-size: 12px; line-height: 1.5; }
|
||||
.btns, .run-row, .task-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
|
||||
.hint, .loading-msg { color: #5e6878; font-size: 12px; line-height: 1.5; }
|
||||
.btns, .run-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
|
||||
.btns { justify-content: center; }
|
||||
.opt-btn, .btn-run, .download, .btn-delete { border: 0; border-radius: 6px; cursor: pointer; transition: background-color .15s ease, color .15s ease, opacity .15s ease; }
|
||||
.opt-btn { padding: 8px 16px; border: 1px solid #3e4a62; background: #2e3a52; color: #c8d2e2; font-size: 13px; }
|
||||
@@ -1190,54 +1252,38 @@ onBeforeUnmount(() => {
|
||||
.btn-run:hover:not(:disabled) { background: #2980b9; }
|
||||
.queue-status { margin-top: 16px; padding: 12px; border-left: 3px solid #409eff; background: #2e3a52; color: #b8d8ef; font-size: 12px; line-height: 1.6; }
|
||||
.queue-status-title { margin-bottom: 4px; }
|
||||
.panel-header { padding: 16px 20px; border-bottom: 1px solid #2e3a52; color: #c8d2e2; font-size: 15px; font-weight: 600; }
|
||||
.task-list-wrap { flex: 1; padding: 16px; overflow-y: auto; }
|
||||
.clean-result-summary { display: grid; grid-template-columns: repeat(4, minmax(120px, 1fr)); gap: 12px; margin-bottom: 18px; }
|
||||
.summary-card { padding: 14px 16px; border: 1px solid #2e3a52; border-radius: 8px; background: #1c2333; }
|
||||
.summary-card strong { display: block; margin-top: 8px; color: #f5f8fc; font-size: 22px; }
|
||||
.summary-label { color: #5e6878; font-size: 12px; }
|
||||
.result-list-wrap { min-height: 260px; border: 1px solid #2e3a52; border-radius: 8px; background: #1c2333; }
|
||||
.result-list-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid #2e3a52; color: #c8d2e2; font-size: 14px; }
|
||||
.empty-tasks { padding: 24px 16px; color: #5e6878; font-size: 13px; }
|
||||
.task-section + .task-section { border-top: 1px solid #333f55; }
|
||||
.task-section-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 14px 16px; background: #222; }
|
||||
.task-title-wrap { display: flex; min-width: 0; flex-direction: column; gap: 4px; color: #c8d2e2; }
|
||||
.task-error, .file-error { color: #ef8d8d; font-size: 12px; }
|
||||
.task-error { padding: 10px 16px 0; }
|
||||
.download { padding: 7px 12px; background: #2d6b46; color: #dff7e8; font-size: 12px; white-space: nowrap; }
|
||||
.download:hover { background: #367d54; }
|
||||
.btn-delete { padding: 7px 12px; background: #472929; color: #efaaaa; font-size: 12px; white-space: nowrap; }
|
||||
.btn-delete:hover:not(:disabled) { background: #5a3030; color: #ffd0d0; }
|
||||
.btn-delete:disabled { cursor: wait; opacity: .55; }
|
||||
.status { display: inline-flex; align-items: center; justify-content: center; min-width: 58px; min-height: 26px; padding: 0 8px; border-radius: 4px; font-size: 12px; white-space: nowrap; }
|
||||
.status.pending { background: #3a3424; color: #e4c56a; }
|
||||
.status.running { background: #24394a; color: #75bff1; }
|
||||
.status.success { background: #233c2d; color: #72d598; }
|
||||
.status.failed { background: #472929; color: #ef8d8d; }
|
||||
.file-list { margin: 0; padding: 0; list-style: none; }
|
||||
.file-item { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 14px 16px; border-top: 1px solid #292929; }
|
||||
.file-main { flex: 1; min-width: 0; }
|
||||
.file-title { overflow: hidden; color: #e1e1e1; font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.file-info { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 6px; }
|
||||
.file-progress-header { display: flex; justify-content: space-between; gap: 12px; margin-top: 10px; color: #969696; font-size: 11px; }
|
||||
.file-progress-track { height: 6px; margin-top: 5px; overflow: hidden; border-radius: 3px; background: #333f55; }
|
||||
.file-progress-fill { height: 100%; border-radius: inherit; background: #8d7b3f; transition: width .25s ease; }
|
||||
.file-progress-fill.running { background: #409eff; }
|
||||
.file-progress-fill.success { background: #42b36b; }
|
||||
.file-progress-fill.failed { background: #d06161; }
|
||||
.file-error { margin-top: 7px; }
|
||||
.file-status { flex-shrink: 0; }
|
||||
.file-error { color: #ef8d8d; font-size: 12px; }
|
||||
.publish-file-list { margin: 0; padding: 0; list-style: none; }
|
||||
.publish-file-row { margin-top: 10px; padding-top: 10px; border-top: 1px dashed #2e3a52; }
|
||||
.publish-file-row:first-child { margin-top: 0; padding-top: 0; border-top: none; }
|
||||
.publish-file-row-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-width: 0; }
|
||||
.publish-file-row-name { overflow: hidden; color: #e1e1e1; font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.publish-file-row-chip { flex-shrink: 0; padding: 2px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; }
|
||||
.publish-file-row-chip.pending { background: #3a3424; color: #e4c56a; }
|
||||
.publish-file-row-chip.running { background: #24394a; color: #75bff1; }
|
||||
.publish-file-row-chip.success { background: #233c2d; color: #72d598; }
|
||||
.publish-file-row-chip.failed { background: #472929; color: #ef8d8d; }
|
||||
.publish-file-row-info { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 5px; color: #5e6878; font-size: 12px; line-height: 1.5; }
|
||||
.publish-file-row-progress-meta { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; color: #969696; font-size: 11px; }
|
||||
.publish-file-row-progress-track { height: 6px; margin-top: 5px; overflow: hidden; border-radius: 3px; background: #333f55; }
|
||||
.publish-file-row-progress-fill { height: 100%; border-radius: inherit; background: #8d7b3f; transition: width .25s ease; }
|
||||
.publish-file-row-progress-fill.running { background: #409eff; }
|
||||
.publish-file-row-progress-fill.success { background: #42b36b; }
|
||||
.publish-file-row-progress-fill.failed { background: #d06161; }
|
||||
.publish-file-row-error { margin-top: 7px; }
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.main-content { height: auto; flex-direction: column; }
|
||||
.left-panel { width: 100%; border-right: 0; border-bottom: 1px solid #2e3a52; }
|
||||
.right-panel { min-height: 420px; }
|
||||
.clean-result-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.clean-result-summary { grid-template-columns: 1fr; }
|
||||
.task-section-header, .file-item { flex-direction: column; }
|
||||
.task-actions { width: 100%; }
|
||||
.publish-file-row-head { flex-direction: column; align-items: flex-start; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user