feat: 新增捷径基础框架与部分设置

- 支持 12 小时制与使用农历
- 支持调整时间样式
- 部分细节优化
This commit is contained in:
imsyy 2023-08-17 18:26:49 +08:00
parent 2806b05b8a
commit 8b338d8e14
22 changed files with 1101 additions and 391 deletions

12
.env
View File

@ -1,5 +1,17 @@
# 站点信息
VITE_SITE_TITLE = "Snavigation"
VITE_SITE_ANTHOR = "無名"
VITE_SITE_KEYWORDS = "Snavigation,导航,网站导航,主页,简约导航,起始页,navigation"
VITE_SITE_DES = "一个简约的起始页,支持自定义搜索引擎,自定义快捷方式,自定义壁纸以及数据备份"
VITE_SITE_LOGO = "/favicon.png"
VITE_SITE_APPLE_LOGO = "/logo/logo.png"
# 进入欢迎词 # 进入欢迎词
VITE_WELCOME_TEXT = "欢迎访问本站" VITE_WELCOME_TEXT = "欢迎访问本站"
# 搜索框提示词 # 搜索框提示词
VITE_INPUT_TIP = "想要搜点什么" VITE_INPUT_TIP = "想要搜点什么"
# ICP 备案号
## 若不需要,请设为空即可
VITE_ICP = "豫ICP备2022018134号-1"

View File

@ -3,9 +3,16 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="%VITE_SITE_LOGO%" />
<link rel="apple-touch-icon" href="%VITE_SITE_APPLE_LOGO%" />
<link rel="bookmark" href="%VITE_SITE_APPLE_LOGO%" />
<link rel="apple-touch-icon-precomposed" sizes="128x128" href="%VITE_SITE_APPLE_LOGO%" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Snavigation</title> <title>%VITE_SITE_TITLE%</title>
<meta name="apple-mobile-web-app-title" content="%VITE_SITE_TITLE%" />
<meta name="author" content="%VITE_SITE_ANTHOR%" />
<meta name="keywords" content="%VITE_SITE_KEYWORDS%" />
<meta name="description" content="%VITE_SITE_DES%" />
<!-- HarmonyOS Sans --> <!-- HarmonyOS Sans -->
<link rel="stylesheet" href="https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css" /> <link rel="stylesheet" href="https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css" />
<!-- IE Out --> <!-- IE Out -->

View File

@ -11,10 +11,10 @@
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"fetch-jsonp": "^1.3.0", "fetch-jsonp": "^1.3.0",
"lunar-calendar": "^0.1.4",
"pinia": "^2.1.4", "pinia": "^2.1.4",
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^3.2.0",
"sass": "^1.64.1", "sass": "^1.64.1",
"vite-plugin-pwa": "^0.16.4",
"vue": "^3.3.4" "vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
@ -22,6 +22,7 @@
"naive-ui": "^2.34.4", "naive-ui": "^2.34.4",
"terser": "^5.19.2", "terser": "^5.19.2",
"vite": "^4.4.5", "vite": "^4.4.5",
"vite-plugin-compression": "^0.5.1" "vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.16.4"
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -90,7 +90,7 @@ const mainContextmenu = (event) => {
// //
const loadComplete = () => { const loadComplete = () => {
nextTick(() => { nextTick().then(() => {
$message.info(getGreeting() + "" + welcomeText, { $message.info(getGreeting() + "" + welcomeText, {
showIcon: false, showIcon: false,
duration: 3000, duration: 3000,

View File

@ -0,0 +1,34 @@
[
{
"name": "翻译",
"url": "@translation"
},
{
"name": "地图",
"url": "@map"
},
{
"name": "图片",
"url": "@picture"
},
{
"name": "云盘",
"url": "@cloud"
},
{
"name": "设置",
"url": "@setting"
},
{
"name": "邮箱",
"url": "https://mail.qq.com/"
},
{
"name": "哔哩哔哩",
"url": "https://mail.qq.com/"
},
{
"name": "GitHub",
"url": "https://mail.qq.com/"
}
]

View File

@ -1,7 +1,7 @@
<template> <template>
<n-tabs class="func" size="large" justify-content="space-evenly" animated> <n-tabs class="all-box" size="large" justify-content="space-evenly" animated >
<n-tab-pane name="link" tab="捷径"> <n-tab-pane class="no-padding" name="link" tab="捷径">
<n-h6 prefix="bar"> 常用 </n-h6> <ShortCut />
</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>
@ -9,5 +9,6 @@
</template> </template>
<script setup> <script setup>
import { NTabs, NTabPane, NH6 } from "naive-ui"; import { NTabs, NTabPane } from "naive-ui";
import ShortCut from "@/components/AllFunc/Box/ShortCut.vue";
</script> </script>

View File

@ -1,6 +1,14 @@
<template> <template>
<!-- 一个盒子 --> <!-- 功能区 -->
<div :class="status.mainBoxBig ? 'main-box big' : 'main-box'" @click.stop> <div
:class="status.mainBoxBig ? 'main-box big' : 'main-box'"
@click.stop
@contextmenu.stop="
(e) => {
e.preventDefault();
}
"
>
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
<AllBox v-if="status.siteStatus === 'box'" /> <AllBox v-if="status.siteStatus === 'box'" />
<AllSet v-else-if="status.siteStatus === 'set'" /> <AllSet v-else-if="status.siteStatus === 'set'" />
@ -28,7 +36,7 @@ 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 { .all-set {
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
:deep(.scrollbar) { :deep(.scrollbar) {
@ -84,6 +92,23 @@ const status = statusStore();
} }
} }
} }
.all-box {
overflow: hidden;
height: 100%;
:deep(.n-tab-pane) {
.scrollbar {
max-height: calc(460px - 84px);
}
.not-shortcut {
min-height: calc(460px - 84px);
}
&.no-padding {
.scrollbar {
max-height: calc(460px - 44px);
}
}
}
}
&.big { &.big {
height: 80%; height: 80%;
margin-top: 0 !important; margin-top: 0 !important;

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="all"> <div class="all-set">
<n-tabs class="set" size="large" justify-content="space-evenly" animated> <n-tabs class="set" size="large" justify-content="space-evenly" animated>
<n-tab-pane name="main" tab="基础设置"> <n-tab-pane name="main" tab="基础设置">
<n-scrollbar class="scrollbar"> <n-scrollbar class="scrollbar">
@ -103,8 +103,40 @@
</div> </div>
<n-switch v-model:value="showBackgroundGray" :round="false" /> <n-switch v-model:value="showBackgroundGray" :round="false" />
</n-card> </n-card>
<n-card class="set-item">
<div class="name">
<span class="title">壁纸模糊</span>
<span class="tip">调整壁纸高斯模糊的程度</span>
</div>
<n-slider
class="set"
v-model:value="backgroundBlur"
:step="0.01"
:min="0"
:max="10"
:tooltip="false"
/>
</n-card>
<n-h6 prefix="bar"> 天气与时间 </n-h6> <n-h6 prefix="bar"> 天气与时间 </n-h6>
<n-card class="set-item"> <n-card class="set-item">
<div class="name">
<span class="title">天气显示</span>
<span class="tip">是否在首页时间下展示天气</span>
</div>
<n-switch v-model:value="showWeather" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">时钟样式</span>
<span class="tip">选择一种时钟样式</span>
</div>
<n-select
class="set"
v-model:value="timeStyle"
:options="timeStyleOptions"
/>
</n-card>
<n-card v-if="timeStyle === 'one'" 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>
@ -118,7 +150,26 @@
</div> </div>
<n-switch v-model:value="showZeroTime" :round="false" /> <n-switch v-model:value="showZeroTime" :round="false" />
</n-card> </n-card>
<n-card class="set-item">
<div class="name">
<span class="title">显示农历</span>
</div>
<n-switch v-model:value="showLunar" :round="false" />
</n-card>
<n-card class="set-item">
<div class="name">
<span class="title">12 小时制</span>
</div>
<n-switch v-model:value="use12HourFormat" :round="false" />
</n-card>
<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-switch v-model:value="smallInput" :round="false" />
</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>
@ -220,6 +271,7 @@ import {
NForm, NForm,
NFormItem, NFormItem,
NInput, NInput,
NSlider,
} from "naive-ui"; } from "naive-ui";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { setStore, statusStore } from "@/stores"; import { setStore, statusStore } from "@/stores";
@ -231,12 +283,18 @@ const {
backgroundType, backgroundType,
backgroundCustom, backgroundCustom,
showBackgroundGray, showBackgroundGray,
backgroundBlur,
smallInput,
autoFocus, autoFocus,
autoInputBlur, autoInputBlur,
showLunar,
showWeather,
showSeconds, showSeconds,
showZeroTime, showZeroTime,
use12HourFormat,
showSuggestions, showSuggestions,
urlJumpType, urlJumpType,
timeStyle,
} = storeToRefs(set); } = storeToRefs(set);
const recoverRef = ref(null); const recoverRef = ref(null);
const customCoverModal = ref(false); const customCoverModal = ref(false);
@ -281,6 +339,18 @@ const urlJumpTypeOptions = [
}, },
]; ];
//
const timeStyleOptions = [
{
label: "横向排布",
value: "one",
},
{
label: "竖向排布",
value: "two",
},
];
// //
const setCustomCover = () => { const setCustomCover = () => {
if (identifyInput(customCoverUrl.value) === "url") { if (identifyInput(customCoverUrl.value) === "url") {

View File

@ -0,0 +1,315 @@
<template>
<!-- 捷径 -->
<Transition name="fade" mode="out-in">
<div v-if="shortcutData[0]" class="shortcut">
<n-scrollbar class="scrollbar">
<n-grid
class="all-shortcut"
responsive="screen"
cols="3 s:4 m:5 l:6"
:x-gap="10"
:y-gap="10"
>
<n-grid-item
v-for="item in shortcutData"
:key="item"
class="shortcut-item"
@contextmenu="shortCutContextmenu($event, item)"
@click="shortCutJump"
>
<span class="name">{{ item.name }}</span>
</n-grid-item>
<n-grid-item
class="shortcut-item"
@contextmenu="
(e) => {
e.preventDefault();
}
"
@click="addShortcutModalOpen"
>
<SvgIcon iconName="icon-add" />
<span class="name">添加捷径</span>
</n-grid-item>
</n-grid>
</n-scrollbar>
</div>
<div v-else class="not-shortcut">
<span class="tip">暂无捷径去添加吧</span>
<n-button strong secondary @click="addShortcutModalOpen">
<template #icon>
<SvgIcon iconName="icon-add" />
</template>
添加捷径
</n-button>
</div>
</Transition>
<!-- 添加捷径 -->
<n-modal
preset="card"
v-model:show="addShortcutModal"
:title="`${addShortcutModalType ? '编辑' : '添加'}捷径`"
:bordered="false"
@mask-click="addShortcutClose"
>
<n-form
ref="addShortcutRef"
:rules="addShortcutRules"
:model="addShortcutValue"
:label-width="80"
>
<n-form-item label="捷径名称" path="name">
<n-input
clearable
type="text"
v-model:value="addShortcutValue.name"
placeholder="请输入捷径名称"
/>
</n-form-item>
<n-form-item label="站点链接" path="url">
<n-input
clearable
type="text"
v-model:value="addShortcutValue.url"
placeholder="请输入站点链接"
/>
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button strong secondary @click="addShortcutClose"> 取消 </n-button>
<n-button strong secondary @click="addShortcutBtn">
{{ addShortcutModalType ? "编辑" : "添加" }}
</n-button>
</n-space>
</template>
</n-modal>
<!-- 捷径右键菜单 -->
<n-dropdown
placement="bottom-start"
trigger="manual"
:x="shortCutDropdownX"
:y="shortCutDropdownY"
:options="shortCutDropdownOptions"
:show="shortCutDropdownShow"
:on-clickoutside="
() => {
shortCutDropdownShow = false;
}
"
@select="shortCutDropdownSelect"
/>
</template>
<script setup>
import { ref, nextTick } from "vue";
import {
NButton,
NScrollbar,
NGrid,
NGridItem,
NSpace,
NModal,
NForm,
NFormItem,
NInput,
NDropdown,
} from "naive-ui";
import { storeToRefs } from "pinia";
import { siteStore } from "@/stores";
import identifyInput from "@/utils/identifyInput";
const site = siteStore();
const { shortcutData } = storeToRefs(site);
//
const addShortcutRef = ref(null);
const addShortcutModal = ref(false);
const addShortcutModalType = ref(false); // false / true
const addShortcutValue = ref({
name: "",
url: "",
});
const addShortcutRules = {
name: {
required: true,
message: "请输入名称",
trigger: ["input", "blur"],
},
url: {
required: true,
validator(rule, value) {
if (!value) {
return new Error("请输入站点链接");
} else if (identifyInput(value) !== "url") {
return new Error("请检查是否为正确的网址");
}
return true;
},
trigger: ["input", "blur"],
},
};
//
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);
const shortCutDropdownShow = ref(false);
const shortCutDropdownOptions = [
{
label: "编辑",
key: "edit",
},
{
label: "删除",
key: "delete",
},
];
//
const shortCutContextmenu = (e, data) => {
e.preventDefault();
shortCutDropdownShow.value = false;
//
const { name, url } = data;
addShortcutValue.value = { name, url };
nextTick().then(() => {
shortCutDropdownShow.value = true;
shortCutDropdownX.value = e.clientX;
shortCutDropdownY.value = e.clientY;
});
};
//
const shortCutDropdownSelect = (key) => {
shortCutDropdownShow.value = false;
console.log(key);
switch (key) {
case "edit":
addShortcutModalType.value = true;
addShortcutModal.value = true;
break;
case "delete":
$message.info("即将支持");
break;
default:
break;
}
};
//
const shortCutJump = () => {
$message.info("即将支持");
};
</script>
<style lang="scss" scoped>
.shortcut {
width: 100%;
height: 100%;
.all-shortcut {
padding: 20px;
box-sizing: border-box;
.shortcut-item {
cursor: pointer;
height: 60px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--main-background-light-color);
border-radius: 8px;
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;
}
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&: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;
}
}
}
}
.not-shortcut {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.tip {
font-size: 24px;
margin-bottom: 20px;
}
}
</style>

View File

@ -5,6 +5,7 @@
class="background" class="background"
alt="background" alt="background"
:src="bgUrl" :src="bgUrl"
:style="{ '--blur': set.backgroundBlur + 'px' }"
@load="imgLoadComplete" @load="imgLoadComplete"
@error.once="imgLoadError" @error.once="imgLoadError"
@animationend="imgAnimationEnd" @animationend="imgAnimationEnd"
@ -71,9 +72,9 @@ const imgAnimationEnd = () => {
// //
const imgLoadError = () => { const imgLoadError = () => {
bgUrl.value = `/background/bg${bgRandom}.jpg`;
console.error("壁纸加载失败:", bgUrl.value); console.error("壁纸加载失败:", bgUrl.value);
$message.error("壁纸加载失败,已临时切换回默认"); $message.error("壁纸加载失败,已临时切换回默认");
bgUrl.value = `/background/bg${bgRandom}.jpg`;
}; };
onMounted(() => { onMounted(() => {
@ -93,8 +94,8 @@ onBeforeUnmount(() => {
background-color: var(--body-background-color); background-color: var(--body-background-color);
&.focus { &.focus {
.background { .background {
filter: blur(10px) brightness(0.8); filter: blur(calc(var(--blur) + 10px)) brightness(0.8);
transform: scale(1.1); transform: scale(1.3);
} }
} }
.background { .background {
@ -105,6 +106,8 @@ onBeforeUnmount(() => {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
backface-visibility: hidden; backface-visibility: hidden;
transform: scale(1.2);
filter: blur(var(--blur));
transition: filter 0.3s, transform 0.3s; transition: filter 0.3s, transform 0.3s;
animation: fade-blur-in 1s cubic-bezier(0.25, 0.46, 0.45, 0.94); animation: fade-blur-in 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
} }

View File

@ -41,8 +41,8 @@
<n-form <n-form
ref="customEngineRef" ref="customEngineRef"
:rules="customEngineRules" :rules="customEngineRules"
:label-width="80"
:model="customEngineValue" :model="customEngineValue"
:label-width="80"
> >
<n-form-item label="自定义搜索引擎地址" path="url"> <n-form-item label="自定义搜索引擎地址" path="url">
<n-input <n-input
@ -131,8 +131,7 @@ const customEngineClick = () => {
}; };
// //
const setCustomEngine = (e) => { const setCustomEngine = () => {
e.preventDefault();
customEngineRef.value?.validate((errors) => { customEngineRef.value?.validate((errors) => {
if (!errors) { if (!errors) {
set.setSearchEngine(customEngineValue.value.url, true); set.setSearchEngine(customEngineValue.value.url, true);

View File

@ -1,6 +1,13 @@
<template> <template>
<!-- 搜索框 --> <!-- 搜索框 -->
<div class="search-input" @click.stop> <div
:class="[
'search-input',
set.smallInput ? 'small' : null,
status.siteStatus === 'focus' ? 'focus' : null,
]"
@click.stop
>
<!-- 搜索框遮罩 --> <!-- 搜索框遮罩 -->
<div <div
v-if="status.siteStatus === 'focus'" v-if="status.siteStatus === 'focus'"
@ -14,8 +21,8 @@
/> />
<!-- 主搜索框 --> <!-- 主搜索框 -->
<div <div
class="all"
ref="searchAllRef" ref="searchAllRef"
:class="status.siteStatus === 'focus' ? 'all focus' : 'all'"
:style="{ pointerEvents: inputClickable ? 'none' : 'auto' }" :style="{ pointerEvents: inputClickable ? 'none' : 'auto' }"
@animationstart="inputClickable = true" @animationstart="inputClickable = true"
@animationend="inputAnimationEnd" @animationend="inputAnimationEnd"
@ -187,7 +194,7 @@ const changeEngine = () => {
align-items: center; align-items: center;
max-width: 680px; max-width: 680px;
width: calc(100% - 60px); width: calc(100% - 60px);
transition: width 0.3s; transition: width 0.35s linear;
.mask { .mask {
position: fixed; position: fixed;
top: 0; top: 0;
@ -211,21 +218,6 @@ const changeEngine = () => {
animation: fade-up-in 0.7s cubic-bezier(0.37, 0.99, 0.36, 1); animation: fade-up-in 0.7s cubic-bezier(0.37, 0.99, 0.36, 1);
transition: transform 0.3s, background-color 0.3s, opacity 0.5s; transition: transform 0.3s, background-color 0.3s, opacity 0.5s;
z-index: 1; z-index: 1;
&.focus {
transform: translateY(-60px);
background-color: var(--main-input-hover-color);
.input {
color: var(--main-text-hover-color);
&::placeholder {
opacity: 0;
}
}
.engine,
.go,
.delete {
color: var(--main-text-hover-color);
}
}
.input { .input {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -239,6 +231,7 @@ const changeEngine = () => {
font-size: 16px; font-size: 16px;
color: var(--main-text-color); color: var(--main-text-color);
&::placeholder { &::placeholder {
width: 100%;
text-align: center; text-align: center;
transform: translateY(1px); transform: translateY(1px);
color: var(--main-text-color); color: var(--main-text-color);
@ -256,7 +249,7 @@ const changeEngine = () => {
width: 64px; width: 64px;
font-size: 20px; font-size: 20px;
border-radius: 30px; border-radius: 30px;
transition: background-color 0.3s; transition: background-color 0.3s, opacity 0.3s;
&:hover { &:hover {
background-color: var(--main-background-color); background-color: var(--main-background-color);
} }
@ -265,5 +258,54 @@ const changeEngine = () => {
} }
} }
} }
&.small {
width: 240px;
.all {
.engine,
.go {
opacity: 0;
}
.input {
&::placeholder {
opacity: 0.3;
}
}
&.focus {
.engine,
.go {
opacity: 1;
}
}
}
&:hover {
width: calc(100% - 60px);
.all {
.input {
&::placeholder {
opacity: 1;
}
}
}
}
}
&.focus {
width: calc(100% - 60px);
.all {
transform: translateY(-60px);
background-color: var(--main-input-hover-color);
.input {
color: var(--main-text-hover-color);
&::placeholder {
opacity: 0;
}
}
.engine,
.go,
.delete {
opacity: 1;
color: var(--main-text-hover-color);
}
}
}
} }
</style> </style>

View File

@ -135,7 +135,7 @@ const keywordsSearch = debounce((val) => {
// //
searchSuggestionsData.value = Array.from(res); searchSuggestionsData.value = Array.from(res);
// //
nextTick(() => { nextTick().then(() => {
changeSuggestionsHeights(); changeSuggestionsHeights();
}); });
}) })

View File

@ -9,11 +9,13 @@
status.siteStatus !== 'focus' status.siteStatus !== 'focus'
? 'hidden' ? 'hidden'
: null, : null,
set.showLunar ? 'lunar' : null,
set.timeStyle,
]" ]"
@click.stop @click.stop
> >
<div <div
:class="['time', set.timeStyle]" class="time"
@click.stop=" @click.stop="
status.setSiteStatus( status.setSiteStatus(
status.siteStatus !== 'normal' && status.siteStatus !== 'focus' status.siteStatus !== 'normal' && status.siteStatus !== 'focus'
@ -25,17 +27,26 @@
<span class="hour">{{ timeData.hour ?? "00" }}</span> <span class="hour">{{ timeData.hour ?? "00" }}</span>
<span class="separator" :key="set.showSeconds">:</span> <span class="separator" :key="set.showSeconds">:</span>
<span class="minute">{{ timeData.minute ?? "00" }}</span> <span class="minute">{{ timeData.minute ?? "00" }}</span>
<template v-if="set.showSeconds"> <Transition name="fade" mode="out-in">
<span v-if="set.showSeconds" class="second">
<span class="separator">:</span> <span class="separator">:</span>
<span class="second">{{ timeData.second ?? "00" }}</span> <span class="second-num">{{ timeData.second ?? "00" }}</span>
</span>
</Transition>
<template v-if="set.use12HourFormat">
<span class="amPm">{{ timeData.amPm ?? "am" }}</span>
</template> </template>
</div> </div>
<div v-if="set.showLunar" class="lunar">
<span class="year">{{ timeData.lunar?.GanZhiYear }}</span>
<span class="text">{{ timeData.lunar?.text }}</span>
</div>
<div class="date"> <div class="date">
<span class="month">{{ timeData.month ?? "0" }}</span> <span class="month">{{ timeData.month ?? "0" }}</span>
<span class="day">{{ timeData.day ?? "0" }}</span> <span class="day">{{ timeData.day ?? "0" }}</span>
<span class="weekday">{{ timeData.weekday ?? "星期八" }}</span> <span class="weekday">{{ timeData.weekday ?? "星期八" }}</span>
</div> </div>
<div v-if="weatherShow" class="weather"> <div v-if="weatherShow && set.showWeather" class="weather">
<span class="status">{{ weatherData.condition ?? "N/A" }}</span> <span class="status">{{ weatherData.condition ?? "N/A" }}</span>
<span class="temperature">{{ weatherData.temp ?? "N/A" }} </span> <span class="temperature">{{ weatherData.temp ?? "N/A" }} </span>
<span class="wind">{{ weatherData.windDir ?? "N/A" }}</span> <span class="wind">{{ weatherData.windDir ?? "N/A" }}</span>
@ -48,7 +59,7 @@
<script setup> <script setup>
import { getCurrentTime } from "@/utils/timeTools"; import { getCurrentTime } from "@/utils/timeTools";
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import { statusStore, setStore } from "@/stores"; import { statusStore, setStore } from "@/stores";
import { getWeather } from "@/api"; import { getWeather } from "@/api";
@ -65,7 +76,7 @@ const weatherData = ref({});
// //
const updateTimeData = () => { const updateTimeData = () => {
timeData.value = getCurrentTime(set.showZeroTime); timeData.value = getCurrentTime(set.showZeroTime, set.use12HourFormat);
}; };
// //
@ -108,6 +119,14 @@ const getWeatherData = () => {
} }
}; };
//
watch(
() => [set.showZeroTime, set.use12HourFormat],
() => {
updateTimeData();
}
);
onMounted(() => { onMounted(() => {
// //
updateTimeData(); updateTimeData();
@ -131,19 +150,8 @@ onBeforeUnmount(() => {
transform: translateY(-140px); transform: translateY(-140px);
color: var(--main-text-color); color: var(--main-text-color);
animation: fade-time-in 0.6s cubic-bezier(0.21, 0.78, 0.36, 1); animation: fade-time-in 0.6s cubic-bezier(0.21, 0.78, 0.36, 1);
transition: transform 0.3s, opacity 0.5s; transition: transform 0.3s, opacity 0.5s, margin-bottom 0.3s;
z-index: 1; z-index: 1;
&.focus {
transform: translateY(-180px);
}
&.box,
&.set {
transform: translateY(-220px);
}
&.hidden {
transform: translateY(-180px);
opacity: 0;
}
.time { .time {
cursor: pointer; cursor: pointer;
font-size: 3rem; font-size: 3rem;
@ -158,6 +166,11 @@ onBeforeUnmount(() => {
transform: translateY(-4px); transform: translateY(-4px);
animation: separator-breathe 0.7s infinite alternate; animation: separator-breathe 0.7s infinite alternate;
} }
.amPm {
font-size: 1rem;
opacity: 0.6;
margin-left: 6px;
}
&:hover { &:hover {
transform: scale(1.08); transform: scale(1.08);
} }
@ -183,6 +196,17 @@ onBeforeUnmount(() => {
} }
} }
} }
.lunar {
font-size: 0.9rem;
opacity: 0.6;
text-shadow: var(--main-text-shadow);
.year {
&::after {
margin-right: 4px;
content: "年";
}
}
}
.weather { .weather {
opacity: 0.7; opacity: 0.7;
font-size: 1rem; font-size: 1rem;
@ -194,5 +218,48 @@ onBeforeUnmount(() => {
margin-left: 6px; margin-left: 6px;
} }
} }
&.focus {
transform: translateY(-180px);
}
&.box,
&.set {
transform: translateY(-220px);
}
&.hidden {
transform: translateY(-180px);
opacity: 0;
}
&.lunar {
margin-bottom: 50px;
}
&.two {
padding-bottom: 60px;
.time {
display: flex;
flex-direction: column;
align-items: center;
span {
line-height: normal;
}
.separator,
.second {
display: none;
}
.hour {
&::after {
content: "/";
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
line-height: 0;
opacity: 0.4;
transform: rotate(50deg);
margin: 12px 0;
}
}
}
}
} }
</style> </style>

View File

@ -1,6 +1,8 @@
// Pinia // Pinia
import useSetDataStore from "@/stores/setData"; import useSetDataStore from "@/stores/setData";
import useSiteDataStore from "@/stores/siteData";
import useStatusDataStore from "@/stores/statusData"; import useStatusDataStore from "@/stores/statusData";
export const setStore = () => useSetDataStore(); export const setStore = () => useSetDataStore();
export const siteStore = () => useSiteDataStore();
export const statusStore = () => useStatusDataStore(); export const statusStore = () => useStatusDataStore();

View File

@ -9,10 +9,14 @@ const useSetDataStore = defineStore("setData", {
backgroundCustom: "", backgroundCustom: "",
// 壁纸遮罩 // 壁纸遮罩
showBackgroundGray: true, showBackgroundGray: true,
// 壁纸模糊
backgroundBlur: 0,
// 搜索引擎 // 搜索引擎
searchEngine: "bing", searchEngine: "bing",
lastSearchEngine: "bing", lastSearchEngine: "bing",
customEngineUrl: "", customEngineUrl: "",
// 搜索框收起
smallInput: false,
// 清空搜索框 // 清空搜索框
showCleanInput: true, showCleanInput: true,
// 搜索框自动 focus // 搜索框自动 focus
@ -21,10 +25,16 @@ const useSetDataStore = defineStore("setData", {
autoInputBlur: true, autoInputBlur: true,
// 时间样式 // 时间样式
timeStyle: "one", timeStyle: "one",
// 显示农历
showLunar: false,
// 是否显秒 // 是否显秒
showSeconds: false, showSeconds: false,
// 是否显零 // 是否显零
showZeroTime: true, showZeroTime: true,
// 12 小时制
use12HourFormat: false,
// 天气显示
showWeather: true,
// 是否显示搜索建议 // 是否显示搜索建议
showSuggestions: true, showSuggestions: true,
// 跳转方式 // 跳转方式

23
src/stores/siteData.js Normal file
View File

@ -0,0 +1,23 @@
import { defineStore } from "pinia";
import defaultShortCut from "@/assets/defaultShortCut";
const useSiteDataStore = defineStore("siteData", {
state: () => {
return {
// 捷径数据
shortcutData: defaultShortCut,
};
},
actions: {
setShortcutData(value) {
this.shortcutData = value;
},
},
// 开启数据持久化
persist: {
key: "siteData",
storage: window.localStorage,
},
});
export default useSiteDataStore;

View File

@ -71,6 +71,9 @@ body {
.n-tab-pane { .n-tab-pane {
padding: 20px; padding: 20px;
box-sizing: border-box; box-sizing: border-box;
&.no-padding {
padding: 0;
}
} }
} }
} }
@ -138,6 +141,13 @@ body {
} }
} }
// NDropdown
.n-dropdown {
--n-border-radius: 8px !important;
--n-color: var(--main-background-light-color) !important;
backdrop-filter: blur(10px);
}
// Transition 动画 // Transition 动画
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
@ -206,12 +216,12 @@ body {
@keyframes fade-blur-in { @keyframes fade-blur-in {
from { from {
filter: blur(20px) brightness(0.4); filter: blur(calc(var(--blur) + 20px)) brightness(0.4);
transform: scale(1.2); transform: scale(1.5);
} }
to { to {
filter: blur(0) brightness(1); filter: blur(var(--blur)) brightness(1);
transform: scale(1); transform: scale(1.2);
} }
} }

View File

@ -1,20 +1,37 @@
import LunarCalendar from "lunar-calendar";
/** /**
* 获取当前时间 * 获取当前时间
* @returns {Object} 时间对象 * @returns {Object} 时间对象
*/ */
export const getCurrentTime = (ShowZero = true) => { export const getCurrentTime = (ShowZero = true, Use12Hour = false) => {
try {
const time = new Date(); const time = new Date();
// 格式化 // 格式化
const formatTime = (value) => (value < 10 ? "0" + value : value); const formatTime = (value) => (value < 10 ? "0" + value : value);
const format12Hour = (hour) => (hour % 12 === 0 ? 12 : hour % 12);
const getAmPm = (hour) => (hour >= 12 ? "PM" : "AM");
// 处理时间 // 处理时间
const year = time.getFullYear(); const year = time.getFullYear();
const month = time.getMonth() + 1; const month = time.getMonth() + 1;
const day = formatTime(time.getDate()); const day = formatTime(time.getDate());
const hour = ShowZero ? formatTime(time.getHours()) : time.getHours(); // 处理时钟
let hour = ShowZero ? formatTime(time.getHours()) : time.getHours();
let amPm = "";
if (Use12Hour) {
hour = format12Hour(hour);
amPm = getAmPm(time.getHours());
}
const minute = formatTime(time.getMinutes()); const minute = formatTime(time.getMinutes());
const second = formatTime(time.getSeconds()); const second = formatTime(time.getSeconds());
const weekdayArr = ["日", "一", "二", "三", "四", "五", "六"]; const weekdayArr = ["日", "一", "二", "三", "四", "五", "六"];
const weekday = "周" + weekdayArr[time.getDay()]; const weekday = "周" + weekdayArr[time.getDay()];
// 获取农历
const lunar = LunarCalendar.solarToLunar(
time.getFullYear(),
time.getMonth() + 1,
time.getDate()
);
// 返回时间 // 返回时间
const currentTime = { const currentTime = {
year, year,
@ -24,8 +41,23 @@ export const getCurrentTime = (ShowZero = true) => {
minute, minute,
second, second,
weekday, weekday,
amPm,
lunar: {
data: lunar,
year: lunar.lunarYear,
month: lunar.lunarMonthName,
day: lunar.lunarDayName,
GanZhiYear: lunar.GanZhiYear,
GanZhiMonth: lunar.GanZhiMonth,
GanZhiDay: lunar.GanZhiDay,
text: lunar.lunarMonthName + lunar.lunarDayName,
},
}; };
return currentTime; return currentTime;
} catch (error) {
console.error("时间获取出错:" + error);
return {};
}
}; };
/** /**

View File

@ -53,7 +53,6 @@ export default defineConfig({
viteCompression(), viteCompression(),
], ],
server: { server: {
host: "0.0.0.0",
port: 5588, port: 5588,
open: true, open: true,
}, },