接入前端真实接口并标记邮件HTML清洗
This commit is contained in:
@@ -28,11 +28,15 @@
|
||||
v-model="filters.keyword"
|
||||
type="search"
|
||||
placeholder="GRP-001 / Booking"
|
||||
@input="resetToFirstPage"
|
||||
>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('taskList.taskType') }}</span>
|
||||
<select v-model="filters.task_type">
|
||||
<select
|
||||
v-model="filters.task_type"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">All</option>
|
||||
<option value="NEW_BOOKING">{{ t('cardType.NEW_BOOKING') }}</option>
|
||||
<option value="UPDATE_BOOKING">{{ t('cardType.UPDATE_BOOKING') }}</option>
|
||||
@@ -43,7 +47,10 @@
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('workbench.taskStatus') }}</span>
|
||||
<select v-model="filters.task_status">
|
||||
<select
|
||||
v-model="filters.task_status"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option value="">All</option>
|
||||
<option value="PENDING_CONFIRM">{{ t('status.PENDING_CONFIRM') }}</option>
|
||||
<option value="READY">{{ t('status.READY') }}</option>
|
||||
@@ -51,6 +58,30 @@
|
||||
<option value="COMPLETED">{{ t('status.COMPLETED') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('workbench.orderStatus') }}</span>
|
||||
<select
|
||||
value=""
|
||||
disabled
|
||||
>
|
||||
<option value="">All</option>
|
||||
<option value="TEMPORARY">{{ t('status.TEMPORARY') }}</option>
|
||||
<option value="ACTIVE">{{ t('status.ACTIVE') }}</option>
|
||||
<option value="ENDED">{{ t('status.ENDED') }}</option>
|
||||
</select>
|
||||
<small class="filter-hint">{{ t('taskList.orderStatusFilterPending') }}</small>
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ t('taskList.queueParticipation') }}</span>
|
||||
<select
|
||||
v-model="filters.queue_participation"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option :value="undefined">{{ t('taskList.queueAll') }}</option>
|
||||
<option :value="true">{{ t('taskList.queueIncluded') }}</option>
|
||||
<option :value="false">{{ t('taskList.queueExcluded') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -61,7 +92,7 @@
|
||||
{{ t('taskList.tableTitle') }}
|
||||
</h2>
|
||||
<p class="th-muted">
|
||||
{{ t('workbench.totalPrefix') }} {{ tasks.length }} {{ t('taskList.totalSuffix') }}
|
||||
{{ t('workbench.totalPrefix') }} {{ total }} {{ t('taskList.totalSuffix') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +170,7 @@
|
||||
{{ t('taskList.viewOrder') }}
|
||||
</RouterLink>
|
||||
<span
|
||||
v-if="!task.source_message_id || !task.external_conversation_id"
|
||||
v-if="!task.source_message_id"
|
||||
class="pending-link"
|
||||
>
|
||||
{{ t('common.endpointMissing') }}
|
||||
@@ -157,28 +188,66 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer class="pagination-bar">
|
||||
<label class="page-size-control">
|
||||
<span>{{ t('common.pageSize') }}</span>
|
||||
<select
|
||||
v-model.number="filters.page_size"
|
||||
@change="resetToFirstPage"
|
||||
>
|
||||
<option :value="10">10</option>
|
||||
<option :value="20">20</option>
|
||||
<option :value="50">50</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="pagination-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="secondary-button"
|
||||
:disabled="currentPage <= 1 || loading"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
{{ t('common.previousPage') }}
|
||||
</button>
|
||||
<span>{{ t('common.pageStatus', { current: currentPage, total: totalPages }) }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="secondary-button"
|
||||
:disabled="currentPage >= totalPages || loading"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
{{ t('common.nextPage') }}
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ReservationStatusBadge from '@/components/reservation/ReservationStatusBadge.vue'
|
||||
import { fetchReservationTaskList } from '@/services/reservationService'
|
||||
import type { ReservationTaskListFilters, ReservationTaskListItem } from '@/types/reservation'
|
||||
import type { ReservationPageResult, ReservationTaskListFilters, ReservationTaskListItem } from '@/types/reservation'
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const tasks = ref<ReservationTaskListItem[]>([])
|
||||
const page = ref(createPage(1, 20, 0))
|
||||
const total = computed(() => page.value.total)
|
||||
const currentPage = computed(() => page.value.page_num)
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(page.value.total / Math.max(page.value.page_size, 1))))
|
||||
let taskListRequestSequence = 0
|
||||
const filters = reactive<ReservationTaskListFilters>({
|
||||
keyword: '',
|
||||
task_type: '',
|
||||
task_status: '',
|
||||
queue_participation: undefined,
|
||||
page_num: 1,
|
||||
page_size: 20,
|
||||
})
|
||||
@@ -201,11 +270,13 @@ async function loadTasks(): Promise<void> {
|
||||
return
|
||||
}
|
||||
tasks.value = result.items
|
||||
page.value = result.page
|
||||
} catch (error) {
|
||||
if (requestSequence !== taskListRequestSequence) {
|
||||
return
|
||||
}
|
||||
tasks.value = []
|
||||
page.value = createPage(filters.page_num ?? 1, filters.page_size ?? 20, 0)
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
if (requestSequence === taskListRequestSequence) {
|
||||
@@ -218,6 +289,25 @@ function resetFilters(): void {
|
||||
filters.keyword = ''
|
||||
filters.task_type = ''
|
||||
filters.task_status = ''
|
||||
filters.queue_participation = undefined
|
||||
filters.page_num = 1
|
||||
filters.page_size = 20
|
||||
}
|
||||
|
||||
function resetToFirstPage(): void {
|
||||
filters.page_num = 1
|
||||
}
|
||||
|
||||
function goToPage(page: number): void {
|
||||
filters.page_num = Math.min(Math.max(page, 1), totalPages.value)
|
||||
}
|
||||
|
||||
function createPage(pageNum: number, pageSize: number, total: number): ReservationPageResult {
|
||||
return {
|
||||
page_num: pageNum,
|
||||
page_size: pageSize,
|
||||
total,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -248,7 +338,7 @@ function resetFilters(): void {
|
||||
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
padding: 18px 20px 0;
|
||||
}
|
||||
@@ -275,6 +365,19 @@ function resetFilters(): void {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.filter-grid select:disabled {
|
||||
color: var(--th-color-slate-500);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.filter-hint {
|
||||
color: var(--th-color-warning);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.text-button,
|
||||
.row-action {
|
||||
border: 0;
|
||||
@@ -355,9 +458,63 @@ td small {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
border-top: 1px solid var(--th-color-slate-200);
|
||||
padding: 14px 20px 18px;
|
||||
}
|
||||
|
||||
.page-size-control,
|
||||
.pagination-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-size-control span,
|
||||
.pagination-actions span {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.page-size-control select {
|
||||
min-height: 34px;
|
||||
border: 1px solid var(--th-color-slate-200);
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-white);
|
||||
color: var(--th-color-slate-900);
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
min-height: 34px;
|
||||
border: 1px solid var(--th-color-slate-200);
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-white);
|
||||
color: var(--th-color-slate-700);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.secondary-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.filter-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user