feat: 优化打包产物与历史筛选
This commit is contained in:
@@ -7,9 +7,9 @@ global.fetch = vi.fn()
|
||||
window.open = vi.fn()
|
||||
|
||||
const mockTasks = [
|
||||
{ id: '1', app_name: 'App1', build_type: 'Ad_Hoc', scheme_name: 'readoor31OtherPayLongSchemeName', status: 'completed', created_at: '2024-01-01T10:00:00', dsym_path: '/path/dsym', oss_url: 'https://oss.com/app1.ipa', qr_code_path: '/path/qr.png' },
|
||||
{ id: '2', app_name: 'App2', build_type: 'App_Store', scheme_name: 'sch2', status: 'failed', created_at: '2024-01-02T10:00:00', error_message: '构建失败', error_category: 'compilation' },
|
||||
{ id: '3', app_name: 'App3', build_type: 'Ad_Hoc', scheme_name: 'sch1', status: 'pending', created_at: '2024-01-03T10:00:00' },
|
||||
{ id: '1', app_name: 'App1', build_type: 'Ad_Hoc', scheme_name: 'readoor31OtherPayLongSchemeName', status: 'completed', created_at: '2024-01-01T10:00:00', config_json: '{"VERSION":"2.196.0"}', has_log: true, dsym_path: '/path/dsym', oss_url: 'https://oss.com/app1.ipa', qr_code_path: '/path/qr.png' },
|
||||
{ id: '2', app_name: 'App2', build_type: 'App_Store', scheme_name: 'sch2', status: 'failed', created_at: '2024-01-02T10:00:00', config_json: '{"VERSION":"2.195.0"}', error_message: '构建失败', error_category: 'compilation' },
|
||||
{ id: '3', app_name: 'App3', build_type: 'Ad_Hoc', scheme_name: 'sch1', status: 'pending', created_at: '2024-01-03T10:00:00', config_json: '{"VERSION":"2.196.0"}' },
|
||||
]
|
||||
|
||||
function createMockRouter() {
|
||||
@@ -86,7 +86,7 @@ describe('HistoryView.vue', () => {
|
||||
await flushPromises()
|
||||
|
||||
const selects = wrapper.findAll('.filter-select')
|
||||
await selects[0].setValue('Ad_Hoc')
|
||||
await selects[2].setValue('Ad_Hoc')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
@@ -101,13 +101,31 @@ describe('HistoryView.vue', () => {
|
||||
await flushPromises()
|
||||
|
||||
const selects = wrapper.findAll('.filter-select')
|
||||
await selects[1].setValue('failed')
|
||||
await selects[3].setValue('failed')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(1) // App2
|
||||
})
|
||||
|
||||
it('按 App 名称和版本号筛选', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const selects = wrapper.findAll('.filter-select')
|
||||
await selects[0].setValue('App1')
|
||||
await wrapper.find('.version-filter').setValue('2.196')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows.length).toBe(1)
|
||||
expect(rows[0].text()).toContain('App1')
|
||||
expect(rows[0].text()).toContain('2.196.0')
|
||||
})
|
||||
|
||||
it('已完成任务显示下载按钮', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
@@ -120,8 +138,7 @@ describe('HistoryView.vue', () => {
|
||||
const buttons = firstRow.findAll('.action-btn')
|
||||
const buttonTexts = buttons.map(b => b.text())
|
||||
expect(buttonTexts).toContain('dSYM')
|
||||
expect(buttonTexts).toContain('下载')
|
||||
expect(buttonTexts).toContain('二维码')
|
||||
expect(firstRow.find('.qr-thumb').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('空列表显示提示', async () => {
|
||||
@@ -148,7 +165,7 @@ describe('HistoryView.vue', () => {
|
||||
expect(wrapper.vm.statusText('failed')).toBe('失败')
|
||||
})
|
||||
|
||||
it('时间仅显示月和日,Scheme 与状态单元格允许完整换行', async () => {
|
||||
it('显示 App 版本号,Scheme 与状态单元格允许完整换行', async () => {
|
||||
const router = createMockRouter()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
@@ -156,13 +173,13 @@ describe('HistoryView.vue', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.vm.formatTime('2024-07-20T10:00:00')).toBe('7/20')
|
||||
expect(wrapper.findAll('tbody tr')[0].text()).toContain('2.196.0')
|
||||
expect(wrapper.find('.scheme-cell').text()).toBe('readoor31OtherPayLongSchemeName')
|
||||
expect(wrapper.find('.status-cell').text()).toContain('已完成')
|
||||
})
|
||||
|
||||
it('点击查看日志跳转', async () => {
|
||||
it('点击查看日志打开日志弹窗', async () => {
|
||||
const router = createMockRouter()
|
||||
router.push = vi.fn()
|
||||
const wrapper = mount(HistoryView, {
|
||||
global: { plugins: [router] },
|
||||
})
|
||||
@@ -171,7 +188,7 @@ describe('HistoryView.vue', () => {
|
||||
const logBtn = wrapper.findAll('.action-btn').find(b => b.text() === '日志')
|
||||
await logBtn.trigger('click')
|
||||
|
||||
expect(router.push).toHaveBeenCalledWith({ path: '/build', query: { taskId: '1' } })
|
||||
expect(wrapper.find('.log-modal').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('点击二维码弹出弹窗', async () => {
|
||||
@@ -181,11 +198,10 @@ describe('HistoryView.vue', () => {
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
const qrBtn = wrapper.findAll('.action-btn').find(b => b.text() === '二维码')
|
||||
await qrBtn.trigger('click')
|
||||
await wrapper.find('.qr-thumb').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('.qr-modal').exists()).toBe(true)
|
||||
expect(wrapper.find('.qr-preview-modal').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('下载二维码')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<div class="header-row">
|
||||
<h2>打包历史</h2>
|
||||
<div class="filters">
|
||||
<select v-model="filterAppName" class="filter-select">
|
||||
<option value="">全部 App</option>
|
||||
<option v-for="appName in appNames" :key="appName" :value="appName">{{ appName }}</option>
|
||||
</select>
|
||||
<input v-model.trim="filterVersion" class="filter-select version-filter" type="search" placeholder="筛选 App 版本号">
|
||||
<select v-model="filterBuildType" class="filter-select">
|
||||
<option value="">全部类型</option>
|
||||
<option value="Ad_Hoc">Ad_Hoc</option>
|
||||
@@ -22,8 +27,8 @@
|
||||
<table class="config-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>时间</th>
|
||||
<th>App</th>
|
||||
<th>App 名称</th>
|
||||
<th>App 版本号</th>
|
||||
<th>打包类型</th>
|
||||
<th>Scheme</th>
|
||||
<th>状态</th>
|
||||
@@ -33,13 +38,8 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="task in filteredTasks" :key="task.id">
|
||||
<td>
|
||||
{{ formatTime(task.created_at) }}
|
||||
<span v-if="task.status === 'completed' || task.status === 'failed'" class="duration-text">
|
||||
{{ formatDuration(task.started_at, task.completed_at) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ task.app_name }}</td>
|
||||
<td>{{ task.app_version }}</td>
|
||||
<td>
|
||||
<span :class="['build-type-badge', task.build_type === 'App_Store' ? 'badge-appstore' : 'badge-adhoc']">
|
||||
{{ task.build_type }}
|
||||
@@ -162,7 +162,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, onUnmounted, inject } from 'vue'
|
||||
|
||||
const getToken = inject('getToken')
|
||||
const getToken = inject('getToken', () => '')
|
||||
const tasks = ref([])
|
||||
|
||||
const authFetch = (url, options = {}) => {
|
||||
@@ -174,6 +174,8 @@ const authFetch = (url, options = {}) => {
|
||||
}
|
||||
const filterBuildType = ref('')
|
||||
const filterStatus = ref('')
|
||||
const filterAppName = ref('')
|
||||
const filterVersion = ref('')
|
||||
const showLogModal = ref(false)
|
||||
const logTask = ref(null)
|
||||
const logLines = ref([])
|
||||
@@ -182,6 +184,16 @@ const showVerboseLogs = ref(false)
|
||||
const qrPreview = ref(null)
|
||||
let logWs = null
|
||||
|
||||
const getAppVersion = (task) => {
|
||||
try {
|
||||
return JSON.parse(task.config_json || '{}').VERSION || '-'
|
||||
} catch {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
const appNames = computed(() => [...new Set(tasks.value.map(task => task.app_name).filter(Boolean))].sort())
|
||||
|
||||
const showQrPreview = (task) => {
|
||||
qrPreview.value = task
|
||||
}
|
||||
@@ -190,6 +202,8 @@ const filteredTasks = computed(() => {
|
||||
return tasks.value.filter(task => {
|
||||
// 默认隐藏已取消的任务
|
||||
if (!filterStatus.value && task.status === 'cancelled') return false
|
||||
if (filterAppName.value && task.app_name !== filterAppName.value) return false
|
||||
if (filterVersion.value && !task.app_version.toLowerCase().includes(filterVersion.value.toLowerCase())) return false
|
||||
if (filterBuildType.value && task.build_type !== filterBuildType.value) return false
|
||||
if (filterStatus.value && task.status !== filterStatus.value) return false
|
||||
return true
|
||||
@@ -198,7 +212,8 @@ const filteredTasks = computed(() => {
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await authFetch('/api/tasks?limit=100')
|
||||
tasks.value = await res.json()
|
||||
const data = await res.json()
|
||||
tasks.value = data.map(task => ({ ...task, app_version: getAppVersion(task) }))
|
||||
})
|
||||
|
||||
const viewLogs = async (taskId) => {
|
||||
@@ -298,11 +313,18 @@ const downloadObfMaps = (taskId) => {
|
||||
}
|
||||
|
||||
const deleteTask = async (taskId) => {
|
||||
if (!confirm('确定要删除这条打包记录吗?')) return
|
||||
if (!confirm('确定要删除这条打包记录及其远端分发文件吗?')) return
|
||||
try {
|
||||
const res = await authFetch(`/api/tasks/${taskId}/delete`, { method: 'DELETE' })
|
||||
if (res.ok) tasks.value = tasks.value.filter(t => t.id !== taskId)
|
||||
} catch {}
|
||||
if (res.ok) {
|
||||
tasks.value = tasks.value.filter(t => t.id !== taskId)
|
||||
} else {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
alert(data.detail || '删除失败,请稍后重试')
|
||||
}
|
||||
} catch {
|
||||
alert('删除失败,请检查网络后重试')
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (t) => {
|
||||
@@ -361,13 +383,13 @@ const errorCategoryLabel = (cat) => {
|
||||
.config-table th, .config-table td { padding: 12px; text-align: left; border-bottom: 1px solid #f0f0f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.config-table th { background: #fafafa; font-weight: 500; color: #666; font-size: 13px; }
|
||||
.config-table td { font-size: 14px; }
|
||||
.config-table th:nth-child(1) { width: 10%; }
|
||||
.config-table th:nth-child(1) { width: 14%; }
|
||||
.config-table th:nth-child(2) { width: 12%; }
|
||||
.config-table th:nth-child(3) { width: 9%; }
|
||||
.config-table th:nth-child(3) { width: 10%; }
|
||||
.config-table th:nth-child(4) { width: 18%; }
|
||||
.config-table th:nth-child(5) { width: 17%; }
|
||||
.config-table th:nth-child(5) { width: 15%; }
|
||||
.config-table th:nth-child(6) { width: 12%; }
|
||||
.config-table th:nth-child(7) { width: 22%; }
|
||||
.config-table th:nth-child(7) { width: 19%; }
|
||||
.config-table tr:hover { background: #fafafa; }
|
||||
|
||||
.build-type-badge { padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
|
||||
@@ -434,9 +456,7 @@ const errorCategoryLabel = (cat) => {
|
||||
.meta-label { font-size: 12px; color: #999; }
|
||||
.meta-value { font-size: 13px; color: #333; font-weight: 500; }
|
||||
|
||||
.duration-text {
|
||||
display: block; font-size: 11px; color: #8b949e; margin-top: 2px;
|
||||
}
|
||||
.version-filter { width: 150px; }
|
||||
|
||||
/* 日志操作栏 */
|
||||
.log-modal-actions {
|
||||
|
||||
Reference in New Issue
Block a user