Compare commits
No commits in common. "nuxt" and "vue" have entirely different histories.
2
.env.development
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_BASE_URL = "/api"
|
||||
VITE_DEV = true
|
2
.env.production
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_BASE_URL = "/"
|
||||
VITE_DEV = false
|
46
.gitea/workflows/build.yaml
Normal 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
@ -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
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
7
LICENCE
Normal 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.
|
71
README.md
@ -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
@ -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>
|
Before Width: | Height: | Size: 111 KiB |
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 }} 提交 >></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>
|
@ -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>
|
@ -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>
|
@ -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">
|
||||
©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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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())
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
72
error.vue
@ -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>
|
@ -1,3 +0,0 @@
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt()
|
14
index.html
Normal 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>
|
@ -1,4 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
const { $mitt } = useNuxtApp()
|
||||
$mitt.emit('startLoading', true)
|
||||
})
|
@ -1,6 +0,0 @@
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
if (to.meta.maintenance === true && !import.meta.env.DEV) {
|
||||
return navigateTo('/error/maintenance')
|
||||
}
|
||||
return true
|
||||
})
|
@ -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,
|
||||
},
|
||||
},
|
||||
})
|
15
nuxt.run.xml
@ -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
53
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
navigateTo('/error/404')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { $mitt } = useNuxtApp()
|
||||
|
||||
onMounted(() => {
|
||||
$mitt.emit('startLoading', false)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nuxt-page />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -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>
|
@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
maintenance: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtWelcome />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>auth view</h1>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
maintenance: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtWelcome />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -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>
|
@ -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>
|
165
pages/index.vue
@ -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>
|
@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
maintenance: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtWelcome />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
maintenance: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtWelcome />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -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)
|
||||
})
|
@ -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,
|
||||
},
|
||||
}
|
||||
})
|
@ -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,
|
||||
},
|
||||
}
|
||||
})
|
@ -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
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@ -1 +0,0 @@
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
34
src/App.vue
Normal 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
@ -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
@ -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
|
||||
}
|
||||
})
|
||||
}
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 647 B |
Before Width: | Height: | Size: 999 KiB After Width: | Height: | Size: 999 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
21
src/components/alert/Alert.vue
Normal 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>
|
54
src/components/card/ArticleCard.vue
Normal 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>
|
90
src/components/card/ArticleDigestViewer.vue
Normal 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>
|
27
src/components/card/CardTitle.vue
Normal 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>
|
110
src/components/card/FullScreenIntroCard.vue
Normal 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>
|
86
src/components/card/GitCard.vue
Normal 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>
|
60
src/components/card/IntroCard.vue
Normal 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>
|
42
src/components/card/SectionCard.vue
Normal 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>
|
40
src/components/footer/MainFooter.vue
Normal 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">©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>
|
73
src/components/loader/LoaderView.vue
Normal 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>
|
166
src/components/nav/NavBar.vue
Normal 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>
|
70
src/components/nav/NavBarEntryItem.vue
Normal 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>
|
14
src/components/sponer/upyun.vue
Normal 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>
|
20
src/components/widget/SocialMediaWidget.vue
Normal 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
@ -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
@ -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;
|
@ -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
@ -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
@ -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
@ -0,0 +1,5 @@
|
||||
declare interface IBlogResponse<T = null> {
|
||||
status: string
|
||||
message: string
|
||||
data: T
|
||||
}
|
34
src/utils/function.ts
Normal 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
@ -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
@ -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)
|
||||
}
|
11
src/views/article/BlogView.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|