feat: 支持自定义搜索引擎与壁纸

This commit is contained in:
imsyy 2023-08-11 17:08:59 +08:00
parent 9aaa9bfea6
commit 2806b05b8a
11 changed files with 406 additions and 225 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "snavigation", "name": "snavigation",
"private": true, "private": true,
"version": "2.0.0 beta 4", "version": "2.0.0 beta 5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
<n-h6 prefix="bar"> 常用 </n-h6> <n-h6 prefix="bar"> 常用 </n-h6>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="note" tab="便签"> 即将完善 </n-tab-pane> <n-tab-pane name="note" tab="便签"> 即将完善 </n-tab-pane>
<n-tab-pane name="more" tab="更多"> 还能有啥呢 😢 </n-tab-pane> <n-tab-pane name="more" tab="待办"> 还能有啥呢 😢 </n-tab-pane>
</n-tabs> </n-tabs>
</template> </template>

View File

@ -28,51 +28,61 @@ const status = statusStore();
border-radius: 8px; border-radius: 8px;
transition: opacity 0.3s, transform 0.3s, margin-top 0.3s, height 0.3s; transition: opacity 0.3s, transform 0.3s, margin-top 0.3s, height 0.3s;
z-index: 2; z-index: 2;
.all {
:deep(.scrollbar) { overflow: hidden;
max-height: calc(460px - 84px); height: 100%;
} :deep(.scrollbar) {
:deep(.set-item) { max-height: calc(460px - 84px);
width: 100%; }
border-radius: 8px; :deep(.set-item) {
margin-bottom: 12px; width: 100%;
border: none; border-radius: 8px;
box-shadow: var(--main-box-shadow); margin-bottom: 12px;
--n-color: var(--main-background-light-color); border: none;
.n-card__content { box-shadow: var(--main-box-shadow);
display: flex; --n-color: var(--main-background-light-color);
flex-direction: row; .n-card__content {
align-items: center;
justify-content: space-between;
.desc {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; .desc {
} display: flex;
.name { flex-direction: row;
display: flex; align-items: center;
flex-direction: column; justify-content: space-between;
.title { width: 100%;
font-size: 16px; @media (max-width: 720px) {
flex-direction: column;
align-items: flex-start;
.name {
margin-bottom: 8px;
}
}
} }
.tip { .name {
font-size: 13px; display: flex;
opacity: 0.8; flex-direction: column;
.title {
font-size: 16px;
}
.tip {
font-size: 13px;
opacity: 0.8;
}
}
.set {
width: 200px;
@media (max-width: 768px) {
width: 140px;
min-width: 140px;
}
} }
} }
.set { &:last-child {
width: 200px; margin-bottom: 0;
@media (max-width: 768px) {
width: 140px;
min-width: 140px;
}
} }
} }
&:last-child {
margin-bottom: 0;
}
} }
&.big { &.big {
height: 80%; height: 80%;

View File

@ -1,176 +1,211 @@
<template> <template>
<n-tabs class="set" size="large" justify-content="space-evenly" animated> <div class="all">
<n-tab-pane name="main" tab="基础设置"> <n-tabs class="set" size="large" justify-content="space-evenly" animated>
<n-scrollbar class="scrollbar"> <n-tab-pane name="main" tab="基础设置">
<n-h6 prefix="bar"> 壁纸 </n-h6> <n-scrollbar class="scrollbar">
<n-card <n-h6 prefix="bar"> 壁纸 </n-h6>
class="set-item cover" <n-card
:content-style="{ flexDirection: 'column', alignItems: 'flex-start' }" class="set-item cover"
> :content-style="{
<div class="desc"> flexDirection: 'column',
<div class="name"> alignItems: 'flex-start',
<span class="title">壁纸偏好</span> }"
<span class="tip" >
>除默认以外的其他选项可能会导致页面载入缓慢</span <div class="desc">
> <div class="name">
</div> <span class="title">壁纸偏好</span>
<n-space> <span class="tip">
<Transition name="fade" mode="out-in"> 除默认以外的其他选项可能会导致页面载入缓慢
<n-button </span>
v-if="backgroundType !== 0" </div>
strong <n-space>
secondary <Transition name="fade" mode="out-in">
@click="changeBackground(0, true)" <n-button
> v-if="backgroundType !== 0"
恢复默认 strong
secondary
@click="changeBackground(0, true)"
>
恢复默认
</n-button>
</Transition>
<n-button strong secondary @click="customCoverModal = true">
<template v-if="backgroundType === 4" #icon>
<SvgIcon iconName="icon-confirm" />
</template>
{{ backgroundType === 4 ? "已开启自定义" : "自定义" }}
</n-button> </n-button>
</Transition> </n-space>
<n-button strong secondary @click="customCover"> </div>
自定义 <n-grid
</n-button> class="cover-selete"
</n-space> responsive="screen"
</div> cols="2 s:3 m:4 l:4"
<n-grid :x-gap="16"
class="cover-selete" :y-gap="16"
responsive="screen"
cols="2 s:3 m:4 l:4"
:x-gap="16"
:y-gap="16"
>
<n-grid-item
v-for="(item, index) in backgroundTypeArr"
:key="index"
:class="index === backgroundType ? 'item check' : 'item'"
@click="changeBackground(index)"
> >
<span class="name" v-html="item.name" /> <n-grid-item
</n-grid-item> v-for="(item, index) in backgroundTypeArr"
</n-grid> :key="index"
</n-card> :class="index === backgroundType ? 'item check' : 'item'"
<n-h6 prefix="bar"> 搜索 </n-h6> @click="changeBackground(index)"
<n-card class="set-item"> >
<div class="name"> <span class="name" v-html="item.name" />
<span class="title">搜索引擎</span> </n-grid-item>
<span class="tip">切换或自定义搜索引擎</span> </n-grid>
</div> </n-card>
<n-button <n-h6 prefix="bar"> 搜索 </n-h6>
strong <n-card class="set-item">
secondary <div class="name">
@click=" <span class="title">搜索引擎</span>
() => { <span class="tip">切换或自定义搜索引擎</span>
status.setSiteStatus('focus'); </div>
status.setEngineChangeStatus(true); <n-button
} strong
" secondary
> @click="
前往调整 () => {
</n-button> status.setSiteStatus('focus');
</n-card> status.setEngineChangeStatus(true);
<n-card class="set-item"> }
<div class="name"> "
<span class="title">搜索建议</span> >
<span class="tip">是否显示搜索建议</span> 前往调整
</div> </n-button>
<n-switch v-model:value="showSuggestions" :round="false" /> </n-card>
</n-card> <n-card class="set-item">
<n-card class="set-item"> <div class="name">
<div class="name"> <span class="title">搜索建议</span>
<span class="title">跳转方式</span> <span class="tip">是否显示搜索建议</span>
<span class="tip">全站链接跳转方式</span> </div>
</div> <n-switch v-model:value="showSuggestions" :round="false" />
<n-select </n-card>
class="set" <n-card class="set-item">
v-model:value="urlJumpType" <div class="name">
:options="urlJumpTypeOptions" <span class="title">跳转方式</span>
<span class="tip">全站链接跳转方式</span>
</div>
<n-select
class="set"
v-model:value="urlJumpType"
:options="urlJumpTypeOptions"
/>
</n-card>
</n-scrollbar>
</n-tab-pane>
<n-tab-pane name="personalization" tab="个性调整">
<n-scrollbar class="scrollbar">
<n-h6 prefix="bar"> 壁纸 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">壁纸遮罩</span>
<span class="tip">壁纸周围是否显示暗色遮罩</span>
</div>
<n-switch v-model:value="showBackgroundGray" :round="false" />
</n-card>
<n-h6 prefix="bar"> 天气与时间 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">时间显秒</span>
<span class="tip">是否在分钟后面显示秒数</span>
</div>
<n-switch v-model:value="showSeconds" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">时钟显零</span>
<span class="tip">是否在时钟小于 10 时补 0</span>
</div>
<n-switch v-model:value="showZeroTime" :round="false" />
</n-card>
<n-h6 prefix="bar"> 搜索框 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">自动聚焦</span>
<span class="tip">打开网站时自动聚焦搜索框</span>
</div>
<n-switch v-model:value="autoFocus" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">自动失焦</span>
<span class="tip">跳转搜索后搜索框自动失焦</span>
</div>
<n-switch v-model:value="autoInputBlur" :round="false" />
</n-card>
</n-scrollbar>
</n-tab-pane>
<n-tab-pane name="other" tab="其他设置">
<n-scrollbar class="scrollbar">
<n-h6 prefix="bar"> 重置 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">站点重置</span>
<span class="tip">若站点显示异常或出现问题时可尝试此操作</span>
</div>
<n-button strong secondary @click="resetSite"> 重置 </n-button>
</n-card>
<n-h6 prefix="bar"> 备份 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">站点备份</span>
<span class="tip">将站点配置及个性化内容进行备份</span>
</div>
<n-button strong secondary @click="backupSite"> 备份 </n-button>
</n-card>
<n-h6 prefix="bar"> 恢复 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">数据恢复</span>
<span class="tip">将备份的站点内容进行恢复</span>
</div>
<input
ref="recoverRef"
type="file"
style="display: none"
accept=".json"
@change="recoverSite"
/>
<n-button strong secondary @click="recoverRef?.click()">
恢复
</n-button>
</n-card>
</n-scrollbar>
</n-tab-pane>
</n-tabs>
<!-- 自定义壁纸 -->
<n-modal
preset="card"
title="自定义壁纸"
v-model:show="customCoverModal"
:bordered="false"
>
<n-form>
<n-form-item label="自定义壁纸链接">
<n-input
clearable
type="text"
v-model:value="customCoverUrl"
placeholder="请输入自定义壁纸链接"
/> />
</n-card> </n-form-item>
</n-scrollbar> </n-form>
</n-tab-pane> <template #footer>
<n-tab-pane name="personalization" tab="个性调整"> <n-space justify="end">
<n-scrollbar class="scrollbar"> <n-button strong secondary @click="customCoverModal = false">
<n-h6 prefix="bar"> 壁纸 </n-h6> 取消
<n-card class="set-item">
<div class="name">
<span class="title">壁纸遮罩</span>
<span class="tip">壁纸周围是否显示暗色遮罩</span>
</div>
<n-switch v-model:value="showBackgroundGray" :round="false" />
</n-card>
<n-h6 prefix="bar"> 天气与时间 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">时间显秒</span>
<span class="tip">是否在分钟后面显示秒数</span>
</div>
<n-switch v-model:value="showSeconds" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">时钟显零</span>
<span class="tip">是否在时钟小于 10 时补 0</span>
</div>
<n-switch v-model:value="showZeroTime" :round="false" />
</n-card>
<n-h6 prefix="bar"> 搜索框 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">自动聚焦</span>
<span class="tip">打开网站时自动聚焦搜索框</span>
</div>
<n-switch v-model:value="autoFocus" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">自动失焦</span>
<span class="tip">跳转搜索后搜索框自动失焦</span>
</div>
<n-switch v-model:value="autoInputBlur" :round="false" />
</n-card>
</n-scrollbar>
</n-tab-pane>
<n-tab-pane name="other" tab="其他设置">
<n-scrollbar class="scrollbar">
<n-h6 prefix="bar"> 重置 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">站点重置</span>
<span class="tip">若站点显示异常或出现问题时可尝试此操作</span>
</div>
<n-button strong secondary @click="resetSite"> 重置 </n-button>
</n-card>
<n-h6 prefix="bar"> 备份 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">站点备份</span>
<span class="tip">将站点配置及个性化内容进行备份</span>
</div>
<n-button strong secondary @click="backupSite"> 备份 </n-button>
</n-card>
<n-h6 prefix="bar"> 恢复 </n-h6>
<n-card class="set-item">
<div class="name">
<span class="title">数据恢复</span>
<span class="tip">将备份的站点内容进行恢复</span>
</div>
<input
ref="recoverRef"
type="file"
style="display: none"
accept=".json"
@change="recoverSite"
/>
<n-button strong secondary @click="recoverRef?.click()">
恢复
</n-button> </n-button>
</n-card> <n-button strong secondary @click="setCustomCover"> 确认 </n-button>
</n-scrollbar> </n-space>
</n-tab-pane> </template>
</n-tabs> </n-modal>
</div>
</template> </template>
<script setup> <script setup>
import { h, ref } from "vue"; import { ref, onMounted } from "vue";
import { import {
NH6,
NTabs, NTabs,
NTabPane, NTabPane,
NSpace, NSpace,
@ -181,15 +216,20 @@ import {
NButton, NButton,
NGrid, NGrid,
NGridItem, NGridItem,
NH6, NModal,
NForm,
NFormItem,
NInput,
} from "naive-ui"; } from "naive-ui";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { setStore, statusStore } from "@/stores"; import { setStore, statusStore } from "@/stores";
import identifyInput from "@/utils/identifyInput";
const set = setStore(); const set = setStore();
const status = statusStore(); const status = statusStore();
const { const {
backgroundType, backgroundType,
backgroundCustom,
showBackgroundGray, showBackgroundGray,
autoFocus, autoFocus,
autoInputBlur, autoInputBlur,
@ -199,6 +239,8 @@ const {
urlJumpType, urlJumpType,
} = storeToRefs(set); } = storeToRefs(set);
const recoverRef = ref(null); const recoverRef = ref(null);
const customCoverModal = ref(false);
const customCoverUrl = ref("");
// //
const backgroundTypeArr = [ const backgroundTypeArr = [
@ -227,16 +269,6 @@ const changeBackground = (type, reset = false) => {
$message.success(`已切换为${backgroundTypeArr[type].name},刷新后生效`); $message.success(`已切换为${backgroundTypeArr[type].name},刷新后生效`);
}; };
//
const customCover = () => {
$dialog.warning({
title: "警告",
content: "你确定?",
positiveText: "确定",
negativeText: "不确定",
});
};
// //
const urlJumpTypeOptions = [ const urlJumpTypeOptions = [
{ {
@ -249,6 +281,18 @@ const urlJumpTypeOptions = [
}, },
]; ];
//
const setCustomCover = () => {
if (identifyInput(customCoverUrl.value) === "url") {
backgroundType.value = 4;
backgroundCustom.value = customCoverUrl.value;
customCoverModal.value = false;
$message.error("已切换为自定义壁纸,刷新后生效");
} else {
$message.error("请输入正确的网址");
}
};
// //
const resetSite = () => { const resetSite = () => {
$dialog.warning({ $dialog.warning({
@ -328,6 +372,11 @@ const recoverSite = async () => {
$message.error("站点数据恢复失败,请重试"); $message.error("站点数据恢复失败,请重试");
} }
}; };
onMounted(() => {
//
if (backgroundCustom.value) customCoverUrl.value = backgroundCustom.value;
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -46,7 +46,11 @@ const setBgUrl = () => {
case 3: case 3:
bgUrl.value = "https://api.aixiaowai.cn/api/api.php"; bgUrl.value = "https://api.aixiaowai.cn/api/api.php";
break; break;
case 4:
bgUrl.value = set.backgroundCustom;
break;
default: default:
bgUrl.value = `/background/bg${bgRandom}.jpg`;
break; break;
} }
}; };
@ -68,7 +72,7 @@ const imgAnimationEnd = () => {
// //
const imgLoadError = () => { const imgLoadError = () => {
bgUrl.value = `/background/bg${bgRandom}.jpg`; bgUrl.value = `/background/bg${bgRandom}.jpg`;
console.error("图片加载失败:", bgUrl.value); console.error("壁纸加载失败:", bgUrl.value);
$message.error("壁纸加载失败,已临时切换回默认"); $message.error("壁纸加载失败,已临时切换回默认");
}; };

View File

@ -18,24 +18,98 @@
<SvgIcon :iconName="`icon-${key}`" /> <SvgIcon :iconName="`icon-${key}`" />
<span class="name">{{ item.name }}</span> <span class="name">{{ item.name }}</span>
</n-grid-item> </n-grid-item>
<n-grid-item class="engine" @click="customEngine"> <n-grid-item
:class="['engine', set.searchEngine === 'custom' ? 'choose' : null]"
@click="customEngineClick"
>
<SvgIcon iconName="icon-custom" /> <SvgIcon iconName="icon-custom" />
<span class="name">自定义</span> <span class="name">自定义</span>
</n-grid-item> </n-grid-item>
<n-grid-item class="engine" @click="customEngineModal = true">
<SvgIcon iconName="icon-custom" />
<span class="name">自定义配置</span>
</n-grid-item>
</n-grid> </n-grid>
</n-scrollbar> </n-scrollbar>
<!-- 自定义搜索引擎 -->
<n-modal
preset="card"
title="自定义搜索引擎"
v-model:show="customEngineModal"
:bordered="false"
>
<n-form
ref="customEngineRef"
:rules="customEngineRules"
:label-width="80"
:model="customEngineValue"
>
<n-form-item label="自定义搜索引擎地址" path="url">
<n-input
clearable
type="text"
v-model:value="customEngineValue.url"
placeholder="请输入自定义搜索引擎地址"
/>
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button strong secondary @click="customEngineModal = false">
取消
</n-button>
<n-button strong secondary @click="setCustomEngine">
确认
</n-button>
</n-space>
</template>
</n-modal>
</div> </div>
</Transition> </Transition>
</template> </template>
<script setup> <script setup>
import { NScrollbar, NGrid, NGridItem } from "naive-ui"; import { ref } from "vue";
import {
NSpace,
NButton,
NScrollbar,
NGrid,
NGridItem,
NModal,
NForm,
NFormItem,
NInput,
} from "naive-ui";
import { statusStore, setStore } from "@/stores"; import { statusStore, setStore } from "@/stores";
import defaultEngine from "@/assets/defaultEngine.json"; import defaultEngine from "@/assets/defaultEngine.json";
const set = setStore(); const set = setStore();
const status = statusStore(); const status = statusStore();
//
const customEngineRef = ref(null);
const customEngineModal = ref(false);
const customEngineValue = ref({
url: set.customEngineUrl,
});
const customEngineRules = {
url: {
required: true,
validator(rule, value) {
if (!value) {
return new Error("请输入自定义搜索引擎地址");
} else if (
!/^https:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/\S*)?$/.test(value)
) {
return new Error("请检查是否为网址且是否为 https:// 开头");
}
return true;
},
trigger: ["input", "blur"],
},
};
// //
const changeSearchEngine = (key) => { const changeSearchEngine = (key) => {
// //
@ -46,9 +120,28 @@ const changeSearchEngine = (key) => {
mainInput?.focus(); mainInput?.focus();
}; };
//
const customEngineClick = () => {
if (set.customEngineUrl) {
changeSearchEngine("custom");
} else {
$message.info("无自定义数据,请配置");
customEngineModal.value = true;
}
};
// //
const customEngine = () => { const setCustomEngine = (e) => {
$message.info("即将支持"); e.preventDefault();
customEngineRef.value?.validate((errors) => {
if (!errors) {
set.setSearchEngine(customEngineValue.value.url, true);
customEngineModal.value = false;
$message.success("已启用自定义搜索引擎");
} else {
$message.error("请检查您的输入");
}
});
}; };
</script> </script>

View File

@ -23,7 +23,11 @@
<div class="engine" title="切换搜索引擎" @click="changeEngine"> <div class="engine" title="切换搜索引擎" @click="changeEngine">
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
<SvgIcon <SvgIcon
:iconName="`icon-${defaultEngine[set.searchEngine].icon}`" :iconName="`icon-${
set.searchEngine !== 'custom'
? defaultEngine[set.searchEngine]?.icon
: 'custom'
}`"
:key="set.searchEngine" :key="set.searchEngine"
/> />
</Transition> </Transition>
@ -108,8 +112,12 @@ const toSearch = (val, type = 1) => {
switch (type) { switch (type) {
// //
case 1: case 1:
const engine = defaultEngine[set.searchEngine]; if (set.searchEngine !== "custom") {
jumpLink(engine.url + searchFormat); const engine = defaultEngine[set.searchEngine];
jumpLink(engine?.url + searchFormat);
} else {
jumpLink(set.customEngineUrl + searchFormat);
}
break; break;
// //
case 2: case 2:

View File

@ -189,6 +189,7 @@ const keyboardEvents = (keyCode, event) => {
toSearch(mainInput.value, 1); toSearch(mainInput.value, 1);
} }
} catch (error) { } catch (error) {
$message.error("出现问题,请尝试重置程序");
console.error("键盘事件出现错误:" + error); console.error("键盘事件出现错误:" + error);
} }
}; };

View File

@ -12,7 +12,7 @@ const useSetDataStore = defineStore("setData", {
// 搜索引擎 // 搜索引擎
searchEngine: "bing", searchEngine: "bing",
lastSearchEngine: "bing", lastSearchEngine: "bing",
customEngine: {}, customEngineUrl: "",
// 清空搜索框 // 清空搜索框
showCleanInput: true, showCleanInput: true,
// 搜索框自动 focus // 搜索框自动 focus
@ -33,10 +33,17 @@ const useSetDataStore = defineStore("setData", {
}; };
}, },
actions: { actions: {
setSearchEngine(value) { setSearchEngine(value, custom = false) {
// 储存上次 // 储存上次
this.lastSearchEngine = this.searchEngine; if (this.searchEngine !== "custom") {
this.lastSearchEngine = this.searchEngine;
}
// 设置新引擎 // 设置新引擎
if (custom) {
this.customEngineUrl = value;
this.searchEngine = "custom";
return;
}
this.searchEngine = value; this.searchEngine = value;
}, },
// 恢复数据 // 恢复数据

View File

@ -97,6 +97,7 @@ body {
// n-dialog // n-dialog
.n-dialog { .n-dialog {
border-radius: 8px; border-radius: 8px;
box-shadow: none;
--n-icon-color: var(--main-text-color); --n-icon-color: var(--main-text-color);
--n-color: var(--main-background-light-color); --n-color: var(--main-background-light-color);
.n-dialog__title { .n-dialog__title {
@ -127,6 +128,14 @@ body {
--n-color-hover: var(--main-background-hover-color); --n-color-hover: var(--main-background-hover-color);
} }
} }
.n-modal {
width: 60vw;
max-width: 700px;
min-width: min(24rem, 100vw);
border-radius: 8px;
box-shadow: none;
--n-color-modal: var(--main-background-light-color);
}
} }
// Transition 动画 // Transition 动画