align(生成记录样办·反馈): 还原旧版交互——去掉重置/清理数据/查看大图按钮,点击缩略图直接放大预览;按钮间距统一10px;前端 clearHistory 适配移除(后端 DELETE /history 端点保留)

This commit is contained in:
2026-09-06 22:25:20 +08:00
parent 4276a90236
commit e81aa9e02e
4 changed files with 27 additions and 106 deletions
@@ -1,14 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
/** 生成记录页 · 像素复刻旧版 admin.html panel-history(自绘:form-box 筛选 + panel-box 表格 + 旧式分页 + thumb 缩略图)。 /** 生成记录页 · 像素复刻旧版 admin.html panel-history(自绘:form-box 筛选 + panel-box 表格 + 旧式分页 + thumb 缩略图)。
* script 逻辑沿用现有 Vue 实现(查询/重置/翻页/预览/清理数据)。 */ * 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 } from './history-api.ts'
import type { HistoryUserOption } from './history-user-option.ts' import type { HistoryUserOption } from './history-user-option.ts'
import type { HistoryRecordItem } from './history-dto.ts' import type { HistoryRecordItem } from './history-dto.ts'
import { historyEmptyText, historyErrorText } from './history-feedback.ts' import { historyEmptyText, historyErrorText } from './history-feedback.ts'
import { historyPanelTypeLabel } from './history-type.ts' import { historyPanelTypeLabel } from './history-type.ts'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage } from 'element-plus'
const loading = ref(false) const loading = ref(false)
const userLoading = ref(false) const userLoading = ref(false)
@@ -87,14 +87,6 @@ function apply() {
load() load()
} }
function reset() {
filter.userId = ''
filter.timeStart = ''
filter.timeEnd = ''
page.value = 1
load()
}
function retry() { function retry() {
load() load()
} }
@@ -119,32 +111,6 @@ function openPreview(item: HistoryRecordItem) {
previewVisible.value = true 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(() => { onMounted(() => {
load() load()
loadUserOptions() loadUserOptions()
@@ -171,20 +137,12 @@ onMounted(() => {
<label>结束时间</label> <label>结束时间</label>
<input v-model="filter.timeEnd" type="datetime-local" /> <input v-model="filter.timeEnd" type="datetime-local" />
</div> </div>
<div class="form-actions"> <button class="btn" type="button" @click="apply">查询</button>
<button class="btn" type="button" @click="apply">查询</button>
<button class="btn btn-secondary" type="button" @click="reset">重置</button>
</div>
</div> </div>
</section> </section>
<section class="panel-box"> <section class="panel-box">
<div class="panel-head"> <h3>生成记录</h3>
<h3>生成记录</h3>
<button class="btn btn-danger" type="button" :disabled="clearing" :aria-busy="clearing" @click="clearAll">
{{ clearing ? '清理中...' : '清理数据' }}
</button>
</div>
<div v-if="errorText" class="msg err"> <div v-if="errorText" class="msg err">
<span>加载失败: {{ errorText }}</span> <span>加载失败: {{ errorText }}</span>
@@ -200,7 +158,6 @@ onMounted(() => {
<th style="width: 160px">类型</th> <th style="width: 160px">类型</th>
<th style="width: 170px">创建时间</th> <th style="width: 170px">创建时间</th>
<th>结果预览</th> <th>结果预览</th>
<th style="width: 96px">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -218,22 +175,19 @@ onMounted(() => {
class="thumb" class="thumb"
:src="url" :src="url"
alt="" alt=""
title="点击查看大图" title="点击放大预览"
@click="openPreview(h)" @click="openPreview(h)"
/> />
<span v-if="!previewUrls(h).length" class="empty-mini">暂无图片</span> <span v-if="!previewUrls(h).length" class="empty-mini">暂无图片</span>
</div> </div>
</td> </td>
<td>
<button class="btn btn-sm" type="button" @click="openPreview(h)">查看大图</button>
</td>
</tr> </tr>
</template> </template>
<tr v-else-if="loading"> <tr v-else-if="loading">
<td colspan="6" class="empty-tip">加载中...</td> <td colspan="5" class="empty-tip">加载中...</td>
</tr> </tr>
<tr v-else> <tr v-else>
<td colspan="6" class="empty-tip">{{ emptyText }}</td> <td colspan="5" class="empty-tip">{{ emptyText }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -313,15 +267,6 @@ h3 {
color: #24384d; color: #24384d;
letter-spacing: 0.2px; 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 { .form-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -363,17 +308,11 @@ h3 {
border-color: #5f85ad; border-color: #5f85ad;
box-shadow: 0 0 0 3px rgba(95, 133, 173, 0.16); 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 { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 6px; gap: 8px;
min-height: 42px; min-height: 42px;
padding: 9px 18px; padding: 9px 18px;
border: 1px solid #4f78a5; border: 1px solid #4f78a5;
@@ -404,13 +343,6 @@ h3 {
border-color: #95b1cb; border-color: #95b1cb;
background: #edf5fb; 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 { .btn-sm {
min-height: 36px; min-height: 36px;
padding: 7px 12px; padding: 7px 12px;
@@ -493,6 +425,9 @@ h3 {
border: 1px solid #d8e3ee; border: 1px solid #d8e3ee;
cursor: zoom-in; cursor: zoom-in;
} }
.thumb:hover {
border-color: #5f85ad;
}
.empty-mini { .empty-mini {
color: #8293a5; color: #8293a5;
font-size: 12.5px; font-size: 12.5px;
@@ -501,7 +436,7 @@ h3 {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
margin-top: 18px; margin-top: 18px;
color: #5b6f83; color: #5b6f83;
font-size: 13px; font-size: 13px;
@@ -630,7 +565,7 @@ h3 {
.modal-foot { .modal-foot {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 10px;
margin-top: 14px; margin-top: 14px;
} }
</style> </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 } }) const { data } = await http.get<unknown>('/api/admin/users', { params: { page: 1, page_size: 999 } })
return parseHistoryUserOptions(data) 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 assert from 'node:assert/strict'
import { readSource } from './helpers.ts' import { readSource } from './helpers.ts'
// 验收反馈:生成记录的数据要可清理 // 用户反馈(round2 像素复刻样办):生成记录页还原旧版交互——「清理数据」为旧版没有的新增入口,从页面移除
// 契约:页面有「清理数据」入口(二次确认)、前端 DELETE /api/admin/history、后端清空端点与日志 // 契约:页面无清理入口、无二次确认文案;前端适配层不再暴露 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') const page = readSource('src/pages/records/RecordsHistoryPage.vue')
assert.match(page, /清理数据/, '页头有清理数据按钮') assert.doesNotMatch(page, /清理数据/, '页面不再有「清理数据按钮')
assert.match(page, /确定清空全部生成记录吗/, '清理前需二次确认') assert.doesNotMatch(page, /clearAll|clearHistory/, '页面不再调用清理逻辑')
assert.match(page, /clearHistory\(\)/, '页面调用清理 API') 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') const api = readSource('src/pages/records/history-api.ts')
assert.match(api, /http\.delete<unknown>\(HISTORY_ENDPOINT\)/, '清理走 DELETE /api/admin/history') assert.doesNotMatch(api, /clearHistory/, '前端适配层不再暴露 clearHistory')
assert.match(api, /clearHistory/, '暴露 clearHistory 适配函数')
}) })
test('align_history_clear_boundary_empty_input', () => { test('align_history_clear_backend_endpoint_kept', () => {
const page = readSource('src/pages/records/RecordsHistoryPage.vue') // 后端契约保留:DELETE /history 端点 + service 清空 + 中文排查日志(从前端摘除入口,不动后端)。
assert.match(page, /已清空 \$\{removed\} 条生成记录/, '成功提示含清理条数')
})
test('align_history_clear_dependency_failure_returns_actionable_message', () => {
// 后端契约:DELETE /history 端点 + service 清空 + 中文排查日志。
const ctrl = readSource('../backend-java/src/main/java/com/nanri/aiimage/modules/imagehistory/controller/ImageHistoryController.java') 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, /@DeleteMapping\("\/history"\)/, '后端有 DELETE /api/admin/history 端点')
assert.match(ctrl, /admin_history/, '清理端点沿用生成记录模块权限') assert.match(ctrl, /admin_history/, '清理端点沿用生成记录模块权限')
@@ -23,10 +23,10 @@ test('align_shop_keys_row_buttons_solid', () => {
assertRowButtonSolid(s, 'remove') 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') const s = readSource('src/pages/records/RecordsHistoryPage.vue')
assert.match(s, /class="btn btn-sm"[^>]*@click="openPreview/, '查看大图用旧版 btn-sm 小按钮') assert.match(s, /class="thumb"[^>]*@click="openPreview\(h\)"/, '点击缩略图直接放大预览')
assert.doesNotMatch(s, /el-button[^>]*openPreview/, '生成记录已自绘,无 EP 按钮') assert.doesNotMatch(s, /查看大图/, '无行内「查看大图」按钮')
}) })
test('align_digital_human_row_buttons_solid', () => { test('align_digital_human_row_buttons_solid', () => {