线路导出

master
lion 1 day ago
parent 561daa5709
commit 3aee410270

@ -8,7 +8,8 @@ import { listTableRowIndex } from '../../utils/listTableRowIndex'
import { resolvePublicMediaUrl } from '../../utils/mediaUrl'
import { adminUploadImageTooLargeMessage, ADMIN_IMAGE_RECOMMEND_LABEL } from '../../utils/adminMediaLimits'
const STUDY_TOUR_LIST_SCROLL_X = 1180
const STUDY_TOUR_LIST_SCROLL_X = 1280
const STUDY_TOUR_EXPORT_MAX = 50
type DictOption = { id: number; item_value: string; item_label: string }
@ -78,6 +79,8 @@ const formBaseline = ref('')
const importDocInput = ref<HTMLInputElement | null>(null)
const importingDoc = ref(false)
const importedFromDoc = ref(false)
const selectedKeys = ref<number[]>([])
const exporting = ref(false)
const form = reactive({
name: '',
@ -576,6 +579,8 @@ async function loadData(keepCurrentPage = false) {
])
rows.value = tourRes.data
venues.value = venueRes.data
const idSet = new Set(rows.value.map((r) => r.id))
selectedKeys.value = selectedKeys.value.filter((id) => idSet.has(id))
if (!keepCurrentPage) listPagination.current = 1
} catch (error: any) {
Message.error(error?.response?.data?.message ?? '加载研学线路失败')
@ -588,6 +593,99 @@ function onListPageChange(page: number) {
listPagination.current = page
}
function onSelectionChange(keys: (string | number)[]) {
selectedKeys.value = keys
.map((k) => Number(k))
.filter((id) => Number.isFinite(id) && id > 0)
}
function filenameFromContentDisposition(header: string | undefined, fallback: string) {
if (!header) return fallback
const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header)
if (star?.[1]) {
try {
const raw = decodeURIComponent(star[1].trim().replace(/^"+|"+$/g, ''))
if (raw) return raw
} catch {
// ignore
}
}
const plain = /filename="?([^";]+)/i.exec(header)
const raw = (plain?.[1] || '').trim().replace(/^"+|"+$/g, '')
return raw || fallback
}
async function messageFromBlobError(error: any): Promise<string> {
const data = error?.response?.data
if (data instanceof Blob) {
try {
const text = await data.text()
const j = JSON.parse(text) as { message?: string }
if (j?.message) return j.message
} catch {
// ignore
}
}
return error?.response?.data?.message ?? error?.message ?? '导出失败'
}
async function exportStudyTours(ids: number[]) {
const uniqueIds = [...new Set(ids.filter((id) => Number.isFinite(id) && id > 0))]
if (!uniqueIds.length) {
Message.warning('请先勾选要导出的线路')
return
}
if (uniqueIds.length > STUDY_TOUR_EXPORT_MAX) {
Message.warning(`单次最多导出 ${STUDY_TOUR_EXPORT_MAX} 条线路`)
return
}
if (exporting.value) return
exporting.value = true
try {
const res = await http.post(
'/study-tours/export',
{ ids: uniqueIds },
{ responseType: 'blob', timeout: 120000 },
)
const blob = res.data as Blob
if (blob.type && (blob.type.includes('json') || blob.type.includes('text/html'))) {
const text = await blob.text()
let msg = '导出失败'
try {
const j = JSON.parse(text) as { message?: string }
if (j?.message) msg = j.message
} catch {
// ignore
}
Message.error(msg)
return
}
const fallback = uniqueIds.length === 1
? '研学线路申报表.docx'
: `研学线路申报表-${new Date().toISOString().slice(0, 10)}.zip`
const filename = filenameFromContentDisposition(res.headers['content-disposition'] as string | undefined, fallback)
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
Message.success(uniqueIds.length === 1 ? '已导出 Word' : `已导出 ${uniqueIds.length} 条线路`)
} catch (error: any) {
Message.error(await messageFromBlobError(error))
} finally {
exporting.value = false
}
}
function exportSelected() {
void exportStudyTours(selectedKeys.value)
}
function exportOne(row: StudyTour) {
void exportStudyTours([row.id])
}
async function toggleShelf(row: StudyTour) {
try {
await http.put(`/study-tours/${row.id}`, { is_on_shelf: !row.is_on_shelf })
@ -893,6 +991,11 @@ onMounted(async () => {
@change="onImportDocSelected"
/>
<a-button :loading="importingDoc" @click="triggerImportDoc"></a-button>
<a-button
:loading="exporting"
:disabled="!selectedKeys.length"
@click="exportSelected"
>导出选中{{ selectedKeys.length ? `${selectedKeys.length}` : '' }}</a-button>
<a-button type="primary" @click="openCreate">线</a-button>
</a-space>
@ -902,7 +1005,9 @@ onMounted(async () => {
:data="rows"
:loading="loading"
row-key="id"
:row-selection="{ type: 'checkbox', selectedRowKeys: selectedKeys, showCheckedAll: true, onlyCurrent: false }"
:pagination="{ current: listPagination.current, pageSize: listPagination.pageSize, total: rows.length, showTotal: true }"
@selection-change="onSelectionChange"
@page-change="onListPageChange"
>
<template #columns>
@ -941,10 +1046,11 @@ onMounted(async () => {
<a-tag :color="record.is_on_shelf ? 'green' : 'gray'">{{ record.is_on_shelf ? '上架' : '下架' }}</a-tag>
</template>
</a-table-column>
<a-table-column title="操作" :width="200" fixed="right" align="center">
<a-table-column title="操作" :width="240" fixed="right" align="center">
<template #cell="{ record }">
<a-space wrap :size="4">
<a-button type="text" @click="openEdit(record)"></a-button>
<a-button type="text" :loading="exporting" @click="exportOne(record)"></a-button>
<a-button type="text" status="warning" @click="toggleShelf(record)">{{ record.is_on_shelf ? '' : '' }}</a-button>
<a-popconfirm content="确定删除该线路?" @ok="removeRow(record)">
<a-button type="text" status="danger">删除</a-button>

Loading…
Cancel
Save