web: 添加hooks
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
.el-loading-spinner {
|
||||||
|
font-size: 36px;
|
||||||
|
color: #b5b5b5;
|
||||||
|
}
|
||||||
|
.el-loading-text {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #b5b5b5 !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { watch, unref, onUnmounted } from "vue";
|
||||||
|
import { ElLoading } from "element-plus";
|
||||||
|
import loadingSvg from "@element-plus/icons-svg/loading.svg?raw";
|
||||||
|
import "element-plus/theme-chalk/el-loading.css";
|
||||||
|
import "./loading.css";
|
||||||
|
|
||||||
|
export const useLoading = (target, text, spinner = loadingSvg) => {
|
||||||
|
// loading spinner
|
||||||
|
const isLoading = ref(false);
|
||||||
|
let loadingInstance = null;
|
||||||
|
const closeLoading = () => (isLoading.value = false);
|
||||||
|
const showLoading = () => (isLoading.value = true);
|
||||||
|
watch(isLoading, (loading) => {
|
||||||
|
if (!loading) return loadingInstance?.close();
|
||||||
|
loadingInstance = ElLoading.service({
|
||||||
|
target: unref(target),
|
||||||
|
spinner: spinner,
|
||||||
|
text: text,
|
||||||
|
lock: true,
|
||||||
|
background: "rgba(0, 0, 0, 0)",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadingWrapper = (promise) => {
|
||||||
|
if (!(promise instanceof Promise))
|
||||||
|
throw TypeError("loadingWrapper argument must be Promise");
|
||||||
|
showLoading();
|
||||||
|
return promise.finally(closeLoading);
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
closeLoading();
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isLoading, showLoading, closeLoading, loadingWrapper };
|
||||||
|
};
|
||||||
@@ -110,23 +110,13 @@
|
|||||||
import jump from "@/plugins/jump";
|
import jump from "@/plugins/jump";
|
||||||
import settings from "@/plugins/config";
|
import settings from "@/plugins/config";
|
||||||
import API from "@api";
|
import API from "@api";
|
||||||
import loadingSvg from "@element-plus/icons-svg/loading.svg?raw";
|
import { useLoading } from "@/hooks/loading";
|
||||||
|
|
||||||
// loading spinner
|
|
||||||
const showLoading = ref(false);
|
|
||||||
const loadingSerive = ref(null);
|
|
||||||
const content = ref();
|
const content = ref();
|
||||||
watch(showLoading, (loading) => {
|
// loading spinner
|
||||||
if (!loading) return loadingSerive.value?.close();
|
const { isLoading, loadingWrapper } = useLoading(content, "正在获取信息");
|
||||||
loadingSerive.value = ElLoading.service({
|
|
||||||
target: content.value,
|
|
||||||
spinner: loadingSvg,
|
|
||||||
text: "正在获取信息",
|
|
||||||
lock: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const store = useBookStore();
|
const store = useBookStore();
|
||||||
|
|
||||||
// 读取阅读配置
|
// 读取阅读配置
|
||||||
try {
|
try {
|
||||||
const browerConfig = JSON.parse(localStorage.getItem("config"));
|
const browerConfig = JSON.parse(localStorage.getItem("config"));
|
||||||
@@ -242,7 +232,6 @@ const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
|||||||
if (reloadChapter) {
|
if (reloadChapter) {
|
||||||
//展示进度条
|
//展示进度条
|
||||||
store.setShowContent(false);
|
store.setShowContent(false);
|
||||||
showLoading.value = true;
|
|
||||||
//强制滚回顶层
|
//强制滚回顶层
|
||||||
jump(top.value, { duration: 0 });
|
jump(top.value, { duration: 0 });
|
||||||
//从目录,按钮切换章节时保存进度 预加载时不保存
|
//从目录,按钮切换章节时保存进度 预加载时不保存
|
||||||
@@ -252,34 +241,34 @@ const getContent = (index, reloadChapter = true, chapterPos = 0) => {
|
|||||||
let bookUrl = sessionStorage.getItem("bookUrl");
|
let bookUrl = sessionStorage.getItem("bookUrl");
|
||||||
let { title, index: chapterIndex } = catalog.value[index];
|
let { title, index: chapterIndex } = catalog.value[index];
|
||||||
|
|
||||||
API.getBookContent(bookUrl, chapterIndex).then(
|
loadingWrapper(
|
||||||
(res) => {
|
API.getBookContent(bookUrl, chapterIndex).then(
|
||||||
if (res.data.isSuccess) {
|
(res) => {
|
||||||
let data = res.data.data;
|
if (res.data.isSuccess) {
|
||||||
let content = data.split(/\n+/);
|
let data = res.data.data;
|
||||||
chapterData.value.push({ index, content, title });
|
let content = data.split(/\n+/);
|
||||||
if (reloadChapter) toChapterPos(chapterPos);
|
chapterData.value.push({ index, content, title });
|
||||||
} else {
|
if (reloadChapter) toChapterPos(chapterPos);
|
||||||
ElMessage({ message: res.data.errorMsg, type: "error" });
|
} else {
|
||||||
let content = [res.data.errorMsg];
|
ElMessage({ message: res.data.errorMsg, type: "error" });
|
||||||
|
let content = [res.data.errorMsg];
|
||||||
|
chapterData.value.push({ index, content, title });
|
||||||
|
}
|
||||||
|
store.setContentLoading(true);
|
||||||
|
noPoint.value = false;
|
||||||
|
store.setShowContent(true);
|
||||||
|
if (!res.data.isSuccess) {
|
||||||
|
throw res.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
ElMessage({ message: "获取章节内容失败", type: "error" });
|
||||||
|
let content = ["获取章节内容失败!"];
|
||||||
chapterData.value.push({ index, content, title });
|
chapterData.value.push({ index, content, title });
|
||||||
|
store.setShowContent(true);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
store.setContentLoading(true);
|
)
|
||||||
showLoading.value = false;
|
|
||||||
noPoint.value = false;
|
|
||||||
store.setShowContent(true);
|
|
||||||
if (!res.data.isSuccess) {
|
|
||||||
throw res.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
ElMessage({ message: "获取章节内容失败", type: "error" });
|
|
||||||
let content = ["获取章节内容失败!"];
|
|
||||||
chapterData.value.push({ index, content, title });
|
|
||||||
showLoading.value = false;
|
|
||||||
store.setShowContent(true);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -327,14 +316,14 @@ const saveReadingBookProgressToBrowser = (index, pos) => {
|
|||||||
// 关闭页面 切换tab 返回桌面 等操作 https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event
|
// 关闭页面 切换tab 返回桌面 等操作 https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event
|
||||||
const onVisibilityChange = () => {
|
const onVisibilityChange = () => {
|
||||||
if (!bookProgress.value) return;
|
if (!bookProgress.value) return;
|
||||||
if (document.visibilityState == 'hidden') {
|
if (document.visibilityState == "hidden") {
|
||||||
// Safari > 14 和 非Safari移除额外pagehide event
|
// Safari > 14 和 非Safari移除额外pagehide event
|
||||||
document.removeEventListener("pagehide", onVisibilityChange);
|
document.removeEventListener("pagehide", onVisibilityChange);
|
||||||
// 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
|
// 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
|
||||||
navigator.sendBeacon(
|
navigator.sendBeacon(
|
||||||
`${import.meta.env.VITE_API || location.origin}/saveBookProgress`,
|
`${import.meta.env.VITE_API || location.origin}/saveBookProgress`,
|
||||||
JSON.stringify(bookProgress.value)
|
JSON.stringify(bookProgress.value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -392,7 +381,7 @@ const loadMore = () => {
|
|||||||
};
|
};
|
||||||
// IntersectionObserver回调 底部加载
|
// IntersectionObserver回调 底部加载
|
||||||
const handleIScrollObserve = (entries) => {
|
const handleIScrollObserve = (entries) => {
|
||||||
if (showLoading.value) return;
|
if (isLoading.value) return;
|
||||||
for (let { isIntersecting } of entries) {
|
for (let { isIntersecting } of entries) {
|
||||||
if (!isIntersecting) return;
|
if (!isIntersecting) return;
|
||||||
loadMore();
|
loadMore();
|
||||||
@@ -444,7 +433,6 @@ const handleKeyPress = (event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
showLoading.value = true;
|
|
||||||
//获取书籍数据
|
//获取书籍数据
|
||||||
let bookUrl = sessionStorage.getItem("bookUrl");
|
let bookUrl = sessionStorage.getItem("bookUrl");
|
||||||
let bookName = sessionStorage.getItem("bookName");
|
let bookName = sessionStorage.getItem("bookName");
|
||||||
@@ -466,39 +454,38 @@ onMounted(() => {
|
|||||||
};
|
};
|
||||||
localStorage.setItem(bookUrl, JSON.stringify(book));
|
localStorage.setItem(bookUrl, JSON.stringify(book));
|
||||||
}
|
}
|
||||||
|
loadingWrapper(
|
||||||
|
API.getChapterList(bookUrl).then(
|
||||||
|
(res) => {
|
||||||
|
if (!res.data.isSuccess) {
|
||||||
|
ElMessage({ message: res.data.errorMsg, type: "error" });
|
||||||
|
setTimeout(toShelf, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = res.data.data;
|
||||||
|
store.setCatalog(data);
|
||||||
|
store.setReadingBook(book);
|
||||||
|
|
||||||
API.getChapterList(bookUrl).then(
|
getContent(chapterIndex, true, chapterPos);
|
||||||
(res) => {
|
window.addEventListener("keyup", handleKeyPress);
|
||||||
showLoading.value = false;
|
// 兼容Safari < 14
|
||||||
if (!res.data.isSuccess) {
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||||
ElMessage({ message: res.data.errorMsg, type: "error" });
|
// 兼容Safari < 14.5
|
||||||
setTimeout(toShelf, 500);
|
document.addEventListener("pagehide", onVisibilityChange);
|
||||||
return;
|
//监听底部加载
|
||||||
|
scrollObserve.value = new IntersectionObserver(handleIScrollObserve, {
|
||||||
|
rootMargin: "-100% 0% 20% 0%",
|
||||||
|
});
|
||||||
|
infiniteLoading.value && scrollObserve.value.observe(loading.value);
|
||||||
|
//第二次点击同一本书 页面标题不会变化
|
||||||
|
document.title = null;
|
||||||
|
document.title = bookName + " | " + catalog.value[chapterIndex].title;
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
ElMessage({ message: "获取书籍目录失败", type: "error" });
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
let data = res.data.data;
|
)
|
||||||
store.setCatalog(data);
|
|
||||||
store.setReadingBook(book);
|
|
||||||
|
|
||||||
getContent(chapterIndex, true, chapterPos);
|
|
||||||
window.addEventListener("keyup", handleKeyPress);
|
|
||||||
// 兼容Safari < 14
|
|
||||||
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
||||||
// 兼容Safari < 14.5
|
|
||||||
document.addEventListener("pagehide", onVisibilityChange);
|
|
||||||
//监听底部加载
|
|
||||||
scrollObserve.value = new IntersectionObserver(handleIScrollObserve, {
|
|
||||||
rootMargin: "-100% 0% 20% 0%",
|
|
||||||
});
|
|
||||||
infiniteLoading.value && scrollObserve.value.observe(loading.value);
|
|
||||||
//第二次点击同一本书 页面标题不会变化
|
|
||||||
document.title = null;
|
|
||||||
document.title = bookName + " | " + catalog.value[chapterIndex].title;
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
showLoading.value = false;
|
|
||||||
ElMessage({ message: "获取书籍目录失败", type: "error" });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -608,19 +595,6 @@ onUnmounted(() => {
|
|||||||
width: 670px;
|
width: 670px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
:deep(.el-loading-mask) {
|
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
:deep(.el-loading-spinner) {
|
|
||||||
font-size: 36px;
|
|
||||||
color: #b5b5b5;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-loading-text) {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #b5b5b5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|||||||
@@ -78,8 +78,8 @@
|
|||||||
import "@/assets/fonts/shelffont.css";
|
import "@/assets/fonts/shelffont.css";
|
||||||
import { useBookStore } from "@/store";
|
import { useBookStore } from "@/store";
|
||||||
import githubUrl from "@/assets/imgs/github.png";
|
import githubUrl from "@/assets/imgs/github.png";
|
||||||
|
import { useLoading } from "@/hooks/loading";
|
||||||
import { Search } from "@element-plus/icons-vue";
|
import { Search } from "@element-plus/icons-vue";
|
||||||
import loadingSvg from "@element-plus/icons-svg/loading.svg?raw";
|
|
||||||
import API from "@api";
|
import API from "@api";
|
||||||
|
|
||||||
const store = useBookStore();
|
const store = useBookStore();
|
||||||
@@ -92,23 +92,13 @@ const readingRecent = ref({
|
|||||||
chapterIndex: 0,
|
chapterIndex: 0,
|
||||||
chapterPos: 0,
|
chapterPos: 0,
|
||||||
});
|
});
|
||||||
const showLoading = ref(false);
|
|
||||||
const shelfWrapper = ref(null);
|
const shelfWrapper = ref(null);
|
||||||
const loadingSerive = ref(null);
|
const { showLoading, closeLoading, loadingWrapper } = useLoading(
|
||||||
watch(showLoading, (loading) => {
|
shelfWrapper,
|
||||||
if (!loading) return loadingSerive.value?.close();
|
"正在获取书籍信息"
|
||||||
loadingSerive.value = ElLoading.service({
|
);
|
||||||
target: shelfWrapper.value,
|
|
||||||
spinner: loadingSvg,
|
|
||||||
text: "正在获取书籍信息",
|
|
||||||
lock: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const books = ref([]);
|
const books = ref([]);
|
||||||
watchEffect(() => {
|
|
||||||
if (books.value.length > 0) showLoading.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const search = ref("");
|
const search = ref("");
|
||||||
const isSearching = ref(false);
|
const isSearching = ref(false);
|
||||||
@@ -131,7 +121,7 @@ const searchBook = () => {
|
|||||||
if (search.value == "") return;
|
if (search.value == "") return;
|
||||||
books.value = [];
|
books.value = [];
|
||||||
store.clearSearchBooks();
|
store.clearSearchBooks();
|
||||||
showLoading.value = true;
|
showLoading();
|
||||||
isSearching.value = true;
|
isSearching.value = true;
|
||||||
API.search(
|
API.search(
|
||||||
search.value,
|
search.value,
|
||||||
@@ -145,7 +135,7 @@ const searchBook = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
showLoading.value = false;
|
closeLoading();
|
||||||
if (books.value.length == 0) {
|
if (books.value.length == 0) {
|
||||||
ElMessage.info("搜索结果为空");
|
ElMessage.info("搜索结果为空");
|
||||||
}
|
}
|
||||||
@@ -196,14 +186,15 @@ onMounted(() => {
|
|||||||
readingRecent.value.chapterIndex = 0;
|
readingRecent.value.chapterIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showLoading.value = true;
|
loadingWrapper(
|
||||||
store
|
store
|
||||||
.saveBookProgress()
|
.saveBookProgress()
|
||||||
//确保各种网络情况下同步请求先完成
|
//确保各种网络情况下同步请求先完成
|
||||||
.finally(fetchBookShelfData);
|
.finally(fetchBookShelfData)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
const fetchBookShelfData = () => {
|
const fetchBookShelfData = () => {
|
||||||
API.getBookShelf()
|
return API.getBookShelf()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
store.setConnectType("success");
|
store.setConnectType("success");
|
||||||
if (response.data.isSuccess) {
|
if (response.data.isSuccess) {
|
||||||
@@ -217,13 +208,11 @@ const fetchBookShelfData = () => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(response.data.errorMsg);
|
ElMessage.error(response.data.errorMsg);
|
||||||
showLoading.value = false;
|
|
||||||
}
|
}
|
||||||
store.setConnectStatus("已连接 ");
|
store.setConnectStatus("已连接 ");
|
||||||
store.setNewConnect(false);
|
store.setNewConnect(false);
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
showLoading.value = false;
|
|
||||||
store.setConnectType("danger");
|
store.setConnectType("danger");
|
||||||
store.setConnectStatus("连接失败");
|
store.setConnectStatus("连接失败");
|
||||||
ElMessage.error("后端连接失败");
|
ElMessage.error("后端连接失败");
|
||||||
|
|||||||
Reference in New Issue
Block a user