mirror of
https://github.com/caojiezi2003/Snavigation.git
synced 2024-11-23 10:59:46 +00:00
feat: 完善捷径
This commit is contained in:
parent
8b338d8e14
commit
dc17328a8f
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh-CN" theme="light">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
File diff suppressed because one or more lines are too long
25
src/App.vue
25
src/App.vue
@ -9,6 +9,7 @@
|
||||
tabindex="0"
|
||||
id="main"
|
||||
:class="`main-${status.siteStatus}`"
|
||||
:style="{ pointerEvents: mainClickable ? 'auto' : 'none' }"
|
||||
@click="status.setSiteStatus('normal')"
|
||||
@contextmenu="mainContextmenu"
|
||||
@keydown="mainPressKeyboard"
|
||||
@ -67,8 +68,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick } from "vue";
|
||||
import { statusStore } from "@/stores";
|
||||
import { onMounted, nextTick, watch, ref } from "vue";
|
||||
import { statusStore, setStore } from "@/stores";
|
||||
import { getGreeting } from "@/utils/timeTools";
|
||||
import Provider from "@/components/Provider.vue";
|
||||
import Cover from "@/components/Cover.vue";
|
||||
@ -77,7 +78,9 @@ import SearchInp from "@/components/SearchInput/SearchInp.vue";
|
||||
import AllFunc from "@/components/AllFunc/AllFunc.vue";
|
||||
import Footer from "@/components/Footer.vue";
|
||||
|
||||
const set = setStore();
|
||||
const status = statusStore();
|
||||
const mainClickable = ref(false);
|
||||
|
||||
// 获取配置
|
||||
const welcomeText = import.meta.env.VITE_WELCOME_TEXT ?? "欢迎访问本站";
|
||||
@ -91,6 +94,7 @@ const mainContextmenu = (event) => {
|
||||
// 加载完成事件
|
||||
const loadComplete = () => {
|
||||
nextTick().then(() => {
|
||||
mainClickable.value = true;
|
||||
$message.info(getGreeting() + "," + welcomeText, {
|
||||
showIcon: false,
|
||||
duration: 3000,
|
||||
@ -109,6 +113,23 @@ const mainPressKeyboard = (event) => {
|
||||
mainInput?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// 根据主题类别更改
|
||||
const changeThemeType = (val) => {
|
||||
const htmlElement = document.querySelector("html");
|
||||
const themeType = val === "light" ? "light" : "dark";
|
||||
htmlElement.setAttribute("theme", themeType);
|
||||
};
|
||||
|
||||
// 监听颜色变化
|
||||
watch(
|
||||
() => set.themeType,
|
||||
(val) => changeThemeType(val)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
changeThemeType(set.themeType);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,34 +1,42 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"name": "翻译",
|
||||
"url": "@translation"
|
||||
"url": "https://www.deepl.com/zh/translator"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "地图",
|
||||
"url": "@map"
|
||||
"url": "https://ditu.amap.com/"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "图片",
|
||||
"url": "@picture"
|
||||
"url": "https://www.pexels.com/zh-cn/"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "云盘",
|
||||
"url": "@cloud"
|
||||
"url": "https://www.aliyundrive.com/drive/"
|
||||
},
|
||||
{
|
||||
"name": "设置",
|
||||
"url": "@setting"
|
||||
"id": 4,
|
||||
"name": "Cloudflare",
|
||||
"url": "https://dash.cloudflare.com/"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "邮箱",
|
||||
"url": "https://mail.qq.com/"
|
||||
},
|
||||
{
|
||||
"name": "哔哩哔哩",
|
||||
"url": "https://mail.qq.com/"
|
||||
"id": 6,
|
||||
"name": "GitHub",
|
||||
"url": "https://github.com/"
|
||||
},
|
||||
{
|
||||
"name": "GitHub",
|
||||
"url": "https://mail.qq.com/"
|
||||
"id": 7,
|
||||
"name": "哔哩哔哩",
|
||||
"url": "https://www.bilibili.com/"
|
||||
}
|
||||
]
|
||||
|
@ -3,7 +3,18 @@
|
||||
<n-tabs class="set" size="large" justify-content="space-evenly" animated>
|
||||
<n-tab-pane name="main" tab="基础设置">
|
||||
<n-scrollbar class="scrollbar">
|
||||
<n-h6 prefix="bar"> 壁纸 </n-h6>
|
||||
<n-h6 prefix="bar"> 主题与壁纸 </n-h6>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
<span class="title">主题类别</span>
|
||||
<span class="tip">切换全站主题类别</span>
|
||||
</div>
|
||||
<n-select
|
||||
class="set"
|
||||
v-model:value="themeType"
|
||||
:options="themeTypeOptions"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card
|
||||
class="set-item cover"
|
||||
:content-style="{
|
||||
@ -280,6 +291,7 @@ import identifyInput from "@/utils/identifyInput";
|
||||
const set = setStore();
|
||||
const status = statusStore();
|
||||
const {
|
||||
themeType,
|
||||
backgroundType,
|
||||
backgroundCustom,
|
||||
showBackgroundGray,
|
||||
@ -308,6 +320,18 @@ const backgroundTypeArr = [
|
||||
{ name: "随机动漫", tip: "随机二次元图,随机更换" },
|
||||
];
|
||||
|
||||
// 主题类别
|
||||
const themeTypeOptions = [
|
||||
{
|
||||
label: "浅色模式",
|
||||
value: "light",
|
||||
},
|
||||
{
|
||||
label: "深色模式",
|
||||
value: "dark",
|
||||
},
|
||||
];
|
||||
|
||||
// 切换壁纸
|
||||
const changeBackground = (type, reset = false) => {
|
||||
if (reset) {
|
||||
@ -321,7 +345,7 @@ const changeBackground = (type, reset = false) => {
|
||||
$message.info("已恢复为默认壁纸,刷新后生效");
|
||||
},
|
||||
});
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
backgroundType.value = type;
|
||||
$message.success(`已切换为${backgroundTypeArr[type].name},刷新后生效`);
|
||||
|
@ -15,7 +15,7 @@
|
||||
:key="item"
|
||||
class="shortcut-item"
|
||||
@contextmenu="shortCutContextmenu($event, item)"
|
||||
@click="shortCutJump"
|
||||
@click="shortCutJump(item.url)"
|
||||
>
|
||||
<span class="name">{{ item.name }}</span>
|
||||
</n-grid-item>
|
||||
@ -47,7 +47,7 @@
|
||||
<!-- 添加捷径 -->
|
||||
<n-modal
|
||||
preset="card"
|
||||
v-model:show="addShortcutModal"
|
||||
v-model:show="addShortcutModalShow"
|
||||
:title="`${addShortcutModalType ? '编辑' : '添加'}捷径`"
|
||||
:bordered="false"
|
||||
@mask-click="addShortcutClose"
|
||||
@ -58,6 +58,15 @@
|
||||
:model="addShortcutValue"
|
||||
:label-width="80"
|
||||
>
|
||||
<n-form-item label="ID" path="id">
|
||||
<n-input-number
|
||||
disabled
|
||||
placeholder="请输入ID"
|
||||
v-model:value="addShortcutValue.id"
|
||||
style="width: 100%"
|
||||
:show-button="false"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="捷径名称" path="name">
|
||||
<n-input
|
||||
clearable
|
||||
@ -78,7 +87,7 @@
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button strong secondary @click="addShortcutClose"> 取消 </n-button>
|
||||
<n-button strong secondary @click="addShortcutBtn">
|
||||
<n-button strong secondary @click="addOrEditShortcuts">
|
||||
{{ addShortcutModalType ? "编辑" : "添加" }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
@ -88,6 +97,7 @@
|
||||
<n-dropdown
|
||||
placement="bottom-start"
|
||||
trigger="manual"
|
||||
size="large"
|
||||
:x="shortCutDropdownX"
|
||||
:y="shortCutDropdownY"
|
||||
:options="shortCutDropdownOptions"
|
||||
@ -102,7 +112,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from "vue";
|
||||
import { ref, nextTick, h } from "vue";
|
||||
import {
|
||||
NButton,
|
||||
NScrollbar,
|
||||
@ -113,24 +123,41 @@ import {
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NDropdown,
|
||||
} from "naive-ui";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { siteStore } from "@/stores";
|
||||
import { siteStore, setStore } from "@/stores";
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import identifyInput from "@/utils/identifyInput";
|
||||
|
||||
const set = setStore();
|
||||
const site = siteStore();
|
||||
const { shortcutData } = storeToRefs(site);
|
||||
|
||||
// 图标渲染
|
||||
const renderIcon = (icon) => {
|
||||
return () => {
|
||||
return h(SvgIcon, { iconName: `icon-${icon}` }, null);
|
||||
};
|
||||
};
|
||||
|
||||
// 添加捷径数据
|
||||
const addShortcutRef = ref(null);
|
||||
const addShortcutModal = ref(false);
|
||||
const addShortcutModalShow = ref(false);
|
||||
const addShortcutModalType = ref(false); // false 添加 / true 编辑
|
||||
const addShortcutValue = ref({
|
||||
id: null,
|
||||
name: "",
|
||||
url: "",
|
||||
});
|
||||
const addShortcutRules = {
|
||||
id: {
|
||||
required: true,
|
||||
type: "number",
|
||||
message: "请输入合法 ID",
|
||||
trigger: ["input", "blur"],
|
||||
},
|
||||
name: {
|
||||
required: true,
|
||||
message: "请输入名称",
|
||||
@ -150,61 +177,6 @@ const addShortcutRules = {
|
||||
},
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const addShortcutClose = () => {
|
||||
addShortcutModal.value = false;
|
||||
addShortcutValue.value = {
|
||||
name: "",
|
||||
url: "",
|
||||
};
|
||||
};
|
||||
|
||||
// 开启添加捷径
|
||||
const addShortcutModalOpen = () => {
|
||||
addShortcutValue.value = {
|
||||
name: "",
|
||||
url: "",
|
||||
};
|
||||
addShortcutModalType.value = false;
|
||||
addShortcutModal.value = true;
|
||||
};
|
||||
|
||||
// 添加或编辑捷径
|
||||
const addShortcutBtn = () => {
|
||||
addShortcutRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
// 添加
|
||||
if (!addShortcutModalType.value) {
|
||||
// 是否有重复
|
||||
const isDuplicate = shortcutData.value[0]
|
||||
? shortcutData.value?.some((item) => {
|
||||
return (
|
||||
item.name === addShortcutValue.value.name ||
|
||||
item.url === addShortcutValue.value.url
|
||||
);
|
||||
})
|
||||
: false;
|
||||
if (isDuplicate) {
|
||||
$message.error("新增名称或链接与已有捷径重复");
|
||||
} else {
|
||||
shortcutData.value.push({
|
||||
name: addShortcutValue.value.name,
|
||||
url: addShortcutValue.value.url,
|
||||
});
|
||||
$message.success("添加成功");
|
||||
addShortcutClose();
|
||||
}
|
||||
}
|
||||
// 编辑
|
||||
else {
|
||||
$message.info("即将支持");
|
||||
}
|
||||
} else {
|
||||
$message.error("请检查您的输入");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 右键菜单数据
|
||||
const shortCutDropdownX = ref(0);
|
||||
const shortCutDropdownY = ref(0);
|
||||
@ -213,20 +185,115 @@ const shortCutDropdownOptions = [
|
||||
{
|
||||
label: "编辑",
|
||||
key: "edit",
|
||||
icon: renderIcon("edit"),
|
||||
},
|
||||
{
|
||||
label: "删除",
|
||||
key: "delete",
|
||||
icon: renderIcon("delete-1"),
|
||||
},
|
||||
];
|
||||
|
||||
// 关闭弹窗
|
||||
const addShortcutClose = () => {
|
||||
addShortcutModalShow.value = false;
|
||||
addShortcutValue.value = {
|
||||
id: null,
|
||||
name: "",
|
||||
url: "",
|
||||
};
|
||||
};
|
||||
|
||||
// 开启添加捷径
|
||||
const addShortcutModalOpen = () => {
|
||||
// 生成 ID
|
||||
const shortcutMaxID = shortcutData.value.reduce((max, item) => {
|
||||
return item.id > max ? item.id : max;
|
||||
}, -1);
|
||||
// 生成表单数据
|
||||
addShortcutValue.value = {
|
||||
id: shortcutMaxID + 1,
|
||||
name: "",
|
||||
url: "",
|
||||
};
|
||||
addShortcutModalType.value = false;
|
||||
addShortcutModalShow.value = true;
|
||||
};
|
||||
|
||||
// 添加或编辑捷径
|
||||
const addOrEditShortcuts = () => {
|
||||
addShortcutRef.value?.validate((errors) => {
|
||||
if (errors) {
|
||||
$message.error("请检查您的输入");
|
||||
return false;
|
||||
}
|
||||
// 新增捷径
|
||||
if (!addShortcutModalType.value) {
|
||||
// 是否重复
|
||||
const isDuplicate = shortcutData.value?.some(
|
||||
(item) =>
|
||||
item.name === addShortcutValue.value.name ||
|
||||
item.url === addShortcutValue.value.url
|
||||
);
|
||||
if (isDuplicate) {
|
||||
$message.error("新增名称或链接与已有捷径重复");
|
||||
return false;
|
||||
}
|
||||
shortcutData.value.push({
|
||||
id: addShortcutValue.value.id,
|
||||
name: addShortcutValue.value.name,
|
||||
url: addShortcutValue.value.url,
|
||||
});
|
||||
$message.success("捷径添加成功");
|
||||
addShortcutClose();
|
||||
return true;
|
||||
} else {
|
||||
// 编辑捷径
|
||||
const index = shortcutData.value.findIndex(
|
||||
(item) => item.id === addShortcutValue.value.id
|
||||
);
|
||||
if (index === -1) {
|
||||
$message.error("捷径中不存在该项,请重试");
|
||||
return false;
|
||||
}
|
||||
shortcutData.value[index].name = addShortcutValue.value.name;
|
||||
shortcutData.value[index].url = addShortcutValue.value.url;
|
||||
$message.success("捷径编辑成功");
|
||||
addShortcutClose();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除捷径
|
||||
const delShortcuts = () => {
|
||||
const deleteId = addShortcutValue.value.id;
|
||||
if (typeof deleteId === "number") {
|
||||
const indexToRemove = shortcutData.value.findIndex(
|
||||
(item) => item.id === deleteId
|
||||
);
|
||||
if (indexToRemove !== -1) {
|
||||
shortcutData.value.splice(indexToRemove, 1);
|
||||
// 将后续元素的 id 前移一位
|
||||
for (let i = indexToRemove; i < shortcutData.value.length; i++) {
|
||||
shortcutData.value[i].id = i;
|
||||
}
|
||||
$message.success("捷径删除成功");
|
||||
return true;
|
||||
}
|
||||
$message.error("捷径删除失败,请重试");
|
||||
} else {
|
||||
$message.error("捷径删除失败,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
// 开启右键菜单
|
||||
const shortCutContextmenu = (e, data) => {
|
||||
e.preventDefault();
|
||||
shortCutDropdownShow.value = false;
|
||||
// 写入弹窗数据
|
||||
const { name, url } = data;
|
||||
addShortcutValue.value = { name, url };
|
||||
const { id, name, url } = data;
|
||||
addShortcutValue.value = { id, name, url };
|
||||
nextTick().then(() => {
|
||||
shortCutDropdownShow.value = true;
|
||||
shortCutDropdownX.value = e.clientX;
|
||||
@ -241,10 +308,18 @@ const shortCutDropdownSelect = (key) => {
|
||||
switch (key) {
|
||||
case "edit":
|
||||
addShortcutModalType.value = true;
|
||||
addShortcutModal.value = true;
|
||||
addShortcutModalShow.value = true;
|
||||
break;
|
||||
case "delete":
|
||||
$message.info("即将支持");
|
||||
$dialog.warning({
|
||||
title: "删除捷径",
|
||||
content: `确认删除 ${addShortcutValue.value.name} 捷径?此操作将无法恢复!`,
|
||||
positiveText: "删除",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: () => {
|
||||
delShortcuts();
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -252,8 +327,14 @@ const shortCutDropdownSelect = (key) => {
|
||||
};
|
||||
|
||||
// 捷径跳转
|
||||
const shortCutJump = () => {
|
||||
$message.info("即将支持");
|
||||
const shortCutJump = (url) => {
|
||||
const urlRegex = /^(https?:\/\/)/i;
|
||||
const urlFormat = urlRegex.test(url) ? url : `//${url}`;
|
||||
if (set.urlJumpType === "href") {
|
||||
window.location.href = urlFormat;
|
||||
} else if (set.urlJumpType === "open") {
|
||||
window.open(urlFormat, "_blank");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -276,10 +357,10 @@ const shortCutJump = () => {
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s, box-shadow 0.3s;
|
||||
.i-icon {
|
||||
width: 0rem;
|
||||
opacity: 0;
|
||||
font-size: 0px;
|
||||
transition: width 0.3s, opacity 0.3s, font-size 0.3s, margin-right 0.3s;
|
||||
width: 1rem;
|
||||
margin-right: 6px;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
.name {
|
||||
overflow: hidden;
|
||||
@ -289,12 +370,6 @@ const shortCutJump = () => {
|
||||
&:hover {
|
||||
background-color: var(--main-background-hover-color);
|
||||
box-shadow: 0 0 0px 2px var(--main-background-hover-color);
|
||||
.i-icon {
|
||||
width: 1rem;
|
||||
margin-right: 6px;
|
||||
font-size: 20px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
|
@ -20,13 +20,7 @@
|
||||
"
|
||||
/>
|
||||
<!-- 主搜索框 -->
|
||||
<div
|
||||
class="all"
|
||||
ref="searchAllRef"
|
||||
:style="{ pointerEvents: inputClickable ? 'none' : 'auto' }"
|
||||
@animationstart="inputClickable = true"
|
||||
@animationend="inputAnimationEnd"
|
||||
>
|
||||
<div class="all" ref="searchAllRef" @animationend="inputAnimationEnd">
|
||||
<div class="engine" title="切换搜索引擎" @click="changeEngine">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<SvgIcon
|
||||
@ -84,7 +78,6 @@ const inputTip = import.meta.env.VITE_INPUT_TIP ?? "想要搜点什么";
|
||||
// 搜索框数据
|
||||
const searchAllRef = ref(null);
|
||||
const searchInputRef = ref(null);
|
||||
const inputClickable = ref(true);
|
||||
|
||||
// 搜索建议子组件
|
||||
const suggestionsRef = ref(null);
|
||||
@ -163,7 +156,6 @@ const toSearch = (val, type = 1) => {
|
||||
// 搜索框动画结束
|
||||
const inputAnimationEnd = () => {
|
||||
console.log("搜索框动画结束");
|
||||
inputClickable.value = false;
|
||||
// 自动 focus
|
||||
if (set.autoFocus) {
|
||||
status.setSiteStatus("focus");
|
||||
|
@ -3,6 +3,8 @@ import { defineStore } from "pinia";
|
||||
const useSetDataStore = defineStore("setData", {
|
||||
state: () => {
|
||||
return {
|
||||
// 主题类别
|
||||
themeType: "light",
|
||||
// 壁纸类别
|
||||
// 0 本地 / 1 必应 / 2 随机风景 / 3 随机动漫 / 4 自定义
|
||||
backgroundType: 2,
|
||||
|
@ -12,6 +12,12 @@
|
||||
--main-text-shadow: 0px 0px 8px #00000066;
|
||||
}
|
||||
|
||||
[theme="dark"] {
|
||||
--main-text-color: #efefef;
|
||||
--main-background-light-color: #00000030;
|
||||
--main-background-hover-color: #00000040;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -145,6 +151,7 @@ body {
|
||||
.n-dropdown {
|
||||
--n-border-radius: 8px !important;
|
||||
--n-color: var(--main-background-light-color) !important;
|
||||
--n-option-color-hover: var(--main-background-hover-color) !important;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
|
@ -68,15 +68,17 @@ export const getGreeting = () => {
|
||||
const currentTime = new Date();
|
||||
const currentHour = currentTime.getHours();
|
||||
let greeting = "";
|
||||
if (currentHour >= 5 && currentHour < 9) {
|
||||
if (currentHour >= 6 && currentHour < 9) {
|
||||
greeting = "早上好";
|
||||
} else if (currentHour >= 9 && currentHour < 12) {
|
||||
greeting = "上午好";
|
||||
} else if (currentHour >= 12 && currentHour < 18) {
|
||||
greeting = "下午好";
|
||||
} else if (currentHour >= 18 && currentHour < 24) {
|
||||
} else if (currentHour >= 18 && currentHour < 20) {
|
||||
greeting = "傍晚好";
|
||||
} else if (currentHour >= 20 && currentHour < 24) {
|
||||
greeting = "晚上好";
|
||||
} else if (currentHour >= 0 && currentHour < 5) {
|
||||
} else if (currentHour >= 4 && currentHour < 6) {
|
||||
greeting = "凌晨好";
|
||||
} else {
|
||||
greeting = "夜深了";
|
||||
|
Loading…
Reference in New Issue
Block a user