align(生成记录样办·反馈): 还原旧版交互——去掉重置/清理数据/查看大图按钮,点击缩略图直接放大预览;按钮间距统一10px;前端 clearHistory 适配移除(后端 DELETE /history 端点保留)
This commit is contained in:
@@ -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', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user