align(生成记录·像素复刻样办): RecordsHistoryPage 重写为自绘复刻 admin panel-history(旧版 form-box/panel-box/原生控件/table-scroll/thumb 缩略图/旧式分页/大图 modal CSS);脚本沿用现有 API,测试断言适配自绘结构
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/** 历史生成记录页(module 13 task 259 对齐 admin panel-history):指定用户下拉 + 起止时间筛选、类型中文、≤3 缩略图、大图预览、空态。 */
|
/** 生成记录页 · 像素复刻旧版 admin.html panel-history(自绘:form-box 筛选 + panel-box 表格 + 旧式分页 + thumb 缩略图)。
|
||||||
|
* script 逻辑沿用现有 Vue 实现(查询/重置/翻页/预览/清理数据)。 */
|
||||||
import { formatDateTime } from '@/utils/datetime'
|
import { formatDateTime } from '@/utils/datetime'
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { fetchHistoryList, fetchHistoryUserOptions, clearHistory } from './history-api.ts'
|
import { fetchHistoryList, fetchHistoryUserOptions, clearHistory } from './history-api.ts'
|
||||||
@@ -21,9 +22,18 @@ const filter = reactive({ userId: '', timeStart: '', timeEnd: '' })
|
|||||||
|
|
||||||
const previewVisible = ref(false)
|
const previewVisible = ref(false)
|
||||||
const previewItem = ref<HistoryRecordItem | null>(null)
|
const previewItem = ref<HistoryRecordItem | null>(null)
|
||||||
|
const jumpPage = ref('')
|
||||||
|
|
||||||
const hasFilter = computed(() => Boolean(filter.userId || filter.timeStart || filter.timeEnd))
|
const hasFilter = computed(() => Boolean(filter.userId || filter.timeStart || filter.timeEnd))
|
||||||
const emptyText = computed(() => historyEmptyText({ hasFilter: hasFilter.value, total: total.value }))
|
const emptyText = computed(() => historyEmptyText({ hasFilter: hasFilter.value, total: total.value }))
|
||||||
|
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize)))
|
||||||
|
|
||||||
|
/** datetime-local 原生值(YYYY-MM-DDTHH:mm) 转后端兼容格式。 */
|
||||||
|
function toApiTime(value: string): string | undefined {
|
||||||
|
const t = (value || '').trim()
|
||||||
|
if (!t) return undefined
|
||||||
|
return `${t.replace('T', ' ')}:00`
|
||||||
|
}
|
||||||
|
|
||||||
/** 预览图顺序:长图在前 + 结果图去重(对齐 admin 缩略图 [long,...result])。 */
|
/** 预览图顺序:长图在前 + 结果图去重(对齐 admin 缩略图 [long,...result])。 */
|
||||||
function previewUrls(item: HistoryRecordItem): string[] {
|
function previewUrls(item: HistoryRecordItem): string[] {
|
||||||
@@ -48,8 +58,8 @@ async function load() {
|
|||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize,
|
pageSize,
|
||||||
userId: filter.userId ? Number(filter.userId) : undefined,
|
userId: filter.userId ? Number(filter.userId) : undefined,
|
||||||
timeStart: filter.timeStart || undefined,
|
timeStart: toApiTime(filter.timeStart),
|
||||||
timeEnd: filter.timeEnd || undefined,
|
timeEnd: toApiTime(filter.timeEnd),
|
||||||
})
|
})
|
||||||
rows.value = result.items
|
rows.value = result.items
|
||||||
total.value = result.total
|
total.value = result.total
|
||||||
@@ -90,10 +100,20 @@ function retry() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changePage(next: number) {
|
function changePage(next: number) {
|
||||||
|
if (next < 1 || next > totalPages.value) return
|
||||||
page.value = next
|
page.value = next
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goJump() {
|
||||||
|
const n = Number.parseInt(jumpPage.value, 10)
|
||||||
|
if (Number.isNaN(n)) {
|
||||||
|
ElMessage.warning('请输入页码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
changePage(n)
|
||||||
|
}
|
||||||
|
|
||||||
function openPreview(item: HistoryRecordItem) {
|
function openPreview(item: HistoryRecordItem) {
|
||||||
previewItem.value = item
|
previewItem.value = item
|
||||||
previewVisible.value = true
|
previewVisible.value = true
|
||||||
@@ -101,7 +121,7 @@ function openPreview(item: HistoryRecordItem) {
|
|||||||
|
|
||||||
const clearing = ref(false)
|
const clearing = ref(false)
|
||||||
|
|
||||||
/** 清空全部生成记录:二次确认后调后端 DELETE /api/admin/history。 */
|
/** 清空全部生成记录:二次确认后调后端 DELETE /api/admin/history(新版增强,保留)。 */
|
||||||
async function clearAll() {
|
async function clearAll() {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm('确定清空全部生成记录吗?该操作会删除所有历史数据,无法恢复。', '清理生成记录', {
|
await ElMessageBox.confirm('确定清空全部生成记录吗?该操作会删除所有历史数据,无法恢复。', '清理生成记录', {
|
||||||
@@ -132,143 +152,485 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-stack">
|
<div class="history-view">
|
||||||
<div class="page-heading">
|
<section class="form-box">
|
||||||
<div>
|
<h3>筛选条件</h3>
|
||||||
<h2>生成记录</h2>
|
<div class="form-row">
|
||||||
<p>查看各用户在后台生成的历史记录与结果图。</p>
|
<div class="form-group" style="min-width: 150px">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-card shadow="never" class="filter-card">
|
|
||||||
<div class="filter-grid">
|
|
||||||
<div class="f-item">
|
|
||||||
<label>指定用户</label>
|
<label>指定用户</label>
|
||||||
<el-select v-model="filter.userId" clearable filterable placeholder="全部用户" style="width: 100%" :loading="userLoading">
|
<select v-model="filter.userId" :disabled="userLoading">
|
||||||
<el-option label="全部用户" value="" />
|
<option value="">全部用户</option>
|
||||||
<el-option v-for="u in userOptions" :key="u.id" :label="u.username" :value="String(u.id)" />
|
<option v-for="u in userOptions" :key="u.id" :value="String(u.id)">{{ u.username }}</option>
|
||||||
</el-select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="f-item">
|
<div class="form-group" style="min-width: 176px">
|
||||||
<label>开始时间</label>
|
<label>开始时间</label>
|
||||||
<el-date-picker
|
<input v-model="filter.timeStart" type="datetime-local" />
|
||||||
v-model="filter.timeStart"
|
|
||||||
type="datetime"
|
|
||||||
placeholder="开始时间"
|
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="f-item">
|
<div class="form-group" style="min-width: 176px">
|
||||||
<label>结束时间</label>
|
<label>结束时间</label>
|
||||||
<el-date-picker
|
<input v-model="filter.timeEnd" type="datetime-local" />
|
||||||
v-model="filter.timeEnd"
|
|
||||||
type="datetime"
|
|
||||||
placeholder="结束时间"
|
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="f-item btn-row">
|
<div class="form-actions">
|
||||||
<el-button type="primary" @click="apply">查询</el-button>
|
<button class="btn" type="button" @click="apply">查询</button>
|
||||||
<el-button @click="reset">重置</el-button>
|
<button class="btn btn-secondary" type="button" @click="reset">重置</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</section>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<section class="panel-box">
|
||||||
<div class="table-toolbar">
|
<div class="panel-head">
|
||||||
<el-button type="danger" plain :loading="clearing" @click="clearAll">清理数据</el-button>
|
<h3>生成记录</h3>
|
||||||
|
<button class="btn btn-danger" type="button" :disabled="clearing" :aria-busy="clearing" @click="clearAll">
|
||||||
|
{{ clearing ? '清理中...' : '清理数据' }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<el-alert
|
|
||||||
v-if="errorText"
|
<div v-if="errorText" class="msg err">
|
||||||
:title="errorText"
|
<span>加载失败: {{ errorText }}</span>
|
||||||
type="error"
|
<button class="btn btn-sm btn-secondary" type="button" @click="retry">重试</button>
|
||||||
show-icon
|
</div>
|
||||||
:closable="false"
|
|
||||||
style="margin-bottom: 12px"
|
<div class="table-scroll history-table-scroll">
|
||||||
>
|
<table>
|
||||||
<template #default>
|
<thead>
|
||||||
<el-button link type="primary" @click="retry">重试</el-button>
|
<tr>
|
||||||
</template>
|
<th style="width: 70px">ID</th>
|
||||||
</el-alert>
|
<th style="width: 130px">用户</th>
|
||||||
<el-table v-loading="loading" :data="rows" stripe :empty-text="emptyText">
|
<th style="width: 160px">类型</th>
|
||||||
<el-table-column prop="id" label="ID" min-width="90" />
|
<th style="width: 170px">创建时间</th>
|
||||||
<el-table-column prop="username" label="用户" min-width="140" />
|
<th>结果预览</th>
|
||||||
<el-table-column label="类型" min-width="150">
|
<th style="width: 96px">操作</th>
|
||||||
<template #default="{ row }">
|
</tr>
|
||||||
{{ historyPanelTypeLabel((row as HistoryRecordItem).panelType) }}
|
</thead>
|
||||||
</template>
|
<tbody>
|
||||||
</el-table-column>
|
<template v-if="rows.length">
|
||||||
<el-table-column prop="createdAt" label="创建时间" min-width="180">
|
<tr v-for="h in rows" :key="h.id">
|
||||||
<template #default="{ row }">{{ formatDateTime((row as HistoryRecordItem).createdAt) }}</template>
|
<td>{{ h.id }}</td>
|
||||||
</el-table-column>
|
<td>{{ h.username || '-' }}</td>
|
||||||
<el-table-column label="结果预览" min-width="220">
|
<td>{{ historyPanelTypeLabel(h.panelType) }}</td>
|
||||||
<template #default="{ row }">
|
<td>{{ formatDateTime(h.createdAt) }}</td>
|
||||||
<div v-if="previewUrls(row as HistoryRecordItem).length" class="thumbs">
|
<td>
|
||||||
<el-image
|
<div class="thumb-wrap">
|
||||||
v-for="(url, index) in previewUrls(row as HistoryRecordItem).slice(0, 3)"
|
<img
|
||||||
|
v-for="url in previewUrls(h).slice(0, 3)"
|
||||||
:key="url"
|
:key="url"
|
||||||
:src="url"
|
|
||||||
:preview-src-list="previewUrls(row as HistoryRecordItem)"
|
|
||||||
:initial-index="index"
|
|
||||||
preview-teleported
|
|
||||||
fit="cover"
|
|
||||||
class="thumb"
|
class="thumb"
|
||||||
|
:src="url"
|
||||||
|
alt=""
|
||||||
|
title="点击查看大图"
|
||||||
|
@click="openPreview(h)"
|
||||||
/>
|
/>
|
||||||
<span v-if="previewUrls(row as HistoryRecordItem).length > 3" class="dim">+{{ previewUrls(row as HistoryRecordItem).length - 3 }}</span>
|
<span v-if="!previewUrls(h).length" class="empty-mini">暂无图片</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-else class="dim">—</span>
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm" type="button" @click="openPreview(h)">查看大图</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
<tr v-else-if="loading">
|
||||||
<el-table-column label="操作" min-width="110" fixed="right">
|
<td colspan="6" class="empty-tip">加载中...</td>
|
||||||
<template #default="{ row }">
|
</tr>
|
||||||
<el-button type="primary" size="small" @click="openPreview(row as HistoryRecordItem)">查看大图</el-button>
|
<tr v-else>
|
||||||
</template>
|
<td colspan="6" class="empty-tip">{{ emptyText }}</td>
|
||||||
</el-table-column>
|
</tr>
|
||||||
</el-table>
|
</tbody>
|
||||||
<div class="table-footer">
|
</table>
|
||||||
<span>共 <b>{{ total.toLocaleString() }}</b> 条</span>
|
|
||||||
<el-pagination background layout="prev, pager, next, jumper" :total="total" :page-size="pageSize" :current-page="page" @current-change="changePage" />
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
|
||||||
|
|
||||||
<el-dialog v-model="previewVisible" :title="previewItem ? `记录 #${previewItem.id} 结果图` : '结果图'" width="820px" top="6vh">
|
<div class="pagination">
|
||||||
|
<span class="page-total">共 {{ total }} 条</span>
|
||||||
|
<button type="button" :disabled="page <= 1" @click="changePage(page - 1)">上一页</button>
|
||||||
|
<span class="page-cur">第 {{ page }} / {{ totalPages }} 页</span>
|
||||||
|
<button type="button" :disabled="page >= totalPages" @click="changePage(page + 1)">下一页</button>
|
||||||
|
<span class="page-jump">
|
||||||
|
跳至
|
||||||
|
<input v-model="jumpPage" type="text" inputmode="numeric" @keyup.enter="goJump" />
|
||||||
|
页
|
||||||
|
<button type="button" @click="goJump">跳转</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div v-if="previewVisible" class="modal-mask" @click.self="previewVisible = false">
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-head">
|
||||||
|
<h3 v-if="previewItem">记录 #{{ previewItem.id }} 结果图</h3>
|
||||||
|
<button class="modal-close" type="button" aria-label="关闭" @click="previewVisible = false">×</button>
|
||||||
|
</div>
|
||||||
<div v-if="previewItem" class="preview-meta">
|
<div v-if="previewItem" class="preview-meta">
|
||||||
<el-tag size="small">{{ previewItem.username }}</el-tag>
|
<span>{{ previewItem.username || '-' }}</span>
|
||||||
|
<span>·</span>
|
||||||
<span>{{ historyPanelTypeLabel(previewItem.panelType) }}</span>
|
<span>{{ historyPanelTypeLabel(previewItem.panelType) }}</span>
|
||||||
|
<span>·</span>
|
||||||
<span>{{ formatDateTime(previewItem.createdAt) }}</span>
|
<span>{{ formatDateTime(previewItem.createdAt) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="previewItem && previewUrls(previewItem).length" class="preview-grid">
|
<div v-if="previewItem && previewUrls(previewItem).length" class="preview-grid">
|
||||||
<el-image
|
<a
|
||||||
v-for="(url, index) in previewUrls(previewItem)"
|
v-for="url in previewUrls(previewItem)"
|
||||||
:key="url"
|
:key="url"
|
||||||
:src="url"
|
:href="url"
|
||||||
:preview-src-list="previewUrls(previewItem)"
|
target="_blank"
|
||||||
:initial-index="index"
|
rel="noopener"
|
||||||
preview-teleported
|
title="新标签打开原图"
|
||||||
fit="contain"
|
>
|
||||||
class="preview-img"
|
<img :src="url" alt="" />
|
||||||
/>
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else class="modal-empty">无结果图片</div>
|
||||||
|
<div class="modal-foot">
|
||||||
|
<button class="btn btn-secondary" type="button" @click="previewVisible = false">关闭</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-else description="无结果图片" />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.filter-card { margin-bottom: 14px; }
|
/* 像素复刻旧版 admin.html 蓝白末层样式:form-box/panel-box 双卡 + table-scroll 表格 + 旧式分页 + thumb 缩略图。 */
|
||||||
.filter-grid { display: flex; flex-wrap: wrap; gap: 12px 18px; }
|
.history-view {
|
||||||
.f-item { display: flex; flex-direction: column; gap: 6px; width: 190px; }
|
font-family: inherit;
|
||||||
.f-item label { color: var(--el-text-color-secondary); font-size: 12px; }
|
color: var(--c-text, #24384d);
|
||||||
.f-item.btn-row { flex-direction: row; align-items: flex-end; gap: 8px; width: auto; }
|
display: flex;
|
||||||
.thumbs { display: flex; align-items: center; gap: 6px; }
|
flex-direction: column;
|
||||||
.thumb { width: 56px; height: 56px; border-radius: 4px; border: 1px solid var(--el-border-color-lighter); flex: none; }
|
gap: 18px;
|
||||||
.table-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 14px; }
|
}
|
||||||
.table-footer span { color: var(--el-text-color-secondary); font-size: 12.5px; }
|
.form-box,
|
||||||
.table-footer b { color: var(--el-text-color-primary); }
|
.panel-box {
|
||||||
.dim { color: var(--el-text-color-secondary); font-size: 12px; }
|
width: 100%;
|
||||||
.preview-meta { display: flex; align-items: center; gap: 12px; color: var(--el-text-color-secondary); font-size: 12px; margin-bottom: 12px; }
|
min-width: 0;
|
||||||
.preview-grid { display: flex; flex-direction: column; gap: 12px; }
|
padding: 20px 22px 24px;
|
||||||
.preview-img { width: 100%; border: 1px solid var(--el-border-color-lighter); border-radius: 6px; }
|
border: 1px solid #d8e3ee;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: linear-gradient(145deg, #ffffff, #f9fbfd);
|
||||||
|
box-shadow: 0 1px 2px rgba(39, 67, 94, 0.04), 0 14px 30px -24px rgba(39, 67, 94, 0.28);
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 650;
|
||||||
|
color: #24384d;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
.panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.panel-head h3 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 14px 18px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
color: #5b6f83;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.form-group input,
|
||||||
|
.form-group select {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #cbd9e6;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: #f8fbfd;
|
||||||
|
color: #24384d;
|
||||||
|
font-size: 13.5px;
|
||||||
|
font-family: inherit;
|
||||||
|
color-scheme: light;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||||||
|
}
|
||||||
|
.form-group input:hover,
|
||||||
|
.form-group select:hover {
|
||||||
|
border-color: #9fb7cd;
|
||||||
|
}
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group select:focus {
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #5f85ad;
|
||||||
|
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16);
|
||||||
|
}
|
||||||
|
.form-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
border: 1px solid #4f78a5;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: linear-gradient(135deg, #5f85ad, #4f78a5);
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13.5px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 8px 18px -14px rgba(79, 120, 165, 0.72);
|
||||||
|
transition: background 160ms ease, box-shadow 160ms ease, transform 120ms ease;
|
||||||
|
}
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, #7094ba, #5d83ac);
|
||||||
|
box-shadow: 0 11px 22px -14px rgba(79, 120, 165, 0.8);
|
||||||
|
}
|
||||||
|
.btn:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.btn-secondary {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #5b6f83;
|
||||||
|
border-color: #c7d7e5;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
color: #2f5d8b;
|
||||||
|
border-color: #95b1cb;
|
||||||
|
background: #edf5fb;
|
||||||
|
}
|
||||||
|
.btn-danger {
|
||||||
|
background: linear-gradient(135deg, #c06d77, #b35f6a);
|
||||||
|
border-color: #b35f6a;
|
||||||
|
}
|
||||||
|
.btn-danger:hover:not(:disabled) {
|
||||||
|
background: linear-gradient(135deg, #cb7c84, #b96570);
|
||||||
|
}
|
||||||
|
.btn-sm {
|
||||||
|
min-height: 36px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
}
|
||||||
|
.msg {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 9px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.msg.err {
|
||||||
|
color: #91474f;
|
||||||
|
background: #f8ebeb;
|
||||||
|
border-color: #e4c2c5;
|
||||||
|
}
|
||||||
|
.table-scroll {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid #dbe5ee;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
.table-scroll table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.history-table-scroll > table {
|
||||||
|
min-width: 900px;
|
||||||
|
}
|
||||||
|
.table-scroll th,
|
||||||
|
.table-scroll td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 13.5px;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-bottom: 1px solid #e0e8ef;
|
||||||
|
vertical-align: middle;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.table-scroll th {
|
||||||
|
background: #edf4fa;
|
||||||
|
color: #4e6479;
|
||||||
|
border-bottom-color: #d5e1eb;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.table-scroll tbody tr:hover td {
|
||||||
|
background: #f1f7fb;
|
||||||
|
}
|
||||||
|
.table-scroll tbody tr:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.empty-tip {
|
||||||
|
padding: 44px 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: #8293a5;
|
||||||
|
font-size: 13.5px;
|
||||||
|
}
|
||||||
|
.thumb-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.thumb {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #d8e3ee;
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
.empty-mini {
|
||||||
|
color: #8293a5;
|
||||||
|
font-size: 12.5px;
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 18px;
|
||||||
|
color: #5b6f83;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.pagination button {
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border: 1px solid #c7d7e5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #5b6f83;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.pagination button:hover:not(:disabled) {
|
||||||
|
background: #edf5fb;
|
||||||
|
border-color: #95b1cb;
|
||||||
|
color: #2f5d8b;
|
||||||
|
}
|
||||||
|
.pagination button:disabled {
|
||||||
|
background: #eef3f7;
|
||||||
|
color: #9baaba;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.page-total {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.page-jump {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.page-jump input {
|
||||||
|
width: 56px;
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid #cbd9e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fbfd;
|
||||||
|
color: #24384d;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.modal-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 3000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: rgba(45, 66, 86, 0.36);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
.modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: min(880px, 100%);
|
||||||
|
max-height: 84vh;
|
||||||
|
padding: 20px 22px;
|
||||||
|
border: 1px solid #c7d7e5;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 22px 60px rgba(39, 67, 94, 0.2);
|
||||||
|
}
|
||||||
|
.modal-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.modal-head h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.modal-close {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: none;
|
||||||
|
color: #5b6f83;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.modal-close:hover {
|
||||||
|
border-color: #cbd9e6;
|
||||||
|
background: #eef4fa;
|
||||||
|
color: #24384d;
|
||||||
|
}
|
||||||
|
.preview-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 12px 0 10px;
|
||||||
|
color: #5b6f83;
|
||||||
|
font-size: 12.5px;
|
||||||
|
}
|
||||||
|
.preview-grid {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
border: 1px solid #dbe5ee;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8fbfd;
|
||||||
|
}
|
||||||
|
.preview-grid a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.preview-grid img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #d8e3ee;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.modal-empty {
|
||||||
|
padding: 48px 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #8293a5;
|
||||||
|
}
|
||||||
|
.modal-foot {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const PAGES = [
|
|||||||
'src/pages/asin/QueryAsinPage.vue',
|
'src/pages/asin/QueryAsinPage.vue',
|
||||||
'src/pages/asin/SkipPricePage.vue',
|
'src/pages/asin/SkipPricePage.vue',
|
||||||
'src/pages/records/RecordsDigitalHumanVersionPage.vue',
|
'src/pages/records/RecordsDigitalHumanVersionPage.vue',
|
||||||
'src/pages/records/RecordsHistoryPage.vue',
|
|
||||||
'src/pages/records/RecordsSoftwareVersionPage.vue',
|
'src/pages/records/RecordsSoftwareVersionPage.vue',
|
||||||
'src/pages/shop/ShopKeysPage.vue',
|
'src/pages/shop/ShopKeysPage.vue',
|
||||||
'src/pages/shop/ShopManagePage.vue',
|
'src/pages/shop/ShopManagePage.vue',
|
||||||
@@ -51,6 +50,10 @@ test('align_column_width_boundary_empty_input', () => {
|
|||||||
const dup = readSource('src/pages/tasks/DuplicateCheckPage.vue')
|
const dup = readSource('src/pages/tasks/DuplicateCheckPage.vue')
|
||||||
assert.equal(/el-table-column/.test(dup), false, '撞款台账已用 reference 原生表格,不再有 el-table-column')
|
assert.equal(/el-table-column/.test(dup), false, '撞款台账已用 reference 原生表格,不再有 el-table-column')
|
||||||
assert.match(dup, /class="ledger"/, '撞款台账仍为表格实现')
|
assert.match(dup, /class="ledger"/, '撞款台账仍为表格实现')
|
||||||
|
const hist = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||||
|
assert.equal(/el-table-column/.test(hist), false, '生成记录已像素复刻 admin panel-history 原生表格,不再有 el-table-column')
|
||||||
|
assert.match(hist, /class="table-scroll history-table-scroll"/, '生成记录用原生 .table-scroll 表格')
|
||||||
|
assert.match(hist, /<th[^>]*>结果预览<\/th>/, '原生表含结果预览列')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_column_width_dependency_failure_returns_actionable_message', () => {
|
test('align_column_width_dependency_failure_returns_actionable_message', () => {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { historyEmptyText } from '../src/pages/records/history-feedback.ts'
|
|||||||
|
|
||||||
test('align_history_column_labels', () => {
|
test('align_history_column_labels', () => {
|
||||||
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||||
assert.match(page, /label="类型"/, '列名对齐参考「类型」')
|
assert.match(page, /<th[^>]*>类型<\/th>/, '列名对齐参考「类型」(自绘 th)')
|
||||||
assert.match(page, /label="创建时间"/, '列名对齐参考「创建时间」')
|
assert.match(page, /<th[^>]*>创建时间<\/th>/, '列名对齐参考「创建时间」(自绘 th)')
|
||||||
assert.doesNotMatch(page, /记录类型/, '去掉“记录类型”列名')
|
assert.doesNotMatch(page, /记录类型/, '去掉“记录类型”列名')
|
||||||
assert.doesNotMatch(page, /label="生成时间"/, '去掉“生成时间”列名')
|
assert.doesNotMatch(page, /label="生成时间"/, '去掉“生成时间”列名')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ test('align_shop_keys_row_buttons_solid', () => {
|
|||||||
assertRowButtonSolid(s, 'remove')
|
assertRowButtonSolid(s, 'remove')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_records_history_preview_solid', () => {
|
test('align_records_history_preview_old_btn', () => {
|
||||||
const s = readSource('src/pages/records/RecordsHistoryPage.vue')
|
const s = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||||
assertRowButtonSolid(s, 'openPreview')
|
assert.match(s, /class="btn btn-sm"[^>]*@click="openPreview/, '查看大图用旧版 btn-sm 小按钮')
|
||||||
|
assert.doesNotMatch(s, /el-button[^>]*openPreview/, '生成记录已自绘,无 EP 按钮')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('align_digital_human_row_buttons_solid', () => {
|
test('align_digital_human_row_buttons_solid', () => {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ test('test_task_259_history_boundary_single_item', () => {
|
|||||||
test('test_task_259_history_boundary_limit_or_missing_field', () => {
|
test('test_task_259_history_boundary_limit_or_missing_field', () => {
|
||||||
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||||
assert.match(page, /指定用户/, '筛选需有“指定用户”标签(admin 语义)')
|
assert.match(page, /指定用户/, '筛选需有“指定用户”标签(admin 语义)')
|
||||||
assert.match(page, /el-date-picker/, '需用日期时间选择器')
|
assert.match(page, /type="datetime-local"/, '自绘复刻用原生日历控件(对齐 admin datetime-local)')
|
||||||
assert.match(page, /historyPanelTypeLabel|panelTypeLabel/, '类型列需走中文映射')
|
assert.match(page, /historyPanelTypeLabel|panelTypeLabel/, '类型列需走中文映射')
|
||||||
assert.match(page, /formatDateTime/, '时间统一格式化')
|
assert.match(page, /formatDateTime/, '时间统一格式化')
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user