前言
之前苦于首屏时间长, 一直找解决方法, 无意间刷到了nuxt/next之类的东西.
nuxt和next都是用于服务端渲染(SSR)应用的开发的框架, nuxt基于vue, next基于react. nuxt/next项目基本上相当于一个全栈项目, 有种之前php的感觉, 但实际上不是.
Nuxt.js 和 Next.js 支持前后端分离, 前端使用单页应用(SPA)或多页应用(MPA), 后端可以作为 API 提供数据.
PHP 网站 通常将前后端集成在一起, 服务器直接渲染 HTML 页面. --chatgpt
了解到这套框架之后决定学习一下, 做一个nuxt小应用. 在chatgpt的意见下准备做个互动故事平台?大概相当于故事接龙?有个人开头, 然后别人接, 大概这样子.
应用的页面
初步设想大概就几个页面
- 首页:让用户选择故事, 可以点进去看.
- 发故事:让用户编辑故事开头.
- 看故事:看故事的页面.
只有前端这些功能基本上是做不到的. 但是刚好有giscus之类的用于博客评论的东西捏, 做这些绰绰有余. - 首页:获取仓库的discussion列表并展示
- 发故事:创建一个discussion
- 接故事:在对应的discussion评论
- 看故事:获取某个discussion 这样的话其实主要任务就只有怎么处理github的api与giscus以及网站的样式了.
giscus的登录
虽然说可以让用户要评论的时候再登录, 不过也可以实现先登录, 再考虑评论.
可以把鼠标放在登录按钮, 复制一下链接, 应该会得到https://giscus.app/api/oauth/authorize?redirect_uri=xxx
, 这个redirect_uri就是登录完成后返回的链接. 登录完成后注意地址栏的变化, 应该会发现跳回来不止是xxx, 后面还跟着一串东西, 然后很快就消失只剩下xxx. 开发者工具里可以找到一个token
的请求, 返回的就是token, 发送的是一个对象{session:string}
, 那其实只要拿到这个session就完成任务了.
新建一个nuxt项目
<template>
<div>
<a
href="https://giscus.app/api/oauth/authorize?redirect_uri=http://localhost:3000"
>login!</a
>
<br />
<NuxtPage />
</div>
</template>
先简单写一下, 点击进去可以正常登录. 这次登录成功后可以清楚地看到地址后面带的东西是什么/?giscus=xxx
这个xxx其实就是需要获取的session
, 之后需要把它存到localstorage里. 这个token其实就没什么必要获取了, 只要把giscus-session:xxx
这个存起来就可以了, giscus组件会自动获取这玩意的.
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
const giscus = computed(() => route.query.giscus);
</script>
再找个地方把它存起来就可以了.
if (giscus&&import.meta.client) {
const str = giscus as string;
localStorage.setItem('giscus-session', `"`+str+`"`);
}
这里要注意一下session要用""包裹.
测试session是否可用
<template>
<div>
<button @click="showComment=!showComment">
{{ showComment?'关闭':'展示' }}
</button>
<Giscus
v-if="showComment"
repo="water2027/StoryLink"
repo-id="R_kgDONIa9nA"
category="Announcements"
category-id="DIC_kwDONIa9nM4Cj2mQ"
mapping="pathname"
strict="0"
reactions-enabled="1"
emit-metadata="0"
input-position="top"
theme="preferred_color_scheme"
lang="zh-CN"
crossorigin="anonymous"
/>
</div>
</template>
<script setup lang="ts">
import { ref, defineAsyncComponent } from 'vue';
const Giscus = defineAsyncComponent(() => import('@giscus/vue'));
const showComment = ref(false);
</script>
这里当时因为我直接存了session没有用双引号包裹, 导致组件加载就会删除session重新登陆, 用双引号包裹之后就正常了.
获取token
很可惜的是, 当时我脑子抽了, 觉得要获取token, 浪费了一些时间去拿这个token.
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
const giscus = computed(() => route.query.giscus);
type returnData = {
token: string;
};
const { data: token, error } = await useFetch<returnData>('https://giscus.app/api/oauth/token', {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: {
session: giscus.value,
},
});
</script>
一个简单的获取token写好了, 但是放进去会发现, 出现了跨域问题, 不给获取. 这个时候就需要用代理了.
<script setup lang="ts">
type returnData = {
token: string;
};
import { useRoute } from 'vue-router';
const route = useRoute();
const giscus = computed(() => route.query.giscus);
const { data: token, error } = await useFetch<returnData>('/api/oauth/token', {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: {
session: giscus.value,
},
});
</script>
然后写个服务器中间件:
// server/middleware/proxy.ts
import { defineEventHandler, sendError, send, readBody } from 'h3';
export default defineEventHandler(async (event) => {
const { method, url } = event.node.req;
if (url === '/api/oauth/token' && method === 'POST') {
try {
const body = await readBody(event);
const response = await fetch('https://giscus.app/api/oauth/token', {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!response.ok) {
return sendError(
event,
new Error('Failed to fetch from target API')
);
}
const data = await response.json();
event.node.res.setHeader('Content-Type', 'application/json');
event.node.res.end(JSON.stringify(data));
} catch (error) {
return sendError(event, new Error('Internal Server Error'));
}
}
});
由服务器发送这个请求而不是从网页发送. 这样之后就解决了跨域问题.
虽然说完全没有必要解决这个问题就是了.
第一次使用nuxt, 又可以hello, world了.