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 350563e commit 9de7b79
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 10 deletions.
4 changes: 4 additions & 0 deletions apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ interface AuthorInfoResponse {
original_article_count: number
}

/**
* 获取公众号主体信息
* @param biz
*/
export async function authorInfo(biz: string) {
return await $fetch<AuthorInfoResponse>('/api/authorinfo', {
method: 'GET',
Expand Down
4 changes: 2 additions & 2 deletions pages/dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
<div id="title"></div>

<div class="hidden md:flex items-center gap-4">
<NuxtLink to="/" class="font-semibold inline-flex items-center justify-center border select-none border-slate-6 bg-slate-2 text-slate-12 hover:bg-slate-4 text-sm h-8 px-3 rounded-md gap-1">旧版UI</NuxtLink>
<a href="https://github.com/jooooock/wechat-article-exporter/blob/master/docs/faq.md" target="_blank" class="font-semibold inline-flex items-center justify-center border select-none border-slate-6 bg-slate-2 text-slate-12 hover:bg-slate-4 text-sm h-8 px-3 rounded-md gap-1">
<!-- <CircleHelp :size="18"/>-->
<span class="inline-flex items-center justify-center gap-1 truncate visible">FAQ</span>
</a>
<a href="https://github.com/jooooock/wechat-article-exporter/issues/new" target="_blank" class="font-semibold inline-flex items-center justify-center border select-none border-slate-6 bg-slate-2 text-slate-12 hover:bg-slate-4 text-sm h-8 px-3 rounded-md gap-1">
Expand Down Expand Up @@ -68,6 +68,6 @@ const items = ref([
{name: '合集下载', icon: Album, href: '/dashboard/album'},
{name: '缓存分析', icon: ChartNoAxesCombined, href: '/dashboard/analytics'},
{name: 'Cookie设置', icon: Cookie, href: '/dashboard/cookie'},
{name: '代理使用量', icon: Globe, href: '/dashboard/proxy'},
{name: '代理使用额度', icon: Globe, href: '/dashboard/proxy'},
])
</script>
202 changes: 199 additions & 3 deletions pages/dashboard/album.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,212 @@
<template>
<div class="flex flex-col h-full">
<Teleport defer to="#title">
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">合集下载</h1>
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">合集下载 <span class="text-sm text-slate-10">合集数据来自于已缓存文章</span>
</h1>
</Teleport>
<div class="px-6 flex-1 overflow-scroll">
<p>~~ 敬请期待 ~~</p>
<div class="flex flex-1 overflow-hidden">

<ul class="flex flex-col h-full w-fit overflow-y-scroll divide-y">
<li v-for="accountInfo in cachedAccountInfos" :key="accountInfo.fakeid" class="relative px-4 pr-16 py-4"
:class="{'bg-slate-3': selectedAccount?.fakeid === accountInfo.fakeid}"
@click="toggleSelectedAccount(accountInfo)">
<p>公众号:
<span v-if="accountInfo.nickname" class="text-xl font-medium">{{ accountInfo.nickname }}</span>
</p>
<p>ID: <span class="font-mono">{{ accountInfo.fakeid }}</span></p>
<UBadge variant="subtle" color="red" class="absolute top-4 right-2">
<Loader v-if="!accountInfo.albums" :size="28" class="animate-spin text-slate-500"/>
<span v-else>{{ accountInfo.albums.length }}</span>
</UBadge>
</li>
</ul>
<ul v-if="selectedAccount" class="flex flex-col h-full w-72 overflow-y-scroll divide-y">
<li v-for="(album, index) in selectedAccount.albums" :key="album.id" class="p-4"
:class="{'bg-slate-3': selectedAlbum?.id === album.id}" @click="toggleSelectedAlbum(album)">
{{ index + 1 }}. {{ album.title }}
</li>
</ul>
<main class="flex-1 h-full overflow-y-scroll bg-[#ededed]" v-if="selectedAccount && selectedAlbum">
<div v-if="albumLoading" class="flex justify-center items-center mt-5">
<Loader :size="28" class="animate-spin text-slate-500"/>
</div>
<div v-else-if="albumBaseInfo" class="max-w-2xl mx-auto bg-white">
<!-- banner -->
<div class="px-5 pt-7 pb-14 banner">
<h2 class="text-2xl text-white font-bold"># {{ albumBaseInfo.title }}</h2>
</div>
<div class="rounded-xl -mt-4 relative z-50 bg-white px-4 py-6">
<!-- 头部信息 -->
<div class="pb-10">
<p class="flex items-center space-x-2 mb-2">
<img class="size-5" :src="albumBaseInfo.brand_icon" alt="">
<span>{{ albumBaseInfo.nickname }}</span>
</p>
<p class="text-sm text-slate-10">
<span>{{ albumBaseInfo.article_count }}篇内容</span>
<span v-if="albumBaseInfo.description"> · {{ albumBaseInfo.description }}</span>
</p>
<div class="flex mt-8 space-x-2 w-fit" @click="toggleReverse">
<ArrowUpNarrowWide v-if="isReverse" />
<ArrowDownNarrowWide v-else />
<span>{{isReverse ? '倒序' : '正序'}}</span>
</div>
</div>
<!-- 文章列表 -->
<ul class="divide-y">
<li class="flex justify-between items-center py-5 px-1" v-for="article in albumArticles"
:key="article.key">
<div class="flex-1">
<h3 class="text-lg mb-2">
<span v-if="article.pos_num">{{ article.pos_num }}. </span>
<span>{{ article.title }}</span>
</h3>
<time class="text-sm text-slate-10">{{ article.create_time }}</time>
</div>
<img class="size-16 ml-4 flex-shrink-0" :src="article.cover_img_1_1" alt="">
</li>
</ul>
<div v-element-visibility="onElementVisibility"></div>
<p v-if="articleLoading" class="flex justify-center items-center mt-2 py-2">
<Loader :size="28" class="animate-spin text-slate-500"/>
</p>
<p v-else-if="noMoreData" class="text-center mt-2 py-2 text-slate-400">已全部加载完毕</p>
</div>
</div>
</main>
</div>
</div>
</template>
<script setup lang="ts">
import {getAllInfo, type Info} from "~/store/info";
import {getArticleCache} from "~/store/article";
import type {AppMsgAlbumInfo} from "~/types/types";
import {Loader} from "lucide-vue-next";
import type {AppMsgAlbumResult, ArticleItem, BaseInfo} from "~/types/album";
import { ArrowDownNarrowWide, ArrowUpNarrowWide } from 'lucide-vue-next';
import {vElementVisibility} from "@vueuse/components"
useHead({
title: '合集下载 | 微信公众号文章导出'
});
interface AccountInfo extends Info {
albums?: AppMsgAlbumInfo[]
}
// 已缓存的公众号信息
const cachedAccountInfos: AccountInfo[] = reactive(await getAllInfo())
cachedAccountInfos.forEach(async accountInfo => {
accountInfo.albums = await getAllAlbums(accountInfo.fakeid)
})
const selectedAccount = ref<AccountInfo | null>(null)
async function toggleSelectedAccount(info: AccountInfo) {
if (info.fakeid !== selectedAccount.value?.fakeid) {
selectedAccount.value = info
}
}
const selectedAlbum = ref<AppMsgAlbumInfo | null>(null)
function toggleSelectedAlbum(album: AppMsgAlbumInfo) {
if (album.id !== selectedAlbum.value?.id) {
selectedAlbum.value = album
isReverse.value = false
getFirstPageAlbumData().catch(() => {})
}
}
async function getAllAlbums(fakeid: string) {
const articles = await getArticleCache(fakeid, Date.now())
const albums: AppMsgAlbumInfo[] = []
articles.flatMap(article => article.appmsg_album_infos).forEach(album => {
if (!albums.some(a => a.id === album.id)) {
albums.push(album)
}
})
return albums
}
const albumArticles: ArticleItem[] = reactive([])
const albumBaseInfo = ref<BaseInfo | null>(null)
const isReverse = ref(false)
const albumLoading = ref(false)
const articleLoading = ref(false)
async function getFirstPageAlbumData() {
albumLoading.value = true
albumArticles.length = 0
const data = await $fetch<AppMsgAlbumResult>('/api/appmsgalbum', {
method: 'GET',
query: {
fakeid: selectedAccount.value!.fakeid,
album_id: selectedAlbum.value!.id,
is_reverse: isReverse.value ? '1' : '0',
}
})
albumLoading.value = false
if (data.base_resp.ret === 0) {
albumBaseInfo.value = data.getalbum_resp.base_info
albumArticles.push(...data.getalbum_resp.article_list)
noMoreData.value = data.getalbum_resp.continue_flag === '0'
}
}
function toggleReverse() {
isReverse.value = !isReverse.value
albumArticles.length = 0
}
async function loadMoreData() {
articleLoading.value = true
const lastArticle = albumArticles[albumArticles.length - 1]
const data = await $fetch<AppMsgAlbumResult>('/api/appmsgalbum', {
method: 'GET',
query: {
fakeid: selectedAccount.value!.fakeid,
album_id: selectedAlbum.value!.id,
is_reverse: isReverse.value ? '1' : '0',
begin_msgid: lastArticle?.msgid,
begin_itemidx: lastArticle?.itemidx,
}
})
articleLoading.value = false
if (data.base_resp.ret === 0) {
albumArticles.push(...data.getalbum_resp.article_list)
noMoreData.value = data.getalbum_resp.continue_flag === '0'
}
}
const noMoreData = ref(false)
// 判断是否触底
const bottomElementIsVisible = ref(false)
function onElementVisibility(visible: boolean) {
bottomElementIsVisible.value = visible
if (visible && !noMoreData.value) {
loadMoreData()
}
}
</script>
<style scoped>
.banner {
background: linear-gradient(rgb(9, 9, 9), rgb(35, 35, 35));
}
</style>
2 changes: 1 addition & 1 deletion pages/dashboard/download.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col h-full">
<Teleport defer to="#title">
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">数据导出</h1>
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">数据导出 <span class="text-sm text-slate-10">导出本地已缓存的文章</span></h1>
</Teleport>
<div class="flex flex-1 overflow-hidden">
<ul class="flex flex-col h-full w-fit overflow-y-scroll divide-y">
Expand Down
4 changes: 2 additions & 2 deletions pages/dashboard/proxy.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col h-full">
<Teleport defer to="#title">
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">代理使用量</h1>
<h1 class="text-[28px] leading-[34px] text-slate-12 font-bold">代理使用额度</h1>
</Teleport>
<div class="px-6 flex-1 overflow-scroll">
<p>~~ 敬请期待 ~~</p>
Expand All @@ -11,6 +11,6 @@

<script setup lang="ts">
useHead({
title: '代理使用量 | 微信公众号文章导出'
title: '代理使用额度 | 微信公众号文章导出'
});
</script>
44 changes: 44 additions & 0 deletions server/api/appmsgalbum.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* 获取合集数据接口
*/

import {proxyMpRequest} from "~/server/utils";

interface AppMsgAlbumQuery {
fakeid: string
album_id: string
is_reverse?: string
count?: number
begin_msgid?: string
begin_itemidx?: string
}

export default defineEventHandler(async (event) => {
const query = getQuery<AppMsgAlbumQuery>(event)
const fakeid = query.fakeid
const album_id = query.album_id
const isReverse = query.is_reverse || '0'
const begin_msgid = query.begin_msgid
const begin_itemidx = query.begin_itemidx
const count: number = query.count || 10


const params: Record<string, string | number | undefined> = {
action: 'getalbum',
__biz: fakeid,
album_id: album_id,
begin_msgid: begin_msgid,
begin_itemidx: begin_itemidx,
count: count,
is_reverse: isReverse,
f: "json",
}

return proxyMpRequest({
event: event,
method: 'GET',
endpoint: 'https://mp.weixin.qq.com/mp/appmsgalbum',
query: params,
parseJson: true,
})
})
4 changes: 2 additions & 2 deletions server/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ interface RequestOptions {
event: H3Event
endpoint: string
method: Method
query?: Record<string, string | number>
body?: Record<string, string | number>
query?: Record<string, string | number | undefined>
body?: Record<string, string | number | undefined>
parseJson?: boolean
withCredentials?: boolean
}
Expand Down
61 changes: 61 additions & 0 deletions types/album.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {RGB} from "~/types/types";

interface BaseResp {
exportkey_token: string
ret: number
}
type BooleanString = "0" | "1"

export interface BaseInfo {
article_count: string
brand_icon: string
cover: string
cover_ban: BooleanString
description: string
fee: string
is_first_screen: BooleanString
is_numbered: BooleanString
is_paid: BooleanString
is_reverse: BooleanString
isupdating: BooleanString
needpay: BooleanString
nickname: string
public_tag_content_num: string
public_tag_link: string
share_brand_icon: string
subtype: string
title: string
type: string
update_frequence: Record<string, string>
username: string
}

export interface ArticleItem {
cover_img_1_1: string
cover_theme_color: RGB
create_time: string
is_pay_subscribe: BooleanString
is_read: BooleanString
item_show_type: string
itemidx: string
key: string
msgid: string
pos_num?: string
title: string
tts_is_ban: string
url: string
user_read_status: string
}

export interface GetAlbumResp {
article_list: ArticleItem[]
base_info: BaseInfo
continue_flag: BooleanString
is_pay_subscribe: BooleanString
reverse_continue_flag: BooleanString
}

export interface AppMsgAlbumResult {
base_resp: BaseResp
getalbum_resp: GetAlbumResp
}

0 comments on commit 9de7b79

Please sign in to comment.