feat: 同步最新接口

- 支持自动添加新加入的接口 #12
This commit is contained in:
imsyy
2023-12-05 17:24:07 +08:00
parent d6155e7c30
commit 72da75ddd7
18 changed files with 194 additions and 110 deletions

View File

@@ -1,29 +1,36 @@
# DailyHot <div align="center">
<img alt="logo" height="120" src="./public/favicon.png" width="120"/>
<h2>今日热榜</h2>
<p>汇聚全网热点,热门尽览无余</p>
<br />
<img src="./screenshots/main.jpg" style="border-radius: 16px" />
</div>
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup ## 示例
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). > 这里是示例站点
## Customize configuration - [今日热榜 - https://hot.imsyy.top/](https://hot.imsyy.top/)
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup ## 部署
```sh ```bash
npm install // 安装依赖
pnpm install
// 开发
pnpm dev
// 打包
pnpm build
``` ```
### Compile and Hot-Reload for Development ## Vercel 部署
```sh 现已支持 Vercel 一键部署,无需服务器
npm run dev
```
### Compile and Minify for Production > 请注意,需要修改环境变量中的 API 地址
```sh ![Powered by Vercel](./public/ico/powered-by-vercel.svg)
npm run build
```

View File

@@ -3,7 +3,7 @@
"description": "今日热榜", "description": "今日热榜",
"author": "imsyy", "author": "imsyy",
"github": "https://github.com/imsyy", "github": "https://github.com/imsyy",
"version": "0.2.1", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo/douban_new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
public/logo/genshin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/logo/kuaishou.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
public/logo/lol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/logo/netease.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/logo/weread.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
screenshots/main.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

View File

@@ -36,6 +36,16 @@ const headerShow = ref(false);
const backTopChange = (val) => { const backTopChange = (val) => {
headerShow.value = val; headerShow.value = val;
}; };
onMounted(() => {
store.checkNewsUpdate();
// 写入默认
nextTick(() => {
if (store.newsArr.length === 0) {
store.newsArr = store.defaultNewsArr;
}
});
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -8,36 +8,39 @@
@click="toList" @click="toList"
> >
<template #header> <template #header>
<Transition name="fade" mode="out-in"> <n-space class="title" justify="space-between">
<template v-if="!hotListData"> <div class="name">
<div class="loading"> <n-avatar
<n-skeleton text round /> class="ico"
</div> :src="`/logo/${hotData.name}.png`"
</template> fallback-src="/ico/icon_error.png"
<template v-else> />
<div class="title"> <n-text class="name-text">{{ hotData.label }}</n-text>
<n-avatar </div>
class="ico" <n-text v-if="hotListData?.subtitle" class="subtitle" :depth="2">
:src="`/logo/${hotType}.png`" {{ hotListData.subtitle }}
fallback-src="/ico/icon_error.png" </n-text>
/> <n-skeleton v-else width="60px" text round />
<n-text class="name">{{ hotListData.title }}</n-text> </n-space>
<n-text class="subtitle" :depth="2">
{{ hotListData.subtitle }}
</n-text>
</div>
</template>
</Transition>
</template> </template>
<n-scrollbar class="news-list" ref="scrollbarRef"> <n-scrollbar class="news-list" ref="scrollbarRef">
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
<template v-if="!hotListData || listLoading"> <template v-if="loadingError">
<n-result
size="small"
status="500"
title="哎呀,加载失败了"
description="生活总会遇到不如意的事情"
style="margin-top: 40px"
/>
</template>
<template v-else-if="!hotListData || listLoading">
<div class="loading"> <div class="loading">
<n-skeleton text round :repeat="10" height="20px" /> <n-skeleton text round :repeat="10" height="20px" />
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="lists" :id="hotType + 'Lists'"> <div class="lists" :id="hotData.name + 'Lists'">
<div <div
class="item" class="item"
v-for="(item, index) in hotListData.data.slice(0, 15)" v-for="(item, index) in hotListData.data.slice(0, 15)"
@@ -129,10 +132,10 @@ import { useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const store = mainStore(); const store = mainStore();
const props = defineProps({ const props = defineProps({
// 热榜类别 // 热榜数据
hotType: { hotData: {
type: String, type: Object,
default: null, default: {},
}, },
}); });
@@ -140,44 +143,38 @@ const props = defineProps({
const updateTime = ref(null); const updateTime = ref(null);
// 刷新按钮数据 // 刷新按钮数据
const lastClickTime = ref(localStorage.getItem(`${props.hotType}Btn`) || 0); const lastClickTime = ref(
localStorage.getItem(`${props.hotData.name}Btn`) || 0
);
// 热榜数据 // 热榜数据
const hotListData = ref(null); const hotListData = ref(null);
const scrollbarRef = ref(null); const scrollbarRef = ref(null);
const listLoading = ref(false); const listLoading = ref(false);
const loadingError = ref(false);
// 获取热榜数据 // 获取热榜数据
const getHotListsData = (type, isNew = false) => { const getHotListsData = async (type, isNew = false) => {
// hotListData.value = null; try {
getHotLists(type, isNew) // hotListData.value = null;
.then((res) => { loadingError.value = false;
console.log(res); const result = await getHotLists(type, isNew);
if (res.code === 200) { // console.log(result);
listLoading.value = false; if (result.code === 200) {
hotListData.value = res; listLoading.value = false;
// 滚动至顶部 hotListData.value = result;
if (scrollbarRef.value) { // 滚动至顶部
scrollbarRef.value.scrollTo({ position: "top", behavior: "smooth" }); if (scrollbarRef.value) {
} scrollbarRef.value.scrollTo({ position: "top", behavior: "smooth" });
} else {
$message.error(res.title + res.message);
} }
}) } else {
.catch((error) => { loadingError.value = true;
console.error("资源请求失败:" + error); $message.error(result.title + result.message);
switch (error?.response.status) { }
case 403: } catch (error) {
router.push("/403"); loadingError.value = true;
break; $message.error("热榜加载失败,请重试");
case 500: }
router.push("/500");
break;
default:
router.push("/404");
break;
}
});
}; };
// 获取最新数据 // 获取最新数据
@@ -186,10 +183,10 @@ const getNewData = () => {
if (now - lastClickTime.value > 60000) { if (now - lastClickTime.value > 60000) {
// 点击事件 // 点击事件
listLoading.value = true; listLoading.value = true;
getHotListsData(props.hotType, true); getHotListsData(props.hotData.name, true);
// 更新最后一次点击时间 // 更新最后一次点击时间
lastClickTime.value = now; lastClickTime.value = now;
localStorage.setItem(`${props.hotType}Btn`, now); localStorage.setItem(`${props.hotData.name}Btn`, now);
} else { } else {
// 不执行点击事件 // 不执行点击事件
$message.info("请稍后再刷新"); $message.info("请稍后再刷新");
@@ -209,11 +206,11 @@ const jumpLink = (data) => {
// 前往全部列表 // 前往全部列表
const toList = () => { const toList = () => {
if (props.hotType) { if (props.hotData.name) {
router.push({ router.push({
path: "/list", path: "/list",
query: { query: {
type: props.hotType, type: props.hotData.name,
}, },
}); });
} else { } else {
@@ -232,7 +229,7 @@ watch(
); );
onMounted(() => { onMounted(() => {
if (props.hotType) getHotListsData(props.hotType); if (props.hotData.name) getHotListsData(props.hotData.name);
}); });
</script> </script>
@@ -255,11 +252,15 @@ onMounted(() => {
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
height: 26px; height: 26px;
.n-avatar { .name {
background-color: transparent; display: flex;
width: 20px; align-items: center;
height: 20px; .n-avatar {
margin-right: 8px; background-color: transparent;
width: 20px;
height: 20px;
margin-right: 8px;
}
} }
.subtitle { .subtitle {
margin-left: auto; margin-left: auto;

View File

@@ -1,92 +1,129 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export const mainStore = defineStore("main", { export const mainStore = defineStore("mainData", {
state: () => { state: () => {
return { return {
// 系统主题 // 系统主题
siteTheme: "light", siteTheme: "light",
siteThemeAuto: true, siteThemeAuto: true,
// 新闻类别 // 新闻类别
newsArr: [ defaultNewsArr: [
{ {
label: "哔哩哔哩", label: "哔哩哔哩",
value: "bilibili", name: "bilibili",
order: 0, order: 0,
show: true, show: true,
}, },
{ {
label: "微博", label: "微博",
value: "weibo", name: "weibo",
order: 1, order: 1,
show: true, show: true,
}, },
{ {
label: "抖音", label: "抖音",
value: "douyin", name: "douyin",
order: 2, order: 2,
show: true, show: true,
}, },
{ {
label: "知乎", label: "知乎",
value: "zhihu", name: "zhihu",
order: 3, order: 3,
show: true, show: true,
}, },
{ {
label: "36氪", label: "36氪",
value: "36kr", name: "36kr",
order: 4, order: 4,
show: true, show: true,
}, },
{ {
label: "百度", label: "百度",
value: "baidu", name: "baidu",
order: 5, order: 5,
show: true, show: true,
}, },
{ {
label: "少数派", label: "少数派",
value: "sspai", name: "sspai",
order: 6, order: 6,
show: true, show: true,
}, },
{ {
label: "IT之家", label: "IT之家",
value: "ithome", name: "ithome",
order: 7, order: 7,
show: true, show: true,
}, },
{ {
label: "澎湃新闻", label: "澎湃新闻",
value: "thepaper", name: "thepaper",
order: 8, order: 8,
show: true, show: true,
}, },
{ {
label: "今日头条", label: "今日头条",
value: "toutiao", name: "toutiao",
order: 9, order: 9,
show: true, show: true,
}, },
{ {
label: "百度贴吧", label: "百度贴吧",
value: "tieba", name: "tieba",
order: 10, order: 10,
show: true, show: true,
}, },
{ {
label: "稀土掘金", label: "稀土掘金",
value: "juejin", name: "juejin",
order: 11, order: 11,
show: true, show: true,
}, },
{ {
label: "腾讯新闻", label: "腾讯新闻",
value: "newsqq", name: "newsqq",
order: 12, order: 12,
show: true, show: true,
}, },
{
label: "豆瓣",
name: "douban_new",
order: 13,
show: true,
},
{
label: "原神",
name: "genshin",
order: 14,
show: true,
},
{
label: "LOL",
name: "lol",
order: 15,
show: true,
},
{
label: "快手",
name: "kuaishou",
order: 16,
show: true,
},
{
label: "网易新闻",
name: "netease",
order: 17,
show: true,
},
{
label: "微信读书",
name: "weread",
order: 18,
show: true,
},
], ],
newsArr: [],
// 链接跳转方式 // 链接跳转方式
linkOpenType: "open", linkOpenType: "open",
// 页头固定 // 页头固定
@@ -105,6 +142,30 @@ export const mainStore = defineStore("main", {
this.siteTheme = val; this.siteTheme = val;
this.siteThemeAuto = false; this.siteThemeAuto = false;
}, },
// 检查更新
checkNewsUpdate() {
const mainData = JSON.parse(localStorage.getItem("mainData"));
let updatedNum = 0;
if (!mainData) return false;
console.log("列表尝试更新", this.defaultNewsArr, this.newsArr);
// 执行比较并迁移
if (this.newsArr.length > 0) {
for (const newItem of this.defaultNewsArr) {
const exists = this.newsArr.some(
(news) => newItem.label === news.label && newItem.name === news.name
);
if (!exists) {
console.log("列表有更新:", newItem);
updatedNum++;
this.newsArr.push(newItem);
}
}
if (updatedNum) $message.success(`成功更新 ${updatedNum} 个榜单数据`);
} else {
console.log("列表无内容,写入默认");
this.newsArr = this.defaultNewsArr;
}
},
}, },
persist: [ persist: [
{ {

View File

@@ -15,7 +15,7 @@
:key="item" :key="item"
:style="{ animationDelay: index / 10 + 0.2 + 's' }" :style="{ animationDelay: index / 10 + 0.2 + 's' }"
> >
<HotList :hotType="item.value" /> <HotList :hotData="item" />
</n-grid-item> </n-grid-item>
</n-grid> </n-grid>
<div class="error" v-else> <div class="error" v-else>

View File

@@ -7,12 +7,12 @@
class="tag" class="tag"
v-for="item in store.newsArr.filter((item) => item.show)" v-for="item in store.newsArr.filter((item) => item.show)"
:key="item" :key="item"
:type="item.value === listType ? 'primary' : 'default'" :type="item.name === listType ? 'primary' : 'default'"
@click="changeType(item.value)" @click="changeType(item.name)"
> >
{{ item.label }} {{ item.label }}
<template #avatar> <template #avatar>
<img :src="`/logo/${item.value}.png`" alt="logo" class="logo" /> <img :src="`/logo/${item.name}.png`" alt="logo" class="logo" />
</template> </template>
</n-tag> </n-tag>
</n-space> </n-space>
@@ -131,7 +131,7 @@ const store = mainStore();
const updateTime = ref(null); const updateTime = ref(null);
const listType = ref( const listType = ref(
router.currentRoute.value.query.type || store.newsArr[0].value router.currentRoute.value.query.type || store.newsArr[0].name
); );
const pageNumber = ref( const pageNumber = ref(
router.currentRoute.value.query.page router.currentRoute.value.query.page

View File

@@ -79,11 +79,7 @@
:content-style="{ display: 'flex', alignItems: 'center' }" :content-style="{ display: 'flex', alignItems: 'center' }"
> >
<div class="desc" :style="{ opacity: element.show ? null : 0.6 }"> <div class="desc" :style="{ opacity: element.show ? null : 0.6 }">
<img <img class="logo" :src="`/logo/${element.name}.png`" alt="logo" />
class="logo"
:src="`/logo/${element.value}.png`"
alt="logo"
/>
<n-text class="news-name" v-html="element.label" /> <n-text class="news-name" v-html="element.label" />
</div> </div>
<n-switch <n-switch
@@ -174,7 +170,7 @@ const saveSoreData = (name = null, open = false) => {
// 重置数据 // 重置数据
const reset = () => { const reset = () => {
if ($timeInterval) clearInterval($timeInterval); if (typeof $timeInterval !== "undefined") clearInterval($timeInterval);
localStorage.clear(); localStorage.clear();
location.reload(); location.reload();
}; };

3
vercel.json Normal file
View File

@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/:path*", "destination": "/index.html" }]
}