style: 优化组件样式和玻璃拟态效果
This commit is contained in:
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pr-8 !bg-transparent">
|
<div class="pl-6 pr-6 !bg-transparent">
|
||||||
<n-card
|
<n-card
|
||||||
embedded
|
embedded
|
||||||
class="mt-4 overflow-hidden rounded-[24px] border border-primary bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
|
class="side-glass-card side-glass-card--tint mt-4 overflow-hidden rounded-[24px]"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center font-semibold text-slate-700">
|
<div class="flex items-center font-semibold text-slate-700">
|
||||||
@ -17,14 +17,14 @@
|
|||||||
<span>今年已过 {{ jq.dayOfYear }} 天</span>
|
<span>今年已过 {{ jq.dayOfYear }} 天</span>
|
||||||
{{ d }}
|
{{ d }}
|
||||||
</div>
|
</div>
|
||||||
<img v-if="jq.type != 0" class="absolute right-4 top-0" width="120" src="@/assets/images/offwork.png" alt="offwork">
|
<img v-if="jq.type != 0" class="absolute right-4 -top-15" width="120" src="@/assets/images/offwork.png" alt="offwork">
|
||||||
<img v-else class="absolute right-4 top-0" width="120" src="@/assets/images/onwork.png" alt="onwork">
|
<img v-else class="absolute right-4 -top-15" width="120" src="@/assets/images/onwork.png" alt="onwork">
|
||||||
</template>
|
</template>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card
|
<n-card
|
||||||
embedded
|
embedded
|
||||||
class="mt-4 overflow-hidden rounded-[24px] border border-white/75 bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
|
class="side-glass-card mt-4 overflow-hidden rounded-[24px]"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center font-semibold text-slate-700">
|
<div class="flex items-center font-semibold text-slate-700">
|
||||||
@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
<n-card
|
<n-card
|
||||||
embedded
|
embedded
|
||||||
class="mt-4 overflow-hidden rounded-[24px] border border-white/75 bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%),linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.8))] backdrop-blur-[18px]"
|
class="side-glass-card mt-4 overflow-hidden rounded-[24px]"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center font-semibold text-slate-700">
|
<div class="flex items-center font-semibold text-slate-700">
|
||||||
@ -140,6 +140,51 @@ onUnmounted(() => {
|
|||||||
src: url('@/assets/font/LCDML.woff2');
|
src: url('@/assets/font/LCDML.woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-glass-card {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.58) !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 22px 40px rgba(86, 111, 137, 0.14),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
|
||||||
|
backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-glass-card::before,
|
||||||
|
.side-glass-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-glass-card::before {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
|
||||||
|
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-glass-card::after {
|
||||||
|
inset: 1px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||||
|
border-radius: inherit;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-glass-card--tint {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top left, rgba(255, 199, 132, 0.12), transparent 34%),
|
||||||
|
radial-gradient(circle at top right, rgba(136, 203, 255, 0.1), transparent 28%),
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.n-card > .n-card__content),
|
:deep(.n-card > .n-card__content),
|
||||||
:deep(.n-card > .n-card__footer) {
|
:deep(.n-card > .n-card__footer) {
|
||||||
padding: 12px 24px !important;
|
padding: 12px 24px !important;
|
||||||
@ -153,4 +198,12 @@ onUnmounted(() => {
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
box-shadow:unset;
|
box-shadow:unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.side-glass-card > .n-card__content),
|
||||||
|
:deep(.side-glass-card > .n-card-header),
|
||||||
|
:deep(.side-glass-card > .n-card__footer) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -4,18 +4,18 @@
|
|||||||
class="relative hidden items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.82)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.28)_100%)] px-4 py-1.5 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-28px] after:h-[34px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:flex"
|
class="relative hidden items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.82)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.28)_100%)] px-4 py-1.5 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-28px] after:h-[34px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:flex"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex ml-4 cursor-pointer items-center rounded-full border border-white/65 bg-white/42 px-4 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px] transition-colors duration-200 hover:bg-white/56"
|
class="menu-pill-glass flex ml-4 cursor-pointer items-center rounded-full px-4 py-1 transition-colors duration-200"
|
||||||
@click="goHome"
|
@click="goHome"
|
||||||
>
|
>
|
||||||
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
|
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex h-[42px] items-center rounded-full border border-white/65 bg-white/42 px-3 py-0.5 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
|
<div class="menu-pill-glass flex h-[42px] items-center rounded-full px-1 py-0.5">
|
||||||
<n-menu :icon-size="12" v-model:value="activeKey" mode="horizontal" :options="menuOptions" />
|
<n-menu :icon-size="12" v-model:value="activeKey" mode="horizontal" :options="menuOptions" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex cursor-pointer items-center rounded-full border border-white/65 bg-white/42 px-4 py-1.5 text-[#ec66ab] shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px] transition-colors duration-200 hover:bg-white/56"
|
class="menu-pill-glass flex cursor-pointer items-center rounded-full px-4 py-1.5 text-[#ec66ab] transition-colors duration-200"
|
||||||
@click="gotoHf"
|
@click="gotoHf"
|
||||||
>
|
>
|
||||||
<span class="flex items-center truncate text-[15px] font-medium text-slate-600">
|
<span class="flex items-center truncate text-[15px] font-medium text-slate-600">
|
||||||
@ -30,11 +30,22 @@
|
|||||||
<span class="ml-4 text-[15px] font-semibold text-slate-700">{{ temp }}°C</span>
|
<span class="ml-4 text-[15px] font-semibold text-slate-700">{{ temp }}°C</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-4 flex w-1/20 items-center justify-end">
|
<div class="mr-4 flex items-center justify-end">
|
||||||
<n-dropdown v-if="userinfo" class="w-[128px] cursor-pointer" :options="oprOp" @select="handleSelect" trigger="hover">
|
<n-dropdown
|
||||||
<div class="flex items-center rounded-full border border-white/65 bg-white/46 px-2 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
|
v-if="userinfo"
|
||||||
|
class="w-[128px] cursor-pointer"
|
||||||
|
trigger="hover"
|
||||||
|
placement="bottom-end"
|
||||||
|
content-class="w-[400px] overflow-hidden rounded-[26px] border border-white/80 p-2.5"
|
||||||
|
:theme-overrides="dropdownThemeOverrides"
|
||||||
|
:node-props="dropdownNodeProps"
|
||||||
|
:render-label="renderDropdownLabel"
|
||||||
|
:options="oprOp"
|
||||||
|
@select="handleSelect"
|
||||||
|
>
|
||||||
|
<div class="menu-pill-glass cursor-pointer flex items-center rounded-full px-2 py-1">
|
||||||
<n-avatar round :src="userinfo.ava_url" class="cursor-pointer ring-2 ring-white/80" alt="用户头像" />
|
<n-avatar round :src="userinfo.ava_url" class="cursor-pointer ring-2 ring-white/80" alt="用户头像" />
|
||||||
<div class="ml-2 truncate text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
|
<div class="ml-4 mr-2 truncate text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
|
||||||
</div>
|
</div>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
|
|
||||||
@ -56,13 +67,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.84)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.32)_100%)] px-3 py-2 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-24px] after:h-[30px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:hidden">
|
<div class="relative flex items-center justify-between bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.18),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.14),transparent_24%),linear-gradient(180deg,rgba(239,231,218,0.84)_0%,rgba(232,240,239,0.68)_46%,rgba(243,241,232,0.32)_100%)] px-3 py-2 shadow-[0_6px_16px_rgba(110,124,112,0.05)] backdrop-blur-[14px] after:pointer-events-none after:absolute after:inset-x-0 after:bottom-[-24px] after:h-[30px] after:bg-[linear-gradient(180deg,rgba(240,236,226,0.58),rgba(232,240,239,0.22),transparent)] after:content-[''] lg:hidden">
|
||||||
<div class="flex items-center rounded-full border border-white/65 bg-white/42 px-3 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
|
<div class="menu-pill-glass flex items-center rounded-full px-3 py-1">
|
||||||
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
|
<img :src="logo" alt="柚子的网站" class="h-9 align-middle drop-shadow-[0_6px_14px_rgba(236,102,171,0.15)]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center mr-2">
|
<div class="flex items-center mr-2 cursor-pointer">
|
||||||
<div v-if="userinfo" class="flex items-center rounded-full border border-white/65 bg-white/46 px-2 py-1 shadow-[0_8px_20px_rgba(112,137,160,0.07)] backdrop-blur-[14px]">
|
<div v-if="userinfo" class="menu-pill-glass flex items-center rounded-full px-2 py-1">
|
||||||
<n-avatar :src="userinfo.ava_url" round class="cursor-pointer ring-2 ring-white/80" alt="用户头像" />
|
<n-avatar :src="userinfo.ava_url" round class=" ring-2 ring-white/80" alt="用户头像" />
|
||||||
<div class="ml-2 text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
|
<div class="ml-2 text-sm font-medium text-slate-600">{{ userinfo.nickname }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -113,7 +124,7 @@ import artiSvg from '@/icon/menu/arti.svg';
|
|||||||
import homeSvg from '@/icon/menu/home.svg';
|
import homeSvg from '@/icon/menu/home.svg';
|
||||||
import linkSvg from '@/icon/menu/link.svg';
|
import linkSvg from '@/icon/menu/link.svg';
|
||||||
import picSvg from '@/icon/menu/pic.svg';
|
import picSvg from '@/icon/menu/pic.svg';
|
||||||
import type { UploadFileInfo } from 'naive-ui';
|
import type { DropdownOption, UploadFileInfo } from 'naive-ui';
|
||||||
|
|
||||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||||
@ -184,7 +195,47 @@ const oprOp = [
|
|||||||
key: 'logout',
|
key: 'logout',
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
}
|
}
|
||||||
]
|
] satisfies DropdownOption[]
|
||||||
|
|
||||||
|
const dropdownThemeOverrides = {
|
||||||
|
padding: '0 0px',
|
||||||
|
borderRadius: '20px',
|
||||||
|
color: '#DCE1D8ee',
|
||||||
|
optionHeightMedium: '52px',
|
||||||
|
optionColorHover: 'transparent',
|
||||||
|
optionColorActive: 'transparent',
|
||||||
|
optionTextColor: '#526377',
|
||||||
|
optionTextColorHover: '#ec66ab',
|
||||||
|
optionTextColorActive: '#ec66ab',
|
||||||
|
peerOverrides: {
|
||||||
|
Popover: {
|
||||||
|
color: 'transparent',
|
||||||
|
boxShadow: 'none',
|
||||||
|
padding: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropdownNodeProps() {
|
||||||
|
return {
|
||||||
|
class: 'group rounded-[20px] px-1 py-1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDropdownLabel(option: DropdownOption) {
|
||||||
|
const labelText =
|
||||||
|
('label' in option && typeof option.label === 'string' && option.label)
|
||||||
|
|| ('title' in option && typeof option.title === 'string' && option.title)
|
||||||
|
|| '未知'
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
class: 'flex w-full items-center justify-center rounded-[18px] border border-white/0 bg-[linear-gradient(180deg,rgba(255,255,255,0.18),rgba(255,255,255,0.06))] px-5 py-3.5 text-[14px] font-semibold text-slate-700 transition-all duration-300 group-hover:border-white/85 group-hover:bg-[linear-gradient(180deg,rgba(255,255,255,0.82),rgba(255,248,244,0.62))] group-hover:text-[#ec66ab] group-hover:shadow-[0_14px_28px_rgba(102,129,156,0.16)]'
|
||||||
|
},
|
||||||
|
labelText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const editModal = ref(false)
|
const editModal = ref(false)
|
||||||
const token = ref('')
|
const token = ref('')
|
||||||
@ -394,6 +445,59 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
.menu-pill-glass {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.58);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.22), rgba(224, 236, 245, 0.1));
|
||||||
|
box-shadow:
|
||||||
|
0 14px 30px rgba(98, 121, 146, 0.1),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.72),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.14);
|
||||||
|
backdrop-filter: blur(22px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(22px) saturate(165%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-pill-glass::before,
|
||||||
|
.menu-pill-glass::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-pill-glass::before {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
|
||||||
|
radial-gradient(circle at top left, rgba(255, 255, 255, 0.42), transparent 42%);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-pill-glass::after {
|
||||||
|
inset: 1px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||||
|
border-radius: inherit;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.12), transparent 38%, rgba(255, 255, 255, 0.06));
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-pill-glass:hover {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.2)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.24), rgba(224, 236, 245, 0.12));
|
||||||
|
box-shadow:
|
||||||
|
0 18px 34px rgba(98, 121, 146, 0.13),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-pill-glass > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.n-menu-item-content__icon) {
|
:deep(.n-menu-item-content__icon) {
|
||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
@ -407,8 +511,14 @@ onUnmounted(() => {
|
|||||||
:deep(.n-menu-item) {
|
:deep(.n-menu-item) {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 12px;
|
||||||
margin-right: 30px !important;
|
margin-right: 30px !important;
|
||||||
}
|
}
|
||||||
|
:deep(.n-menu-item:last-of-type) {
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.n-menu--horizontal .n-menu-item-content) {
|
:deep(.n-menu--horizontal .n-menu-item-content) {
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] px-[1.1rem] py-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.55)]">
|
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.55)]">
|
||||||
<n-input class="gallery-search" round size="large" v-model:value="kw" @keyup.enter="onSearch"
|
<n-input class="gallery-search" round size="large" v-model:value="kw" @keyup.enter="onSearch"
|
||||||
placeholder="请输入关键词,按回车开始搜索">
|
placeholder="请输入关键词,按回车开始搜索">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
@ -277,4 +277,17 @@ onUnmounted(() => {
|
|||||||
:deep(.n-image img) {
|
:deep(.n-image img) {
|
||||||
border-radius: 1rem !important;
|
border-radius: 1rem !important;
|
||||||
}
|
}
|
||||||
|
:deep(.n-input) {
|
||||||
|
--n-border: none !important;
|
||||||
|
--n-border-hover: none !important;
|
||||||
|
--n-border-focus: none !important;
|
||||||
|
--n-box-shadow-focus: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input__input-el),
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
color: #314155 !important;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -10,55 +10,71 @@
|
|||||||
<n-layout-content class="relative !bg-transparent">
|
<n-layout-content class="relative !bg-transparent">
|
||||||
<div ref="searchRef" class="relative z-[1] hidden px-12 pt-8 lg:block">
|
<div ref="searchRef" class="relative z-[1] hidden px-12 pt-8 lg:block">
|
||||||
<!-- <div class="panel-caption">快捷搜索</div> -->
|
<!-- <div class="panel-caption">快捷搜索</div> -->
|
||||||
<n-input-group class="overflow-hidden rounded-[22px] border border-white/75 bg-white/72 shadow-[0_18px_45px_rgba(111,144,175,0.16)] backdrop-blur-[16px]">
|
<div ref="searchShellRef" class="search-shell relative">
|
||||||
<n-select
|
<n-input-group class="glass-surface overflow-hidden rounded-[22px]">
|
||||||
class="w-24 search-select rounded-full"
|
<n-select
|
||||||
size="large"
|
class="w-24 search-select rounded-full"
|
||||||
v-model:value="broswer"
|
size="large"
|
||||||
:options="options"
|
v-model:value="broswer"
|
||||||
@click="cancelSbox"
|
:options="options"
|
||||||
/>
|
:menu-props="{ class: 'search-select-menu' }"
|
||||||
<n-input
|
@click="cancelSbox"
|
||||||
class="flex-1"
|
/>
|
||||||
size="large"
|
<n-input
|
||||||
autofocus
|
ref="searchInputRef"
|
||||||
v-model:value="searchWord"
|
class="flex-1"
|
||||||
@blur="cancelSbox"
|
size="large"
|
||||||
@keyup.enter="search"
|
autofocus
|
||||||
placeholder="请输入搜索内容"
|
v-model:value="searchWord"
|
||||||
>
|
@blur="cancelSbox"
|
||||||
<template #suffix>
|
@keyup.enter="search"
|
||||||
<n-icon class=" cursor-pointer" size="large">
|
placeholder="请输入搜索内容"
|
||||||
<icon-search />
|
>
|
||||||
</n-icon>
|
<template #suffix>
|
||||||
</template>
|
<n-icon class=" cursor-pointer" size="large">
|
||||||
</n-input>
|
<icon-search />
|
||||||
</n-input-group>
|
</n-icon>
|
||||||
<div
|
</template>
|
||||||
v-if="searchBox"
|
</n-input>
|
||||||
class="absolute left-34 mt-3 z-10 max-w-88 rounded-2xl border border-white/80 bg-[linear-gradient(180deg,rgba(255,255,255,0.88),rgba(244,249,253,0.84))] px-3 py-3 text-sm text-slate-500 shadow-[0_20px_48px_rgba(102,129,156,0.18),inset_0_1px_0_rgba(255,255,255,0.9)] backdrop-blur-[20px] before:pointer-events-none before:absolute before:inset-0 before:rounded-2xl before:bg-[radial-gradient(circle_at_top_left,rgba(255,199,132,0.12),transparent_34%),radial-gradient(circle_at_top_right,rgba(136,203,255,0.1),transparent_28%)] before:content-['']"
|
</n-input-group>
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(item, idx) in searchItems"
|
|
||||||
:key="idx"
|
|
||||||
class="relative z-[1] flex items-center truncate rounded-xl border border-transparent p-3 pr-20 text-slate-500 transition-colors duration-200"
|
|
||||||
:class="selecedIdx === idx
|
|
||||||
? 'bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_10px_22px_rgba(236,102,171,0.22)]'
|
|
||||||
: 'hover:border-slate-200/90 hover:bg-white/72 hover:text-slate-600'"
|
|
||||||
:style="{ marginTop: idx === 0 ? '0' : '0.35rem' }"
|
|
||||||
@click="goExtra(item.menu_link)"
|
|
||||||
@keyup.enter="goExtra(item.menu_link)"
|
|
||||||
>
|
|
||||||
<span v-if="idx">导航:</span>
|
|
||||||
{{ item.menu_name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="searchBox" class="search-suggest-portal pointer-events-none fixed inset-0 z-[1200]">
|
||||||
|
<div
|
||||||
|
class="search-suggest-panel glass-surface glass-floating-surface pointer-events-auto absolute rounded-[26px] px-3 py-3 text-sm text-slate-500"
|
||||||
|
:style="searchPanelStyle"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item, idx) in searchItems"
|
||||||
|
:key="idx"
|
||||||
|
class="search-suggest-item relative z-[1] flex items-center truncate rounded-[18px] border border-transparent p-3.5 pr-6 text-slate-500 transition-[transform,background-color,color,box-shadow,border-color] duration-200"
|
||||||
|
:class="selecedIdx === idx
|
||||||
|
? 'bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_14px_26px_rgba(236,102,171,0.24)]'
|
||||||
|
: 'hover:-translate-y-0.5 hover:border-white/55 hover:bg-white/62 hover:text-slate-700 hover:shadow-[0_12px_24px_rgba(102,129,156,0.12)]'"
|
||||||
|
:style="{ marginTop: idx === 0 ? '0' : '0.45rem' }"
|
||||||
|
@click="goExtra(item.menu_link)"
|
||||||
|
@keyup.enter="goExtra(item.menu_link)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mr-2 inline-flex h-6 shrink-0 items-center rounded-full px-2.5 text-[11px] font-semibold tracking-[0.04em]"
|
||||||
|
:class="selecedIdx === idx
|
||||||
|
? 'bg-white/18 text-white'
|
||||||
|
: idx ? 'bg-slate-700/8 text-slate-500' : 'bg-[rgba(236,102,171,0.12)] text-[rgb(212,88,154)]'"
|
||||||
|
>
|
||||||
|
{{ idx ? '导航' : '搜索' }}
|
||||||
|
</span>
|
||||||
|
<span class="truncate">{{ item.menu_name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
<n-scrollbar class="w-full overflow-x-auto" :style="navStyle" trigger="none">
|
<n-scrollbar class="w-full overflow-x-auto" :style="navStyle" trigger="none">
|
||||||
<div
|
<div
|
||||||
v-if="navlist.length"
|
v-if="navlist.length"
|
||||||
class="mt-6 mb-4 flex flex-wrap items-center gap-2 rounded-[20px] border border-white/70 bg-white/60 px-3 py-3 shadow-[0_12px_35px_rgba(118,144,169,0.12)] backdrop-blur-[14px] lg:mx-12 lg:px-4"
|
class="glass-surface mt-6 mb-4 flex flex-wrap items-center gap-2 rounded-[20px] px-3 py-3 lg:mx-12 lg:px-4"
|
||||||
>
|
>
|
||||||
<d-tag
|
<d-tag
|
||||||
v-for="tag in tagList"
|
v-for="tag in tagList"
|
||||||
@ -89,7 +105,7 @@
|
|||||||
v-for="(item, index) in navlist"
|
v-for="(item, index) in navlist"
|
||||||
:key="item.nid ?? index"
|
:key="item.nid ?? index"
|
||||||
embedded
|
embedded
|
||||||
class="h-28 box-border overflow-hidden rounded-[24px] border border-white/75 bg-[linear-gradient(180deg,rgba(255,255,255,0.9),rgba(244,250,255,0.72))] shadow-[0_16px_32px_rgba(118,144,169,0.12)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_38px_rgba(103,128,154,0.16)]"
|
class="glass-nav-card h-28 box-border overflow-hidden rounded-[24px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1"
|
||||||
@click="goExtra(item.menu_link)"
|
@click="goExtra(item.menu_link)"
|
||||||
@contextmenu.prevent="handdleContextMenu($event, item)"
|
@contextmenu.prevent="handdleContextMenu($event, item)"
|
||||||
>
|
>
|
||||||
@ -121,7 +137,7 @@
|
|||||||
|
|
||||||
<n-card
|
<n-card
|
||||||
embedded
|
embedded
|
||||||
class="h-28 box-border overflow-hidden rounded-[24px] border border-white/75 bg-[linear-gradient(180deg,rgba(255,253,248,0.92),rgba(255,247,235,0.75))] shadow-[0_16px_32px_rgba(118,144,169,0.12)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_38px_rgba(103,128,154,0.16)]"
|
class="glass-nav-card glass-nav-card--warm h-28 box-border overflow-hidden rounded-[24px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<div @click="addNav" class="w-full h-full flex flex-col items-center justify-center cursor-pointer">
|
<div @click="addNav" class="w-full h-full flex flex-col items-center justify-center cursor-pointer">
|
||||||
<div
|
<div
|
||||||
@ -264,6 +280,7 @@ const menuS = '!m-0 py-3 px-6 text-sm text-gray-700 w-full flex items-center jus
|
|||||||
const currentClickedItem = ref(0)
|
const currentClickedItem = ref(0)
|
||||||
const editModal = ref(false)
|
const editModal = ref(false)
|
||||||
const editInput = ref('')
|
const editInput = ref('')
|
||||||
|
const searchInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const navData = reactive<NavItem>({
|
const navData = reactive<NavItem>({
|
||||||
@ -275,12 +292,14 @@ const navData = reactive<NavItem>({
|
|||||||
const formNav = ref<any>(null)
|
const formNav = ref<any>(null)
|
||||||
const navcards = ref<any>(null)
|
const navcards = ref<any>(null)
|
||||||
const searchRef = useTemplateRef('searchRef')
|
const searchRef = useTemplateRef('searchRef')
|
||||||
|
const searchShellRef = useTemplateRef('searchShellRef')
|
||||||
|
|
||||||
const nav: any = $store.nav.useNavStore()
|
const nav: any = $store.nav.useNavStore()
|
||||||
const usrLog = $store.log.useLogStore()
|
const usrLog = $store.log.useLogStore()
|
||||||
|
|
||||||
const contentStyle = ref<Record<string, string>>({})
|
const contentStyle = ref<Record<string, string>>({})
|
||||||
const navStyle = ref<Record<string, string>>({})
|
const navStyle = ref<Record<string, string>>({})
|
||||||
|
const searchPanelStyle = ref<Record<string, string>>({})
|
||||||
const searchWord = ref('')
|
const searchWord = ref('')
|
||||||
const broswer = ref('bing')
|
const broswer = ref('bing')
|
||||||
const searchItems = ref<SearchItem[]>([])
|
const searchItems = ref<SearchItem[]>([])
|
||||||
@ -384,6 +403,8 @@ function handleInput() {
|
|||||||
tag: item.tag,
|
tag: item.tag,
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
updateSearchPanelPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handdleKeyup(e: KeyboardEvent) {
|
function handdleKeyup(e: KeyboardEvent) {
|
||||||
@ -559,6 +580,27 @@ function goExtra(link: string) {
|
|||||||
window.open(link, '_blank')
|
window.open(link, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSearchPanelPosition() {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
const rect = searchShellRef.value?.getBoundingClientRect()
|
||||||
|
if (!rect) return
|
||||||
|
|
||||||
|
const viewportPadding = 24
|
||||||
|
const offsetLeft = 112
|
||||||
|
const left = Math.max(viewportPadding, rect.left + offsetLeft)
|
||||||
|
const availableWidth = Math.max(280, window.innerWidth - left - viewportPadding)
|
||||||
|
const width = Math.min(672, availableWidth, Math.max(320, rect.width - offsetLeft))
|
||||||
|
const availableHeight = Math.max(180, window.innerHeight - rect.bottom - 32)
|
||||||
|
|
||||||
|
searchPanelStyle.value = {
|
||||||
|
left: `${Math.round(left)}px`,
|
||||||
|
top: `${Math.round(rect.bottom + 14)}px`,
|
||||||
|
width: `${Math.round(width)}px`,
|
||||||
|
maxHeight: `${Math.round(Math.min(availableHeight, 420))}px`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function search() {
|
function search() {
|
||||||
const keyword = searchWord.value.trim()
|
const keyword = searchWord.value.trim()
|
||||||
if (!keyword) return
|
if (!keyword) return
|
||||||
@ -642,6 +684,10 @@ const handleResize = () => {
|
|||||||
navStyle.value = {
|
navStyle.value = {
|
||||||
height: `${window.innerHeight - (searchHeight + searchH)}px`
|
height: `${window.innerHeight - (searchHeight + searchH)}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchBox.value) {
|
||||||
|
updateSearchPanelPosition()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleWindowKeyup = (event: Event) => {
|
const handleWindowKeyup = (event: Event) => {
|
||||||
@ -656,16 +702,27 @@ onMounted(() => {
|
|||||||
navStyle.value = {
|
navStyle.value = {
|
||||||
height: `${window.innerHeight - navcards.value.getBoundingClientRect().y}px`
|
height: `${window.innerHeight - navcards.value.getBoundingClientRect().y}px`
|
||||||
}
|
}
|
||||||
|
searchInputRef.value?.focus()
|
||||||
|
|
||||||
|
|
||||||
getNavList()
|
getNavList()
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
|
window.addEventListener('scroll', handleResize, true)
|
||||||
window.addEventListener('keyup', handleWindowKeyup)
|
window.addEventListener('keyup', handleWindowKeyup)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', handleResize)
|
window.removeEventListener('resize', handleResize)
|
||||||
|
window.removeEventListener('scroll', handleResize, true)
|
||||||
window.removeEventListener('keyup', handleWindowKeyup)
|
window.removeEventListener('keyup', handleWindowKeyup)
|
||||||
removeTimer()
|
removeTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(searchBox, (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
updateSearchPanelPosition()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
@ -685,7 +742,6 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
:deep(.n-layout-sider) {
|
:deep(.n-layout-sider) {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
backdrop-filter: blur(18px);
|
|
||||||
border-left: none !important;
|
border-left: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
@ -702,6 +758,7 @@ onBeforeUnmount(() => {
|
|||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
padding-right: 0.2rem;
|
padding-right: 0.2rem;
|
||||||
min-height: 40px !important;
|
min-height: 40px !important;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.search-select .n-base-selection__border),
|
:deep(.search-select .n-base-selection__border),
|
||||||
@ -718,7 +775,22 @@ onBeforeUnmount(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 2.5rem !important;
|
min-height: 2.5rem !important;
|
||||||
border-radius: 9999px !important;
|
border-radius: 9999px !important;
|
||||||
background: transparent !important;
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.08)) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.36),
|
||||||
|
inset -1px 0 0 rgba(255, 255, 255, 0.1);
|
||||||
|
padding-right: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-select .n-base-selection-input) {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #42556b !important;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-select .n-base-selection__suffix) {
|
||||||
|
color: #6d839a !important;
|
||||||
|
margin-right: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-input) {
|
:deep(.n-input) {
|
||||||
@ -734,6 +806,220 @@ onBeforeUnmount(() => {
|
|||||||
color: #314155 !important;
|
color: #314155 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.glass-surface,
|
||||||
|
.glass-nav-card {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.58) !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 22px 40px rgba(86, 111, 137, 0.14),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
|
||||||
|
backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface {
|
||||||
|
box-shadow:
|
||||||
|
0 18px 38px rgba(86, 111, 137, 0.12),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.68),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.14) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-floating-surface {
|
||||||
|
box-shadow:
|
||||||
|
0 24px 52px rgba(86, 111, 137, 0.18),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.74),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-shell {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-suggest-portal {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-suggest-panel {
|
||||||
|
overflow: auto;
|
||||||
|
transform-origin: top left;
|
||||||
|
animation: search-panel-enter 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-suggest-item {
|
||||||
|
min-height: 3.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes search-panel-enter {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-6px) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav-card::before,
|
||||||
|
.glass-nav-card::after,
|
||||||
|
.glass-surface::before,
|
||||||
|
.glass-surface::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav-card::before,
|
||||||
|
.glass-surface::before {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
|
||||||
|
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav-card::after,
|
||||||
|
.glass-surface::after {
|
||||||
|
inset: 1px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav-card:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.74) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 28px 48px rgba(86, 111, 137, 0.18),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-nav-card--warm {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 249, 242, 0.4), rgba(255, 245, 231, 0.2)),
|
||||||
|
linear-gradient(180deg, rgba(255, 236, 211, 0.14), rgba(244, 225, 204, 0.06)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.glass-nav-card > .n-card__content) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-surface > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.58) !important;
|
||||||
|
border-radius: 22px !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.16)),
|
||||||
|
linear-gradient(180deg, rgba(241, 248, 252, 0.2), rgba(224, 236, 245, 0.08)) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 24px 52px rgba(86, 111, 137, 0.18),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.74),
|
||||||
|
inset 0 -1px 0 rgba(255, 255, 255, 0.16) !important;
|
||||||
|
backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
-webkit-backdrop-filter: blur(24px) saturate(165%);
|
||||||
|
transform-origin: top left;
|
||||||
|
animation: search-panel-enter 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu::before),
|
||||||
|
:global(.search-select-menu::after) {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu::before) {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.08) 48%, rgba(151, 197, 224, 0.08)),
|
||||||
|
radial-gradient(circle at top left, rgba(255, 255, 255, 0.45), transparent 42%);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu::after) {
|
||||||
|
inset: 1px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14), transparent 36%, rgba(255, 255, 255, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-menu-option-wrapper),
|
||||||
|
:global(.search-select-menu .n-base-select-menu__empty) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-menu-option-wrapper) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option) {
|
||||||
|
min-height: 2.45rem;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0.7rem !important;
|
||||||
|
padding-right: 0.7rem !important;
|
||||||
|
border-radius: 16px;
|
||||||
|
color: #475569;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease,
|
||||||
|
border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option:not(.n-base-select-option--selected).n-base-select-option--pending) {
|
||||||
|
transform: none;
|
||||||
|
background: rgba(255, 255, 255, 0.62);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 16px rgba(120, 144, 168, 0.1),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option--selected) {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 240, 246, 0.98), rgba(255, 233, 223, 0.96));
|
||||||
|
color: #364152;
|
||||||
|
box-shadow:
|
||||||
|
0 10px 18px rgba(236, 102, 171, 0.12),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option--selected .n-base-select-option__content),
|
||||||
|
:global(.search-select-menu .n-base-select-option--selected .n-base-select-option__check) {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option__content) {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-option__check) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.search-select-menu .n-base-select-menu__empty) {
|
||||||
|
padding: 0.6rem 0.4rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.modal-input .n-input) {
|
:deep(.modal-input .n-input) {
|
||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
border: 1px solid rgba(198, 212, 226, 0.92) !important;
|
border: 1px solid rgba(198, 212, 226, 0.92) !important;
|
||||||
@ -819,3 +1105,4 @@ onBeforeUnmount(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
|
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
|
||||||
:style="boxStyle"
|
:style="boxStyle">
|
||||||
>
|
<div
|
||||||
<div class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]"></div>
|
class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]">
|
||||||
<div class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]"></div>
|
</div>
|
||||||
<div class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]"></div>
|
<div
|
||||||
|
class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]">
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="relative z-[1] mx-auto flex h-full w-[86%] gap-6 py-6">
|
<div class="relative z-[1] mx-auto flex h-full w-[86%] gap-6 py-6">
|
||||||
<aside class="hidden w-[21rem] shrink-0 lg:block">
|
<aside class="hidden w-[21rem] shrink-0 lg:block">
|
||||||
<div class="sticky top-6">
|
<div class="sticky top-6">
|
||||||
<section class="relative rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
|
<section class="blog-detail-card p-6">
|
||||||
<div class="relative z-[1]">
|
<div class="relative z-[1]">
|
||||||
<h1 class="mt-3 text-[2rem] text-center leading-[1.2] text-[#2c3a4a]">阅读目录</h1>
|
<h1 class="mt-3 text-[2rem] text-center leading-[1.2] text-[#2c3a4a]">阅读目录</h1>
|
||||||
|
|
||||||
<div class="mt-6 rounded-[22px] border border-white/70 bg-white/58 p-4 shadow-[0_12px_28px_rgba(112,128,118,0.1)]">
|
<div class="blog-detail-card-soft mt-6 p-4">
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-3 flex items-center justify-between">
|
||||||
<span class="text-sm font-semibold text-[#526376]">目录</span>
|
<span class="text-sm font-semibold text-[#526376]">目录</span>
|
||||||
<span class="rounded-full cursor-pointer bg-primary px-3 py-1 text-xs text-white">正文导航</span>
|
<span class="rounded-full cursor-pointer bg-primary px-3 py-1 text-xs text-white">正文导航</span>
|
||||||
@ -23,35 +28,30 @@
|
|||||||
<div class="text-[13px] leading-5 text-[#526376]">
|
<div class="text-[13px] leading-5 text-[#526376]">
|
||||||
<template v-for="item in tocItems" :key="item.id">
|
<template v-for="item in tocItems" :key="item.id">
|
||||||
<details v-if="item.children.length" class="group" :open="item.open">
|
<details v-if="item.children.length" class="group" :open="item.open">
|
||||||
<summary class="flex cursor-pointer list-none items-center gap-2 py-1 text-[#526376] marker:hidden select-none">
|
<summary
|
||||||
<span class="text-[11px] text-[#8a98a1] transition-transform duration-200 group-open:rotate-90">▶</span>
|
class="flex cursor-pointer list-none items-center gap-2 py-1 text-[#526376] marker:hidden select-none">
|
||||||
<span
|
<span
|
||||||
class="truncate transition-colors duration-200"
|
class="text-[11px] text-[#8a98a1] transition-transform duration-200 group-open:rotate-90">▶</span>
|
||||||
|
<span class="truncate transition-colors duration-200"
|
||||||
:class="isNodeActive(item) ? 'text-primary font-semibold' : 'hover:text-[#2f4050]'"
|
:class="isNodeActive(item) ? 'text-primary font-semibold' : 'hover:text-[#2f4050]'"
|
||||||
@click.stop="scrollToHeading(item.id)"
|
@click.stop="scrollToHeading(item.id)">
|
||||||
>
|
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</span>
|
</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="ml-4 border-l border-[#d7dfdf] pl-3">
|
<div class="ml-4 border-l border-[#d7dfdf] pl-3">
|
||||||
<div v-for="child in item.children" :key="child.id" class="py-1">
|
<div v-for="child in item.children" :key="child.id" class="py-1">
|
||||||
<div
|
<div class="cursor-pointer truncate transition-colors duration-200"
|
||||||
class="cursor-pointer truncate transition-colors duration-200"
|
|
||||||
:class="activeHeadingId === child.id ? 'text-primary font-semibold' : 'text-[#687983] hover:text-[#2f4050]'"
|
:class="activeHeadingId === child.id ? 'text-primary font-semibold' : 'text-[#687983] hover:text-[#2f4050]'"
|
||||||
@click="scrollToHeading(child.id)"
|
@click="scrollToHeading(child.id)">
|
||||||
>
|
|
||||||
{{ child.text }}
|
{{ child.text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<div
|
<div v-else class="cursor-pointer py-1 transition-colors duration-200"
|
||||||
v-else
|
|
||||||
class="cursor-pointer py-1 transition-colors duration-200"
|
|
||||||
:class="activeHeadingId === item.id ? 'text-primary font-semibold' : 'text-[#526376] hover:text-[#2f4050]'"
|
:class="activeHeadingId === item.id ? 'text-primary font-semibold' : 'text-[#526376] hover:text-[#2f4050]'"
|
||||||
@click="scrollToHeading(item.id)"
|
@click="scrollToHeading(item.id)">
|
||||||
>
|
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -66,23 +66,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section class="min-w-0 flex-1 overflow-hidden rounded-[30px] border border-white/70 bg-transparent p-4 ring-1 ring-inset ring-[rgba(255,255,255,0.38)] shadow-[0_20px_46px_rgba(109,126,117,0.1)] sm:p-6">
|
<section class="blog-list-panel min-w-0 flex-1 overflow-hidden p-4 sm:p-6">
|
||||||
<n-scrollbar ref="detailScrollbar" :style="contentStyle" trigger="none" class="pr-1 mb-4 overflow-hidden">
|
<n-scrollbar ref="detailScrollbar" :style="contentStyle" trigger="none" class="pr-1 mb-4 rounded-[3em] overflow-hidden">
|
||||||
<div class="space-y-5 pr-1">
|
<div class="space-y-5 pr-1">
|
||||||
<section class="rounded-[28px] border border-white/68 bg-transparent p-6 ring-1 ring-inset ring-[rgba(255,255,255,0.34)] shadow-[0_18px_38px_rgba(116,132,124,0.09)]">
|
<section class="blog-detail-card p-6">
|
||||||
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章详情</div>
|
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章详情</div>
|
||||||
<h1 class="my-4 !w-full ml-1/2 text-center text-[1.9rem] font-semibold leading-[1.3] text-[#2f4050] sm:text-[2.15rem]">
|
<h1
|
||||||
|
class="my-4 !w-full ml-1/2 text-center text-[1.9rem] font-semibold leading-[1.3] text-[#2f4050] sm:text-[2.15rem]">
|
||||||
{{ blogData.title || '文章内容' }}
|
{{ blogData.title || '文章内容' }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<n-breadcrumb class="shrink-0 rounded-full border border-white/65 bg-transparent px-4 text-[#526376]" separator="|">
|
<n-breadcrumb class="shrink-0 rounded-full border border-white/52 bg-white/16 px-4 text-[#526376]"
|
||||||
|
separator="|">
|
||||||
<n-breadcrumb-item href="/blog/blog">文章</n-breadcrumb-item>
|
<n-breadcrumb-item href="/blog/blog">文章</n-breadcrumb-item>
|
||||||
<n-breadcrumb-item>{{ blogData.title }}</n-breadcrumb-item>
|
<n-breadcrumb-item>{{ blogData.title }}</n-breadcrumb-item>
|
||||||
</n-breadcrumb>
|
</n-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center justify-right gap-x-5 gap-y-2 text-sm text-[#708091]">
|
<div class="flex flex-wrap items-center justify-right gap-x-5 gap-y-2 text-sm text-[#708091]">
|
||||||
<em class="flex items-center gap-1 not-italic text-primary">
|
<em class="flex items-center gap-1 not-italic text-primary">
|
||||||
<n-icon><icon-author /></n-icon>
|
<n-icon><icon-author /></n-icon>
|
||||||
{{ blogData.nickname }}
|
{{ blogData.nickname }}
|
||||||
@ -94,19 +96,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-wrap justify-center gap-3">
|
<div class="mt-4 flex flex-wrap justify-center gap-3">
|
||||||
<div
|
<div v-for="(item, index) in tags" :key="index" :style="{ backgroundColor: getTagColor(item) }"
|
||||||
v-for="item in tags"
|
class="inline-flex cursor-pointer items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]">
|
||||||
:key="item"
|
|
||||||
:style="{ backgroundColor: getTagColor(item) }"
|
|
||||||
class="inline-flex cursor-pointer items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]"
|
|
||||||
>
|
|
||||||
<n-icon><icon-tag /></n-icon>
|
<n-icon><icon-tag /></n-icon>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="rounded-[26px] border border-white/66 bg-transparent p-4 ring-1 ring-inset ring-[rgba(255,255,255,0.32)] shadow-[0_16px_34px_rgba(116,132,124,0.09)] sm:p-5">
|
<section class="blog-detail-card p-4 sm:p-5">
|
||||||
<n-collapse @item-header-click="AISum" arrow-placement="right">
|
<n-collapse @item-header-click="AISum" arrow-placement="right">
|
||||||
<n-collapse-item>
|
<n-collapse-item>
|
||||||
<template #header>
|
<template #header>
|
||||||
@ -127,14 +125,21 @@
|
|||||||
</n-collapse>
|
</n-collapse>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="rounded-[26px] border border-white/66 bg-transparent px-5 py-6 ring-1 ring-inset ring-[rgba(255,255,255,0.32)] shadow-[0_18px_36px_rgba(116,132,124,0.09)] sm:px-8 sm:py-8">
|
<section class="blog-detail-card px-5 py-6 sm:px-8 sm:py-8">
|
||||||
<MdPreview
|
<MdPreview class="relative article-preview !bg-transparent" :modelValue="markdown" />
|
||||||
class="relative article-preview !bg-transparent"
|
|
||||||
:modelValue="markdown"
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-show="showBackToTop"
|
||||||
|
type="button"
|
||||||
|
aria-label="回到顶部"
|
||||||
|
class="absolute bottom-6 right-6 z-[3] flex h-11 w-11 items-center justify-center rounded-full outline-none border-none bg-white/24 text-primary shadow-[0_16px_28px_rgba(109,126,117,0.14),inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[16px] transition-all duration-300 hover:-translate-y-1 hover:bg-white/36"
|
||||||
|
@click="scrollToTop"
|
||||||
|
>
|
||||||
|
<icon-backtop class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,6 +228,7 @@ const editorRef = ref<ExposeParam>()
|
|||||||
const detailScrollbar = ref<DetailScrollbarInst | null>(null)
|
const detailScrollbar = ref<DetailScrollbarInst | null>(null)
|
||||||
const scrollElement = ref<HTMLElement | undefined>(undefined)
|
const scrollElement = ref<HTMLElement | undefined>(undefined)
|
||||||
const activeHeadingId = ref('')
|
const activeHeadingId = ref('')
|
||||||
|
const showBackToTop = ref(false)
|
||||||
const headingElementMap = shallowRef(new Map<string, HTMLElement>())
|
const headingElementMap = shallowRef(new Map<string, HTMLElement>())
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const nav = $store.nav.useNavStore() as { navH: number }
|
const nav = $store.nav.useNavStore() as { navH: number }
|
||||||
@ -353,6 +359,7 @@ function syncPreviewHeadings() {
|
|||||||
|
|
||||||
function updateActiveHeading() {
|
function updateActiveHeading() {
|
||||||
const container = scrollElement.value
|
const container = scrollElement.value
|
||||||
|
showBackToTop.value = !!container && container.scrollTop > 240
|
||||||
const previewRoot = getPreviewRoot()
|
const previewRoot = getPreviewRoot()
|
||||||
if (!container || !previewRoot) return
|
if (!container || !previewRoot) return
|
||||||
|
|
||||||
@ -392,6 +399,14 @@ function scrollToHeading(id: string) {
|
|||||||
activeHeadingId.value = id
|
activeHeadingId.value = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
detailScrollbar.value?.scrollTo?.({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
})
|
||||||
|
activeHeadingId.value = tocItems.value[0]?.id || ''
|
||||||
|
}
|
||||||
|
|
||||||
watch([markdown, tocItems], async () => {
|
watch([markdown, tocItems], async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -416,18 +431,18 @@ async function getBlogDetail() {
|
|||||||
async function AISum() {
|
async function AISum() {
|
||||||
if (aimask.value || !blogData.value.cont) return
|
if (aimask.value || !blogData.value.cont) return
|
||||||
|
|
||||||
const response = await fetch('http://38.12.26.19:8317/v1/chat/completions', {
|
const response = await fetch('https://api.siliconflow.cn/v1/chat/completions', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: 'Bearer sk-7wys75B2TsQYceMbZ'
|
Authorization: 'Bearer sk-jwwmhmxsjtseyekknqmamlvzmrkmwfvuacnssbwfufogrkdg'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: 'gpt-5.4',
|
model: 'Qwen/Qwen3-8B',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: '你是一个摘要生成工具,请用中文总结我发送给你的文章内容,不要换行,不要超过300字,只介绍文章内容,不需要提出建议。开头固定为“这篇文章介绍了”,语气自然一些。'
|
content: '你是一个摘要生成工具,请用中文总结我发送给你的文章内容,不要换行,不要超过300字,只介绍文章内容,语气需要俏皮一些,不需要提出建议。开头固定为“这篇文章介绍了”。'
|
||||||
},
|
},
|
||||||
{ role: 'user', content: blogData.value.cont }
|
{ role: 'user', content: blogData.value.cont }
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,138 +1,193 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
|
class="relative overflow-hidden bg-[radial-gradient(circle_at_12%_18%,rgba(215,177,123,0.24),transparent_24%),radial-gradient(circle_at_86%_12%,rgba(120,162,182,0.18),transparent_24%),radial-gradient(circle_at_50%_100%,rgba(176,198,172,0.14),transparent_28%),linear-gradient(180deg,#efe7da_0%,#e8f0ef_44%,#f3f1e8_100%)]"
|
||||||
:style="boxStyle"
|
:style="boxStyle">
|
||||||
>
|
<div
|
||||||
<div class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]"></div>
|
class="pointer-events-none absolute left-[-5rem] top-6 h-80 w-80 rounded-full bg-[rgba(234,176,107,0.45)] opacity-48 blur-[88px]">
|
||||||
<div class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]"></div>
|
</div>
|
||||||
<div class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]"></div>
|
<div
|
||||||
|
class="pointer-events-none absolute right-[-5rem] top-32 h-[22rem] w-[22rem] rounded-full bg-[rgba(118,176,214,0.32)] opacity-48 blur-[88px]">
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 opacity-14 [background-image:linear-gradient(rgba(255,255,255,0.18)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.18)_1px,transparent_1px)] [background-size:36px_36px] [mask-image:linear-gradient(180deg,rgba(0,0,0,0.8),transparent_85%)]">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="relative z-[1] mx-auto flex h-full w-[86%] gap-6 py-6">
|
<div class="relative z-[1] mx-auto flex h-full w-[86%] items-stretch gap-6 py-6">
|
||||||
<aside class="hidden w-[21rem] shrink-0 lg:block">
|
<aside class="hidden h-full w-[21rem] shrink-0 lg:block">
|
||||||
<div class="sticky top-6">
|
<section class="blog-glass-panel flex h-full flex-col p-5">
|
||||||
<section class="relative rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
|
<div class="relative z-[1] flex h-full min-h-0 flex-col">
|
||||||
<div class="relative z-[1]">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<h1 class="mt-3 text-[2rem] text-center leading-[1.2] text-[#2c3a4a]">文章分类</h1>
|
<div class="min-w-0">
|
||||||
<div class="mt-6 grid gap-4">
|
<h1 class="mt-2 text-[1.8rem] leading-[1.15] text-[#2c3a4a]">文章分类</h1>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="clearCate"
|
|
||||||
class="flex items-center justify-between rounded-[22px] border px-4 py-4 text-left transition-[transform,box-shadow,border-color,background-color,color] duration-300 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)]"
|
|
||||||
:class="currentCateIdx === -1
|
|
||||||
? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_16px_34px_rgba(236,102,171,0.24)]'
|
|
||||||
: 'border-white/70 bg-white/66 text-[#435468] shadow-[0_12px_28px_rgba(112,128,118,0.1)] hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/76'"
|
|
||||||
>
|
|
||||||
<span class="flex items-center gap-3">
|
|
||||||
<icon-arti class="h-5 w-5" :class="currentCateIdx === -1 ? 'text-white' : 'text-primary'" />
|
|
||||||
<span class="text-base font-semibold">全部文章</span>
|
|
||||||
</span>
|
|
||||||
<span class="rounded-full px-3 py-1 text-sm font-semibold" :class="currentCateIdx === -1 ? 'bg-white/20 text-white' : 'bg-primary text-white'">
|
|
||||||
{{ allTotal }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-for="(item, idx) in cateList"
|
|
||||||
:key="item.cid"
|
|
||||||
type="button"
|
|
||||||
@click="clickCate(item, idx)"
|
|
||||||
class="flex items-center justify-between rounded-[22px] border px-4 py-4 text-left transition-[transform,box-shadow,border-color,background-color,color] duration-300 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)]"
|
|
||||||
:class="currentCateIdx === idx
|
|
||||||
? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_16px_34px_rgba(236,102,171,0.24)]'
|
|
||||||
: 'border-white/70 bg-white/66 text-[#435468] shadow-[0_12px_28px_rgba(112,128,118,0.1)] hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/76'"
|
|
||||||
>
|
|
||||||
<span class="flex min-w-0 items-center gap-3">
|
|
||||||
<icon-arti class="h-5 w-5 shrink-0" :class="currentCateIdx === idx ? 'text-white' : 'text-primary'" />
|
|
||||||
<span class="truncate text-base font-semibold">{{ item.name }}</span>
|
|
||||||
</span>
|
|
||||||
<span class="shrink-0 rounded-full px-3 py-1 text-sm font-semibold" :class="currentCateIdx === idx ? 'bg-white/20 text-white' : 'bg-primary text-white'">
|
|
||||||
{{ item.total || 0 }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</div>
|
<div class="mt-5 grid min-h-0 flex-1 gap-3"
|
||||||
|
:style="{ gridTemplateRows: `repeat(${categoryButtonCount}, minmax(0, 1fr))` }">
|
||||||
|
<button type="button" @click="clearCate" class="blog-filter-btn" :class="currentCateIdx === -1
|
||||||
|
? 'blog-filter-btn-active'
|
||||||
|
: 'blog-filter-btn-idle'">
|
||||||
|
<span class="relative z-[1] flex min-w-0 items-center gap-4">
|
||||||
|
<span
|
||||||
|
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-[16px] shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
|
:class="currentCateIdx === -1
|
||||||
|
? 'bg-white/18'
|
||||||
|
: 'bg-white/62 text-primary'">
|
||||||
|
<icon-arti class="h-5 w-5" :class="currentCateIdx === -1 ? 'text-white' : 'text-primary'" />
|
||||||
|
</span>
|
||||||
|
<span class="min-w-0">
|
||||||
|
<span class="block text-[0.98rem] font-semibold tracking-[0.01em] leading-[1.2]">全部文章</span>
|
||||||
|
<span class="mt-1 block text-[0.74rem] leading-5"
|
||||||
|
:class="currentCateIdx === -1 ? 'text-white/78' : 'text-[#7b8796]'">
|
||||||
|
浏览站内全部内容
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="blog-filter-total"
|
||||||
|
:class="currentCateIdx === -1 ? 'bg-white/14 text-white' : 'bg-white/68 text-primary'">
|
||||||
|
<span class="text-[1.25rem] font-semibold leading-none">{{ allTotal }}</span>
|
||||||
|
<span class="mt-1 text-[0.62rem] font-semibold tracking-[0.22em]"
|
||||||
|
:class="currentCateIdx === -1 ? 'text-white/72' : 'text-[#91a0af]'">
|
||||||
|
POSTS
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button v-for="(item, idx) in cateList" :key="item.cid" type="button" @click="clickCate(item, idx)"
|
||||||
|
class="blog-filter-btn" :class="currentCateIdx === idx
|
||||||
|
? 'blog-filter-btn-active'
|
||||||
|
: 'blog-filter-btn-idle'">
|
||||||
|
<span class="relative z-[1] flex min-w-0 items-center gap-4">
|
||||||
|
<span
|
||||||
|
class="flex h-11 w-11 shrink-0 items-center justify-center rounded-[18px] shadow-[inset_0_1px_0_rgba(255,255,255,0.25)]"
|
||||||
|
:class="currentCateIdx === idx
|
||||||
|
? 'bg-white/18'
|
||||||
|
: 'bg-white/62 text-primary'">
|
||||||
|
<icon-arti class="h-5 w-5 shrink-0"
|
||||||
|
:class="currentCateIdx === idx ? 'text-white' : 'text-primary'" />
|
||||||
|
</span>
|
||||||
|
<span class="min-w-0">
|
||||||
|
<span class="block truncate text-xl font-semibold tracking-[0.01em] leading-[1.2]">{{
|
||||||
|
item.name }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="blog-filter-total"
|
||||||
|
:class="currentCateIdx === idx ? 'bg-white/14 text-white' : 'bg-white/68 text-primary'">
|
||||||
|
<span class="text-[1.25rem] font-semibold leading-none">{{ item.total || 0 }}</span>
|
||||||
|
<span class="mt-1 text-[0.62rem] font-semibold tracking-[0.22em]"
|
||||||
|
:class="currentCateIdx === idx ? 'text-white/72' : 'text-[#91a0af]'">
|
||||||
|
POSTS
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section class="min-w-0 flex-1 rounded-[30px] border border-white/62 bg-white/42 p-4 shadow-[0_18px_42px_rgba(109,126,117,0.1)] backdrop-blur-[18px] sm:p-6">
|
<section class="blog-list-panel min-w-0 flex h-full flex-1 flex-col p-4 sm:p-6">
|
||||||
|
<div class="pointer-events-none absolute right-[-4rem] top-12 h-40 w-40 rounded-full bg-[rgba(236,102,171,0.16)] blur-[88px]"></div>
|
||||||
|
<div class="pointer-events-none absolute left-[-3rem] bottom-10 h-36 w-36 rounded-full bg-[rgba(118,176,214,0.14)] blur-[82px]"></div>
|
||||||
<div class="mb-5 flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
<div class="mb-5 flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||||
<div>
|
<div class="relative z-[1]">
|
||||||
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章列表</div>
|
<div class="text-[0.82rem] font-semibold tracking-[0.16em] text-[#718195]">文章列表</div>
|
||||||
<div class="mt-2 text-[1.6rem] font-semibold text-[#2f4050]">{{ currentCateLabel }}</div>
|
<div class="mt-2 text-[1.6rem] font-semibold text-[#2f4050]">{{ currentCateLabel }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-flex items-center whitespace-nowrap rounded-full border border-white/80 bg-white/68 px-4 py-[0.65rem] text-[#526376]">
|
<div class="blog-stat-card relative z-[1] min-w-[126px] text-right">
|
||||||
当前 {{ blogList.length }} 篇
|
<div class="relative z-[1] flex items-end justify-end">
|
||||||
|
<span class="relative z-[1] mr-3 text-sm text-[#8c99a8]">当前列表统计</span>
|
||||||
|
<span class="text-[2rem] leading-none font-semibold text-primary">{{ blogList.length }}</span>
|
||||||
|
<span class=" text-sm ml-1 font-medium text-[#748395]">篇</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 flex flex-wrap gap-2 lg:hidden">
|
<div class="mb-4 flex flex-wrap gap-2 lg:hidden">
|
||||||
<button
|
<button type="button" @click="clearCate" class="blog-mobile-filter-pill"
|
||||||
type="button"
|
:class="currentCateIdx === -1 ? 'blog-mobile-filter-pill-active' : ''">
|
||||||
@click="clearCate"
|
|
||||||
class="rounded-full border px-4 py-2 text-sm font-semibold transition-colors duration-200"
|
|
||||||
:class="currentCateIdx === -1 ? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white' : 'border-white/80 bg-white/70 text-[#526376]'"
|
|
||||||
>
|
|
||||||
全部
|
全部
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button v-for="(item, idx) in cateList" :key="item.cid" type="button" @click="clickCate(item, idx)"
|
||||||
v-for="(item, idx) in cateList"
|
class="blog-mobile-filter-pill" :class="currentCateIdx === idx ? 'blog-mobile-filter-pill-active' : ''">
|
||||||
:key="item.cid"
|
|
||||||
type="button"
|
|
||||||
@click="clickCate(item, idx)"
|
|
||||||
class="rounded-full border px-4 py-2 text-sm font-semibold transition-colors duration-200"
|
|
||||||
:class="currentCateIdx === idx ? 'border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white' : 'border-white/80 bg-white/70 text-[#526376]'"
|
|
||||||
>
|
|
||||||
{{ item.name }} · {{ item.total || 0 }}
|
{{ item.name }} · {{ item.total || 0 }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-scrollbar :style="listStyle" trigger="none" class="pr-1">
|
<n-scrollbar trigger="none" class="relative z-[1] min-h-0 flex-1 pr-1">
|
||||||
<div class="space-y-5">
|
<div class="blog-scroll-panel min-h-full">
|
||||||
<div v-if="blogList.length === 0" class="rounded-[24px] border border-dashed border-white/75 bg-white/48 px-6 py-16 text-center text-[#7a8797]">
|
<div class="space-y-5">
|
||||||
暂时还没有相关文章。
|
<div v-if="blogList.length === 0"
|
||||||
|
class="rounded-[24px] border border-dashed border-white/46 bg-white/20 px-6 py-16 text-center text-[#7a8797] backdrop-blur-[16px]">
|
||||||
|
暂时还没有相关文章。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article v-for="(item, index) in blogList" :key="item.aid"
|
||||||
|
class="blog-list-card blog-card-animate group cursor-pointer"
|
||||||
|
:style="{ animationDelay: `${index * 90}ms` }" @click="$router.push(`/blog/${item.aid}`)">
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute -right-10 top-[-2rem] h-28 w-28 rounded-full bg-[rgba(236,102,171,0.18)] transition duration-500 group-hover:scale-125 group-hover:opacity-100">
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute -left-8 bottom-[-2rem] h-24 w-24 rounded-full bg-[rgba(104,170,201,0.16)] transition duration-500 group-hover:translate-x-2">
|
||||||
|
</div>
|
||||||
|
<div class="pointer-events-none absolute inset-x-6 top-0 h-18 bg-[linear-gradient(180deg,rgba(255,255,255,0.24),transparent)] opacity-80 "></div>
|
||||||
|
|
||||||
|
<div class="relative z-[1]">
|
||||||
|
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||||
|
<div
|
||||||
|
class="inline-flex w-fit items-center rounded-full border border-white/58 bg-white/22 px-3 py-1 text-[0.72rem] font-semibold tracking-[0.18em] text-[#7a8797] shadow-[0_8px_16px_rgba(102,123,112,0.08)] backdrop-blur-[14px]">
|
||||||
|
ARTICLE {{ String(index + 1).padStart(2, '0') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap justify-end gap-x-4 gap-y-2 text-sm text-[#708091]">
|
||||||
|
<em class="flex items-center gap-1 not-italic">
|
||||||
|
<n-icon><icon-date /></n-icon>
|
||||||
|
{{ formatTime(item.updated_at, 'YYYY年MM月DD日') }}
|
||||||
|
</em>
|
||||||
|
<em class="flex items-center gap-1 not-italic">
|
||||||
|
<n-icon><icon-pen /></n-icon>
|
||||||
|
{{ getSize(item.cont) }} 字
|
||||||
|
</em>
|
||||||
|
<em class="flex items-center gap-1 not-italic">
|
||||||
|
<n-icon><icon-author /></n-icon>
|
||||||
|
{{ item.nickname }}
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
class="mt-4 text-[1.45rem] font-semibold leading-[1.35] text-primary transition-transform duration-300 group-hover:translate-x-1">
|
||||||
|
{{ item.title }}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-3 line-clamp-3 indent-[2em] leading-[1.9] text-[#66778b]">{{ item.pro }}</p>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-wrap items-center justify-between gap-4">
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<div v-for="(tag, tagIdx) in getTags(item.tags)" :key="tag"
|
||||||
|
:style="{ backgroundColor: getTagColor(tag), animationDelay: `${index * 90 + tagIdx * 60 + 140}ms` }"
|
||||||
|
class="blog-chip-animate inline-flex items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]">
|
||||||
|
<icon-tag2 class="h-4 w-4" />
|
||||||
|
{{ tag }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center gap-2 rounded-full border border-white/52 bg-white/24 px-4 py-2 text-sm font-semibold text-[#5e6f82] shadow-[0_10px_18px_rgba(102,123,112,0.08)] backdrop-blur-[16px] transition-all duration-300 group-hover:translate-x-1 group-hover:text-primary">
|
||||||
|
阅读全文
|
||||||
|
<span class="text-base leading-none">+</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<article
|
<div v-if="blogList.length > page_size"
|
||||||
v-for="item in blogList"
|
class="mt-8 flex justify-center rounded-[22px] border border-white/44 bg-white/18 px-4 py-5 shadow-[0_10px_24px_rgba(116,132,124,0.08)] backdrop-blur-[14px]">
|
||||||
:key="item.aid"
|
<n-pagination v-model:page="page_num" :page-count="total" :page-slot="5" @update:page="getBlogList" />
|
||||||
class="cursor-pointer rounded-[26px] border border-white/72 bg-[linear-gradient(145deg,rgba(255,251,246,0.78),rgba(237,246,244,0.62))] p-6 shadow-[0_16px_34px_rgba(116,132,124,0.12)] backdrop-blur-[18px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1 hover:border-white/90 hover:shadow-[0_22px_42px_rgba(102,123,112,0.16)]"
|
</div>
|
||||||
@click="$router.push(`/blog/${item.aid}`)"
|
|
||||||
>
|
|
||||||
<div class="flex flex-wrap justify-end gap-x-4 gap-y-2 text-sm text-[#708091]">
|
|
||||||
<em class="flex items-center gap-1 not-italic">
|
|
||||||
<n-icon><icon-date /></n-icon>
|
|
||||||
{{ formatTime(item.updated_at, 'YYYY年MM月DD日') }}
|
|
||||||
</em>
|
|
||||||
<em class="flex items-center gap-1 not-italic">
|
|
||||||
<n-icon><icon-pen /></n-icon>
|
|
||||||
{{ getSize(item.cont) }} 字
|
|
||||||
</em>
|
|
||||||
<em class="flex items-center gap-1 not-italic">
|
|
||||||
<n-icon><icon-author /></n-icon>
|
|
||||||
{{ item.nickname }}
|
|
||||||
</em>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="mt-4 text-[1.45rem] font-semibold leading-[1.35] text-primary">{{ item.title }}</h2>
|
|
||||||
<p class="mt-3 line-clamp-3 leading-[1.85] text-[#66778b]">{{ item.pro }}</p>
|
|
||||||
|
|
||||||
<div class="mt-5 flex flex-wrap gap-3">
|
|
||||||
<div
|
|
||||||
v-for="tag in getTags(item.tags)"
|
|
||||||
:key="tag"
|
|
||||||
:style="{ backgroundColor: getTagColor(tag) }"
|
|
||||||
class="inline-flex items-center gap-1 rounded-full px-3 py-1 text-sm text-white shadow-[0_8px_20px_rgba(88,108,125,0.16)]"
|
|
||||||
>
|
|
||||||
<icon-tag2 class="h-4 w-4" />
|
|
||||||
{{ tag }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="blogList.length > page_size" class="mt-8 flex justify-center rounded-[22px] border border-white/70 bg-white/52 px-4 py-5 shadow-[0_10px_24px_rgba(116,132,124,0.08)]">
|
|
||||||
<n-pagination v-model:page="page_num" :page-count="total" :page-slot="5" @update:page="getBlogList" />
|
|
||||||
</div>
|
</div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</section>
|
</section>
|
||||||
@ -208,7 +263,6 @@ const SOFT_TAG_PALETTE = [
|
|||||||
|
|
||||||
const blogList = ref<BlogItem[]>([])
|
const blogList = ref<BlogItem[]>([])
|
||||||
const boxStyle = ref<Record<string, string>>({})
|
const boxStyle = ref<Record<string, string>>({})
|
||||||
const listStyle = ref<Record<string, string>>({})
|
|
||||||
const nav = $store.nav.useNavStore() as { navH: number }
|
const nav = $store.nav.useNavStore() as { navH: number }
|
||||||
const cateList = ref<CateItem[]>([])
|
const cateList = ref<CateItem[]>([])
|
||||||
const currentCateIdx = ref(-1)
|
const currentCateIdx = ref(-1)
|
||||||
@ -219,6 +273,7 @@ const category = ref('')
|
|||||||
const tagColorList = ref<TagColorItem[]>([])
|
const tagColorList = ref<TagColorItem[]>([])
|
||||||
|
|
||||||
const allTotal = computed(() => cateList.value.reduce((acc, cur) => acc + (cur.total || 0), 0))
|
const allTotal = computed(() => cateList.value.reduce((acc, cur) => acc + (cur.total || 0), 0))
|
||||||
|
const categoryButtonCount = computed(() => Math.max(cateList.value.length + 1, 1))
|
||||||
|
|
||||||
const currentCateLabel = computed(() => {
|
const currentCateLabel = computed(() => {
|
||||||
if (currentCateIdx.value === -1) return '全部文章'
|
if (currentCateIdx.value === -1) return '全部文章'
|
||||||
@ -229,7 +284,6 @@ const currentCateLabel = computed(() => {
|
|||||||
function updateLayout() {
|
function updateLayout() {
|
||||||
const viewportHeight = window.innerHeight - nav.navH - 1
|
const viewportHeight = window.innerHeight - nav.navH - 1
|
||||||
boxStyle.value = { height: `${viewportHeight}px` }
|
boxStyle.value = { height: `${viewportHeight}px` }
|
||||||
listStyle.value = { height: `${Math.max(viewportHeight - 124, 280)}px` }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetPaging() {
|
function resetPaging() {
|
||||||
|
|||||||
@ -1,11 +1,75 @@
|
|||||||
import { defineConfig } from "unocss";
|
import { defineConfig, presetUno } from "unocss";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// ...UnoCSS options
|
presets: [presetUno()],
|
||||||
|
shortcuts: {
|
||||||
|
"blog-glass-panel": "relative overflow-hidden rounded-[30px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']",
|
||||||
|
"blog-filter-btn": "relative flex h-full min-h-0 w-full items-center justify-between gap-3 overflow-hidden rounded-[22px] border-none px-4 py-3 text-left outline-none ring-0 backdrop-blur-[26px] transition-[transform,box-shadow,background-color,color] duration-350 [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] before:pointer-events-none before:absolute before:inset-[1px] before:rounded-[20px] before:border-0 before:content-[''] after:pointer-events-none after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.28),transparent_42%)] after:opacity-80 after:content-['']",
|
||||||
|
"blog-filter-btn-idle": "bg-[linear-gradient(135deg,rgba(255,255,255,0.5),rgba(255,255,255,0.18))] text-[#435468] shadow-[0_14px_30px_rgba(112,128,118,0.1)] hover:-translate-y-1 hover:translate-x-1 hover:bg-[linear-gradient(135deg,rgba(255,255,255,0.68),rgba(244,248,247,0.3))] hover:shadow-[0_20px_36px_rgba(112,128,118,0.14)]",
|
||||||
|
"blog-filter-btn-active": "bg-[linear-gradient(135deg,rgba(236,102,171,0.96),rgba(244,138,111,0.88))] text-white shadow-[0_18px_38px_rgba(236,102,171,0.28)]",
|
||||||
|
"blog-stat-card": "relative overflow-hidden rounded-[22px] border border-white/52 bg-[linear-gradient(135deg,rgba(255,255,255,0.28),rgba(240,247,247,0.14))] px-4 py-3 shadow-[0_18px_34px_rgba(112,128,118,0.12),inset_0_1px_0_rgba(255,255,255,0.2)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),transparent_42%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[20px] after:border after:border-white/18 after:content-['']",
|
||||||
|
"blog-filter-total": "relative z-[1] flex min-w-[72px] shrink-0 flex-col items-center justify-center rounded-[18px] border-none px-3 py-2 text-center shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]",
|
||||||
|
"blog-list-panel": "relative overflow-hidden rounded-[30px] border border-white/54 bg-[linear-gradient(145deg,rgba(255,255,255,0.28),rgba(240,247,247,0.14))] shadow-[0_24px_60px_rgba(109,126,117,0.12)] backdrop-blur-[28px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.24),transparent_38%,rgba(151,197,224,0.08))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[28px] after:border after:border-white/22 after:content-['']",
|
||||||
|
"blog-scroll-panel": "relative overflow-hidden rounded-[24px] border border-white/36 bg-[linear-gradient(180deg,rgba(255,255,255,0.16),rgba(255,255,255,0.07))] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[18px]",
|
||||||
|
"blog-detail-card": "relative overflow-hidden rounded-[28px] border border-white/54 bg-[linear-gradient(145deg,rgba(255,255,255,0.32),rgba(240,247,247,0.14))] shadow-[0_18px_42px_rgba(109,126,117,0.12),inset_0_1px_0_rgba(255,255,255,0.24)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.24),transparent_44%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[26px] after:border after:border-white/18 after:content-['']",
|
||||||
|
"blog-detail-card-soft": "relative overflow-hidden rounded-[22px] border border-white/46 bg-[linear-gradient(145deg,rgba(255,255,255,0.24),rgba(244,248,247,0.12))] shadow-[0_14px_28px_rgba(109,126,117,0.09),inset_0_1px_0_rgba(255,255,255,0.2)] backdrop-blur-[18px] before:pointer-events-none before:absolute before:inset-0 before:bg-[radial-gradient(circle_at_top_left,rgba(255,255,255,0.24),transparent_44%)] before:content-['']",
|
||||||
|
"blog-list-card": "relative overflow-hidden rounded-[28px] border border-white/52 bg-[linear-gradient(145deg,rgba(255,255,255,0.34),rgba(240,247,247,0.16))] p-6 shadow-[0_18px_40px_rgba(116,132,124,0.14),inset_0_1px_0_rgba(255,255,255,0.26)] backdrop-blur-[24px] before:pointer-events-none before:absolute before:inset-0 before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),transparent_48%,rgba(151,197,224,0.1))] before:content-[''] after:pointer-events-none after:absolute after:inset-[1px] after:rounded-[26px] after:border after:border-white/18 after:content-[''] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-2 hover:border-white/72 hover:shadow-[0_28px_52px_rgba(102,123,112,0.18),inset_0_1px_0_rgba(255,255,255,0.32)]",
|
||||||
|
"blog-card-animate": "opacity-0 [will-change:transform,opacity] motion-safe:[animation:blog-card-in_720ms_cubic-bezier(0.22,1,0.36,1)_both]",
|
||||||
|
"blog-chip-animate": "opacity-0 [will-change:transform,opacity] motion-safe:[animation:blog-chip-in_560ms_cubic-bezier(0.2,0.8,0.2,1)_both]",
|
||||||
|
"blog-mobile-filter-pill": "rounded-full border border-white/72 bg-white/58 px-4 py-2 text-sm font-semibold text-[#526376] shadow-[0_10px_22px_rgba(112,128,118,0.08)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color,background-color,color] duration-250 hover:-translate-y-0.5 hover:border-white/90 hover:bg-white/74",
|
||||||
|
"blog-mobile-filter-pill-active": "border-transparent bg-[linear-gradient(135deg,#ec66ab,#f48a6f)] text-white shadow-[0_12px_24px_rgba(236,102,171,0.22)]",
|
||||||
|
"home-glass-surface": "relative border border-white/58 bg-[linear-gradient(135deg,rgba(255,255,255,0.34),rgba(255,255,255,0.16)),linear-gradient(180deg,rgba(241,248,252,0.2),rgba(224,236,245,0.08))] shadow-[0_18px_38px_rgba(86,111,137,0.12),inset_0_1px_0_rgba(255,255,255,0.68),inset_0_-1px_0_rgba(255,255,255,0.14)] backdrop-blur-[24px] saturate-[165%] before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:bg-[linear-gradient(135deg,rgba(255,255,255,0.34),rgba(255,255,255,0.08)_48%,rgba(151,197,224,0.08)),radial-gradient(circle_at_top_left,rgba(255,255,255,0.45),transparent_42%)] before:opacity-[0.95] before:content-[''] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-white/24 after:bg-[linear-gradient(180deg,rgba(255,255,255,0.14),transparent_36%,rgba(255,255,255,0.08))] after:content-['']",
|
||||||
|
"home-glass-floating": "shadow-[0_24px_52px_rgba(86,111,137,0.18),inset_0_1px_0_rgba(255,255,255,0.74),inset_0_-1px_0_rgba(255,255,255,0.16)]",
|
||||||
|
"home-nav-card": "home-glass-surface transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:border-white/74 hover:shadow-[0_28px_48px_rgba(86,111,137,0.18),inset_0_1px_0_rgba(255,255,255,0.78),inset_0_-1px_0_rgba(255,255,255,0.2)]",
|
||||||
|
"home-nav-card-warm": "bg-[linear-gradient(135deg,rgba(255,249,242,0.4),rgba(255,245,231,0.2)),linear-gradient(180deg,rgba(255,236,211,0.14),rgba(244,225,204,0.06))]",
|
||||||
|
"home-search-select": "rounded-full bg-transparent px-0.5 py-0 min-h-[2.5rem] relative before:pointer-events-none before:absolute before:inset-[1px] before:rounded-[9999px] before:bg-[linear-gradient(135deg,rgba(255,255,255,0.28),rgba(255,255,255,0.08))] before:shadow-[inset_0_1px_0_rgba(255,255,255,0.36),inset_-1px_0_0_rgba(255,255,255,0.1)] before:content-['']",
|
||||||
|
"home-search-panel-animate": "overflow-auto origin-top motion-safe:[animation:home-search-panel-enter_180ms_cubic-bezier(0.22,1,0.36,1)_both]",
|
||||||
|
},
|
||||||
|
preflights: [
|
||||||
|
{
|
||||||
|
getCSS: () => `
|
||||||
|
@keyframes blog-card-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 28px, 0) scale(0.97);
|
||||||
|
}
|
||||||
|
65% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blog-chip-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 12px, 0) scale(0.92);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes home-search-panel-enter {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-6px) scale(0.98);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: "#ec66ab",
|
primary: "#ec66ab",
|
||||||
deepp: "#a7415d"
|
deepp: "#a7415d",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user