Skip to content

Commit

Permalink
feat: 完善消息发布界面
Browse files Browse the repository at this point in the history
  • Loading branch information
nonhana committed Nov 27, 2024
1 parent 0cacfe2 commit 8c972c8
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 43 deletions.
4 changes: 2 additions & 2 deletions components/main/Footer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
</script>

<template>
<div class="mx-auto flex justify-between p-8 md:max-w-[90%] xl:max-w-[70%]">
<div class="mx-auto flex h-20 items-center justify-between px-8 md:max-w-[90%] xl:max-w-[70%]">
<span>Copyright © 2024 Hana's Garden</span>
<span>愛する人を失った世界には、どんな色の花が咲く?</span>
<span class="hidden md:block">灰色的花终会绽放。</span>
</div>
</template>
15 changes: 8 additions & 7 deletions components/thoughts/Input.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
const emits = defineEmits<{
(e: 'published'): void
(e: 'published', value: number | undefined): void
}>()
const { callHanaMessage } = useMessage()
Expand All @@ -18,26 +18,27 @@ async function handlePublish() {
const objData = {
content: value.value,
}
const { data } = await useAsyncData('post-message', () => $fetch('/api/messages/post', {
const data = await $fetch('/api/messages/post', {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
method: 'POST',
body: JSON.stringify(objData),
}))
if (data.value?.success) {
})
if (data.success) {
const newMsgId = data.payload?.id
emits('published', newMsgId)
value.value = ''
callHanaMessage({
type: 'success',
message: '发布成功',
})
emits('published')
}
else {
const errorList = data.value?.payload?.map(item => item.message).join(', ')
const errorList = data.error?.map(item => item.message).join(', ')
callHanaMessage({
type: 'error',
message: errorList || data.value?.statusMessage || '发布失败',
message: errorList || data.statusMessage || '发布失败',
})
}
}
Expand Down
46 changes: 34 additions & 12 deletions components/thoughts/Messages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,61 @@
import type { MessageItem } from '~/types/message'
const props = defineProps<{
needRefresh: boolean
newMsgId: number | undefined
}>()
const emits = defineEmits<{
(e: 'refreshed'): void
}>()
const { needRefresh } = toRefs(props)
const msgContainerRef = ref<HTMLDivElement | null>(null)
const { newMsgId } = toRefs(props)
const messages = ref<MessageItem[]>([])
async function fetchMessages() {
async function fetchOldMessages() {
const data = await $fetch('/api/messages/list')
if (data.success) {
messages.value = data.payload!
setTimeout(() => {
scrollToBottom()
}, 100 + Math.floor(messages.value.length / 2) * 100)
}
}
onMounted(async () => {
await fetchOldMessages()
})
async function fetchNewMessage(id: number) {
const data = await $fetch('/api/messages/single', { query: { id } })
if (data.success) {
messages.value = data.payload ?? []
messages.value.push({ ...data.payload!, isNew: true })
await scrollToBottom()
}
}
watch(needRefresh, async (newV) => {
async function scrollToBottom() {
if (msgContainerRef.value) {
await nextTick()
msgContainerRef.value.scrollTo({
top: msgContainerRef.value.scrollHeight,
behavior: 'smooth',
})
}
}
watch(newMsgId, async (newV) => {
if (newV) {
await fetchMessages()
await fetchNewMessage(newV)
emits('refreshed')
}
})
onMounted(async () => {
await fetchMessages()
})
</script>

<template>
<div class="relative mb-5 flex w-full flex-col gap-5">
<ThoughtsMessagesItem v-for="(message, index) in messages" :key="message.id" :index="index" :message="message" />
<div ref="msgContainerRef" class="mb-5 flex h-[calc(100vh-27rem)] w-full flex-col gap-5 overflow-auto">
<ThoughtsMessagesItem v-for="(message, index) in messages" :key="message.id" :index="message.isNew ? 0 : index" :message="message" />
</div>
</template>
2 changes: 1 addition & 1 deletion layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const isHome = computed(() => fullPath.value === '/')
<MainHeader />
</header>
<main class="flex-1">
<div id="main-wrapper" class="relative mx-auto p-8 md:max-w-[90%] xl:max-w-[70%]">
<div class="mx-auto flex-1 p-8 md:max-w-[90%] xl:max-w-[70%]">
<slot />
</div>
</main>
Expand Down
12 changes: 6 additions & 6 deletions pages/thoughts.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<script setup lang="ts">
const needRefresh = ref(false)
const newMsgId = ref<number>()
function handlePublished() {
needRefresh.value = true
function handlePublished(id: number | undefined) {
newMsgId.value = id
}
function handleRefreshed() {
needRefresh.value = false
newMsgId.value = undefined
}
</script>

<template>
<div class="w-full">
<ThoughtsMessages :need-refresh="needRefresh" @refreshed="handleRefreshed" />
<div class="relative size-full overflow-auto">
<ThoughtsMessages :new-msg-id="newMsgId" @refreshed="handleRefreshed" />
<ThoughtsInput @published="handlePublished" />
</div>
</template>
6 changes: 4 additions & 2 deletions server/api/messages/post.post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ export default formattedEventHandler(async (event) => {
return {
statusCode: 400,
statusMessage: 'Invalid request body',
payload: errorList,
error: errorList,
success: false,
}
}

const { content } = result

await prisma.message.create({
const newItem = await prisma.message.create({
data: {
content,
authorId: id,
},
})

return { payload: { id: newItem.id } }
})
49 changes: 49 additions & 0 deletions server/api/messages/single.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import dayjs from 'dayjs'
import prisma from '~/lib/prisma'

async function selectSingleMessage(id: number) {
const message = await prisma.message.findUnique({
where: { id },
select: {
id: true,
parent: {
select: {
id: true,
content: true,
},
},
author: {
select: {
id: true,
username: true,
site: true,
avatar: true,
},
},
content: true,
publishedAt: true,
editedAt: true,
},
})

return message
}

export default formattedEventHandler(async (event) => {
const query = getQuery(event) as { id: string }
const id = Number.parseInt(query.id)
const message = await selectSingleMessage(id)
if (!message) {
return {
statusCode: 404,
statusMessage: 'Message not found',
success: false,
}
}
const result = {
...message,
publishedAt: dayjs(message.publishedAt).format('YYYY-MM-DD HH:mm:ss'),
editedAt: dayjs(message.editedAt).format('YYYY-MM-DD HH:mm:ss'),
}
return { payload: result }
})
28 changes: 16 additions & 12 deletions server/utils/formattedEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
interface ApiResponse<T> {
interface ApiResponse<T, S> {
statusCode: number
statusMessage: string
success: boolean
payload: T
error: S
}

interface HandlerResponse<T> {
interface HandlerResponse<T, S> {
statusCode?: number
statusMessage?: string
success?: boolean
payload?: T
error?: S
}

export function formattedEventHandler<T>(
handler: (event: any) => Promise<HandlerResponse<T> | void> | HandlerResponse<T> | void,
export function formattedEventHandler<T, S>(
handler: (event: any) => Promise<HandlerResponse<T, S> | void> | HandlerResponse<T, S> | void,
) {
return defineEventHandler(async (event) => {
return defineEventHandler(async (event): Promise<ApiResponse<T | null, S | null>> => {
try {
const res = await handler(event)

Expand All @@ -25,29 +27,31 @@ export function formattedEventHandler<T>(
statusMessage: 'OK',
success: true,
payload: null,
} as ApiResponse<null>
error: null,
} as ApiResponse<null, null>
}

const { statusCode, statusMessage, success, payload } = res
const { statusCode, statusMessage, success, payload, error } = res

const formattedPayload = (payload === undefined || payload === null
? (null as T extends void ? null : T)
: payload) as T extends void ? null : T
const formattedPayload = payload === undefined || payload === null ? null : payload
const formattedError = error === undefined || error === null ? null : error

return {
statusCode: statusCode || 200,
statusMessage: statusMessage || 'OK',
success: success ?? true,
payload: formattedPayload,
} as ApiResponse<T extends void ? null : T>
error: formattedError,
} as ApiResponse<T extends void ? null : T, S extends void ? null : S>
}
catch (error: any) {
return {
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Internal Server Error',
success: false,
payload: null,
} as ApiResponse<null>
error: error.message || error,
} as ApiResponse<null, any>
}
})
}
6 changes: 5 additions & 1 deletion store/modules/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export const useUserInfoStore = defineStore('userInfo', () => {
setUserInfo,
logout,
}
}, { persist: true })
}, {
persist: {
storage: piniaPluginPersistedstate.localStorage(),
},
})
1 change: 1 addition & 0 deletions types/message.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export interface MessageItem {
site: string | null
avatar: string | null
} | null
isNew?: boolean
}

0 comments on commit 8c972c8

Please sign in to comment.