Compare commits

...

3 Commits

Author SHA1 Message Date
6e3f55480a 修正了一堆东西 2024-12-01 13:09:44 +08:00
376b5964a1 use yarn 2024-11-18 10:34:45 +08:00
ed007bf667 add pinia 2024-11-18 10:34:32 +08:00
20 changed files with 5819 additions and 10804 deletions

View File

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

View File

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

View File

@ -16,8 +16,8 @@ emitter.on('startLoading', on => {
<div ref="loading" class="loading"></div>
<div class="relative flex min-h-screen flex-col overflow-hidden bg-blue-50">
<nav-bar/>
<NuxtPage/>
<nuxt-page/>
<footer-main/>
<alert-notification/>
</div>
</template>

View File

@ -0,0 +1,47 @@
<script lang="ts" setup>
const notifications = ref<INotification[]>([]);
const 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',
};
const addNotification = (notification: INotification) => {
notifications.value.push(notification);
//
setTimeout(() => {
notifications.value.shift();
}, 5000);
console.log(1)
};
emitter.on('eventBus', addNotification)
onBeforeUnmount(() => {
emitter.off('eventBus', addNotification);
});
</script>
<template>
<div class="fixed top-5 right-5 space-y-4 z-50">
<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="font-bold text-lg">{{ notification.title }}</h3>
<p>{{ notification.message }}</p>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -30,7 +30,7 @@ const handleReload = () => {
</div>
<div v-else>
<!-- 正式内容 -->
<span v-if="empty" class="text-2xl font-bold">最近没有动态</span>
<span v-if="empty" class="text-center text-2xl font-bold">最近没有动态</span>
<slot v-else name="default"/>
</div>
</div>

View File

@ -7,6 +7,12 @@ type Widget = {
title: string
}
const props = defineProps({
navColor: {
default: false,
type: Boolean
}
})
// 使 ref
const scrolled = ref(false)
const littleWidget: Widget[] = [
@ -94,7 +100,7 @@ const handleScroll = () => {
const debouncedScroll = debounce(handleScroll, 10)
const alwaysBlueBackground = computed(() => route.name !== 'index')
const alwaysBlueBackground = computed(() => route.name !== 'index' || props.navColor)
//
onMounted(() => {

View File

@ -3,6 +3,7 @@ import mitt from 'mitt'
type Events = {
openPost: IPost
startLoading: boolean
eventBus: INotification
}
const emitter = mitt<Events>()

View File

@ -1,7 +1,9 @@
export const getAssetURL = (image: string) => {
// 参数一: 相对路径
// 参数二: 当前路径的URL
return new URL(`../assets/images/${image}`, import.meta.url).href
if (import.meta.client)
return new URL(`../assets/images/${image}`, import.meta.url).href
return `/_nuxt/assets/images/${image}`
}
export const debounce = (fn: Function, delay: number) => {

View File

@ -1,24 +1,53 @@
import axios from 'axios'
import type {AxiosRequestConfig, AxiosError} from 'axios'
import type { AsyncData, UseFetchOptions } from '#app'
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: {},
export function requestCore<DataT>(url: string, options: UseFetchOptions<DataT>) {
// const config = useRuntimeConfig()
return useFetch(url, {
baseURL: "https://cantyonion.site",
retry: false,
onRequest({ options }) {
let token: string = ""
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}`)
return
}
// 业务code状态码判断
// if (response._data.code !== 200) {
//
// }
},
onResponseError({ response }) {
emitter.emit('eventBus', {
level: 'error',
title: `HTTP错误${response.status}`,
message: response.statusText
})
},
...options
})
}
instance.interceptors.request.use(config => {
return config
}, error => error)
export function get<DataT, ErrorT>(url: string, options?: UseFetchOptions<DataT>): () => Promise<DataT> {
let result: AsyncData<DataT, ErrorT> | null = null
// 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 async (): Promise<DataT> => {
if (result != null) {
await result.refresh()
return result.data.value
}
return instance.request<T>(config).then(res => res.data)
result = await requestCore(url, {
method: 'GET',
...options,
}) as AsyncData<DataT, ErrorT>
return result.data.value
}
}

60
error.vue Normal file
View File

@ -0,0 +1,60 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps({
error: Object as () => NuxtError
})
const 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: '请求在穿越洋葱星球时迷路了,请返回首页重新探索!',
},
};
const errorContent = computed(() => errorMessages[props.error!.statusCode] || errorMessages['default']);
console.error(props.error)
onMounted(() => {
emitter.emit("startLoading", false)
})
</script>
<template>
<div class="relative flex min-h-screen flex-col overflow-hidden bg-blue-50">
<nav-bar :nav-color="true"/>
<alert :title="errorContent.title" :message="errorContent.message" :icon="errorContent.icon"/>
<footer-main/>
<alert-notification/>
</div>
</template>
<style scoped>
</style>

View File

@ -3,17 +3,21 @@
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: true },
modules: [
'@pinia/nuxt',
'@pinia-plugin-persistedstate/nuxt'
],
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的超级基地进来看看吧'},
{ 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'},
{ rel: 'icon', href: '/favicon.png' },
]
}
},
@ -29,7 +33,7 @@ export default defineNuxtConfig({
},
},
build: {
transpile: ['@fortawesome/vue-fontawesome', 'vue3-typed-js']
transpile: ['@fortawesome/vue-fontawesome', 'vue3-typed-js', 'pinia-plugin-persistedstate']
},
vite: {
css: {
@ -39,14 +43,11 @@ export default defineNuxtConfig({
}
}
},
server: {
proxy: {
'/api': {
target: 'https://cantyonion.site',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
},
runtimeConfig: {
public: {
gitApiKey: 'fb8aec429ea7d0a36f7238dbffda9d2d66c7b045',
baseURL: 'https://cantyonion.site'
}
}
})

10692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "nuxt-app",
"name": "web-index",
"private": true,
"type": "module",
"scripts": {
@ -16,7 +16,10 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
"@pinia/nuxt": "^0.7.0",
"axios": "^1.7.7",
"mathjax": "^3.2.2",
"mitt": "^3.0.1",
"nuxt": "^3.14.159",
"vue": "latest",

11
pages/auth.vue Normal file
View File

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

14
pages/blog/[cid].vue Normal file
View File

@ -0,0 +1,14 @@
<script setup lang="ts">
const route = useRoute()
const cid = route.params.cid as string
onMounted( () => {
})
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -1,48 +1,18 @@
<script setup lang="ts">
const route = useRoute()
const errorCode = route.params.code as string
const 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: '请求在穿越洋葱星球时迷路了,请返回首页重新探索!',
},
};
const errorCode = parseInt(route.params.code as string, 10)
const errorContent = computed(() => errorMessages[errorCode] || errorMessages['default']);
if (import.meta.server)
throw createError({
statusCode: errorCode,
})
else
showError({statusCode: errorCode})
onMounted(() => {
emitter.emit("startLoading", false)
})
</script>
<template>
<alert :title="errorContent.title" :message="errorContent.message" :icon="errorContent.icon"/>
</template>
<style scoped>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import {computed, onMounted, ref} from "vue";
import {getBlogRecentPost} from "~/api/blog";
import {getActivity} from "~/api/git"
const recentPosts = ref<IPost[] | null>(null);
const recentActivities = ref<IActivity[] | null>(null);
@ -14,6 +12,17 @@ const isError = ref({
git: false
});
const config = useRuntimeConfig()
const getBlogRecentPost = get<IBlogResponse<IPostsData>, any>('/blog/index.php/api/posts')
const getActivity = get<IActivity[], any>('/git/api/v1/users/cantyonion/activities/feeds', {
headers: {
Authorization: ` ${config.public.gitApiKey}`
},
params: {
limit: 10,
}
})
const hasPosts = computed(() => (recentPosts.value ?? []).length > 0);
const hasActivities = computed(() => (recentActivities.value ?? []).length > 0);
const postsData = computed(() => {
@ -41,30 +50,33 @@ const activitiesData = computed((): IActivity<IContent>[] => {
const reloadPosts = async () => {
try {
isLoading.value.blog = true
const postData = await getBlogRecentPost();
recentPosts.value = postData.data.dataSet
isLoading.value.blog = false
const postData = await getBlogRecentPost()
recentPosts.value = postData!.data.dataSet
} catch (e) {
isLoading.value.blog = false
isError.value.blog = true
}
finally {
isLoading.value.blog = false
}
}
const reloadActivities = async () => {
try {
isLoading.value.git = true
recentActivities.value = await getActivity()
isLoading.value.git = false
} catch (e) {
isLoading.value.git = false
isError.value.git = true
console.log(e)
}
finally {
isLoading.value.git = false
}
}
onMounted(async () => {
await reloadPosts()
await reloadActivities()
await reloadPosts()
await reloadActivities()
onMounted(() => {
emitter.emit('startLoading', false)
})
</script>

5
types/notification.ts Normal file
View File

@ -0,0 +1,5 @@
declare interface INotification {
level: 'warning' | 'error' | 'info' | 'success'
title: string
message: string
}

5570
yarn.lock Normal file

File diff suppressed because it is too large Load Diff