From 350563e5624cf2f2f37a3a94824a490078787f50 Mon Sep 17 00:00:00 2001 From: jooooock Date: Fri, 20 Sep 2024 11:51:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=89=B9=E9=87=8F=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composables/useBatchDownload.ts | 56 ++++++++++++++++ pages/dashboard/download.vue | 112 ++++++++++++++++++++++---------- 2 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 composables/useBatchDownload.ts diff --git a/composables/useBatchDownload.ts b/composables/useBatchDownload.ts new file mode 100644 index 0000000..cd64433 --- /dev/null +++ b/composables/useBatchDownload.ts @@ -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, filename: Ref) { + 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, + } +} diff --git a/pages/dashboard/download.vue b/pages/dashboard/download.vue index 412328e..38762ed 100644 --- a/pages/dashboard/download.vue +++ b/pages/dashboard/download.vue @@ -5,16 +5,16 @@
    -
  • +
  • 公众号: - {{ info.nickname }} - + {{ accountInfo.nickname }}

    -

    ID: {{ info.fakeid }}

    - {{info.articles}} +

    ID: {{ accountInfo.fakeid }}

    + {{ accountInfo.articles }}
+
@@ -56,25 +56,38 @@ + + 搜索
- 批量下载 + + + {{ batchDownloadPhase }}: + {{ batchDownloadedArticles.length }}/{{ selectedArticles.length }} + {{ batchPackedArticles.length }}/{{ batchDownloadedArticles.length }} + + 批量下载
- - +
+ - + - + - + + @@ -88,12 +101,18 @@ +
序号序号 标题发布日期发布日期 作者是否原创是否原创所属合集
{{ article.author_name }} {{ article.copyright_stat === 1 && article.copyright_type === 1 ? '原创' : '' }} +

+ #{{ album.title }} +

+
- 共 {{displayedArticles.length}} 条有效数据,已选中 {{selectedArticles.length}} 条数据 + 共 {{ displayedArticles.length }} 条有效数据,已选中 {{ selectedArticles.length }} 条数据
@@ -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([]) -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([]) +const loading = ref(false) + +const checkAll = ref(false) +const isIndeterminate = ref(false) + const displayedArticles = computed(() => { return articles.filter(article => article.display) }) @@ -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) + '...' } @@ -194,7 +217,7 @@ function toggleArticleCheck(article: Article) { } } -function onCheckAllChange(evt: InputEvent) { +function onCheckAllChange() { if (checkAll.value) { articles.forEach(article => { article.checked = true @@ -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()) @@ -238,6 +264,7 @@ interface ArticleQuery { dateRange: { start: Date, end: Date } authors: string[] isOriginal: '原创' | '非原创' | '所有' + albums: string[] } const query = reactive({ @@ -245,6 +272,7 @@ const query = reactive({ dateRange: {start: sub(new Date(), {days: 14}), end: new Date()}, authors: [], isOriginal: '所有', + albums: [], }) function search() { @@ -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)