feat: 新增抖音接口 & 优化移动端

This commit is contained in:
imsyy
2023-07-03 17:22:16 +08:00
parent 9371443ade
commit 5700e54606
18 changed files with 935 additions and 4270 deletions

4
.env
View File

@@ -1,6 +1,6 @@
# 全局 API 地址 # 全局 API 地址
# VITE_GLOBAL_API="http://localhost:6688" VITE_GLOBAL_API="http://localhost:6688"
VITE_GLOBAL_API="https://api-hot.imsyy.top" # VITE_GLOBAL_API="https://api-hot.imsyy.top"
# ICP 备案号 # ICP 备案号
VITE_ICP = "豫ICP备2022018134号-1" VITE_ICP = "豫ICP备2022018134号-1"

View File

@@ -1 +0,0 @@
if('serviceWorker' in navigator) navigator.serviceWorker.register('/dev-sw.js?dev-sw', { scope: '/', type: 'classic' })

View File

@@ -1,100 +0,0 @@
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// If the loader is already loaded, just stop.
if (!self.define) {
let registry = {};
// Used for `eval` and `importScripts` where we can't get script URL by other means.
// In both cases, it's safe to use a global var because those functions are synchronous.
let nextDefineUri;
const singleRequire = (uri, parentUri) => {
uri = new URL(uri + ".js", parentUri).href;
return registry[uri] || (
new Promise(resolve => {
if ("document" in self) {
const script = document.createElement("script");
script.src = uri;
script.onload = resolve;
document.head.appendChild(script);
} else {
nextDefineUri = uri;
importScripts(uri);
resolve();
}
})
.then(() => {
let promise = registry[uri];
if (!promise) {
throw new Error(`Module ${uri} didnt register its module`);
}
return promise;
})
);
};
self.define = (depsNames, factory) => {
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
if (registry[uri]) {
// Module is already loading or loaded.
return;
}
let exports = {};
const require = depUri => singleRequire(depUri, uri);
const specialDeps = {
module: { uri },
exports,
require
};
registry[uri] = Promise.all(depsNames.map(
depName => specialDeps[depName] || require(depName)
)).then(deps => {
factory(...deps);
return exports;
});
};
}
define(['./workbox-25adc094'], (function (workbox) { 'use strict';
self.skipWaiting();
workbox.clientsClaim();
/**
* The precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
* See https://goo.gl/S9QRab
*/
workbox.precacheAndRoute([{
"url": "registerSW.js",
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"revision": null,
"url": "index.html"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
allowlist: [/^\/$/]
}));
workbox.registerRoute(/(.*?)\.(woff2|woff|ttf)/, new workbox.CacheFirst({
"cacheName": "file-cache",
plugins: []
}), 'GET');
workbox.registerRoute(/(.*?)\.(webp|png|jpe?g|svg|gif|bmp|psd|tiff|tga|eps)/, new workbox.CacheFirst({
"cacheName": "image-cache",
plugins: []
}), 'GET');
}));

File diff suppressed because it is too large Load Diff

View File

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

1296
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/logo/douyin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -49,6 +49,9 @@ axios.interceptors.response.use(
case 301: case 301:
$message.error(data.message ? data.message : "请求路径发生跳转"); $message.error(data.message ? data.message : "请求路径发生跳转");
break; break;
case 403:
$message.error(data.message ? data.message : "暂无访问权限");
break;
case 404: case 404:
$message.error(data.message ? data.message : "请求资源不存在"); $message.error(data.message ? data.message : "请求资源不存在");
break; break;

View File

@@ -25,7 +25,7 @@
</div> </div>
<div class="controls"> <div class="controls">
<n-space justify="end"> <n-space justify="end">
<n-popover> <n-popover v-if="showRefresh">
<template #trigger> <template #trigger>
<n-button secondary strong round @click="router.go(0)"> <n-button secondary strong round @click="router.go(0)">
<template #icon> <template #icon>
@@ -102,8 +102,8 @@ import { useRouter } from "vue-router";
const router = useRouter(); const router = useRouter();
const store = mainStore(); const store = mainStore();
const timeInterval = ref(null); const timeInterval = ref(null);
const showRefresh = ref(false);
// 移动端时间模块 // 移动端时间模块
const timeRender = () => { const timeRender = () => {
@@ -195,10 +195,20 @@ const menuOptionsSelect = (val) => {
} }
}; };
// 监听路由参数变化
watch(
() => router.currentRoute.value,
(val) => {
const isHome = val.path === "/";
showRefresh.value = isHome ? true : false;
}
);
onMounted(() => { onMounted(() => {
window.$timeInterval = timeInterval.value = setInterval(() => { window.$timeInterval = timeInterval.value = setInterval(() => {
store.timeData = getCurrentTime(); store.timeData = getCurrentTime();
}, 1000); }, 1000);
showRefresh.value = router.currentRoute.value?.path === "/" ? true : false;
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@@ -150,19 +150,34 @@ const listLoading = ref(false);
// 获取热榜数据 // 获取热榜数据
const getHotListsData = (type, isNew = false) => { const getHotListsData = (type, isNew = false) => {
// hotListData.value = null; // hotListData.value = null;
getHotLists(type, isNew).then((res) => { getHotLists(type, isNew)
console.log(res); .then((res) => {
if (res.code === 200) { console.log(res);
listLoading.value = false; if (res.code === 200) {
hotListData.value = res; listLoading.value = false;
// 滚动至顶部 hotListData.value = res;
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 { })
$message.error(res.title + res.message); .catch((error) => {
} console.error("资源请求失败:" + error);
}); switch (error?.response.status) {
case 403:
router.push("/403");
break;
case 500:
router.push("/500");
break;
default:
router.push("/404");
break;
}
});
}; };
// 获取最新数据 // 获取最新数据

View File

@@ -11,9 +11,9 @@
<n-loading-bar-provider> <n-loading-bar-provider>
<n-dialog-provider> <n-dialog-provider>
<n-notification-provider> <n-notification-provider>
<n-message-provider> <n-message-provider :max="1">
<NaiveProviderContent />
<slot></slot> <slot></slot>
<NaiveProviderContent />
</n-message-provider> </n-message-provider>
</n-notification-provider> </n-notification-provider>
</n-dialog-provider> </n-dialog-provider>
@@ -35,6 +35,7 @@ import {
import { mainStore } from "@/store"; import { mainStore } from "@/store";
const store = mainStore(); const store = mainStore();
const osThemeRef = useOsTheme();
// 明暗切换 // 明暗切换
let theme = ref(null); let theme = ref(null);
@@ -46,6 +47,13 @@ const changeTheme = () => {
} }
}; };
// 根据系统决定明暗切换
const osThemeChange = (val) => {
if (store.siteThemeAuto) {
val == "dark" ? (store.siteTheme = "dark") : (store.siteTheme = "light");
}
};
// 监听明暗变化 // 监听明暗变化
watch( watch(
() => store.siteTheme, () => store.siteTheme,
@@ -55,11 +63,10 @@ watch(
); );
// 监听系统明暗变化 // 监听系统明暗变化
const osThemeRef = useOsTheme();
watch( watch(
() => osThemeRef.value, () => osThemeRef.value,
(value) => { (val) => {
value == "dark" ? store.setSiteTheme("dark") : store.setSiteTheme("light"); osThemeChange(val);
} }
); );
@@ -96,5 +103,6 @@ const NaiveProviderContent = defineComponent({
onMounted(() => { onMounted(() => {
changeTheme(); changeTheme();
osThemeChange(osThemeRef.value);
}); });
</script> </script>

View File

@@ -35,6 +35,15 @@ const routes = [
}, },
component: () => import("@/views/Test.vue"), component: () => import("@/views/Test.vue"),
}, },
// 403
{
path: "/403",
name: "403",
meta: {
title: "403",
},
component: () => import("@/views/403.vue"),
},
// 404 // 404
{ {
path: "/404", path: "/404",
@@ -44,6 +53,15 @@ const routes = [
}, },
component: () => import("@/views/404.vue"), component: () => import("@/views/404.vue"),
}, },
// 500
{
path: "/500",
name: "500",
meta: {
title: "500",
},
component: () => import("@/views/500.vue"),
},
{ {
path: "/:pathMatch(.*)", path: "/:pathMatch(.*)",
redirect: "/404", redirect: "/404",

View File

@@ -5,6 +5,7 @@ export const mainStore = defineStore("main", {
return { return {
// 系统主题 // 系统主题
siteTheme: "light", siteTheme: "light",
siteThemeAuto: true,
// 新闻类别 // 新闻类别
newsArr: [ newsArr: [
{ {
@@ -19,64 +20,70 @@ export const mainStore = defineStore("main", {
order: 1, order: 1,
show: true, show: true,
}, },
{
label: "抖音",
value: "douyin",
order: 2,
show: true,
},
{ {
label: "知乎", label: "知乎",
value: "zhihu", value: "zhihu",
order: 2, order: 3,
show: true, show: true,
}, },
{ {
label: "36氪", label: "36氪",
value: "36kr", value: "36kr",
order: 3, order: 4,
show: true, show: true,
}, },
{ {
label: "百度", label: "百度",
value: "baidu", value: "baidu",
order: 4, order: 5,
show: true, show: true,
}, },
{ {
label: "少数派", label: "少数派",
value: "sspai", value: "sspai",
order: 5, order: 6,
show: true, show: true,
}, },
{ {
label: "IT之家", label: "IT之家",
value: "ithome", value: "ithome",
order: 6, order: 7,
show: true, show: true,
}, },
{ {
label: "澎湃新闻", label: "澎湃新闻",
value: "thepaper", value: "thepaper",
order: 7, order: 8,
show: true, show: true,
}, },
{ {
label: "今日头条", label: "今日头条",
value: "toutiao", value: "toutiao",
order: 8, order: 9,
show: true, show: true,
}, },
{ {
label: "百度贴吧", label: "百度贴吧",
value: "tieba", value: "tieba",
order: 9, order: 10,
show: true, show: true,
}, },
{ {
label: "稀土掘金", label: "稀土掘金",
value: "juejin", value: "juejin",
order: 10, order: 11,
show: true, show: true,
}, },
{ {
label: "腾讯新闻", label: "腾讯新闻",
value: "newsqq", value: "newsqq",
order: 11, order: 12,
show: true, show: true,
}, },
], ],
@@ -96,12 +103,19 @@ export const mainStore = defineStore("main", {
showIcon: false, showIcon: false,
}); });
this.siteTheme = val; this.siteTheme = val;
this.siteThemeAuto = false;
}, },
}, },
persist: [ persist: [
{ {
storage: localStorage, storage: localStorage,
paths: ["siteTheme", "newsArr", "linkOpenType", "headerFixed"], paths: [
"siteTheme",
"siteThemeAuto",
"newsArr",
"linkOpenType",
"headerFixed",
],
}, },
], ],
}); });

38
src/views/403.vue Normal file
View File

@@ -0,0 +1,38 @@
<template>
<n-layout
embedded
class="state"
:content-style="{
padding: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}"
>
<n-result
class="error"
status="403"
title="403 禁止访问"
description="总有些门是对你关闭的"
>
<template #footer>
<n-button @click="goHome">重新载入</n-button>
</template>
</n-result>
</n-layout>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
const goHome = () => {
router.push("/");
};
</script>
<style lang="scss" scoped>
.state {
height: 100%;
}
</style>

38
src/views/500.vue Normal file
View File

@@ -0,0 +1,38 @@
<template>
<n-layout
embedded
class="state"
:content-style="{
padding: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}"
>
<n-result
class="error"
status="500"
title="500 服务器错误"
description="服务器寄了,等会再试吧"
>
<template #footer>
<n-button @click="goHome">重新载入</n-button>
</template>
</n-result>
</n-layout>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
const goHome = () => {
router.push("/");
};
</script>
<style lang="scss" scoped>
.state {
height: 100%;
}
</style>

View File

@@ -246,6 +246,11 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.n-card__content) {
@media (max-width: 740px) {
padding: 0 12px 12px 12px;
}
}
.header { .header {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -287,6 +292,28 @@ onMounted(() => {
} }
} }
} }
@media (max-width: 740px) {
display: flex;
justify-content: flex-start;
.logo {
img {
width: 32px;
height: 32px;
}
}
.name {
margin-left: 12px;
align-items: flex-end;
flex-direction: row;
.subtitle {
margin-bottom: 3px;
margin-left: 8px;
}
}
.data {
margin-left: auto;
}
}
} }
.all { .all {
display: flex; display: flex;
@@ -352,6 +379,14 @@ onMounted(() => {
.pagination { .pagination {
margin: 20px 0; margin: 20px 0;
} }
@media (max-width: 740px) {
:deep(.n-list-item) {
padding: 12px 10px;
.n-list-item__prefix {
margin-right: 12px;
}
}
}
} }
} }
} }

View File

@@ -2,6 +2,32 @@
<div class="setting"> <div class="setting">
<div class="title">全局设置</div> <div class="title">全局设置</div>
<n-h6 prefix="bar"> 基础设置 </n-h6> <n-h6 prefix="bar"> 基础设置 </n-h6>
<n-card class="set-item">
<div class="top">
<div class="name">
<n-text class="text">明暗模式</n-text>
</div>
<n-select
class="set"
v-model:value="siteTheme"
:options="themeOptions"
@update:value="siteThemeAuto = false"
/>
</div>
</n-card>
<n-card class="set-item">
<div class="top">
<div class="name">
<n-text class="text">明暗模式跟随系统</n-text>
<n-text class="tip" :depth="3"> 明暗模式是否跟随系统当前模式 </n-text>
</div>
<n-switch
v-model:value="siteThemeAuto"
:round="false"
@update:value="themeAutoOpen"
/>
</div>
</n-card>
<n-card class="set-item"> <n-card class="set-item">
<div class="top"> <div class="top">
<div class="name"> <div class="name">
@@ -93,10 +119,25 @@
<script setup> <script setup>
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { mainStore } from "@/store"; import { mainStore } from "@/store";
import { useOsTheme } from "naive-ui";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
const store = mainStore(); const store = mainStore();
const { newsArr, linkOpenType, headerFixed } = storeToRefs(store); const osThemeRef = useOsTheme();
const { siteTheme, siteThemeAuto, newsArr, linkOpenType, headerFixed } =
storeToRefs(store);
// 深浅模式
const themeOptions = ref([
{
label: "浅色模式",
value: "light",
},
{
label: "深色模式",
value: "dark",
},
]);
// 榜单跳转 // 榜单跳转
const linkOptions = [ const linkOptions = [
@@ -110,6 +151,14 @@ const linkOptions = [
}, },
]; ];
// 开启明暗自动跟随
const themeAutoOpen = (val) => {
console.log(osThemeRef.value);
if (val) {
siteTheme.value = osThemeRef.value;
}
};
// 恢复默认排序 // 恢复默认排序
const restoreDefault = () => { const restoreDefault = () => {
newsArr.value = newsArr.value.sort((a, b) => a.order - b.order); newsArr.value = newsArr.value.sort((a, b) => a.order - b.order);
@@ -139,27 +188,33 @@ const reset = () => {
font-size: 40px; font-size: 40px;
font-weight: bold; font-weight: bold;
} }
.n-h { .n-h {
padding-left: 16px; padding-left: 16px;
font-size: 20px; font-size: 20px;
margin-left: 4px; margin-left: 4px;
} }
.set-item { .set-item {
width: 100%; width: 100%;
border-radius: 8px; border-radius: 8px;
margin-bottom: 12px; margin-bottom: 12px;
.top { .top {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.name { .name {
font-size: 18px; font-size: 18px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.tip { .tip {
font-size: 12px; font-size: 12px;
} }
} }
.set { .set {
max-width: 200px; max-width: 200px;
} }
@@ -170,25 +225,32 @@ const reset = () => {
display: grid; display: grid;
grid-template-columns: repeat(5, minmax(0px, 1fr)); grid-template-columns: repeat(5, minmax(0px, 1fr));
gap: 24px; gap: 24px;
@media (max-width: 1666px) { @media (max-width: 1666px) {
grid-template-columns: repeat(4, minmax(0px, 1fr)); grid-template-columns: repeat(4, minmax(0px, 1fr));
} }
@media (max-width: 1200px) { @media (max-width: 1200px) {
grid-template-columns: repeat(3, minmax(0px, 1fr)); grid-template-columns: repeat(3, minmax(0px, 1fr));
} }
@media (max-width: 890px) { @media (max-width: 890px) {
grid-template-columns: repeat(2, minmax(0px, 1fr)); grid-template-columns: repeat(2, minmax(0px, 1fr));
} }
@media (max-width: 620px) { @media (max-width: 620px) {
grid-template-columns: repeat(1, minmax(0px, 1fr)); grid-template-columns: repeat(1, minmax(0px, 1fr));
} }
.item { .item {
cursor: pointer; cursor: pointer;
.desc { .desc {
display: flex; display: flex;
align-items: center; align-items: center;
width: 100%; width: 100%;
transition: all 0.3s; transition: all 0.3s;
.logo { .logo {
width: 40px; width: 40px;
height: 40px; height: 40px;
@@ -199,6 +261,7 @@ const reset = () => {
font-size: 16px; font-size: 16px;
} }
} }
.switch { .switch {
margin-left: auto; margin-left: auto;
} }

View File

@@ -29,9 +29,6 @@ export default defineConfig({
// PWA // PWA
VitePWA({ VitePWA({
registerType: "autoUpdate", registerType: "autoUpdate",
devOptions: {
enabled: true,
},
workbox: { workbox: {
cleanupOutdatedCaches: true, cleanupOutdatedCaches: true,
runtimeCaching: [ runtimeCaching: [