fix(modules/web): 重构阅读API;封面图片优先使用源站资源

- 添加图片代理接口API;API地址测试(3s超时)
This commit is contained in:
Xwite
2024-09-29 18:07:39 +08:00
parent ed5d13f455
commit bf91a6f206
12 changed files with 222 additions and 176 deletions
+2 -3
View File
@@ -5,6 +5,7 @@
"ComputedRef": true, "ComputedRef": true,
"EffectScope": true, "EffectScope": true,
"ElMessage": true, "ElMessage": true,
"ElMessageBox": true,
"InjectionKey": true, "InjectionKey": true,
"PropType": true, "PropType": true,
"Ref": true, "Ref": true,
@@ -78,8 +79,6 @@
"watch": true, "watch": true,
"watchEffect": true, "watchEffect": true,
"watchPostEffect": true, "watchPostEffect": true,
"watchSyncEffect": true, "watchSyncEffect": true
"toValue": true,
"WritableComputedRef": true
} }
} }
+1 -16
View File
@@ -1,25 +1,10 @@
import axios from "axios"; import axios from "axios";
const SECOND = 1000; const SECOND = 1000;
const remoteIp = ref(localStorage.getItem("remoteIp"));
const ajax = axios.create({ const ajax = axios.create({
// baseURL: import.meta.env.VITE_API || location.origin, baseURL: import.meta.env.VITE_API || localStorage.getItem("remoteIp") || location.origin,
timeout: 120 * SECOND, timeout: 120 * SECOND,
}); });
ajax.interceptors.request.use((config) => {
config.baseURL = baseUrl();
return config;
});
export default ajax; export default ajax;
export const setRemoteIp = (ip) => {
remoteIp.value = ip;
localStorage.setItem("remoteIp", ip);
};
export const baseUrl = () => {
return remoteIp.value || location.origin;
};
+86 -14
View File
@@ -1,14 +1,48 @@
import ajax, { baseUrl } from "./axios"; import ajax from "./axios";
import { ElMessage } from "element-plus/es"; import { ElMessage } from "element-plus/es";
/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/api */ /** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/api */
/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/web */ /** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/web */
const getUrl = () => { let legado_http_origin
const { hostname, port } = new URL(baseUrl()); let legado_webSocket_origin
return `${hostname}:${Number(port) + 1}`;
const setLeagdoHttpUrl = (http_url) => {
let legado_webSocket_port;
const { protocol, hostname, port } = new URL(http_url);
if (!protocol.startsWith("http"))
throw new Error("unexpect protocol:" + http_url);
ajax.defaults.baseURL = http_url;
legado_http_origin = http_url;
if (port !== "") {
legado_webSocket_port = Number(port) + 1;
} else {
legado_webSocket_port = protocol.startsWith("https:") ? "444" : "81";
}
legado_webSocket_origin =
`${protocol.startsWith("https:") ? "wss://" : "ws://"}${hostname}:${legado_webSocket_port}`;
console.info("legado_server_config:");
console.table({legado_http_origin, legado_webSocket_origin});
}; };
// 手动初始化 阅读web服务地址
setLeagdoHttpUrl(ajax.defaults.baseURL);
const testLeagdoHttpUrlConnection = async (http_url) => {
const {data = {}} = await ajax.get("/getReadConfig", {
baseURL: http_url,
timeout: 3000
})
// 返回结果应该是JSON 并有键值isSuccess
try {
if ("isSuccess" in data) return
throw new Error("ReadConfig后端返回格式错误" )
} catch {
throw new Error("ReadConfig后端返回格式错误" )
}
}
const isSourecEditor = /source/i.test(location.href); const isSourecEditor = /source/i.test(location.href);
const APIExceptionHandler = (error) => { const APIExceptionHandler = (error) => {
if (isSourecEditor) { if (isSourecEditor) {
@@ -19,8 +53,10 @@ const APIExceptionHandler = (error) => {
} }
throw error; throw error;
}; };
ajax.interceptors.response.use((response) => response, APIExceptionHandler); ajax.interceptors.response.use((response) => response, APIExceptionHandler);
// 书架API
// Http // Http
const getReadConfig = () => ajax.get("/getReadConfig"); const getReadConfig = () => ajax.get("/getReadConfig");
const saveReadConfig = (config) => ajax.post("/saveReadConfig", config); const saveReadConfig = (config) => ajax.post("/saveReadConfig", config);
@@ -32,8 +68,8 @@ const saveBookProgressWithBeacon = (bookProgress) => {
if (!bookProgress) return; if (!bookProgress) return;
// 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon
navigator.sendBeacon( navigator.sendBeacon(
`${baseUrl()}/saveBookProgress`, `${legado_http_origin}/saveBookProgress`,
JSON.stringify(bookProgress), JSON.stringify(bookProgress)
); );
}; };
@@ -44,22 +80,23 @@ const getChapterList = (/** @type {string} */ bookUrl) =>
const getBookContent = ( const getBookContent = (
/** @type {string} */ bookUrl, /** @type {string} */ bookUrl,
/** @type {number} */ chapterIndex, /** @type {number} */ chapterIndex
) => ) =>
ajax.get( ajax.get(
"/getBookContent?url=" + "/getBookContent?url=" +
encodeURIComponent(bookUrl) + encodeURIComponent(bookUrl) +
"&index=" + "&index=" +
chapterIndex, chapterIndex
); );
// webSocket
const search = ( const search = (
/** @type {string} */ searchKey, /** @type {string} */ searchKey,
/** @type {(data: string) => void} */ onReceive, /** @type {(data: string) => void} */ onReceive,
/** @type {() => void} */ onFinish, /** @type {() => void} */ onFinish
) => { ) => {
// webSocket
const url = `ws://${getUrl()}/searchBook`; const url = `${legado_webSocket_origin}/searchBook`;
const socket = new WebSocket(url); const socket = new WebSocket(url);
socket.onopen = () => { socket.onopen = () => {
@@ -77,6 +114,7 @@ const deleteBook = (book) => ajax.post("/deleteBook", book);
const isBookSource = /bookSource/i.test(location.href); const isBookSource = /bookSource/i.test(location.href);
// 源编辑API
// Http // Http
const getSources = () => const getSources = () =>
isBookSource ? ajax.get("/getBookSources") : ajax.get("/getRssSources"); isBookSource ? ajax.get("/getBookSources") : ajax.get("/getRssSources");
@@ -96,14 +134,15 @@ const deleteSource = (data) =>
? ajax.post("/deleteBookSources", data) ? ajax.post("/deleteBookSources", data)
: ajax.post("/deleteRssSources", data); : ajax.post("/deleteRssSources", data);
// webSocket
const debug = ( const debug = (
/** @type {string} */ sourceUrl, /** @type {string} */ sourceUrl,
/** @type {string} */ searchKey, /** @type {string} */ searchKey,
/** @type {(data: string) => void} */ onReceive, /** @type {(data: string) => void} */ onReceive,
/** @type {() => void} */ onFinish, /** @type {() => void} */ onFinish
) => { ) => {
// webSocket
const url = `ws://${getUrl()}/${ const url = `${legado_webSocket_origin}/${
isBookSource ? "bookSource" : "rssSource" isBookSource ? "bookSource" : "rssSource"
}Debug`; }Debug`;
@@ -123,6 +162,32 @@ const debug = (
}; };
}; };
/**
* 从阅读获取需要特定处理的书籍封面
* @param {string} coverUrl
*/
const getProxyCoverUrl = (coverUrl) => {
if(coverUrl.startsWith(legado_http_origin)) return coverUrl
return legado_http_origin + "/cover?path=" + encodeURIComponent(coverUrl);
}
/**
* 从阅读获取需要特定处理的图片
* @param {string} src
* @param {`{number}`} width
*/
const getProxyImageUrl = (src, width) => {
if (src.startsWith(legado_http_origin)) return src
return (
legado_http_origin +
"/image?path=" +
encodeURIComponent(src) +
"&url=" +
encodeURIComponent(sessionStorage.getItem("bookUrl")) +
"&width=" +
width
);
}
export default { export default {
getReadConfig, getReadConfig,
saveReadConfig, saveReadConfig,
@@ -140,4 +205,11 @@ export default {
saveSource, saveSource,
deleteSource, deleteSource,
debug, debug,
getProxyCoverUrl,
getProxyImageUrl,
testLeagdoHttpUrlConnection,
setLeagdoHttpUrl,
legado_http_origin,
}; };
+2 -4
View File
@@ -1,12 +1,12 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope']
const ElMessage: typeof import('element-plus/es')['ElMessage'] const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed'] const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp'] const createApp: typeof import('vue')['createApp']
@@ -62,7 +62,6 @@ declare global {
const toRaw: typeof import('vue')['toRaw'] const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef'] const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs'] const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef'] const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref'] const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs']
@@ -82,6 +81,5 @@ declare global {
// for type re-export // for type re-export
declare global { declare global {
// @ts-ignore // @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
import('vue')
} }
+10 -5
View File
@@ -12,6 +12,7 @@
class="cover" class="cover"
:src="getCover(book.coverUrl)" :src="getCover(book.coverUrl)"
:key="book.coverUrl" :key="book.coverUrl"
@error.once="proxyImage"
alt="" alt=""
loading="lazy" loading="lazy"
/> />
@@ -49,16 +50,20 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { dateFormat } from "../utils/utils"; import { dateFormat, isLegadoUrl } from "../utils/utils";
import { baseUrl } from "@/api/axios.js"; import API from "@api";
const props = defineProps(["books", "isSearch"]); const props = defineProps(["books", "isSearch"]);
const emit = defineEmits(["bookClick"]); const emit = defineEmits(["bookClick"]);
const handleClick = (book) => emit("bookClick", book); const handleClick = (book) => emit("bookClick", book);
const getCover = (coverUrl) => { const getCover = (coverUrl) => {
return /^data:/.test(coverUrl) return isLegadoUrl(coverUrl)
? coverUrl ? API.getProxyCoverUrl(coverUrl) : coverUrl
: baseUrl() + "/cover?path=" + encodeURIComponent(coverUrl);
}; };
const proxyImage = (event) => {
event.target.src = API.getProxyCoverUrl(event.target.src);
};
const subJustify = computed(() => const subJustify = computed(() =>
props.isSearch ? "space-between" : "flex-start", props.isSearch ? "space-between" : "flex-start",
@@ -18,7 +18,8 @@
</template> </template>
<script setup> <script setup>
import { getImageFromLegado, isLegadoUrl } from "@/utils/utils"; import { isLegadoUrl } from "@/utils/utils";
import API from "@api";
import jump from "@/plugins/jump"; import jump from "@/plugins/jump";
const props = defineProps({ const props = defineProps({
@@ -33,11 +34,11 @@ const props = defineProps({
const getImageSrc = (content) => { const getImageSrc = (content) => {
const imgPattern = /<img[^>]*src="([^"]*(?:"[^>]+\})?)"[^>]*>/; const imgPattern = /<img[^>]*src="([^"]*(?:"[^>]+\})?)"[^>]*>/;
const src = content.match(imgPattern)[1]; const src = content.match(imgPattern)[1];
if (isLegadoUrl(src)) return getImageFromLegado(src); if (isLegadoUrl(src)) return API.getProxyImageUrl(src, useBookStore().config.readWidth)
return src; return src;
}; };
const proxyImage = (event) => { const proxyImage = (event) => {
event.target.src = getImageFromLegado(event.target.src); event.target.src = API.getProxyImageUrl(event.target.src, useBookStore().config.readWidth);
}; };
const calculateWordCount = (paragraph) => { const calculateWordCount = (paragraph) => {
-2
View File
@@ -4,5 +4,3 @@ import router from "@/router";
import store from "@/store"; import store from "@/store";
createApp(App).use(store).use(router).mount("#app"); createApp(App).use(store).use(router).mount("#app");
import("./pages/bookshelf/config");
-17
View File
@@ -1,17 +0,0 @@
import API from "@api";
import { useBookStore } from "@/store";
import "@/assets/bookshelf.css";
/**
* 加载配置
*/
API.getReadConfig().then((res) => {
var data = res.data.data;
if (data) {
const bookStore = useBookStore();
let config = JSON.parse(data);
let defaultConfig = bookStore.config;
config = Object.assign(defaultConfig, config);
bookStore.setConfig(config);
}
});
-2
View File
@@ -4,5 +4,3 @@ import bookRouter from "@/router";
import store from "@/store"; import store from "@/store";
createApp(App).use(store).use(bookRouter).mount("#app"); createApp(App).use(store).use(bookRouter).mount("#app");
import("./config");
+4 -6
View File
@@ -49,6 +49,9 @@ export const useBookStore = defineStore("book", {
durChapterTitle: title, durChapterTitle: title,
}; };
}, },
theme: (state) => {
return state.config.theme
},
}, },
actions: { actions: {
setConnectStatus(connectStatus) { setConnectStatus(connectStatus) {
@@ -57,11 +60,6 @@ export const useBookStore = defineStore("book", {
setConnectType(connectType) { setConnectType(connectType) {
this.connectType = connectType; this.connectType = connectType;
}, },
resetConnect() {
this.connectStatus = "正在连接后端服务器……";
this.connectType = "";
this.clearBooks();
},
setNewConnect(newConnect) { setNewConnect(newConnect) {
this.newConnect = newConnect; this.newConnect = newConnect;
}, },
@@ -84,7 +82,7 @@ export const useBookStore = defineStore("book", {
this.readingBook = readingBook; this.readingBook = readingBook;
}, },
setConfig(config) { setConfig(config) {
Object.assign(this.config, config); this.config = Object.assign({}, this.config, config);
}, },
setReadSettingsVisible(visible) { setReadSettingsVisible(visible) {
this.readSettingsVisible = visible; this.readSettingsVisible = visible;
+1 -15
View File
@@ -1,5 +1,4 @@
import { formatDate } from "@vueuse/shared"; import { formatDate } from "@vueuse/shared";
import { baseUrl } from "@/api/axios.js";
export const isLegadoUrl = (/** @type {string} */ url) => export const isLegadoUrl = (/** @type {string} */ url) =>
/,\s*\{/.test(url) || /,\s*\{/.test(url) ||
@@ -8,20 +7,7 @@ export const isLegadoUrl = (/** @type {string} */ url) =>
url.startsWith("data:") || url.startsWith("data:") ||
url.startsWith("blob:") url.startsWith("blob:")
); );
/**
* @param {string} src
*/
export function getImageFromLegado(src) {
return (
baseUrl() +
"/image?path=" +
encodeURIComponent(src) +
"&url=" +
encodeURIComponent(sessionStorage.getItem("bookUrl")) +
"&width=" +
useBookStore().config.readWidth
);
}
// @ts-ignore // @ts-ignore
export const dateFormat = (/** @type {number} */ t) => { export const dateFormat = (/** @type {number} */ t) => {
let time = new Date().getTime(); let time = new Date().getTime();
+111 -88
View File
@@ -29,7 +29,7 @@
readingRecent.name, readingRecent.name,
readingRecent.author, readingRecent.author,
readingRecent.chapterIndex, readingRecent.chapterIndex,
readingRecent.chapterPos, readingRecent.chapterPos
) )
" "
:class="{ 'no-point': readingRecent.url == '' }" :class="{ 'no-point': readingRecent.url == '' }"
@@ -40,65 +40,52 @@
</div> </div>
<div class="setting-wrapper"> <div class="setting-wrapper">
<div class="setting-title">基本设定</div> <div class="setting-title">基本设定</div>
<div class="setting-ip"> <div class="setting-item">
<el-input <el-tag
class="setting-input" :type="connectType"
size="small" size="large"
:disabled="ipInput.disable" class="setting-connect"
v-model="ipInput.ip" :class="{ 'no-point': newConnect }"
@keydown.enter="setIP" @click="setIP"
/> >
<el-tag {{ connectStatus }}
type="primary" </el-tag>
class="setting-toggle" </div>
@click="toggleIpConfig"
>
{{ ipInput.disable ? "修改" : "取消" }}
</el-tag>
</div>
<div class="setting-item">
<el-tag :type="connectType" size="large" class="setting-connect">
{{ connectStatus }}
</el-tag>
</div> </div>
</div> </div>
<div class="bottom-icons">
<a
href="https://github.com/gedoor/legado_web_bookshelf"
target="_blank"
>
<div class="bottom-icon">
<img :src="githubUrl" alt="" />
</div>
</a>
</div>
</div> </div>
<div class="bottom-icons"> <div class="shelf-wrapper" ref="shelfWrapper">
<a <book-items
href="https://github.com/gedoor/legado_web_bookshelf" :books="books"
target="_blank" @bookClick="handleBookClick"
> :isSearch="isSearching"
<div class="bottom-icon"> ></book-items>
<img :src="githubUrl" alt="" />
</div>
</a>
</div> </div>
</div>
<div class="shelf-wrapper" ref="shelfWrapper">
<book-items
:books="books"
@bookClick="handleBookClick"
:isSearch="isSearching"
></book-items>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import "@/assets/bookshelf.css";
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 { useLoading } from "@/hooks/loading";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import API from "@api"; import API from "@api";
import { baseUrl, setRemoteIp } from "@/api/axios.js";
const store = useBookStore(); const store = useBookStore();
const { connectStatus, connectType, newConnect, shelf } = storeToRefs(store); const { connectStatus, connectType, newConnect, shelf, theme } = storeToRefs(store);
const theme = computed(() => {
return store.config.theme;
});
const isNight = computed(() => theme.value == 6); const isNight = computed(() => theme.value == 6);
const readingRecent = ref({ const readingRecent = ref({
@@ -111,7 +98,7 @@ const readingRecent = ref({
const shelfWrapper = ref(null); const shelfWrapper = ref(null);
const { showLoading, closeLoading, loadingWrapper, isLoading } = useLoading( const { showLoading, closeLoading, loadingWrapper, isLoading } = useLoading(
shelfWrapper, shelfWrapper,
"正在获取书籍信息", "正在获取书籍信息"
); );
const books = shallowRef([]); const books = shallowRef([]);
@@ -159,22 +146,59 @@ const searchBook = () => {
if (books.value.length == 0) { if (books.value.length == 0) {
ElMessage.info("搜索结果为空"); ElMessage.info("搜索结果为空");
} }
}, }
); );
}; };
const ipInput = reactive({
ip: baseUrl(),
disable: true,
});
const toggleIpConfig = () => {
ipInput.ip = baseUrl();
ipInput.disable = !ipInput.disable;
};
const setIP = () => { const setIP = () => {
setRemoteIp(ipInput.ip); ElMessageBox.prompt(
ipInput.disable = true; "请输入 IP 和端口 ( 如:127.0.0.1:9527 或者通过内网穿透的地址)",
loadShelf(); "提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern:
/^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?:([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-6][0-5][0-5][0-3][0-5])$/,
inputErrorMessage: "url 形式不正确",
beforeClose: (action, instance, done) => {
if (action === "confirm") {
store.setNewConnect(true);
instance.confirmButtonLoading = true;
instance.confirmButtonText = "校验中……";
// instance.inputValue
API.testLeagdoHttpUrlConnection("http://" + instance.inputValue)
//API.getBookShelf()
.then(function (response) {
instance.confirmButtonLoading = false;
// that.$store.commit(
// "increaseBookNum",
// response.data.data.length
// );
//store.addBooks(response.data.data);
const ip = instance.inputValue;
store.setConnectType("success");
store.setConnectStatus("已连接 " + ip);
store.clearSearchBooks();
store.setNewConnect(false);
API.setLeagdoHttpUrl("http://" + ip);
//持久化
localStorage.setItem("remoteIp", ip);
fetchBookShelfData()
done();
})
.catch(function (error) {
instance.confirmButtonLoading = false;
instance.confirmButtonText = "确定";
ElMessage.error("访问失败,请检查您输入的 url");
store.setNewConnect(false);
throw error;
});
} else {
done();
}
},
}
);
}; };
const router = useRouter(); const router = useRouter();
@@ -221,16 +245,24 @@ onMounted(() => {
readingRecent.value.chapterIndex = 0; readingRecent.value.chapterIndex = 0;
} }
} }
loadShelf(); API.testLeagdoHttpUrlConnection()
.then(loadReadConfig)
.then(loadShelf)
.catch(function (error) {
store.setConnectType("danger");
store.setConnectStatus("连接异常");
ElMessage.error("后端连接失败异常,请检查阅读WEB服务或者设置其它可用IP")
store.setNewConnect(false);
throw error;
});
}); });
const loadShelf = () => { const loadShelf = () => {
store.resetConnect();
loadingWrapper( loadingWrapper(
store store
.saveBookProgress() .saveBookProgress()
//确保各种网络情况下同步请求先完成 //确保各种网络情况下同步请求先完成
.finally(fetchBookShelfData), .finally(fetchBookShelfData)
); );
}; };
@@ -245,22 +277,29 @@ const fetchBookShelfData = () => {
var x = a["durChapterTime"] || 0; var x = a["durChapterTime"] || 0;
var y = b["durChapterTime"] || 0; var y = b["durChapterTime"] || 0;
return y - x; return y - x;
}), })
); );
} else { } else {
ElMessage.error(response.data.errorMsg); ElMessage.error(response.data.errorMsg ?? "后端返回格式错误!");
} }
store.setConnectStatus("已连接 "); store.setConnectStatus("已连接 " + API.legado_http_origin);
store.setNewConnect(false); store.setNewConnect(false);
}) })
.catch(function (error) {
store.setConnectType("danger");
store.setConnectStatus("连接失败");
ElMessage.error("后端连接失败");
store.setNewConnect(false);
throw error;
});
}; };
/**
* 加载阅读配置
*/
const loadReadConfig = () => {
return API.getReadConfig().then((res) => {
var data = res.data.data;
if (data) {
let config = JSON.parse(data);
store.setConfig(config);
}
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -340,31 +379,15 @@ const fetchBookShelfData = () => {
font-family: FZZCYSK; font-family: FZZCYSK;
} }
.setting-ip {
margin-top: 16px;
white-space: nowrap;
}
.setting-input {
width: 216px;
margin-right: 4px;
}
.no-point { .no-point {
pointer-events: none; pointer-events: none;
} }
.setting-toggle {
font-size: 10px;
cursor: pointer;
//margin-top: 4px;
}
.setting-connect { .setting-connect {
font-size: 10px; font-size: 8px;
margin-top: 4px; margin-top: 16px;
// color: #6B7C87; // color: #6B7C87;
//cursor: pointer; cursor: pointer;
} }
} }