align(生成记录样办·反馈): 还原旧版交互——去掉重置/清理数据/查看大图按钮,点击缩略图直接放大预览;按钮间距统一10px;前端 clearHistory 适配移除(后端 DELETE /history 端点保留)
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
/** 生成记录页 · 像素复刻旧版 admin.html panel-history(自绘:form-box 筛选 + panel-box 表格 + 旧式分页 + thumb 缩略图)。
|
||||
* script 逻辑沿用现有 Vue 实现(查询/重置/翻页/预览/清理数据)。 */
|
||||
* script 逻辑沿用现有 Vue 实现(查询/翻页/跳页/点击缩略图放大预览)。 */
|
||||
import { formatDateTime } from '@/utils/datetime'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { fetchHistoryList, fetchHistoryUserOptions, clearHistory } from './history-api.ts'
|
||||
import { fetchHistoryList, fetchHistoryUserOptions } from './history-api.ts'
|
||||
import type { HistoryUserOption } from './history-user-option.ts'
|
||||
import type { HistoryRecordItem } from './history-dto.ts'
|
||||
import { historyEmptyText, historyErrorText } from './history-feedback.ts'
|
||||
import { historyPanelTypeLabel } from './history-type.ts'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const loading = ref(false)
|
||||
const userLoading = ref(false)
|
||||
@@ -87,14 +87,6 @@ function apply() {
|
||||
load()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
filter.userId = ''
|
||||
filter.timeStart = ''
|
||||
filter.timeEnd = ''
|
||||
page.value = 1
|
||||
load()
|
||||
}
|
||||
|
||||
function retry() {
|
||||
load()
|
||||
}
|
||||
@@ -119,32 +111,6 @@ function openPreview(item: HistoryRecordItem) {
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
const clearing = ref(false)
|
||||
|
||||
/** 清空全部生成记录:二次确认后调后端 DELETE /api/admin/history(新版增强,保留)。 */
|
||||
async function clearAll() {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定清空全部生成记录吗?该操作会删除所有历史数据,无法恢复。', '清理生成记录', {
|
||||
confirmButtonText: '确认清空',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
clearing.value = true
|
||||
try {
|
||||
const removed = await clearHistory()
|
||||
ElMessage.success(`已清空 ${removed} 条生成记录`)
|
||||
page.value = 1
|
||||
await load()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '清理失败')
|
||||
} finally {
|
||||
clearing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load()
|
||||
loadUserOptions()
|
||||
@@ -171,20 +137,12 @@ onMounted(() => {
|
||||
<label>结束时间</label>
|
||||
<input v-model="filter.timeEnd" type="datetime-local" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="btn" type="button" @click="apply">查询</button>
|
||||
<button class="btn btn-secondary" type="button" @click="reset">重置</button>
|
||||
</div>
|
||||
<button class="btn" type="button" @click="apply">查询</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-box">
|
||||
<div class="panel-head">
|
||||
<h3>生成记录</h3>
|
||||
<button class="btn btn-danger" type="button" :disabled="clearing" :aria-busy="clearing" @click="clearAll">
|
||||
{{ clearing ? '清理中...' : '清理数据' }}
|
||||
</button>
|
||||
</div>
|
||||
<h3>生成记录</h3>
|
||||
|
||||
<div v-if="errorText" class="msg err">
|
||||
<span>加载失败: {{ errorText }}</span>
|
||||
@@ -200,7 +158,6 @@ onMounted(() => {
|
||||
<th style="width: 160px">类型</th>
|
||||
<th style="width: 170px">创建时间</th>
|
||||
<th>结果预览</th>
|
||||
<th style="width: 96px">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -218,22 +175,19 @@ onMounted(() => {
|
||||
class="thumb"
|
||||
:src="url"
|
||||
alt=""
|
||||
title="点击查看大图"
|
||||
title="点击放大预览"
|
||||
@click="openPreview(h)"
|
||||
/>
|
||||
<span v-if="!previewUrls(h).length" class="empty-mini">暂无图片</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm" type="button" @click="openPreview(h)">查看大图</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-else-if="loading">
|
||||
<td colspan="6" class="empty-tip">加载中...</td>
|
||||
<td colspan="5" class="empty-tip">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="6" class="empty-tip">{{ emptyText }}</td>
|
||||
<td colspan="5" class="empty-tip">{{ emptyText }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -313,15 +267,6 @@ h3 {
|
||||
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;
|
||||
@@ -363,17 +308,11 @@ h3 {
|
||||
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;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
padding: 9px 18px;
|
||||
border: 1px solid #4f78a5;
|
||||
@@ -404,13 +343,6 @@ h3 {
|
||||
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;
|
||||
@@ -493,6 +425,9 @@ h3 {
|
||||
border: 1px solid #d8e3ee;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
.thumb:hover {
|
||||
border-color: #5f85ad;
|
||||
}
|
||||
.empty-mini {
|
||||
color: #8293a5;
|
||||
font-size: 12.5px;
|
||||
@@ -501,7 +436,7 @@ h3 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
color: #5b6f83;
|
||||
font-size: 13px;
|
||||
@@ -630,7 +565,7 @@ h3 {
|
||||
.modal-foot {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,11 +17,3 @@ export async function fetchHistoryUserOptions(): Promise<HistoryUserOption[]> {
|
||||
const { data } = await http.get<unknown>('/api/admin/users', { params: { page: 1, page_size: 999 } })
|
||||
return parseHistoryUserOptions(data)
|
||||
}
|
||||
|
||||
/** 清空全部生成记录(验收反馈:生成记录数据要可清理)。返回清理条数。 */
|
||||
export async function clearHistory(): Promise<number> {
|
||||
const { data } = await http.delete<unknown>(HISTORY_ENDPOINT)
|
||||
const record = data && typeof data === 'object' ? (data as Record<string, unknown>) : null
|
||||
const removed = record?.data
|
||||
return typeof removed === 'number' ? removed : 0
|
||||
}
|
||||
|
||||
@@ -2,29 +2,23 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
|
||||
// 验收反馈:生成记录的数据要可清理。
|
||||
// 契约:页面有「清理数据」入口(二次确认)、前端 DELETE /api/admin/history、后端清空端点与日志。
|
||||
// 用户反馈(round2 像素复刻样办):生成记录页还原旧版交互——「清理数据」为旧版没有的新增入口,从页面移除。
|
||||
// 契约:页面无清理入口、无二次确认文案;前端适配层不再暴露 clearHistory;后端 DELETE /history 端点保留(非破坏性,供后续需要时再接线)。
|
||||
|
||||
test('align_history_clear_normal_primary_path', () => {
|
||||
test('align_history_clear_removed_from_page', () => {
|
||||
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||
assert.match(page, /清理数据/, '页头有清理数据按钮')
|
||||
assert.match(page, /确定清空全部生成记录吗/, '清理前需二次确认')
|
||||
assert.match(page, /clearHistory\(\)/, '页面调用清理 API')
|
||||
assert.doesNotMatch(page, /清理数据/, '页面不再有「清理数据」按钮')
|
||||
assert.doesNotMatch(page, /clearAll|clearHistory/, '页面不再调用清理逻辑')
|
||||
assert.doesNotMatch(page, /确定清空全部生成记录吗/, '无清理二次确认文案')
|
||||
})
|
||||
|
||||
test('align_history_clear_normal_variant_input', () => {
|
||||
test('align_history_clear_api_no_longer_exposed', () => {
|
||||
const api = readSource('src/pages/records/history-api.ts')
|
||||
assert.match(api, /http\.delete<unknown>\(HISTORY_ENDPOINT\)/, '清理走 DELETE /api/admin/history')
|
||||
assert.match(api, /clearHistory/, '暴露 clearHistory 适配函数')
|
||||
assert.doesNotMatch(api, /clearHistory/, '前端适配层不再暴露 clearHistory')
|
||||
})
|
||||
|
||||
test('align_history_clear_boundary_empty_input', () => {
|
||||
const page = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||
assert.match(page, /已清空 \$\{removed\} 条生成记录/, '成功提示含清理条数')
|
||||
})
|
||||
|
||||
test('align_history_clear_dependency_failure_returns_actionable_message', () => {
|
||||
// 后端契约:DELETE /history 端点 + service 清空 + 中文排查日志。
|
||||
test('align_history_clear_backend_endpoint_kept', () => {
|
||||
// 后端契约保留:DELETE /history 端点 + service 清空 + 中文排查日志(从前端摘除入口,不动后端)。
|
||||
const ctrl = readSource('../backend-java/src/main/java/com/nanri/aiimage/modules/imagehistory/controller/ImageHistoryController.java')
|
||||
assert.match(ctrl, /@DeleteMapping\("\/history"\)/, '后端有 DELETE /api/admin/history 端点')
|
||||
assert.match(ctrl, /admin_history/, '清理端点沿用生成记录模块权限')
|
||||
|
||||
@@ -23,10 +23,10 @@ test('align_shop_keys_row_buttons_solid', () => {
|
||||
assertRowButtonSolid(s, 'remove')
|
||||
})
|
||||
|
||||
test('align_records_history_preview_old_btn', () => {
|
||||
test('align_records_history_thumb_click_opens_preview', () => {
|
||||
const s = readSource('src/pages/records/RecordsHistoryPage.vue')
|
||||
assert.match(s, /class="btn btn-sm"[^>]*@click="openPreview/, '查看大图用旧版 btn-sm 小按钮')
|
||||
assert.doesNotMatch(s, /el-button[^>]*openPreview/, '生成记录已自绘,无 EP 按钮')
|
||||
assert.match(s, /class="thumb"[^>]*@click="openPreview\(h\)"/, '点击缩略图直接放大预览')
|
||||
assert.doesNotMatch(s, /查看大图/, '无行内「查看大图」按钮')
|
||||
})
|
||||
|
||||
test('align_digital_human_row_buttons_solid', () => {
|
||||
|
||||
Reference in New Issue
Block a user