Skip to content

Commit

Permalink
完善批量下载
Browse files Browse the repository at this point in the history
  • Loading branch information
jooooock committed Sep 20, 2024
1 parent b20f06d commit 350563e
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 33 deletions.
56 changes: 56 additions & 0 deletions composables/useBatchDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type {AppMsgExWithHTML} from "~/types/types";
import {downloadArticleHTMLs, packHTMLAssets} from "~/utils";
import JSZip from "jszip";
import {saveAs} from "file-saver";
import {uploadProxy} from "~/store/proxy";
import {format} from 'date-fns'


/**
* 批量下载文章
* @param articles
* @param filename
*/
export default function useBatchDownload(articles: Ref<AppMsgExWithHTML[]>, filename: Ref<string>) {
const loading = ref(false)
const phase = ref()

// 已下载的文章列表
const downloadedArticles = computed(() => articles.value.filter(article => !!article.html))
// 已打包的文章列表
const packedArticles = computed(() => articles.value.filter(article => !!article.packed))

async function download() {
loading.value = true

phase.value = '下载文章内容'
await downloadArticleHTMLs(articles.value)

phase.value = '打包'
const zip = new JSZip()
for (const article of downloadedArticles.value) {
try {
await packHTMLAssets(article.html!, article.title.replaceAll('.', '_'), zip.folder(format(new Date(article.update_time * 1000), 'yyyy-MM-dd') + ' ' + article.title.replace(/\//g, '_'))!)
article.packed = true
} catch (e: any) {
console.info('打包失败:')
console.warn(e.message)
}
}

const blob = await zip.generateAsync({type: 'blob'})
saveAs(blob, `${filename.value}.zip`)

await uploadProxy()

loading.value = false
}

return {
loading,
phase,
downloadedArticles,
packedArticles,
download,
}
}
112 changes: 79 additions & 33 deletions pages/dashboard/download.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
</Teleport>
<div class="flex flex-1 overflow-hidden">
<ul class="flex flex-col h-full w-fit overflow-y-scroll divide-y">
<li v-for="info in infos" :key="info.fakeid" class="relative px-4 pr-16 py-4"
:class="{'bg-slate-3': selectedAccount === info.fakeid}" @click="selectAccount(info)">
<li v-for="accountInfo in cachedAccountInfos" :key="accountInfo.fakeid" class="relative px-4 pr-16 py-4"
:class="{'bg-slate-3': selectedAccount === accountInfo.fakeid}" @click="toggleSelectedAccount(accountInfo)">
<p>公众号:
<span v-if="info.nickname" class="text-xl font-medium">{{ info.nickname }}</span>
<span v-else></span>
<span v-if="accountInfo.nickname" class="text-xl font-medium">{{ accountInfo.nickname }}</span>
</p>
<p>ID: <span class="font-mono">{{ info.fakeid }}</span></p>
<UBadge variant="subtle" color="red" class="absolute top-4 right-2">{{info.articles}}</UBadge>
<p>ID: <span class="font-mono">{{ accountInfo.fakeid }}</span></p>
<UBadge variant="subtle" color="red" class="absolute top-4 right-2">{{ accountInfo.articles }}</UBadge>
</li>
</ul>

<main class="flex-1 h-full overflow-y-scroll">
<div v-if="loading" class="flex justify-center items-center mt-5">
<Loader :size="28" class="animate-spin text-slate-500"/>
Expand Down Expand Up @@ -56,25 +56,38 @@

<USelect v-model="query.isOriginal" :options="originalOptions" color="blue"/>

<USelectMenu class="w-40" color="blue" v-model="query.albums" :options="articleAlbums" multiple
placeholder="选择合集"/>

<UButton color="gray" variant="solid" @click="search">搜索</UButton>
</div>
<div>
<UButton color="black" variant="solid" :disabled="selectedArticles.length === 0" @click="download">批量下载
<UButton color="black" variant="solid" class="disabled:bg-slate-4 disabled:text-slate-12"
:disabled="selectedArticles.length === 0 || batchDownloadLoading" @click="batchDownload">
<Loader v-if="batchDownloadLoading" :size="20" class="animate-spin"/>
<span v-if="batchDownloadLoading">{{ batchDownloadPhase }}:
<span
v-if="batchDownloadPhase === '下载文章内容'">{{ batchDownloadedArticles.length }}/{{ selectedArticles.length }}</span>
<span
v-if="batchDownloadPhase === '打包'">{{ batchPackedArticles.length }}/{{ batchDownloadedArticles.length }}</span>
</span>
<span v-else>批量下载</span>
</UButton>
</div>
</div>
<table class="w-full border-collapse border rounded-md">
<thead class="sticky top-[40px] z-10 bg-white">
<table class="w-full border-collapse">
<thead class="sticky top-[40px] z-10 h-[40px] bg-white">
<tr>
<th>
<UCheckbox class="justify-center" :indeterminate="isIndeterminate" v-model="checkAll"
@change="onCheckAllChange" color="blue"/>
</th>
<th>序号</th>
<th class="w-14">序号</th>
<th>标题</th>
<th>发布日期</th>
<th class="w-52">发布日期</th>
<th>作者</th>
<th>是否原创</th>
<th class="w-24">是否原创</th>
<th class="w-36">所属合集</th>
</tr>
</thead>
<tbody>
Expand All @@ -88,12 +101,18 @@
<td class="text-center">{{ article.author_name }}</td>
<td class="text-center">{{ article.copyright_stat === 1 && article.copyright_type === 1 ? '原创' : '' }}
</td>
<td>
<p class="flex flex-wrap">
<span v-for="album in article.appmsg_album_infos" :key="album.id"
class="text-blue-600 mr-2">#{{ album.title }}</span>
</p>
</td>
</tr>
</tbody>
</table>
<!-- 状态栏 -->
<div class="sticky bottom-0 h-[40px] bg-white text-rose-500 flex items-center px-4">
共 {{displayedArticles.length}} 条有效数据,已选中 {{selectedArticles.length}} 条数据
共 {{ displayedArticles.length }} 条有效数据,已选中 {{ selectedArticles.length }} 条数据
</div>
</div>
</main>
Expand All @@ -109,34 +128,38 @@ import {formatTimeStamp} from "~/utils";
import {Loader} from "lucide-vue-next";
import {sleep} from "@antfu/utils";
import {type Duration, format, isSameDay, sub} from 'date-fns'
import useBatchDownload from "~/composables/useBatchDownload";
interface Article extends AppMsgEx {
checked: boolean
display: boolean
}
const date = ref(new Date())
useHead({
title: '数据导出 | 微信公众号文章导出'
});
})
const infos = await getAllInfo()
// 已缓存的公众号信息
const cachedAccountInfos = await getAllInfo()
const selectedAccount = ref('')
const articles = reactive<Article[]>([])
const loading = ref(false)
const selectedAccountName = ref('')
const checkAll = ref(false)
const isIndeterminate = ref(false)
async function selectAccount(info: Info) {
async function toggleSelectedAccount(info: Info) {
if (info.fakeid !== selectedAccount.value) {
selectedAccount.value = info.fakeid
selectedAccountName.value = info.nickname || info.fakeid
switchTableData(info.fakeid).catch(() => {
})
}
}
const articles = reactive<Article[]>([])
const loading = ref(false)
const checkAll = ref(false)
const isIndeterminate = ref(false)
const displayedArticles = computed(() => {
return articles.filter(article => article.display)
})
Expand Down Expand Up @@ -169,7 +192,7 @@ async function switchTableData(fakeid: string) {
}
}
function maxLen(text: string, max = 35): string {
function maxLen(text: string, max = 45): string {
if (text.length > max) {
return text.slice(0, max) + '...'
}
Expand All @@ -194,7 +217,7 @@ function toggleArticleCheck(article: Article) {
}
}
function onCheckAllChange(evt: InputEvent) {
function onCheckAllChange() {
if (checkAll.value) {
articles.forEach(article => {
article.checked = true
Expand All @@ -211,6 +234,9 @@ function onCheckAllChange(evt: InputEvent) {
const articleAuthors = computed(() => {
return [...new Set(articles.map(article => article.author_name).filter(author => !!author))]
})
const articleAlbums = computed(() => {
return [...new Set(articles.flatMap(article => article.appmsg_album_infos).map(album => album.title))]
})
function isRangeSelected(duration: Duration) {
return isSameDay(query.dateRange.start, sub(new Date(), duration)) && isSameDay(query.dateRange.end, new Date())
Expand Down Expand Up @@ -238,13 +264,15 @@ interface ArticleQuery {
dateRange: { start: Date, end: Date }
authors: string[]
isOriginal: '原创' | '非原创' | '所有'
albums: string[]
}
const query = reactive<ArticleQuery>({
title: '',
dateRange: {start: sub(new Date(), {days: 14}), end: new Date()},
authors: [],
isOriginal: '所有',
albums: [],
})
function search() {
Expand All @@ -270,29 +298,47 @@ function search() {
if (new Date(article.update_time * 1000) < query.dateRange.start || new Date(article.update_time * 1000) > query.dateRange.end) {
article.display = false
}
if (query.albums.length > 0 && article.appmsg_album_infos.every(album => !query.albums.includes(album.title))) {
article.display = false
}
})
}
function download() {
alert('敬请期待')
}
const {
loading: batchDownloadLoading,
phase: batchDownloadPhase,
downloadedArticles: batchDownloadedArticles,
packedArticles: batchPackedArticles,
download: batchDownload,
} = useBatchDownload(selectedArticles, selectedAccountName)
</script>

<style scoped>
table {
border-collapse: collapse;
}
table th {
padding: 0.5rem 0.25rem;
}
table th,
table td {
border: 1px solid #00002d17;
padding: 0.25rem 0.5rem;
}
td:first-child,
th:first-child {
border-left: none;
}
td:last-child,
th:last-child {
border-right: none;
}
th {
border: 1px solid #00002d17;
border-top: none;
}
tr:nth-child(even) {
background-color: #00005506;
}
Expand Down

0 comments on commit 350563e

Please sign in to comment.