Compare commits

...

No commits in common. "nuxt" and "vue" have entirely different histories.
nuxt ... vue

118 changed files with 5287 additions and 9522 deletions

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
VITE_BASE_URL = "/api"
VITE_DEV = true

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
VITE_BASE_URL = "/"
VITE_DEV = false

View File

@ -0,0 +1,46 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
run: |
git clone https://cantyonion.site/git/cantyonion/WebIndex.git
- name: Set up Node.js
uses: https://gitea.com/actions/setup-node@v4
with:
node-version: '22' # 可以根据需要选择 Node.js 版本
- name: Set NPM Mirror
run: npm config set registry https://registry.npmmirror.com
- name: Install dependencies
working-directory: WebIndex
run: npm install
- name: Build project
working-directory: WebIndex
run: npm run build
- name: Compress build artifacts
run: |
mkdir -p artifacts
zip -r artifacts/build.zip WebIndex/dist/
- name: Upload Release Asset
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: |-
artifacts/build.zip

38
.gitignore vendored
View File

@ -1,24 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Misc
.DS_Store
.fleet
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
# Local env files
.env
.env.*
!.env.example
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

7
LICENCE Normal file
View File

@ -0,0 +1,7 @@
Copyright © 2024 Jeffrey Hsu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,75 +1,14 @@
# Nuxt Minimal Starter
# My WebIndex
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
## How to build
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
## Run dev server
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
npm run dev
```

30
app.vue
View File

@ -1,30 +0,0 @@
<script setup lang="ts">
const loading = ref<HTMLDivElement | null>(null),
{ $mitt } = useNuxtApp()
$mitt.on('startLoading', (on) => {
if (on) {
loading.value?.classList.remove('stop')
removeMobileTopColor()
}
else {
loading.value?.classList.add('stop')
setMobileTopColor()
}
})
</script>
<template>
<div
ref="loading"
class="loading"
>
<logo-text-logo-with-index-row class="h-12 lg:h-[72px] fill-gray-600" />
</div>
<div class="relative flex min-h-screen flex-col overflow-hidden bg-blue-50">
<nav-bar />
<nuxt-page />
<footer-main />
<alert-notification />
</div>
</template>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

View File

@ -1,37 +0,0 @@
<script lang="ts" setup>
defineProps<{
icon: string
title: string
message: string
}>()
</script>
<template>
<div class="flex flex-grow flex-col items-center justify-center p-4 text-center min-h-[30rem]">
<div class="w-full max-w-md rounded-xl bg-white p-6 shadow-lg">
<div class="mb-4 text-6xl">
<span
v-if="icon"
class="block text-blue-500"
>{{ icon }}</span>
<span v-else></span>
</div>
<h1 class="mb-2 text-2xl font-bold">
{{ title }}
</h1>
<p class="mb-6 text-gray-600">
{{ message }}
</p>
<router-link
to="/"
class="inline-block rounded-lg bg-blue-500 px-6 py-2 text-white shadow transition duration-200 hover:bg-blue-600"
>
返回首页
</router-link>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,48 +0,0 @@
<script lang="ts" setup>
const { $mitt } = useNuxtApp(),
notifications = ref<INotification[]>([]),
typeClasses = {
success: 'bg-green-100 text-green-800 border border-green-400',
error: 'bg-red-100 text-red-800 border border-red-400',
warning: 'bg-yellow-100 text-yellow-800 border border-yellow-400',
info: 'bg-blue-100 text-blue-800 border border-blue-400',
},
addNotification = (notification: INotification) => {
notifications.value.push(notification)
//
setTimeout(() => {
notifications.value.shift()
}, 5000)
console.log(1)
}
$mitt.on('eventBus', addNotification)
onBeforeUnmount(() => {
$mitt.off('eventBus', addNotification)
})
</script>
<template>
<div class="fixed top-5 right-5 z-50 space-y-4">
<div
v-for="(notification, index) in notifications"
:key="index"
:class="[
'p-4 rounded-lg shadow-lg transition-all duration-300',
typeClasses[notification.level],
]"
>
<h3 class="text-lg font-bold">
{{ notification.title }}
</h3>
<p>{{ notification.message }}</p>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,44 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{
post: IPost
}>(),
pid = useAsyncData('pid', async () => (Math.floor(Math.random() * (7 - 1 + 1)) + 1).toString()),
thumbUrl = computed(() => props.post.fields.thumb.value),
randomId = computed(() => pid.data.value!)
</script>
<template>
<nuxt-link
:title="post.title"
class="container relative overflow-hidden rounded-2xl bg-white transition-all duration-300 ease-in-out h-[31rem] hover:-translate-y-2 hover:cursor-pointer hover:shadow"
:to="{ name: 'article-cid', params: { cid: post.cid } }"
>
<!-- 图片 -->
<div class="relative">
<!-- 渐变 -->
<div class="absolute top-0 right-0 bottom-0 left-0 bg-gradient-to-t from-white" />
<img
v-if="thumbUrl"
:src="thumbUrl"
alt=""
class="aspect-video w-full h-auto object-cover object-center"
>
<card-article-default-image :id="randomId" />
</div>
<!-- 文字部分 -->
<div class="px-5">
<!-- 标题 -->
<h4 class="text-2xl font-bold">{{ post.title }}</h4>
<p class="mt-4 text-lg px-1 text-gray-700">{{ post.digest }}</p>
</div>
</nuxt-link>
</template>
<style scoped>
</style>

View File

@ -1,49 +0,0 @@
<script setup lang="ts">
defineProps<{
id: string
}>()
</script>
<template>
<img
v-if="id === '1'"
src="~/assets/images/common/1.jpg"
alt=""
>
<img
v-else-if="id === '2'"
src="~/assets/images/common/2.jpg"
alt=""
>
<img
v-else-if="id === '3'"
src="~/assets/images/common/3.jpg"
alt=""
>
<img
v-else-if="id === '4'"
src="~/assets/images/common/4.jpg"
alt=""
>
<img
v-else-if="id === '5'"
src="~/assets/images/common/5.jpg"
alt=""
>
<img
v-else-if="id === '6'"
src="~/assets/images/common/6.jpg"
alt=""
>
<img
v-else-if="id === '7'"
src="~/assets/images/common/7.jpg"
alt=""
>
</template>
<style scoped>
img {
@apply aspect-[16/10] w-full h-auto object-cover object-center
}
</style>

View File

@ -1,21 +0,0 @@
<script lang="ts" setup>
defineProps<{
title: string
icon: string[]
}>()
</script>
<template>
<div class="my-16 relative">
<h3 class="text-5xl font-bold text-shadow-lg shadow-blue-200 text-blue-400 relative z-10">
{{ title }}
</h3>
<div class="h-12 w-auto text-5xl text-blue-200 absolute left-48 top-0 -translate-y-6">
<font-awesome-icon :icon="icon" />
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,135 +0,0 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
// 使 ref x y
const mouseX = ref(0),
mouseY = ref(0),
typedString = ['Hello, Welcome to this site!^1000', '欢迎访问本网站!^1000'],
littleWidget = [
{
icon: ['fab', 'qq'],
url: 'https://qm.qq.com/q/9fhtO8JJt0',
title: 'QQ',
},
{
icon: ['fab', 'github-alt'],
url: 'https://github.com/Aurora1949',
title: 'Github',
},
{
icon: ['fab', 'weibo'],
url: 'https://weibo.com/u/7923648952',
title: '微博',
},
{
icon: ['fab', 'steam-symbol'],
url: 'https://steamcommunity.com/id/cantyonion/',
title: 'Steam',
},
],
//
handleMouseMove = (event: MouseEvent) => {
//
mouseX.value = -(event.clientX / window.innerWidth - 0.5) * 100
mouseY.value = -(event.clientY / window.innerHeight - 0.5) * 100
},
debouncedMouseMove = debounce(handleMouseMove, 5),
//
backgroundStyle = computed(() => ({
backgroundPosition: `${mouseX.value}px ${mouseY.value}px`,
}))
onMounted(() => {
})
</script>
<template>
<div
:style="backgroundStyle"
class="relative flex h-svh w-full items-center justify-center bg-blue-400 bg-fixed bg min-h-[40rem]"
@mousemove="debouncedMouseMove"
>
<div
class="relative mx-4 w-full rounded-xl bg-white p-8 transition-transform duration-300 min-h-96
md:mx-0 md:max-w-screen-md"
>
<p class="text-gray-500">
你好欢迎来到
</p>
<div class="relative mt-4 h-72 text-5xl text-blue-400 transition-all text-shadow-lg shadow-blue-200 md:text-6xl lg:text-7xl">
<h1 style="font-family: BaconyScript,sans-serif">
CantyOni_on's
</h1>
<h1 class="font-bold">
超级基地
</h1>
<div class="absolute -z-10 text-blue-100 top-[3px] left-[3px]">
<h1
class=""
style="font-family: BaconyScript,sans-serif"
>
CantyOni_on's
</h1>
<h1 class="font-bold">
超级基地
</h1>
</div>
</div>
<!-- TypedJS -->
<div class="flex flex-row text-gray-500">
<vue-typed
:auto-insert-css="true"
:back-speed="30"
:loop="true"
:show-cursor="true"
:strings="typedString"
:type-speed="50"
/>
</div>
<!-- 社交媒体组件 -->
<div class="flex flex-row text-gray-500">
<widget-social-media-widget
v-for="item in littleWidget"
:key="item.url"
:icon="item.icon"
:title="item.title"
:url="item.url"
class="h-10 w-10"
/>
</div>
<!-- 人物背景 -->
<div
class="invisible absolute top-0 -right-6 -bottom-6 opacity-0 transition-all
sm:visible sm:opacity-100 lg:-top-28 lg:-right-28 lg:-bottom-10"
>
<img
src="~/assets/images/common/hoshino.png"
alt=""
class="float-right h-full"
draggable="false"
>
</div>
</div>
<!-- 下箭头 -->
<font-awesome-icon
:icon="['fas', 'chevron-down']"
beat
class="absolute bottom-12 h-auto w-6 text-white"
/>
</div>
</template>
<style scoped>
.bg {
background-image: url("assets/images/common/bg.png");
background-size: 150px 150px;
}
</style>

View File

@ -1,123 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{
commit: IStrictActivity
}>(),
commit_ref_name = computed(() => props.commit.ref_name.substring(props.commit.ref_name.lastIndexOf('/') + 1)),
icon = computed(() => {
switch (props.commit.op_type) {
case 'merge_pull_request':
return ['fas', 'code-merge']
case 'create_repo':
return ['fas', 'book-medical']
case 'delete_branch':
case 'delete_tag':
case 'commit_repo':
default:
return ['fas', 'code-commit']
}
}),
time = computed(() => timeDifference(props.commit.created)),
timeDifference = (dateString: string): string => {
const targetDate = new Date(dateString),
currentDate = new Date(),
diffInMillis = currentDate.getTime() - targetDate.getTime(),
minutes = Math.floor(diffInMillis / (1000 * 60)),
hours = Math.floor(diffInMillis / (1000 * 60 * 60)),
days = Math.floor(diffInMillis / (1000 * 60 * 60 * 24)),
months = Math.floor(diffInMillis / (1000 * 60 * 60 * 24 * 30)),
years = Math.floor(diffInMillis / (1000 * 60 * 60 * 24 * 365))
if (minutes < 60) {
return `${minutes} 分钟前`
}
else if (hours < 24) {
return `${hours} 小时前`
}
else if (days < 30) {
return `${days} 天前`
}
else if (months < 12) {
return `${months} 个月前`
}
return `${years} 年前`
},
calcHeight = (n: number) => {
/*
* Content:
* text-base: 1rem
* my-2: 1rem (0.5 + 0.5)
* Compare URL:
* text-base: 1rem
* */
return `${2 * n + (n > 1 ? 2 : 0)}rem`
}
</script>
<template>
<div class="relative bg-transparent p-4 transition-all duration-500 group lg:hover:scale-105 lg:hover:rounded-xl lg:hover:shadow">
<!-- 标题部分 -->
<div class="relative flex items-center gap-4 text-lg">
<font-awesome-icon
class="h-8 w-10"
:icon="icon"
/>
<p v-if="commit.op_type === 'commit_repo' && commit.content">
推送到了仓库 {{ commit.repo.owner.login }}/{{ commit.repo.name }} <span>{{ commit_ref_name }}</span> 分支
</p>
<p v-else-if="commit.op_type === 'commit_repo' && commit.content === null">
{{ commit.repo.owner.login }}/{{ commit.repo.name }} 创建了分支 {{ commit_ref_name }}
</p>
<p v-else-if="commit.op_type === 'create_repo'">
创建了仓库 {{ commit.repo.owner.login }}/{{ commit.repo.name }}
</p>
<span class="absolute top-0 right-0 hidden lg:block">{{ time }}</span>
</div>
<!-- 提交内容 -->
<div
v-if="commit.content"
class="ml-14 overflow-hidden transition-all duration-500 lg:invisible lg:opacity-0
lg:h-0 lg:group-hover:h-80 lg:group-hover:visible lg:group-hover:opacity-100"
:style="{ maxHeight: calcHeight(commit.content.Commits.length) }"
>
<div
v-for="content in commit.content.Commits"
:key="content.Sha1"
class="my-2 flex items-center gap-2"
>
<font-awesome-icon
class="h-4 w-5"
:icon="icon"
/>
<!-- SHA1 -->
<nuxt-link
:to="`https://cantyonion.site/git/${commit.repo.owner.login}/${commit.repo.name}/commit/${content.Sha1}`"
class="rounded bg-gray-200 px-2 transition-all hover:bg-gray-400"
style="font-family: 'LXGW WenKai Mono', monospace"
>{{ content.Sha1.substring(0, 10) }}</nuxt-link>
<!-- 提交信息 -->
<span class="truncate text-nowrap">{{ content.Message }}</span>
</div>
<!-- 比较链接 -->
<nuxt-link
v-if="commit.content.Commits.length > 1"
:to="`https://cantyonion.site/git/${commit.content.CompareURL}`"
class="text-blue-500 hover:text-blue-700"
style="font-family: 'LXGW WenKai Mono', monospace"
>比较 {{ commit.content.Len }} 提交 &gt;&gt;</nuxt-link>
</div>
<div class="absolute top-0 right-0 bottom-0 left-0 -z-10 transition-all duration-500 lg:group-hover:rounded-xl lg:group-hover:bg-white/10 lg:group-hover:backdrop-blur-2xl" />
</div>
</template>
<style scoped>
</style>

View File

@ -1,71 +0,0 @@
<script lang="ts" setup>
interface Widget {
icon: string[]
url: string
title: string
}
// Const welcomeWords = ['👋']
const littleWidget: Widget[] = [
{
icon: ['fab', 'qq'],
url: 'tencent://message/?uin=1922471905&Site=&Menu=yes',
title: 'QQ',
},
{
icon: ['fab', 'github-alt'],
url: 'https://github.com/Aurora1949',
title: 'Github',
},
{
icon: ['fab', 'weibo'],
url: 'https://weibo.com/u/7923648952',
title: '微博',
},
{
icon: ['fab', 'steam-symbol'],
url: 'https://steamcommunity.com/id/cantyonion/',
title: 'Steam',
},
]
</script>
<template>
<div class="container my-16 flex w-full">
<!-- 头像部分 -->
<div class="hidden h-32 w-32 overflow-hidden rounded-full bg-white hover:animate-pulse sm:block">
<img
alt=""
src="https://q.qlogo.cn/g?b=qq&nk=1922471905&s=640"
>
</div>
<!-- 文字部分 -->
<div class="container flex flex-1 flex-col justify-between px-4 sm:px-8">
<div class="text-4xl">
Jeffrey Hsu
</div>
<div class="text-2xl text-gray-400">
你好👋很高兴认识你
</div>
<!-- <vue-typed-js :strings="['First text', 'Second Text']"> -->
<!-- <h1 class="typing"></h1> -->
<!-- </vue-typed-js> -->
<div class="flex gap-4 text-xl">
<a
v-for="item in littleWidget"
:key="item.url"
:href="item.url"
:title="item.title"
class="flex h-10 w-10 items-center justify-center rounded-full bg-green-400 text-white transition-all hover:scale-125"
>
<font-awesome-icon :icon="item.icon" />
</a>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,99 +0,0 @@
<script lang="ts" setup>
defineProps<{
loading: boolean
error: boolean
empty: boolean
errMsg?: object
}>()
const emit = defineEmits(['reload']),
handleReload = () => {
emit('reload')
}
</script>
<template>
<div
:class="{ 'justify-center': (empty || error) }"
class="container flex"
>
<!-- 加载骨架 -->
<div
v-if="loading"
class="h-56 w-full text-2xl font-bold"
>
<slot name="skeleton">
<div class="grid w-full grid-cols-1 gap-4 overflow-hidden sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div
v-for="index of 3"
:key="index"
class="h-56 animate-pulse rounded-2xl bg-gray-200"
/>
</div>
</slot>
</div>
<div
v-else
class="w-full"
>
<!-- 错误处理 -->
<div
v-if="error"
@click="handleReload"
>
<div class="flex items-center justify-center gap-8">
<p class="text-2xl text-gray-700 border-2 border-black py-2 px-8 rounded-full rounded-br-lg">
载入错误
</p>
<img
class="h-32 w-auto relative"
src="~/assets/images/common/plana_e.png"
alt=""
>
</div>
<div
v-if="errMsg"
class="bg-white mt-4 p-4 border border-dashed rounded-md"
style="font-family: 'LXGW WenKai Mono', monospace"
>
{{ errMsg }}
</div>
<p class="text-right text-xs">
(点击重试)
</p>
</div>
<div v-else>
<!-- 正式内容 -->
<div v-if="empty">
<div
v-if="empty"
class="flex items-center justify-center gap-8"
@click="handleReload"
>
<p class="text-2xl text-gray-700 border-2 border-black py-2 px-8 rounded-full rounded-br-lg">
最近没有动态~
</p>
<img
class="h-32 w-auto relative"
src="~/assets/images/common/plana_e.png"
alt=""
>
</div>
<p class="text-right text-xs">
(点击重试)
</p>
</div>
<slot
v-else
name="default"
/>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,52 +0,0 @@
<script setup lang="ts">
</script>
<template>
<footer class="mt-8 items-end pt-48 pb-8 min-h-48 bg">
<div class="container mx-auto px-4 md:px-0 xl:max-w-screen-xl">
<div class="grid-cols-4 gird min-h-48">
<div>
<div class="flex items-center gap-4">
<logo-no-text-logo
:color="true"
class="h-20 w-20"
/>
<logo-text-logo-with-index-col class="h-16 fill-gray-600" />
</div>
<p class="mt-20 leading-8 text-gray-500">
&copy;2025 All rights reserved.
</p>
</div>
</div>
<div class="flex flex-col text-gray-500 md:flex-row">
<a
class="mr-4 hover:text-black"
href="https://beian.miit.gov.cn/"
target="_blank"
>苏ICP备2022016243号-1</a>
<div>
<img
alt=""
class="inline-block w-4 pb-1"
src="~/assets/images/common/beian.png"
>
<a
class="hover:text-black"
href="https://beian.mps.gov.cn/#/query/webSearch?code=32050602011641/"
target="_blank"
>苏公网安备32050602011641</a>
</div>
</div>
</div>
</footer>
</template>
<style scoped lang="scss">
.bg {
background: url("assets/images/common/footer-bg.svg");
background-size: 100% auto;
background-color: white;
background-repeat: no-repeat;
}
</style>

View File

@ -1,74 +0,0 @@
<template>
<div class="loader">
<svg
class="icon"
height="200"
p-id="1166"
t="1676267704832"
version="1.1"
viewBox="0 0 1024 1024"
width="200"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M787.009641 293.979897c21.792821-0.183795 51.922051-0.459487 53.39241-0.722051a12.826256 12.826256 0 0 0 6.642872-3.610256l58.092308-57.737846a12.589949 12.589949 0 0 0 3.662769-6.603488c0.393846-2.376205-0.131282-4.424205-1.496615-5.802666l-67.872821-67.413334c-1.352205-1.378462-3.452718-1.890462-5.77641-1.522871a12.616205 12.616205 0 0 0-6.695385 3.610256l-58.092307 57.737846a12.826256 12.826256 0 0 0-3.675898 6.669128c-0.236308 1.575385 4.030359 35.997538 3.977846 57.803488-54.350769-40.539897-114.346667-68.096-184.595692-78.336V116.36841h25.915077c2.691282 0 10.660103-3.990974 12.524308-5.356307 2.021744-1.378462 8.388923-6.445949 8.388923-8.375795V7.286154c0-1.929846-6.419692-3.807179-8.310154-5.172513-1.929846-1.352205-9.885538-2.113641-12.603077-2.113641H419.052308c-2.743795 0-11.106462 0.774564-13.049436 2.139897-1.890462 1.352205-8.756513 3.21641-8.756513 5.146257v95.310769c0 1.969231 6.892308 7.036718 8.795897 8.402051 1.929846 1.339077 10.266256 5.369436 13.036308 5.369436h24.996103v81.657436C256.761436 227.511795 91.897436 400.108308 91.897436 608.518564 91.897436 837.618872 281.888821 1024 512.380718 1024 742.859487 1024 931.577436 837.632 931.577436 608.518564c-0.026256-125.702564-55.492923-238.316308-144.567795-314.538667z m-67.413333 146.116924L530.825846 627.698872a25.665641 25.665641 0 0 1-35.551179-0.590769 25.337436 25.337436 0 0 1-0.577641-35.341129l188.783589-187.602051a25.521231 25.521231 0 0 1 18.064411-7.443692 25.665641 25.665641 0 0 1 18.051282 7.443692 25.403077 25.403077 0 0 1 0 35.905641z"
fill="#ffffff"
p-id="1167"
/>
</svg>
</div>
</template>
<script lang="ts" setup>
import { watch } from 'vue'
const props = defineProps({
loading: {
type: Boolean,
default: false,
},
})
watch(
() => props.loading,
(v) => {
const el: HTMLElement = document.querySelector('body') as HTMLElement
if (v) {
el.classList.add('loading')
}
else {
el.classList.remove('loading')
}
},
)
</script>
<style scoped>
.loader::before {
top: 50%;
left: 50%;
z-index: 2;
content: "";
width: 100vmax;
height: 100vmax;
position: fixed;
border-radius: 66%;
background: var(--lime-soap);
transition: transform 0.5s cubic-bezier(0, 0, 0.5, 1.25);
transform: translate(-50%, -50%) scale(0);
}
.loader svg {
top: 50%;
left: 50%;
opacity: 0;
z-index: 3;
height: 8em;
color: white;
position: fixed;
visibility: hidden;
transform: translate(-50%, -50%) scale(0);
transition: opacity 0.3s, transform 0.5s cubic-bezier(0.5, 0, 0.5, 1.5),
visibility 0.3s;
}
</style>

View File

@ -1,31 +0,0 @@
<script setup lang="ts">
defineProps<{
color: boolean
}>()
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1078.16 1075.42"
>
<g>
<path
:class="[color ? 'fill-red-500' : 'fill-white']"
d="M1040.51,386.14l-.07-.19C924.96,101.02,581.05.8,331.04,142.01c-.07.04-.13.08-.2.12-45.27,25.59-87.46,59.11-124.65,100.88-24.7,26.36-45.78,55.48-63.22,86.56-.14.23-.26.46-.39.69-102.76,183.72-79.1,435.44,69.67,588.36,202.89,229.48,602.4,202.48,773.92-50.9,98.35-136.7,120.07-327.28,54.34-481.58ZM905.19,713.98l-.09.2c-136.91,335.55-649.64,259.46-679.31-105.17-5.21-58.59,4.29-122.37,27.28-174.38,107.31-248.62,475.89-287.09,621.75-50.87,63.72,95.56,75.41,224.73,30.37,330.22Z"
/>
<path
:class="[color ? 'fill-red-500' : 'fill-white']"
d="M672.09,623.7c5.78-4.85,12.97-2.42,19.42.18,26.81,11.11,78.95,32.03,106,43.64,2.5,1.24,4.48,2.51,5.9,3.95,6.04,6.42,1.06,15-3.11,23.75-116.7,225.31-479.56,140.92-475.27-119.33-1.14-133.26,103.89-236.04,233.34-245.87,101.07-9.29,203.23,42.83,246.35,139.61,4.93,12.74-3.21,16.51-13.33,20.61-29.05,11.47-77.18,32.55-106.28,43.01-6.68,1.77-11.51-.53-16.5-6.62-2.77-3.25-5.74-7.45-9.18-11.82-54.17-72.65-179.86-36.59-182.63,55.95-6.76,95.95,117.06,143.89,179.46,71.37,5.12-5.79,10.52-14.2,15.73-18.35l.1-.08Z"
/>
<path
:class="[color ? 'fill-green-500' : 'fill-white']"
d="M331.04,142.01c-.07.04-.13.08-.2.12-45.27,25.59-87.46,59.11-124.65,100.88-24.7,26.36-45.78,55.48-63.22,86.56-.14.23-.26.46-.39.69C.99,297.1-68.86,196.33,94.28,204.91c4.04.21,8.23.49,12.56.84,4.24.43,11.3.67,12.3-.99,1.3-1.93-.33-3.81-3.08-7.2-14.62-14.86-30.47-32.36-44.48-50.76-26.08-34.26-45.8-71.65-39.41-100.91,3.63-8.06,9.34-12.97,17.84-15.39,28.09-3.22,61.48,13.75,92.79,36.88,20.06,14.82,39.27,32.17,55.67,48.35,3.31,2.7,5.25,4.34,7.13,2.99,1.54-1.03,1.29-7.68.88-11.65-.45-4.65-.82-9.65-1.1-14.81-.49-9.37-.63-19.27-.24-28.55,3.49-110.15,80.17-64.6,114.06,38.85,4.81,13.59,8.57,26.72,11.84,39.45Z"
/>
</g>
</svg>
</template>
<style scoped>
</style>

View File

@ -1,35 +0,0 @@
<script setup lang="ts">
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 309.02 102.76"
>
<g>
<path d="M29.53.7c4.61,0,7.78,2.95,7.78,7.2,0,5.04-6.19,9.94-11.95,9.43,1.94-.86,4.18-4.46,4.18-6.77,0-1.3-.65-2.02-1.66-2.02-4.61,0-15.63,14.55-15.63,24.55-.14,1.8.36,4.1,2.3,4.1,2.74,0,9.29-4.32,15.48-11.74,1.87.86,4.32,2.81,4.32,4.9-.07,2.88-12.46,16.35-21.75,16.2C-12.02,46.14,8.5.7,29.53.7Z" />
<path d="M35.07,33.03c0-8.71,8.86-18.72,19.66-18.72,5.98,0,9,3.1,9.79,6.19-2.16,3.82-2.95,14.83-2.3,14.91,2.09-1.44,4.54-4.39,5.98-5.9,2.38-2.38,3.24-1.66,3.24-.5-3.96,6.91-8.35,13.39-9.94,13.47-6.84,0-9.58-3.96-9.72-6.98-2.45,2.66-4.68,7.49-7.49,7.49s-9.22-1.87-9.22-9.94ZM46.52,33.68c2.23,0,7.2-10.8,6.91-13.39-2.95,0-8.21,4.1-8.21,10.08,0,1.87.36,3.31,1.3,3.31Z" />
<path d="M82.3,37.5c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M97.06,34.18c.29-4.1,5.04-16.06,7.06-20.38-1.66-.07-4.03,0-10.58,0-3.17,0-.65-6.84,3.02-6.84h11.59c.58-.94,2.38-3.74,2.81-4.32,3.38-4.46,13.39-2.59,11.67.58-.94,1.3-1.58,2.81-2.02,3.53,1.51,0,4.75.07,8.28.07,4.75,0,.36,6.55-1.01,6.55-.86,0-9.86-.14-12.02,0-1.66.94-7.99,18.87-8.14,21.75,1.94-.65,3.82-1.8,5.98-3.96,2.59-2.66,3.53-.36,2.74.58-1.22,1.44-7.27,10.3-10.15,11.23-2.16,0-9.79-1.66-9.22-8.78Z" />
<path d="M113.33,50.89c-.29-2.02,3.24-9.22,6.12-14.33-3.31-.5-5.04-3.02-4.75-6.19.14-2.23,1.08-15.12,3.89-15.12,2.38,0,7.42.72,7.49,2.88,0,2.09-.58,8.78.07,10.01,2.3-1.51,5.76-12.82,7.2-13.03,3.67-.43,8.93,3.53,7.92,6.26-2.09,2.81-17.21,29.16-18.15,35.79-6.48.58-9.36-3.46-9.79-6.26Z" />
<path d="M140.12,29.14c0-18.07,14.19-28.44,22.11-28.44s11.88,6.55,11.88,15.7c0,14.76-11.16,30.24-21.75,30.24-7.27,0-12.24-6.84-12.24-17.5ZM153.58,35.91c2.45,0,10.58-4.82,10.58-18.29,0-3.89-1.01-6.19-3.31-6.12-4.68.22-10.87,9.72-10.87,17.79,0,3.6,1.22,6.62,3.6,6.62Z" />
<path d="M187.99,37.5c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M201.82,38.22c.14-4.82,3.67-14.04,6.77-22.83,3.82,0,9,1.94,9.72,4.68-1.8,3.31-6.19,15.27-5.62,15.27,1.87-.29,3.89-2.81,5.11-3.96,2.16-1.94,3.02-1.01,3.02.29-3.89,5.83-8.71,10.01-9.94,10.87-4.1,0-7.56-2.45-9.07-4.32ZM216.94,3.73c3.96,0,5.9,2.45,5.9,4.18s-1.73,5.11-5.9,5.11-5.76-2.02-5.76-3.38-.29-5.9,5.76-5.9Z" />
<path d="M149.61,57.23c-2.74.86-4.97-4.39-3.02-4.9,21.75-5.98,110.17-9.58,125.36-1.51,2.09,1.08,1.08,5.76-2.16,4.75-26.64-8.21-95.84-5.62-120.18,1.66Z" />
<path d="M218.02,30.01c0-8.14,8.71-14.98,16.63-14.98,7.34,0,10.87,6.19,10.87,13.68,0,6.7-9.43,13.25-16.2,13.25-5.69,0-11.31-5.11-11.31-11.95ZM231.05,32.38c1.08,0,5.18-1.8,5.18-5.33,0-3.17-1.01-4.32-3.53-4.32-2.66,0-4.75,3.1-4.75,6.41,0,2.66,2.02,3.24,3.1,3.24Z" />
<path d="M261.29,37.5c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M280.3,16.54c1.3-.86,4.39-2.74,4.46-3.38.07-1.01-1.3-2.09-1.94-4.03-1.01-3.31.94-6.34,5.33-6.34,3.53,0,5.26,2.02,5.26,4.03,0,4.18-7.13,14.54-10.37,14.62-2.16.07-3.46-4.39-2.74-4.9Z" />
<path d="M286.85,38.29c.43-2.81,2.23-5.26,4.32-5.26,1.87.14,2.88,2.3,4.9,2.3.29,0,2.23-.07,2.52-2.02.36-2.81-9.07-5.83-8.35-10.58.22-1.66,4.25-6.55,14.26-6.55,2.3,0,4.97,0,4.46,3.67-.14,1.22-.72,1.94-1.87,2.38-2.45.79-5.98.72-6.05,1.37,3.6,1.08,8.14,4.46,7.49,9.43-1.01,6.77-9.29,10.23-12.38,10.23-3.96,0-9.5-3.02-9.29-4.97Z" />
<path d="M0,92.68c2.38-10.87,11.66-38.31,13.9-42.84,4.68.86,10.08,3.53,8.21,6.05-4.18,10.73-7.85,25.27-11.81,39.82-4.54.07-10.51-.72-10.3-3.02Z" />
<path d="M31.9,87.5c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M47.31,83.82c0-8.93,9.86-18.79,18.72-18.79h3.38c1.01-3.38,3.46-8.93,6.55-11.95,1.08-1.08,2.45-1.44,3.74-1.44,4.18,0,8.5,4.03,7.2,5.26-1.8,1.8-7.56,14.33-9.58,22.03-.5,2.88-.65,5.62-.36,5.62,1.87-.29,3.96-2.81,5.18-3.96.94-.86,1.58-1.15,2.02-1.15.72,0,1.01.79,1.01,1.37-1.73,2.95-3.46,5.76-4.97,7.92s-2.95,3.96-3.82,3.96h-.86c-2.59-.14-4.61-.65-5.98-1.58l-.22-.14-.07-.07c-3.38-1.94-4.46-5.04-4.32-5.62-4.18,5.83-7.27,7.7-8.42,7.7s-9.22-.14-9.22-9.15ZM59.33,84.33c2.88,0,7.42-10.01,7.2-13.68-2.88-.07-9,7.2-9,11.23.07,1.58,1.01,2.45,1.8,2.45Z" />
<path d="M82.23,83.46c0-8.64,7.92-18.43,17.79-18.43,1.01,0,6.12.14,6.12,4.46,0,7.92-11.23,11.52-13.25,11.52,0,2.52,1.44,4.25,4.61,4.25,3.82,0,7.2-2.52,10.15-4.61.79-.58,1.51-.86,2.02-.86,1.08,0,1.3,1.15-.29,2.88-3.53,3.82-10.66,10.3-18,10.3-5.4,0-9.14-3.6-9.14-9.5ZM98.72,71.87c0-.58-.29-1.22-1.22-1.22-1.8,0-5.47,3.38-5.47,5.54,2.88-.86,6.7-2.66,6.7-4.32Z" />
<path d="M97.49,98.59c-.36-2.23,9.29-11.45,16.63-17.86-1.15-3.1-1.87-6.48-1.87-12.1,0-2.02,2.66-2.16,3.38-2.16,7.34-.43,6.77,6.55,7.7,7.85,2.52-.72,5.04-7.56,5.54-9.29,8.86,2.81,6.91,6.91,5.76,8.28-.86,1.01-4.9,4.68-7.7,7.2,1.01,1.87,2.02,3.6,4.54,3.6,2.88,0,4.97-.94,6.55-1.8.5-.36.94-.5,1.3-.5.72,0,1.15.65.94,1.73-2.81,2.88-7.85,7.27-11.45,8.86-4.75,0-8.5-2.95-10.08-5.18-2.16.07-8.28,8.93-12.31,15.55-3.6-.5-8.5-2.02-8.93-4.18Z" />
</g>
</svg>
</template>
<style scoped>
</style>

View File

@ -1,35 +0,0 @@
<script setup lang="ts">
</script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 465.39 57.48"
>
<g>
<path d="M28.16.86c4.61,0,7.78,2.95,7.78,7.2,0,5.04-6.19,9.94-11.95,9.43,1.94-.86,4.18-4.46,4.18-6.77,0-1.3-.65-2.02-1.66-2.02-4.61,0-15.63,14.55-15.63,24.55-.14,1.8.36,4.1,2.3,4.1,2.74,0,9.29-4.32,15.48-11.74,1.87.86,4.32,2.81,4.32,4.9-.07,2.88-12.46,16.35-21.75,16.2C-13.39,46.3,7.13.86,28.16.86Z" />
<path d="M33.7,33.19c0-8.71,8.86-18.72,19.66-18.72,5.98,0,9,3.1,9.79,6.19-2.16,3.82-2.95,14.83-2.3,14.91,2.09-1.44,4.54-4.39,5.98-5.9,2.38-2.38,3.24-1.66,3.24-.5-3.96,6.91-8.35,13.39-9.94,13.47-6.84,0-9.58-3.96-9.72-6.98-2.45,2.66-4.68,7.49-7.49,7.49s-9.22-1.87-9.22-9.94ZM45.15,33.84c2.23,0,7.2-10.8,6.91-13.39-2.95,0-8.21,4.1-8.21,10.08,0,1.87.36,3.31,1.3,3.31Z" />
<path d="M80.93,37.66c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M95.69,34.35c.29-4.1,5.04-16.06,7.06-20.38-1.66-.07-4.03,0-10.58,0-3.17,0-.65-6.84,3.02-6.84h11.59c.58-.94,2.38-3.74,2.81-4.32,3.38-4.46,13.39-2.59,11.67.58-.94,1.3-1.58,2.81-2.02,3.53,1.51,0,4.75.07,8.28.07,4.75,0,.36,6.55-1.01,6.55-.86,0-9.86-.14-12.02,0-1.66.94-7.99,18.87-8.14,21.75,1.94-.65,3.82-1.8,5.98-3.96,2.59-2.66,3.53-.36,2.74.58-1.22,1.44-7.27,10.3-10.15,11.23-2.16,0-9.79-1.66-9.22-8.78Z" />
<path d="M111.96,51.05c-.29-2.02,3.24-9.22,6.12-14.33-3.31-.5-5.04-3.02-4.75-6.19.14-2.23,1.08-15.12,3.89-15.12,2.38,0,7.42.72,7.49,2.88,0,2.09-.58,8.78.07,10.01,2.3-1.51,5.76-12.82,7.2-13.03,3.67-.43,8.93,3.53,7.92,6.26-2.09,2.81-17.21,29.16-18.15,35.79-6.48.58-9.36-3.46-9.79-6.26Z" />
<path d="M138.75,29.31c0-18.07,14.19-28.44,22.11-28.44s11.88,6.55,11.88,15.7c0,14.76-11.16,30.24-21.75,30.24-7.27,0-12.24-6.84-12.24-17.5ZM152.21,36.08c2.45,0,10.58-4.82,10.58-18.29,0-3.89-1.01-6.19-3.31-6.12-4.68.22-10.87,9.72-10.87,17.79,0,3.6,1.22,6.62,3.6,6.62Z" />
<path d="M186.63,37.66c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M200.45,38.38c.14-4.82,3.67-14.04,6.77-22.83,3.82,0,9,1.94,9.72,4.68-1.8,3.31-6.19,15.27-5.62,15.27,1.87-.29,3.89-2.81,5.11-3.96,2.16-1.94,3.02-1.01,3.02.29-3.89,5.83-8.71,10.01-9.94,10.87-4.1,0-7.56-2.45-9.07-4.32ZM215.57,3.89c3.96,0,5.9,2.45,5.9,4.18s-1.73,5.11-5.9,5.11-5.76-2.02-5.76-3.38-.29-5.9,5.76-5.9Z" />
<path d="M148.24,57.39c-2.74.86-4.97-4.39-3.02-4.9,21.75-5.98,110.17-9.58,125.36-1.51,2.09,1.08,1.08,5.76-2.16,4.75-26.64-8.21-95.84-5.62-120.18,1.66Z" />
<path d="M216.65,30.17c0-8.14,8.71-14.98,16.63-14.98,7.34,0,10.87,6.19,10.87,13.68,0,6.7-9.43,13.25-16.2,13.25-5.69,0-11.31-5.11-11.31-11.95ZM229.68,32.55c1.08,0,5.18-1.8,5.18-5.33,0-3.17-1.01-4.32-3.53-4.32-2.66,0-4.75,3.1-4.75,6.41,0,2.66,2.02,3.24,3.1,3.24Z" />
<path d="M259.92,37.66c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M278.93,16.71c1.3-.86,4.39-2.74,4.46-3.38.07-1.01-1.3-2.09-1.94-4.03-1.01-3.31.94-6.34,5.33-6.34,3.53,0,5.26,2.02,5.26,4.03,0,4.18-7.13,14.54-10.37,14.62-2.16.07-3.46-4.39-2.74-4.9Z" />
<path d="M285.48,38.45c.43-2.81,2.23-5.26,4.32-5.26,1.87.14,2.88,2.3,4.9,2.3.29,0,2.23-.07,2.52-2.02.36-2.81-9.07-5.83-8.35-10.58.22-1.66,4.25-6.55,14.26-6.55,2.3,0,4.97,0,4.46,3.67-.14,1.22-.72,1.94-1.87,2.38-2.45.79-5.98.72-6.05,1.37,3.6,1.08,8.14,4.46,7.49,9.43-1.01,6.77-9.29,10.23-12.38,10.23-3.96,0-9.5-3.02-9.29-4.97Z" />
<path d="M325.08,42.84c2.38-10.87,11.66-38.31,13.9-42.84,4.68.86,10.08,3.53,8.21,6.05-4.18,10.73-7.85,25.27-11.81,39.82-4.54.07-10.51-.72-10.3-3.02Z" />
<path d="M356.97,37.66c-.5-1.3.07-8.5.72-13.61-3.74,1.01-7.92,18.79-7.99,18.72-3.74-.5-8.64-2.16-8.64-4.61,0-7.2,4.97-21.17,6.7-21.67,2.02-1.37,6.7-1.87,7.42.79.07.43-.14,1.51-.36,2.38,2.59-1.94,7.2-4.46,10.44-4.46,4.32,0,5.54,3.82,5.18,5.33-.65,1.58-3.67,11.95-3.31,15.27,1.8-.58,3.17-2.16,5.4-4.03,2.23-1.94,2.59-.5,2.74.29.22.94-8.57,10.15-9.72,11.09-3.6,0-7.63-3.17-8.57-5.47Z" />
<path d="M372.38,33.99c0-8.93,9.86-18.79,18.72-18.79h3.38c1.01-3.38,3.46-8.93,6.55-11.95,1.08-1.08,2.45-1.44,3.74-1.44,4.18,0,8.5,4.03,7.2,5.26-1.8,1.8-7.56,14.33-9.58,22.03-.5,2.88-.65,5.62-.36,5.62,1.87-.29,3.96-2.81,5.18-3.96.94-.86,1.58-1.15,2.02-1.15.72,0,1.01.79,1.01,1.37-1.73,2.95-3.46,5.76-4.97,7.92s-2.95,3.96-3.82,3.96h-.86c-2.59-.14-4.61-.65-5.98-1.58l-.22-.14-.07-.07c-3.38-1.94-4.46-5.04-4.32-5.62-4.18,5.83-7.27,7.7-8.42,7.7s-9.22-.14-9.22-9.15ZM384.41,34.49c2.88,0,7.42-10.01,7.2-13.68-2.88-.07-9,7.2-9,11.23.07,1.58,1.01,2.45,1.8,2.45Z" />
<path d="M407.3,33.63c0-8.64,7.92-18.43,17.79-18.43,1.01,0,6.12.14,6.12,4.46,0,7.92-11.23,11.52-13.25,11.52,0,2.52,1.44,4.25,4.61,4.25,3.82,0,7.2-2.52,10.15-4.61.79-.58,1.51-.86,2.02-.86,1.08,0,1.3,1.15-.29,2.88-3.53,3.82-10.66,10.3-18,10.3-5.4,0-9.14-3.6-9.14-9.5ZM423.79,22.03c0-.58-.29-1.22-1.22-1.22-1.8,0-5.47,3.38-5.47,5.54,2.88-.86,6.7-2.66,6.7-4.32Z" />
<path d="M422.56,48.75c-.36-2.23,9.29-11.45,16.63-17.86-1.15-3.1-1.87-6.48-1.87-12.1,0-2.02,2.66-2.16,3.38-2.16,7.34-.43,6.77,6.55,7.7,7.85,2.52-.72,5.04-7.56,5.54-9.29,8.86,2.81,6.91,6.91,5.76,8.28-.86,1.01-4.9,4.68-7.7,7.2,1.01,1.87,2.02,3.6,4.54,3.6,2.88,0,4.97-.94,6.55-1.8.5-.36.94-.5,1.3-.5.72,0,1.15.65.94,1.73-2.81,2.88-7.85,7.27-11.45,8.86-4.75,0-8.5-2.95-10.08-5.18-2.16.07-8.28,8.93-12.31,15.55-3.6-.5-8.5-2.02-8.93-4.18Z" />
</g>
</svg>
</template>
<style scoped>
</style>

View File

@ -1,39 +0,0 @@
<script lang="ts" setup>
defineProps<{
content: string
}>()
</script>
<template>
<div
class="markdown-body"
v-html="$mdRender.render(content)"
/>
</template>
<style lang="scss">
.markdown-body {
font-family: "LXGW WenKai", sans-serif;
.MathJax {
display: inline-block;
svg {
margin: 0 auto;
}
}
table {
overflow-x: auto; /* X 轴允许滚动 */
overflow-y: visible; /* Y 轴完全显示 */
white-space: nowrap; /* 防止表格内容换行 */
/* 隐藏滚动条 */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari */
}
-ms-overflow-style: none; /* IE 10+ */
scrollbar-width: none; /* Firefox */
}
}
</style>

View File

@ -1,197 +0,0 @@
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
interface Widget {
icon: string[]
url: string
title: string
}
const props = defineProps({
navColor: {
default: false,
type: Boolean,
},
}),
// 使 ref
scrolled = ref(false),
littleWidget: Widget[] = [
{
icon: ['fas', 'gauge'],
url: '/netdata/',
title: 'Pi Dashboard',
},
{
icon: ['fas', 'cloud'],
url: '/nas/',
title: 'Nextcloud',
},
{
icon: ['fas', 'code-branch'],
url: '/git/',
title: 'Git Repository',
},
],
// Const logoUrl = getAssetURL("w.png")
logoType = 'logo',
showLogo = computed(() => logoType === 'text'),
isExpended = ref<boolean>(false),
route = useRoute(),
entry = [
{
title: '主页',
icon: ['fas', 'home'],
entry: [],
to: 'index',
},
{
title: '文章',
icon: ['fas', 'pen'],
entry: [
{
title: '博客',
url: false,
to: 'article',
},
{
title: '日记',
url: false,
to: 'diary',
},
],
},
{
title: '作品',
icon: ['fas', 'brush'],
entry: [
{
title: '软件',
url: false,
to: 'soft',
},
{
title: '仓库',
url: true,
to: 'https://cantyonion.site/git/',
},
],
},
{
title: '杂谈',
icon: ['fas', 'chess-rook'],
entry: [],
to: 'talk',
},
],
menuExpanded = () => {
isExpended.value = !isExpended.value
},
menuClose = () => {
isExpended.value = false
},
// scroll
handleScroll = () => {
scrolled.value = window.scrollY > 0
},
debouncedScroll = debounce(handleScroll, 10),
alwaysBlueBackground = computed(() => route.name !== 'index' || props.navColor)
//
onMounted(() => {
window.addEventListener('scroll', debouncedScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', debouncedScroll)
})
</script>
<template>
<nav
:class="{ 'bg-transparent': !scrolled && !alwaysBlueBackground,
'bg-blue-400': scrolled || alwaysBlueBackground,
'shadow': scrolled }"
class="fixed top-0 z-20 h-16 w-full text-white transition-all duration-300"
>
<div class="container mx-auto flex h-16 w-full items-center justify-between px-4 md:px-0 xl:max-w-screen-xl">
<!-- 下拉菜单按钮 -->
<div
class="flex h-8 w-10 items-center justify-center rounded-md border-gray-600 md:hidden"
@click="menuExpanded"
>
<font-awesome-icon
:icon="['fas', 'bars']"
class="h-6"
/>
</div>
<!-- Logo -->
<div class="absolute left-1/2 -translate-x-1/2 md:relative md:left-0 md:translate-x-0">
<!-- 文字模式 -->
<div
v-if="showLogo"
class="h-full text-4xl min-w-16 logo py-3.5"
>
<span>CantyOni_on's Index</span>
</div>
<!-- 图片模式 -->
<div
v-else
class="py-2"
>
<logo-no-text-logo
:color="false"
class="w-12 h-12"
/>
</div>
</div>
<!-- Entry -->
<div
:class="{ 'h-screen opacity-100 visible': isExpended, 'h-0 opacity-0 invisible': !isExpended }"
class="absolute top-full right-0 left-0 bg-white transition-all md:visible md:relative md:top-0 md:flex
md:h-auto md:bg-transparent md:opacity-100"
>
<nav-bar-entry-item
v-for="item in entry"
:key="item.title"
:entry="item.entry"
:icon="item.icon"
:title="item.title"
:to="item.to"
@on-go="menuClose"
/>
</div>
<!-- 快速跳转小组件 -->
<div class="flex items-center justify-between gap-4 text-xl">
<a
v-for="item in littleWidget"
:key="item.url"
:href="item.url"
:title="item.title"
class="transition-all hover:scale-125"
>
<font-awesome-icon :icon="item.icon" />
</a>
</div>
</div>
</nav>
<!-- 用于隔开元素 -->
<div
v-if="alwaysBlueBackground"
class="h-16 w-full"
/>
</template>
<style scoped>
.logo {
font-family: BaconyScript, serif;
}
</style>

View File

@ -1,76 +0,0 @@
<script lang="ts" setup>
const router = useRouter()
interface Entry {
title: string
url: boolean
to: string
}
const emit = defineEmits<(e: 'onGo') => void>()
defineProps<{
title: string
icon: string[]
to?: string
entry: Entry[]
}>()
const onClick = (to: string | undefined, url: boolean) => {
if (to === undefined)
return
if (url)
window.open(to)
emit('onGo')
router.push({ name: to })
}
</script>
<template>
<div
class="container relative w-full max-w-max flex-auto shrink-0 cursor-pointer group md:w-16 md:hover:w-full"
@click="onClick(to, false)"
>
<div
class="my-1 flex h-14 w-screen items-center rounded-md pr-4 pl-2 text-gray-500
md:w-auto md:text-white md:group-hover:bg-gray-100 md:group-hover:bg-opacity-30"
>
<font-awesome-icon
:icon="icon"
class="mx-2 h-5 w-5 md:h-8 md:w-8"
/>
<span
class="w-full max-w-max overflow-hidden text-lg transition-all duration-300 text-nowrap font-bold
md:font-normal md:w-0 md:text-xl md:group-hover:w-full"
>
{{ title }}
</span>
</div>
<!-- 下拉菜单 -->
<div
v-if="entry"
class="grid grid-cols-2 overflow-hidden transition-all duration-300 min-w-32 item md:invisible md:absolute
md:top-full md:left-1/2 md:block md:rounded-xl md:bg-white md:opacity-0 md:shadow-md
md:group-hover:visible md:group-hover:-translate-x-1/2 md:group-hover:opacity-100"
>
<div
v-for="item in entry"
:key="item.title"
class="px-11 py-2 text-gray-500 text-md md:px-0 md:text-center md:text-lg md:hover:bg-gray-100"
@click="onClick(item.to, item.url)"
>
{{ item.title }}
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.item {
@media (min-width: 768px) {
transform: perspective(500px) rotateX(-45deg) translateX(-50%);
}
}
</style>

View File

@ -1,20 +0,0 @@
<script lang="ts" setup>
</script>
<template>
<a
class="flex items-center justify-center"
href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral"
>
本网站由<img
:src="getAssetURL('upyun.png')"
alt="又拍云"
class="h-8 w-auto"
>提供CDN加速/云储存服务
</a>
</template>
<style scoped>
</style>

View File

@ -1,24 +0,0 @@
<script lang="ts" setup>
defineProps<{
icon: string[]
url: string
title: string
}>()
</script>
<template>
<div class="flex gap-4 text-xl">
<a
:key="url"
:href="url"
:title="title"
class="flex items-center justify-center transition-all hover:scale-125"
>
<font-awesome-icon :icon="icon" />
</a>
</div>
</template>
<style scoped>
</style>

View File

@ -1,38 +0,0 @@
export const getAssetURL = (image: string) => {
// 参数一: 相对路径
// 参数二: 当前路径的URL
if (import.meta.client) {
return new URL(`../assets/images/${image}`, import.meta.url).href
}
return `/_nuxt/assets/images/${image}`
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export const debounce = (fn: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: unknown[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
fn(...args)
}, delay)
}
}
export const setMobileTopColor = () => {
const lightMeta = document.createElement('meta')
lightMeta.setAttribute('name', 'theme-color')
lightMeta.setAttribute('media', '(prefers-color-scheme: light)')
lightMeta.setAttribute('content', '#60a5fa')
const darkMeta = document.createElement('meta')
darkMeta.setAttribute('name', 'theme-color')
darkMeta.setAttribute('media', '(prefers-color-scheme: dark)')
darkMeta.setAttribute('content', '#60a5fa')
document.head.appendChild(lightMeta)
document.head.appendChild(darkMeta)
}
export const removeMobileTopColor = () => {
document.querySelectorAll('meta[name="theme-color"]').forEach(meta => meta.remove())
}

View File

@ -1,58 +0,0 @@
import type { AsyncData, UseFetchOptions } from '#app'
export function requestCore<DataT>(url: string, options: UseFetchOptions<DataT>) {
// Const config = useRuntimeConfig()
const { $mitt } = useNuxtApp()
return useFetch(url, {
baseURL: 'https://cantyonion.site',
retry: false,
timeout: 3000,
onRequest({ options }) {
let token = ''
if (import.meta.client) {
token = localStorage.getItem('token') || ''
}
if (!options.headers.get('Authorization')) {
options.headers.set('Authorization', `Bearer ${token}`)
}
},
onResponse({ response }) {
// HTTP状态码2XX/3XX执行否则不执行
if (response.status < 200 || response.status >= 400) {
console.error(`HTTP 错误: ${response.status}`)
}
// 业务code状态码判断
// If (response._data.code !== 200) {
//
// }
},
onResponseError({ response }) {
$mitt.emit('eventBus', {
level: 'error',
title: `HTTP错误${response.status}`,
message: response.statusText,
})
},
...options,
})
}
export function get<DataT, ErrorT>(url: string, options?: UseFetchOptions<DataT>): () => Promise<DataT> {
let result: AsyncData<DataT, ErrorT> | null = null
return async (): Promise<DataT> => {
if (result != null) {
await result.refresh()
if (result.status.value === 'success') {
return result.data.value
}
throw result.error
}
result = await requestCore(url, {
method: 'GET',
...options,
}) as AsyncData<DataT, ErrorT>
return result.data.value
}
}

View File

@ -1,72 +0,0 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
import { setMobileTopColor } from '~/composables/function'
const props = defineProps({
error: Object as () => NuxtError,
}),
{ $mitt } = useNuxtApp(),
errorMessages: Record<string, { icon: string, title: string, message: string }> = {
400: {
icon: '❓',
title: '哦豁!请求有点问题',
message: '请求似乎有点小问题,服务器君有点摸不着头脑呢!',
},
403: {
icon: '🔒',
title: '哎呀!大门被锁住了',
message: '你没有权限访问这个角落,试试返回首页吧!',
},
404: {
icon: '🌌',
title: '哎呀!你发现了超级基地的秘密角落',
message: '看起来你找的页面藏到了未知的时空层里!',
},
500: {
icon: '🚀',
title: '糟糕!超级基地出了一点故障',
message: '服务器君正在努力解决问题,请稍等片刻!',
},
502: {
icon: '👽',
title: '糟糕!基地信号被外星人拦截了',
message: '网络通道似乎遇到了问题,请稍后再试!',
},
default: {
icon: '⚠️',
title: '哦豁!此时遇到了点小麻烦',
message: '请求在穿越洋葱星球时迷路了,请返回首页重新探索!',
},
},
errorContent = computed(() => errorMessages[props.error!.statusCode] || errorMessages.default)
console.error(props.error)
onMounted(() => {
$mitt.emit('startLoading', false)
setMobileTopColor()
})
onUnmounted(() => {
removeMobileTopColor()
})
</script>
<template>
<div class="relative flex min-h-screen flex-col overflow-hidden bg-blue-50">
<nav-bar :nav-color="true" />
<alert
:title="errorContent.title"
:message="errorContent.message"
:icon="errorContent.icon"
/>
<footer-main />
<alert-notification />
</div>
</template>
<style scoped>
</style>

View File

@ -1,3 +0,0 @@
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt()

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<link href="/logo-c.png" rel="icon" type="image/png"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>CANTYONION.SITE</title>
</head>
<body>
<div id="loading" class="loading"></div>
<div id="app"></div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>

View File

@ -1,4 +0,0 @@
export default defineNuxtRouteMiddleware(() => {
const { $mitt } = useNuxtApp()
$mitt.emit('startLoading', true)
})

View File

@ -1,6 +0,0 @@
export default defineNuxtRouteMiddleware((to) => {
if (to.meta.maintenance === true && !import.meta.env.DEV) {
return navigateTo('/error/maintenance')
}
return true
})

View File

@ -1,67 +0,0 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
// See https://blog.csdn.net/m0_53022813/article/details/137379480
import eslintPlugin from 'vite-plugin-eslint'
export default defineNuxtConfig({
modules: ['@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt', '@nuxt/image', '@nuxt/eslint'],
components: true,
devtools: { enabled: false },
app: {
head: {
title: 'CANTYONION.SITE',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'charset', content: 'utf-8' },
{ name: 'keywords', content: 'cantyonion, onion, 洋葱, 博客, 学习, 主页, index' },
{ name: 'description', content: 'cantyonion的超级基地进来看看吧' },
],
link: [
{ rel: 'icon', href: '/favicon.png' },
],
},
},
css: [
'@fortawesome/fontawesome-svg-core/styles.css',
'github-markdown-css/github-markdown-light.css',
'~/assets/css/main.scss',
],
runtimeConfig: {
public: {
gitApiKey: 'fb8aec429ea7d0a36f7238dbffda9d2d66c7b045',
baseURL: 'https://cantyonion.site',
},
},
build: {
transpile: ['@fortawesome/vue-fontawesome', 'vue3-typed-js', 'pinia-plugin-persistedstate'],
},
devServer: {
host: '0.0.0.0',
},
compatibilityDate: '2024-04-03',
vite: {
plugins: [
eslintPlugin({
include: ['**/*.ts', '**/*.vue'],
}),
],
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler', // Or "modern"
},
},
},
},
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
eslint: {
config: {
stylistic: true,
},
},
})

View File

@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="client: chrome" type="JavascriptDebugType" uri="https://127.0.0.1:3000" useFirstLineBreakpoints="true">
<method v="2" />
</configuration>
<configuration default="false" name="server: nuxt" type="NodeJSConfigurationType" application-parameters="dev" path-to-js-file="$PROJECT_DIR$/node_modules/nuxi/bin/nuxi.mjs" working-dir="$PROJECT_DIR$">
<method v="2" />
</configuration>
<configuration default="false" name="fullstack: nuxt" type="CompoundRunConfigurationType">
<toRun name="client: chrome" type="JavascriptDebugType" />
<toRun name="server: nuxt" type="NodeJSConfigurationType" />
<method v="2" />
</configuration>
</component>

3627
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,12 @@
{
"name": "web-index",
"name": "webindex",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"stylefix": "eslint . --fix"
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@callmebill/lxgw-wenkai-web": "^1.501.0",
@ -18,33 +15,25 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@nuxt/image": "1.8.1",
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
"@pinia/nuxt": "^0.7.0",
"github-markdown-css": "^5.8.1",
"markdown-it": "^14.1.0",
"markdown-it-mathjax3": "^4.3.2",
"axios": "^1.7.7",
"mitt": "^3.0.1",
"nuxt": "^3.14.159",
"vue": "latest",
"vue-router": "latest",
"prismjs": "^1.29.0",
"vue": "^3.4.37",
"vue-router": "^4.4.5",
"vue3-typed-js": "^0.0.1"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@nuxt/eslint": "^0.7.5",
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.5.5",
"@types/prismjs": "^1.26.4",
"@vitejs/plugin-vue": "^5.1.2",
"autoprefixer": "^10.4.20",
"eslint": "^9.18.0",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.14.0",
"jiti": "^2.4.2",
"postcss": "^8.4.49",
"sass-embedded": "^1.81.0",
"tailwindcss": "^3.4.5",
"typescript": "^5.7.3",
"typescript-eslint": "^8.19.1",
"vite-plugin-eslint": "^1.8.1"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"postcss": "^8.4.47",
"sass": "^1.79.4",
"sass-loader": "^16.0.2",
"tailwindcss": "^3.4.13",
"typescript": "^5.5.3",
"vite": "^5.4.1",
"vite-plugin-prismjs": "^0.0.11",
"vue-tsc": "^2.0.29"
}
}

View File

@ -1,7 +0,0 @@
<script setup lang="ts">
navigateTo('/error/404')
</script>
<style scoped>
</style>

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
const { $mitt } = useNuxtApp()
onMounted(() => {
$mitt.emit('startLoading', false)
})
</script>
<template>
<nuxt-page />
</template>
<style scoped>
</style>

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
const route = useRoute(),
cid = route.params.cid as string,
content = ref<IBlogResponse<IPostFull>>(),
getPost = get<IBlogResponse<IPostFull>, unknown>('/blog/index.php/api/post', {
params: {
cid,
md: false,
},
}),
title = computed(() => content.value?.data.title || 'Can not see me... can not see me...'),
text = computed(() => content.value?.data.text || '## Ops!'),
date = computed(() => new Date((content.value?.data.date.timeStamp || 0) * 1000)
.toISOString()
.split('T')[0],
),
category = computed(() => content.value?.data.category || 'default'),
url = computed(() => content.value?.data.url || '/error/404')
content.value = await getPost()
</script>
<template>
<section class="container m-4 mx-auto rounded-xl bg-white p-8 shadow-md lg:max-w-screen-lg">
<div class="mb-4 border-b-2 border-dashed border-gray-200 pb-4">
<h1 class="text-center text-3xl font-bold">
{{ title }}
</h1>
<div class="flex w-full items-center justify-center gap-4 text-gray-600">
<!-- 日期 -->
<div class="flex items-center gap-2">
<font-awesome-icon :icon="['far', 'clock']" />
<time>{{ date }}</time>
</div>
<!-- 分类 -->
<div class="flex items-center gap-2">
<font-awesome-icon :icon="['far', 'folder']" />
<span>{{ category }}</span>
</div>
<div class="flex items-center gap-2">
<font-awesome-icon :icon="['fas', 'link']" />
<a :href="url">原文链接</a>
</div>
</div>
</div>
<markdown-section :content="text" />
</section>
</template>
<style scoped>
</style>

View File

@ -1,13 +0,0 @@
<script setup lang="ts">
definePageMeta({
maintenance: true,
})
</script>
<template>
<NuxtWelcome />
</template>
<style scoped>
</style>

View File

@ -1,11 +0,0 @@
<script setup lang="ts">
</script>
<template>
<h1>auth view</h1>
</template>
<style scoped>
</style>

View File

@ -1,13 +0,0 @@
<script setup lang="ts">
definePageMeta({
maintenance: true,
})
</script>
<template>
<NuxtWelcome />
</template>
<style scoped>
</style>

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
const route = useRoute(),
errorCode = parseInt(route.params.code as string, 10)
if (import.meta.server) {
throw createError({
statusCode: errorCode,
})
}
else { showError({ statusCode: errorCode }) }
</script>
<style scoped>
</style>

View File

@ -1,19 +0,0 @@
<script setup lang="ts">
const { $mitt } = useNuxtApp()
onMounted(() => {
$mitt.emit('startLoading', false)
})
</script>
<template>
<alert
icon="🚧"
title="建设中"
message="此页面正在建设中,请稍后再来查看更新。"
/>
</template>
<style scoped>
</style>

View File

@ -1,165 +0,0 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
interface errObj {
blog?: object
git?: object
}
const { $mitt } = useNuxtApp(),
recentPosts = ref<IPost[] | null>(null),
recentActivities = ref<IActivity[] | null>(null),
isLoading = ref({
blog: true,
git: true,
}),
isError = ref({
blog: false,
git: false,
}),
errMsg = ref<errObj>({
blog: undefined,
git: undefined,
}),
getBlogRecentPost = get<IBlogResponse<IPostsData>, unknown>('/blog/index.php/api/posts', {
params: {
showDigest: 'excerpt',
limit: 100,
},
}),
getActivity = get<IActivity[], unknown>('/git/api/v1/users/cantyonion/activities/feeds', {
params: {
'limit': 10,
'only-performed-by': true,
},
}),
hasPosts = computed(() => (recentPosts.value ?? []).length > 0),
hasActivities = computed(() => (recentActivities.value ?? []).length > 0),
postsData = computed(() => {
if (!recentPosts.value) {
return []
}
if (recentPosts.value.length > 4) {
return recentPosts.value.slice(0, 4)
}
return recentPosts.value
}),
activitiesData = computed((): IStrictActivity[] => {
if (!Array.isArray(recentActivities.value)) {
return []
}
return recentActivities.value.map((item) => {
return {
...item,
content: typeof item.content === 'string' && item.content ? JSON.parse(item.content) : null,
}
})
}),
reloadPosts = async () => {
try {
isLoading.value.blog = true
isError.value.blog = false
const postData = await getBlogRecentPost()
if (postData !== null) {
recentPosts.value = postData.data.dataSet
}
}
catch (e) {
isError.value.blog = true
errMsg.value.blog = e as object
}
finally {
isLoading.value.blog = false
}
},
reloadActivities = async () => {
try {
isLoading.value.git = true
isError.value.git = false
recentActivities.value = await getActivity()
}
catch (e) {
isError.value.git = true
errMsg.value.git = e as object
}
finally {
isLoading.value.git = false
}
}
await Promise.allSettled([reloadPosts(), reloadActivities()])
onMounted(() => {
$mitt.emit('startLoading', false)
})
</script>
<template>
<div class="main">
<!-- 个人介绍 -->
<card-full-screen-intro-card />
<div class="container mx-auto xl:max-w-screen-xl">
<!-- 博客部分 -->
<section class="w-full px-4 sm:px-0">
<!-- 标题部分 -->
<card-title
:icon="['fas', 'blog']"
title="最近文章"
/>
<card-section-card
:empty="!hasPosts"
:error="isError.blog"
:loading="isLoading.blog"
:err-msg="errMsg.blog"
@reload="reloadPosts"
>
<template #default>
<div class="grid w-full grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
<card-article-card
v-for="item in postsData"
:key="item.cid"
:post="item"
/>
</div>
</template>
</card-section-card>
</section>
<!-- Git部分 -->
<section class="my-4 w-full px-4 sm:px-0">
<card-title
:icon="['fas', 'code']"
title="最近活动"
/>
<card-section-card
:empty="!hasActivities"
:error="isError.git"
:loading="isLoading.git"
:err-msg="errMsg.git"
@reload="reloadActivities"
>
<template #default>
<div class="w-full rounded-xl bg-white divide-y divide-dashed">
<card-git-card
v-for="item in activitiesData"
:key="item.id"
:commit="item"
/>
</div>
</template>
</card-section-card>
</section>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,13 +0,0 @@
<script setup lang="ts">
definePageMeta({
maintenance: true,
})
</script>
<template>
<NuxtWelcome />
</template>
<style scoped>
</style>

View File

@ -1,13 +0,0 @@
<script setup lang="ts">
definePageMeta({
maintenance: true,
})
</script>
<template>
<NuxtWelcome />
</template>
<style scoped>
</style>

View File

@ -1,34 +0,0 @@
import { config, library } from '@fortawesome/fontawesome-svg-core'
import {
faBars,
faBlog,
faBrush,
faChessRook,
faChevronDown,
faChevronRight,
faCloud,
faCode,
faCodeBranch,
faCodeCommit,
faGauge,
faHouse,
faLink,
faPen,
faXmark,
faBookMedical,
} from '@fortawesome/free-solid-svg-icons'
import { faClock, faFolder } from '@fortawesome/free-regular-svg-icons'
import { faGithubAlt, faQq, faSteamSymbol, faWeibo } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
// 因为默认添加了 nuxt会造成一些错误所以不自动添加样式
config.autoAddCss = false
export default defineNuxtPlugin((nuxtApp) => {
library.add(
faClock, faXmark, faBlog, faGauge, faCodeBranch, faCloud, faWeibo, faQq, faGithubAlt, faSteamSymbol, faChevronRight,
faCodeCommit, faCode, faHouse, faPen, faBrush, faChessRook, faBars, faChevronDown, faFolder, faLink, faBookMedical,
)
nuxtApp.vueApp.component('font-awesome-icon', FontAwesomeIcon)
})

View File

@ -1,23 +0,0 @@
import md, { type Options } from 'markdown-it'
import mm from 'markdown-it-mathjax3'
const options: Options = {
breaks: true,
html: true,
langPrefix: 'language-',
linkify: true,
quotes: '“”‘’',
typographer: true,
xhtmlOut: true,
}
export default defineNuxtPlugin(() => {
const render = md(options)
.use(mm, {
})
return {
provide: {
mdRender: render,
},
}
})

View File

@ -1,17 +0,0 @@
import mitt from 'mitt'
interface Events {
openPost: IPost
startLoading: boolean
eventBus: INotification
}
export default defineNuxtPlugin(() => {
const emitter = mitt<Events>()
return {
provide: {
mitt: emitter,
},
}
})

View File

@ -1,6 +0,0 @@
// @ts-expect-error: 'vue3-typed-js' has no types
import VueTyped from 'vue3-typed-js'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueTyped)
})

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1 +0,0 @@

View File

@ -1,3 +0,0 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

34
src/App.vue Normal file
View File

@ -0,0 +1,34 @@
<script lang="ts" setup>
import {onMounted} from "vue";
import emitter from "@/utils/mitt.ts";
import NavBar from "@/components/nav/NavBar.vue";
import {removeMobileTopColor, setMobileTopColor} from "@/utils/function.ts";
import MainFooter from "@/components/footer/MainFooter.vue";
const loadingElement = document.getElementById("loading") as HTMLDivElement;
onMounted(() => {
emitter.on('startLoading', on => {
if (on) {
loadingElement.classList.remove('stop')
removeMobileTopColor()
} else {
loadingElement.classList.add('stop')
setMobileTopColor()
}
}
)
})
</script>
<template>
<div class="relative flex min-h-screen flex-col overflow-hidden bg-blue-50">
<nav-bar/>
<router-view/>
<main-footer/>
</div>
</template>
<style scoped>
</style>

8
src/api/blog.ts Normal file
View File

@ -0,0 +1,8 @@
import {request} from "@/utils/network";
export async function getBlogRecentPost(): Promise<IBlogResponse<IPostsData>> {
return request({
url: "/blog/index.php/api/posts",
method: "get"
})
}

16
src/api/git.ts Normal file
View File

@ -0,0 +1,16 @@
import {request} from "@/utils/network.ts";
const TOKEN = "fb8aec429ea7d0a36f7238dbffda9d2d66c7b045"
export async function getActivity(): Promise<IActivity[]> {
return request({
url: '/git/api/v1/users/cantyonion/activities/feeds',
method: 'get',
headers: {
Authorization: ` ${TOKEN}`,
},
params: {
limit: 10
}
})
}

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 647 B

View File

Before

Width:  |  Height:  |  Size: 999 KiB

After

Width:  |  Height:  |  Size: 999 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,21 @@
<script lang="ts" setup>
defineProps<{
title: string
message: string
}>()
</script>
<template>
<div class="flex flex-grow items-center justify-center bg-blue-50 min-h-96">
<div class="w-full max-w-md rounded-lg bg-white p-6 text-center shadow-lg">
<h1 class="mb-4 text-4xl font-bold text-gray-800">{{ title }}</h1>
<p class="text-lg text-gray-600">
{{ message }}
</p>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,54 @@
<script lang="ts" setup>
import {computed} from "vue";
import {getAssetURL} from "@/utils/function.ts";
// import emitter from "@/utils/mitt.ts";
const props = defineProps<{
post: IPost
}>()
const thumbUrl = computed(() => {
//
props.post.fields.thumb.value = props.post.fields.thumb.value ? props.post.fields.thumb.value : getAssetURL(
`${Math.floor(Math.random() * (7 - 1 + 1)) + 1}.jpg`
)
return props.post.fields.thumb.value
})
const handleOnCardClick = () => {
// emitter.emit('openPost', props.post)
window.open(props.post.url, '_self')
}
</script>
<template>
<div :title="post.title"
class="container relative h-56 overflow-hidden rounded-2xl text-white hover:cursor-pointer hover:text-amber-200 hover:shadow-lg"
@click="handleOnCardClick">
<!-- 图片 -->
<div class="container h-full">
<img :src="thumbUrl" alt=""
class="h-full w-full object-cover object-center transition-all duration-300 hover:scale-110">
</div>
<!-- 文字部分 -->
<div class="container relative">
<div class="absolute bottom-0 h-12 w-full truncate bg-black bg-opacity-60 p-2 leading-8 backdrop-blur-3xl">
<span class="px-1 text-xl font-bold transition-all">{{ post.title }}</span>
</div>
</div>
<!-- 分类 -->
<div class="absolute top-0 left-0 m-2 rounded-lg bg-black bg-opacity-60 px-2 text-white backdrop-blur-3xl">
<span>{{ post.category }}</span>
</div>
<!-- 日期 -->
<div class="absolute right-0 bottom-12 m-2 rounded-lg bg-black bg-opacity-60 px-2 text-white backdrop-blur-3xl">
<font-awesome-icon :icon="['far', 'clock']" class="mr-1 text-sm"/>
<span>{{ post.date.year }}-{{ post.date.month }}-{{ post.date.day }}</span>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,90 @@
<script lang="ts" setup>
import {computed, onMounted, onUnmounted, ref} from "vue";
import emitter from "@/utils/mitt.ts";
import {getAssetURL} from "@/utils/function.ts";
// import Prism from "prismjs";
const currentPost = ref<IPost | null>(null)
const onOpenPost = (e: IPost) => {
currentPost.value = e;
// Prism.highlightAll()
}
const onClosePost = () => {
currentPost.value = null
}
const thumbUrl = computed(() => {
if (currentPost.value && currentPost.value.fields.thumb.value) {
return currentPost.value.fields.thumb.value
}
return getAssetURL(`${Math.floor(Math.random() * (7 - 1 + 1)) + 1}.jpg`)
})
onMounted(() => {
emitter.on('openPost', onOpenPost)
})
onUnmounted(() => {
emitter.off('openPost', onOpenPost)
})
</script>
<template>
<div v-if="currentPost" class="fixed top-0 right-0 bottom-0 left-0 z-20 bg-black bg-opacity-60">
<div class="container mx-auto my-10 max-h-full overflow-hidden overscroll-y-contain rounded-3xl bg-white">
<!-- 标题栏 -->
<div class="relative w-full border-b-2 border-b-gray-200">
<!-- 文章头图 -->
<div class="min-h-52">
<img :src="thumbUrl" alt="" class="max-h-64 w-full object-cover object-center"/>
</div>
<!-- 预览标题 -->
<div
class="absolute top-0 right-0 left-0 h-12 truncate bg-black bg-opacity-60 pr-16 pl-8 text-white backdrop-blur-3xl py-1.5">
<span :title="currentPost.title" class="text-3xl font-bold">
文章预览 /
</span>
</div>
<!-- 关闭按钮 -->
<div
class="absolute top-0 right-2 flex h-9 w-9 cursor-pointer items-center justify-center rounded-full text-white transition-all my-1.5 hover:bg-gray-200 hover:text-black"
@click="onClosePost"
>
<font-awesome-icon :icon="['fas', 'xmark']" class="p-2"/>
</div>
</div>
<!-- 正文 -->
<div class="container overflow-y-auto p-8 h-[60vh] preview" v-html="currentPost.digest"></div>
</div>
</div>
</template>
<style lang="scss">
.preview {
h1 {
@apply mb-2 border-l-8 border-l-blue-400 text-3xl font-bold;
}
h2 {
@apply mb-2 border-l-4 border-l-blue-400 text-2xl font-bold;
}
h3 {
@apply mb-2 border-l-2 border-l-blue-400 text-xl font-bold;
}
h4 {
@apply text-lg font-bold;
}
h5 {
@apply text-sm font-bold;
}
ul {
@apply line-clamp-1;
}
}
</style>

View File

@ -0,0 +1,27 @@
<script lang="ts" setup>
defineProps<{
title: string
mainColor: string
subColor: string
url: string
icon: string[]
}>()
</script>
<template>
<div class="mb-4 flex justify-between text-2xl font-bold text-white">
<div :class="[mainColor]" class="flex w-max items-center justify-center overflow-hidden rounded-2xl">
<div :class="[subColor]" class="flex h-full w-14 items-center justify-center rounded-2xl px-4 py-2 text-center">
<font-awesome-icon :icon="icon"/>
</div>
<span class="px-4">{{ title }}</span>
</div>
<a :class="[mainColor]" :href="url" class="rounded-2xl px-4 py-2">
<font-awesome-icon :icon="['fas', 'chevron-right']"/>
</a>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,110 @@
<script lang="ts" setup>
import {computed, ref} from "vue";
import {debounce, getAssetURL} from "@/utils/function.ts";
import SocialMediaWidget from "@/components/widget/SocialMediaWidget.vue";
// 使 ref x y
const mouseX = ref(0)
const mouseY = ref(0)
// const card = ref<HTMLDivElement | null>(null)
// const rotation = reactive({
// rotateX: 0,
// rotateY: 0,
// });
const typedString = ['Hello, Welcome to this site!^1000', '欢迎访问本网站!^1000']
const littleWidget = [
{
icon: ['fab', 'qq'],
url: "https://qm.qq.com/q/9fhtO8JJt0",
title: "QQ",
},
{
icon: ['fab', 'github-alt'],
url: "https://github.com/Aurora1949",
title: "Github",
},
{
icon: ['fab', 'weibo'],
url: "https://weibo.com/u/7923648952",
title: "微博"
},
{
icon: ['fab', 'steam-symbol'],
url: "https://steamcommunity.com/id/cantyonion/",
title: "Steam"
}
]
const characterUrl = getAssetURL('hoshino.png')
//
const handleMouseMove = (event: MouseEvent) => {
//
mouseX.value = -(event.clientX / window.innerWidth - 0.5) * 100
mouseY.value = -(event.clientY / window.innerHeight - 0.5) * 100
}
const debouncedMouseMove = debounce(handleMouseMove, 5)
//
const backgroundStyle = computed(() => ({
backgroundPosition: `${mouseX.value}px ${mouseY.value}px`,
backgroundImage: `url(${getAssetURL('bg.png')})`,
backgroundSize: "150px",
}))
</script>
<template>
<div :style="backgroundStyle"
class="relative flex h-screen w-full items-center justify-center bg-blue-400 bg-fixed"
@mousemove="debouncedMouseMove"
>
<div class="relative mx-4 w-full rounded-xl bg-white p-8 transition-transform duration-300 min-h-96
md:mx-0 md:max-w-screen-md"
>
<p class="text-gray-500">你好欢迎来到</p>
<div class="relative mt-4 h-72 text-5xl text-blue-400 transition-all md:text-6xl lg:text-7xl">
<h1 style="font-family: BaconyScript,sans-serif">CantyOn_ion's</h1>
<h1 class="font-bold">超级基地</h1>
<div class="absolute -z-10 text-blue-100 top-[3px] left-[3px]">
<h1 class="" style="font-family: BaconyScript,sans-serif">CantyOn_ion's</h1>
<h1 class="font-bold">超级基地</h1>
</div>
</div>
<!-- TypedJS -->
<div class="flex flex-row text-gray-500">
<vue-typed :auto-insert-css="true" :backSpeed="30"
:loop="true"
:showCursor="true"
:strings="typedString"
:typeSpeed="50"
/>
</div>
<!-- 社交媒体组件 -->
<div class="flex flex-row text-gray-500">
<social-media-widget v-for="item in littleWidget"
:key="item.url"
:icon="item.icon"
:title="item.title"
:url="item.url"
class="h-10 w-10"
/>
</div>
<!-- 人物背景 -->
<div class="invisible absolute top-0 -right-6 -bottom-6 opacity-0 transition-all
sm:visible sm:opacity-100 lg:-top-28 lg:-right-28 lg:-bottom-10"
>
<img :src="characterUrl" alt="" class="float-right h-full" draggable="false"/>
</div>
</div>
<!-- 下箭头 -->
<font-awesome-icon :icon="['fas', 'chevron-down']" beat class="absolute bottom-12 h-auto w-6 text-white"/>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,86 @@
<script lang="ts" setup>
import {FontAwesomeIcon} from "@fortawesome/vue-fontawesome";
import {computed} from "vue";
const props = defineProps<{
commit: IActivity<IContent>
}>()
const icon = computed(() => {
switch (props.commit.op_type) {
case "merge_pull_request":
return ['fas', 'code-merge']
case "create_repo":
return ['fab', 'git-alt']
case "commit_repo":
default:
return ['fas', 'code-commit']
}
})
const time = computed(() => timeDifference(props.commit.created))
const timeDifference = (dateString: string): string => {
const targetDate = new Date(dateString);
const currentDate = new Date();
const diffInMillis = currentDate.getTime() - targetDate.getTime();
const minutes = Math.floor(diffInMillis / (1000 * 60));
const hours = Math.floor(diffInMillis / (1000 * 60 * 60));
const days = Math.floor(diffInMillis / (1000 * 60 * 60 * 24));
const months = Math.floor(diffInMillis / (1000 * 60 * 60 * 24 * 30));
const years = Math.floor(diffInMillis / (1000 * 60 * 60 * 24 * 365));
if (minutes < 60) {
return `${minutes} 分钟前`;
} else if (hours < 24) {
return `${hours} 小时前`;
} else if (days < 30) {
return `${days} 天前`;
} else if (months < 12) {
return `${months} 个月前`;
} else {
return `${years} 年前`;
}
}
const handleClick = () => {
window.open(props.commit.repo.html_url, '_self')
}
</script>
<template>
<div class="group container relative flex h-36 w-full overflow-hidden rounded-2xl transition-all
hover:cursor-pointer hover:shadow-md"
@click="handleClick"
>
<!-- 提交图标 -->
<div class="before:absolute relative before:top-0 before:right-0 before:bottom-0 flex h-36 before:w-2 w-36
items-center justify-center bg-pink-300 before:bg-pink-300 text-5xl text-white before:opacity-0
before:blur before:transition-all group-hover:before:opacity-100"
>
<font-awesome-icon :icon="icon" class="transition-all group-hover:scale-110"/>
</div>
<!-- 内容 -->
<div class="flex-1 overflow-hidden bg-white p-4">
<div class="mr-12 truncate text-2xl font-bold">
仓库{{ commit.repo.name }}
</div>
<span class="text-xl line-clamp-2">{{ commit.content.HeadCommit.Message }}</span>
<span class="absolute top-0 right-0 m-4 hidden rounded bg-gray-300 px-2 sm:block">
{{ commit.content.HeadCommit.Sha1.slice(0, 10) }}
</span>
<span class="absolute right-0 bottom-0 m-4 rounded bg-gray-300 px-2">
<font-awesome-icon :icon="['far', 'clock']" class="mr-1 text-sm"/>
{{ time }}
</span>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,60 @@
<script lang="ts" setup>
type Widget = {
icon: string[]
url: string
title: string
}
// const welcomeWords = ['👋']
const littleWidget: Widget[] = [
{
icon: ['fab', 'qq'],
url: "tencent://message/?uin=1922471905&Site=&Menu=yes",
title: "QQ",
},
{
icon: ['fab', 'github-alt'],
url: "https://github.com/Aurora1949",
title: "Github",
},
{
icon: ['fab', 'weibo'],
url: "https://weibo.com/u/7923648952",
title: "微博"
},
{
icon: ['fab', 'steam-symbol'],
url: "https://steamcommunity.com/id/cantyonion/",
title: "Steam"
}
]
</script>
<template>
<div class="container my-16 flex w-full">
<!-- 头像部分 -->
<div class="hidden h-32 w-32 overflow-hidden rounded-full bg-white hover:animate-pulse sm:block">
<img alt="" src="https://q.qlogo.cn/g?b=qq&nk=1922471905&s=640">
</div>
<!-- 文字部分 -->
<div class="container flex flex-1 flex-col justify-between px-4 sm:px-8">
<div class="text-4xl">Jeffrey Hsu</div>
<div class="text-2xl text-gray-400">你好👋很高兴认识你</div>
<!-- <vue-typed-js :strings="['First text', 'Second Text']">-->
<!-- <h1 class="typing"></h1>-->
<!-- </vue-typed-js>-->
<div class="flex gap-4 text-xl">
<a v-for="item in littleWidget"
:key="item.url" :href=item.url :title=item.title
class="flex h-10 w-10 items-center justify-center rounded-full bg-green-400 text-white transition-all hover:scale-125">
<font-awesome-icon :icon="item.icon"/>
</a>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,42 @@
<script lang="ts" setup>
defineProps<{
loading: boolean
error: boolean
empty: boolean
}>()
const emit = defineEmits(['reload'])
const handleReload = () => {
emit('reload')
}
</script>
<template>
<div :class="{'justify-center': (empty || error)}" class="container flex">
<!-- 加载骨架 -->
<div v-if="loading" class="h-56 w-full text-2xl font-bold">
<slot name="skeleton">
<div class="grid w-full grid-cols-1 gap-4 overflow-hidden sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div v-for="index of 3" :key="index" class="h-56 animate-pulse rounded-2xl bg-gray-200"></div>
</div>
</slot>
</div>
<div v-else class="w-full">
<!-- 错误处理 -->
<div v-if="error" class="flex h-56 flex-col items-center justify-center" @click="handleReload">
<span class="mb-4 text-8xl"></span>
<span class="text-center text-2xl font-bold">载入错误</span>
</div>
<div v-else>
<!-- 正式内容 -->
<span v-if="empty" class="text-2xl font-bold">最近没有动态</span>
<slot v-else name="default"/>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
</script>
<template>
<footer class="mt-8 items-end pt-48 pb-8 min-h-48 bg">
<div class="container mx-auto px-4 md:px-0 xl:max-w-screen-xl">
<div class="grid-cols-4 gird min-h-48">
<div>
<div class="flex items-center gap-4">
<img alt="" class="h-20 w-20" src="@/assets/logo-c.png">
<div class="text-4xl leading-7 text-gray-500" style="font-family: BaconyScript, sans-serif">
<p>CantyOn_ion's</p>
<p>Index</p>
</div>
</div>
<p class="mt-20 leading-8 text-gray-500">&copy;2024 All rights reserved.</p>
</div>
</div>
<div class="flex flex-col text-gray-500 md:flex-row">
<a class="mr-4 hover:text-black" href="https://beian.miit.gov.cn/"
target="_blank">苏ICP备2022016243号-1</a>
<div>
<img alt="" class="inline-block w-4 pb-1" src="@/assets/beian.png"/>
<a class="hover:text-black" href="https://beian.mps.gov.cn/#/query/webSearch?code=32050602011641/"
target="_blank">苏公网安备32050602011641</a>
</div>
</div>
</div>
</footer>
</template>
<style scoped lang="scss">
.bg {
background: url("@/assets/footer-bg.svg");
background-size: 100% auto;
background-color: white;
background-repeat: no-repeat;
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<div class="loader">
<svg
class="icon"
height="200"
p-id="1166"
t="1676267704832"
version="1.1"
viewBox="0 0 1024 1024"
width="200"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M787.009641 293.979897c21.792821-0.183795 51.922051-0.459487 53.39241-0.722051a12.826256 12.826256 0 0 0 6.642872-3.610256l58.092308-57.737846a12.589949 12.589949 0 0 0 3.662769-6.603488c0.393846-2.376205-0.131282-4.424205-1.496615-5.802666l-67.872821-67.413334c-1.352205-1.378462-3.452718-1.890462-5.77641-1.522871a12.616205 12.616205 0 0 0-6.695385 3.610256l-58.092307 57.737846a12.826256 12.826256 0 0 0-3.675898 6.669128c-0.236308 1.575385 4.030359 35.997538 3.977846 57.803488-54.350769-40.539897-114.346667-68.096-184.595692-78.336V116.36841h25.915077c2.691282 0 10.660103-3.990974 12.524308-5.356307 2.021744-1.378462 8.388923-6.445949 8.388923-8.375795V7.286154c0-1.929846-6.419692-3.807179-8.310154-5.172513-1.929846-1.352205-9.885538-2.113641-12.603077-2.113641H419.052308c-2.743795 0-11.106462 0.774564-13.049436 2.139897-1.890462 1.352205-8.756513 3.21641-8.756513 5.146257v95.310769c0 1.969231 6.892308 7.036718 8.795897 8.402051 1.929846 1.339077 10.266256 5.369436 13.036308 5.369436h24.996103v81.657436C256.761436 227.511795 91.897436 400.108308 91.897436 608.518564 91.897436 837.618872 281.888821 1024 512.380718 1024 742.859487 1024 931.577436 837.632 931.577436 608.518564c-0.026256-125.702564-55.492923-238.316308-144.567795-314.538667z m-67.413333 146.116924L530.825846 627.698872a25.665641 25.665641 0 0 1-35.551179-0.590769 25.337436 25.337436 0 0 1-0.577641-35.341129l188.783589-187.602051a25.521231 25.521231 0 0 1 18.064411-7.443692 25.665641 25.665641 0 0 1 18.051282 7.443692 25.403077 25.403077 0 0 1 0 35.905641z"
fill="#ffffff"
p-id="1167"
></path>
</svg>
</div>
</template>
<script lang="ts" setup>
import {watch} from "vue";
const props = defineProps({
loading: {
type: Boolean,
default: false,
},
});
watch(
() => props.loading,
(v) => {
const el: HTMLElement = document.querySelector("body") as HTMLElement;
if (v) {
el.classList.add("loading");
} else {
el.classList.remove("loading");
}
}
);
</script>
<style scoped>
.loader::before {
top: 50%;
left: 50%;
z-index: 2;
content: "";
width: 100vmax;
height: 100vmax;
position: fixed;
border-radius: 66%;
background: var(--lime-soap);
transition: transform 0.5s cubic-bezier(0, 0, 0.5, 1.25);
transform: translate(-50%, -50%) scale(0);
}
.loader svg {
top: 50%;
left: 50%;
opacity: 0;
z-index: 3;
height: 8em;
color: white;
position: fixed;
visibility: hidden;
transform: translate(-50%, -50%) scale(0);
transition: opacity 0.3s, transform 0.5s cubic-bezier(0.5, 0, 0.5, 1.5),
visibility 0.3s;
}
</style>

View File

@ -0,0 +1,166 @@
<script lang="ts" setup>
import {debounce, getAssetURL} from "@/utils/function.ts";
import NavBarEntryItem from "@/components/nav/NavBarEntryItem.vue";
import {computed, onMounted, onUnmounted, ref} from "vue";
import {useRoute} from "vue-router";
type Widget = {
icon: string[]
url: string
title: string
}
// 使 ref
const scrolled = ref(false)
const littleWidget: Widget[] = [
{
icon: ['fas', 'gauge'],
url: "/netdata/",
title: "Pi Dashboard",
},
{
icon: ['fas', 'cloud'],
url: "/nas/",
title: "Nextcloud",
},
{
icon: ['fas', 'code-branch'],
url: "/git/",
title: "Git Repository"
}
]
const logoUrl = getAssetURL("logo-w.png")
const logoType: string = "logo";
const showLogo = computed(() => logoType === 'text')
const isExpended = ref<boolean>(false)
const route = useRoute()
const entry = [
{
title: '主页',
icon: ['fas', 'home'],
entry: [],
to: 'home'
},
{
title: '文章',
icon: ['fas', 'pen'],
entry: [
{
title: '博客',
url: false,
to: 'blog'
},
{
title: '日记',
url: false,
to: 'diary'
},
],
},
{
title: '作品',
icon: ['fas', 'brush'],
entry: [
{
title: '软件',
url: false,
to: 'soft'
},
{
title: '仓库',
url: true,
to: 'https://cantyonion.site/git/'
},
],
},
{
title: '杂谈',
icon: ['fas', 'chess-rook'],
entry: [],
to: 'talk'
}
]
const menuExpanded = () => {
isExpended.value = !isExpended.value
}
const menuClose = () => {
isExpended.value = false
}
// scroll
const handleScroll = () => {
scrolled.value = window.scrollY > 0
}
const debouncedScroll = debounce(handleScroll, 10)
const alwaysBlueBackground = computed(() => route.name !== 'home')
//
onMounted(() => {
window.addEventListener('scroll', debouncedScroll)
})
onUnmounted(() => {
window.removeEventListener('scroll', debouncedScroll)
})
</script>
<template>
<nav :class="{'bg-transparent': !scrolled && !alwaysBlueBackground,
'bg-blue-400': scrolled || alwaysBlueBackground,
'shadow': scrolled}"
class="fixed top-0 z-20 h-16 w-full text-white transition-all duration-300"
>
<div class="container mx-auto flex h-16 w-full items-center justify-between px-4 md:px-0 xl:max-w-screen-xl">
<!-- 下拉菜单按钮 -->
<div class="flex h-8 w-10 items-center justify-center rounded-md border-gray-600 md:hidden"
@click="menuExpanded"
>
<font-awesome-icon :icon="['fas', 'bars']" class="h-6"/>
</div>
<!-- Logo -->
<div class="absolute left-1/2 -translate-x-1/2 md:relative md:left-0 md:translate-x-0">
<!-- 文字模式 -->
<div v-if="showLogo" class="h-full text-4xl min-w-16 logo py-3.5">
<span>CantyOni_on's Index</span>
</div>
<!-- 图片模式 -->
<div v-else class="py-2">
<img :src="logoUrl" alt="" class="h-12 w-12"/>
</div>
</div>
<!-- Entry -->
<div :class="{'h-screen opacity-100 visible': isExpended, 'h-0 opacity-0 invisible': !isExpended}"
class="absolute top-full right-0 left-0 bg-white transition-all md:visible md:relative md:top-0 md:flex
md:h-auto md:bg-transparent md:opacity-100"
>
<nav-bar-entry-item v-for="item in entry" :key="item.title"
:entry="item.entry" :icon="item.icon"
:title="item.title" :to="item.to"
@on-go="menuClose"
/>
</div>
<!-- 快速跳转小组件 -->
<div class="flex items-center justify-between gap-4 text-xl">
<a v-for="item in littleWidget"
:key="item.url" :href=item.url :title=item.title
class="transition-all hover:scale-125">
<font-awesome-icon :icon="item.icon"/>
</a>
</div>
</div>
</nav>
</template>
<style scoped>
.logo {
font-family: BaconyScript, serif;
}
</style>

View File

@ -0,0 +1,70 @@
<script lang="ts" setup>
import router from "@/router";
type Entry = {
title: string,
url: boolean,
to: string,
}
const emit = defineEmits<{
(e: 'onGo'): void
}>();
defineProps<{
title: string
icon: string[]
to?: string
entry: Entry[]
}>()
const onClick = (to: string | undefined, url: boolean) => {
if (to === undefined)
return
if (url)
window.open(to)
emit('onGo')
router.push({name: to})
}
</script>
<template>
<div class="container relative w-full max-w-max flex-auto shrink-0 cursor-pointer group md:w-16 md:hover:w-full"
@click="onClick(to, false)"
>
<div class="my-1 flex h-14 w-screen items-center rounded-md pr-4 pl-2 text-gray-500
md:w-auto md:text-white md:group-hover:bg-gray-100 md:group-hover:bg-opacity-30"
>
<font-awesome-icon :icon="icon" class="mx-2 h-5 w-5 md:h-8 md:w-8"/>
<span class="w-full max-w-max overflow-hidden text-lg transition-all duration-300 text-nowrap font-bold
md:font-normal md:w-0 md:text-xl md:group-hover:w-full"
>
{{ title }}
</span>
</div>
<!-- 下拉菜单 -->
<div v-if="entry"
class="grid grid-cols-2 overflow-hidden transition-all duration-300 min-w-32 item md:invisible md:absolute
md:top-full md:left-1/2 md:block md:rounded-xl md:bg-white md:opacity-0 md:shadow-md
md:group-hover:visible md:group-hover:-translate-x-1/2 md:group-hover:opacity-100"
>
<div v-for="item in entry" :key="item.title"
class="px-11 py-2 text-gray-500 text-md md:px-0 md:text-center md:text-lg md:hover:bg-gray-100"
@click="onClick(item.to, item.url)"
>
{{ item.title }}
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.item {
@media (min-width: 768px) {
transform: perspective(500px) rotateX(-45deg) translateX(-50%);
}
}
</style>

View File

@ -0,0 +1,14 @@
<script lang="ts" setup>
import {getAssetURL} from "@/utils/function.ts";
</script>
<template>
<a class="flex items-center justify-center" href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral">
本网站由<img :src="getAssetURL('upyun.png')" alt="又拍云" class="h-8 w-auto">提供CDN加速/云储存服务
</a>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,20 @@
<script lang="ts" setup>
defineProps<{
icon: string[]
url: string
title: string
}>()
</script>
<template>
<div class="flex gap-4 text-xl">
<a :key="url" :href=url :title=title
class="flex items-center justify-center transition-all hover:scale-125">
<font-awesome-icon :icon="icon"/>
</a>
</div>
</template>
<style scoped>
</style>

37
src/main.ts Normal file
View File

@ -0,0 +1,37 @@
import {createApp} from 'vue'
import './style.scss'
import App from './App.vue'
// @ts-expect-error
import VueTyped from 'vue3-typed-js'
import router from "@/router";
import {library} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
import {faClock} from '@fortawesome/free-regular-svg-icons'
import {
faXmark,
faBlog,
faGauge,
faCodeBranch,
faCloud,
faChevronRight,
faCodeCommit,
faCode,
faHouse,
faPen,
faBrush,
faChessRook,
faBars,
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
import {faWeibo, faQq, faGithubAlt, faSteamSymbol} from '@fortawesome/free-brands-svg-icons'
library.add(
faClock, faXmark, faBlog, faGauge, faCodeBranch, faCloud, faWeibo, faQq, faGithubAlt, faSteamSymbol, faChevronRight,
faCodeCommit, faCode, faHouse, faPen, faBrush, faChessRook, faBars, faChevronDown
)
createApp(App)
.use(router)
.use(VueTyped)
.component('font-awesome-icon', FontAwesomeIcon)
.mount('#app')

89
src/router/index.ts Normal file
View File

@ -0,0 +1,89 @@
import {createRouter, createWebHashHistory} from "vue-router";
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
component: () => import('@/views/home/HomeView.vue'),
name: 'home',
meta: {
maintenance: false
}
},
{
path: "/article",
children: [
{
path: "/blog",
component: () => import('@/views/article/BlogView.vue'),
name: 'blog',
meta: {
maintenance: true
},
},
{
path: "/diary",
component: () => import('@/views/article/DiaryView.vue'),
name: 'diary',
meta: {
maintenance: true
}
}
]
},
{
path: "/work",
children: [
{
path: "/soft",
component: () => import('@/views/article/DiaryView.vue'),
name: 'soft',
meta: {
maintenance: true
}
}
]
},
{
path: "/talk",
component: () => import('@/views/talk/TalkView.vue'),
name: 'talk',
meta: {
maintenance: true
}
},
{
path: "/maintenance",
component: () => import('@/views/error/MaintenanceView.vue'),
name: 'maintenance'
},
{
name: 'error',
path: '/error',
children: [
{
name: '404',
path: '/error/404',
component: () => import('@/views/error/NotFoundView.vue')
},
{
name: '502',
path: '/error/502',
component: () => import('@/views/error/BadGateway.vue')
}
]
},
{
path: '/:catchAll(.*)',
redirect: '/error/404'
}
]
});
router.beforeEach((to, _, next) => {
if (to.meta.maintenance === true && !import.meta.env.DEV) next({name: 'maintenance'})
else next()
})
export default router;

View File

@ -6,7 +6,7 @@
@font-face {
font-family: 'BaconyScript';
src: url("assets/fonts/BaconyScript.otf") format("opentype");
src: url("./assets/BaconyScript_PERSONAL_USE_ONLY.otf") format("opentype");
}
body {
@ -19,6 +19,13 @@ pre, code {
.loading {
@apply fixed h-screen w-screen flex justify-center items-center z-[999] bg-blue-50 backdrop-blur-3xl bg-opacity-45 transition-all duration-500 opacity-100;
&::before {
content: 'CantyOni_on\'s Index';
font-family: 'BaconyScript', sans-serif;
@apply text-5xl lg:text-7xl text-gray-600;
}
}
.stop {

59
src/types/blog.d.ts vendored Normal file
View File

@ -0,0 +1,59 @@
declare interface IPostsData {
page: number
pageSize: number
pages: number
count: number
dataSet: []
}
declare interface IPost {
cid: string
title: string
created: string
modified: string
slug: string
commentsNum: string
type: string
digest: string
password: string
categories: ICategory[]
category: string
directory: string[]
date: IDate
year: string
month: string
day: string
hidden: boolean
pathinfo: string
permalink: string
url: string
isMarkdown: boolean
feedUrl: string
feedRssUrl: string
feedAtomUrl: string
fields: any
}
declare interface ICategory {
mid: string
name: string
slug: string
type: string
description: string
count: string
order: string
parent: string
cid: string
directory: string[]
url: string
feedUrl: string
feedRssUrl: string
feedAtomUrl: string
}
declare interface IDate {
timestamp: number
year: string
month: string
day: string
}

27
src/types/git.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
declare interface IActivity<T = string> {
id: string;
content: T
op_type: string
repo: {
name: string
html_url: string
}
created: string
}
declare interface IContent {
Commits: ICommit[]
HeadCommit: ICommit
CompareURL: string
Len: number
}
declare interface ICommit {
Sha1: string,
Message: string,
AuthorEmail: string,
AuthorName: string,
CommitterEmail: string,
CommitterName: string,
Timestamp: string
}

5
src/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare interface IBlogResponse<T = null> {
status: string
message: string
data: T
}

34
src/utils/function.ts Normal file
View File

@ -0,0 +1,34 @@
export const getAssetURL = (image: string) => {
// 参数一: 相对路径
// 参数二: 当前路径的URL
return new URL(`../assets/${image}`, import.meta.url).href
}
export const debounce = (fn: Function, delay: number) => {
let timeoutId: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
fn(...args)
}, delay)
}
}
export const setMobileTopColor = () => {
const lightMeta = document.createElement('meta');
lightMeta.setAttribute('name', 'theme-color');
lightMeta.setAttribute('media', '(prefers-color-scheme: light)');
lightMeta.setAttribute('content', '#60a5fa');
const darkMeta = document.createElement('meta');
darkMeta.setAttribute('name', 'theme-color');
darkMeta.setAttribute('media', '(prefers-color-scheme: dark)');
darkMeta.setAttribute('content', '#60a5fa');
document.head.appendChild(lightMeta);
document.head.appendChild(darkMeta);
}
export const removeMobileTopColor = () => {
document.querySelectorAll('meta[name="theme-color"]').forEach(meta => meta.remove());
}

9
src/utils/mitt.ts Normal file
View File

@ -0,0 +1,9 @@
import mitt from 'mitt'
type Events = {
openPost: IPost
startLoading: boolean
}
const emitter = mitt<Events>()
export default emitter

23
src/utils/network.ts Normal file
View File

@ -0,0 +1,23 @@
import axios, {AxiosError, AxiosRequestConfig} from 'axios'
export async function request<T = unknown>(config: AxiosRequestConfig<any>): Promise<T> {
const instance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 5000,
headers: {},
})
instance.interceptors.request.use(config => {
return config
}, error => error)
// bug fixed on csdn https://blog.csdn.net/qq_45325810/article/details/120704910
instance.interceptors.response.use(resource => {
if (resource.status === 200) return resource
return Promise.reject(new Error(resource.data))
}, (error: AxiosError) => {
return Promise.reject(error.response ? error.response.data : error.code)
})
return instance.request<T>(config).then(res => res.data)
}

View File

@ -0,0 +1,11 @@
<script lang="ts" setup>
</script>
<template>
</template>
<style scoped>
</style>

Some files were not shown because too many files have changed in this diff Show More