Browse Source

fix: migrate console API calls to useClientApi fetchData

- Replace request+unwrap with fetchData across /me admin, posts, timeline, rss, profile
- Inline form errors: profile uploads/save use notify:false + getApiErrorMessage
- Remove duplicate extractError/toast in media-storage, orphans, markdown editor
- PostComments and logout use fetchData for consistent error toasts

Made-with: Cursor
tags/邮箱功能前置
npmrun 4 weeks ago
parent
commit
f73d766565
  1. 4
      app/components/AppShell.vue
  2. 29
      app/components/PostBodyMarkdownEditor.vue
  3. 59
      app/components/PostComments.vue
  4. 4
      app/pages/index/index.vue
  5. 7
      app/pages/me/admin/config/index.vue
  6. 28
      app/pages/me/admin/media-storage.vue
  7. 10
      app/pages/me/admin/users/index.vue
  8. 6
      app/pages/me/index.vue
  9. 32
      app/pages/me/media/orphans.vue
  10. 9
      app/pages/me/posts/[id].vue
  11. 6
      app/pages/me/posts/index.vue
  12. 8
      app/pages/me/posts/new.vue
  13. 44
      app/pages/me/profile/index.vue
  14. 19
      app/pages/me/rss/index.vue
  15. 15
      app/pages/me/timeline/index.vue

4
app/components/AppShell.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../utils/http/factory'
import { useAuthSession } from '../composables/useAuthSession'
withDefaults(
@ -12,6 +11,7 @@ withDefaults(
const route = useRoute()
const { loggedIn, user, refresh, clear, pending } = useAuthSession()
const { fetchData } = useClientApi()
const { allowRegister, siteName } = useGlobalConfig()
const logoutLoading = ref(false)
@ -91,7 +91,7 @@ const accountMenuItems = computed(() => {
async function logout() {
logoutLoading.value = true
try {
unwrapApiBody(await request<ApiResponse<{ success: boolean }>>('/api/auth/logout', { method: 'POST' }))
await fetchData<{ success: boolean }>('/api/auth/logout', { method: 'POST' })
clear()
await navigateTo('/login')
} finally {

29
app/components/PostBodyMarkdownEditor.vue

@ -1,7 +1,6 @@
<script setup lang="ts">
import { MdEditor } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { request, unwrapApiBody, type ApiResponse } from '~/utils/http/factory'
const props = defineProps<{
modelValue: string
@ -11,7 +10,7 @@ const emit = defineEmits<{
'update:modelValue': [string]
}>()
const toast = useToast()
const { fetchData } = useClientApi()
const editorId = `post-body-md-${useId()}`
const local = computed({
@ -19,40 +18,18 @@ const local = computed({
set: (v: string) => emit('update:modelValue', v),
})
function extractUploadError(e: unknown): string {
if (e && typeof e === 'object') {
const fe = e as {
statusMessage?: string
message?: string
data?: { message?: string }
}
if (typeof fe.statusMessage === 'string' && fe.statusMessage.length) {
return fe.statusMessage
}
if (typeof fe.data?.message === 'string' && fe.data.message.length) {
return fe.data.message
}
if (typeof fe.message === 'string' && fe.message.length) {
return fe.message
}
}
return '图片上传失败'
}
async function onUploadImg(files: File[], callback: (urls: string[]) => void) {
const form = new FormData()
for (const f of files) {
form.append('file', f)
}
try {
const res = await request<ApiResponse<{ files: { url: string }[] }>>('/api/file/upload', {
const { files: uploaded } = await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
method: 'POST',
body: form,
})
const { files: uploaded } = unwrapApiBody(res)
callback(uploaded.map((x) => x.url))
} catch (e: unknown) {
toast.add({ title: extractUploadError(e), color: 'error' })
} catch {
callback([])
}
}

59
app/components/PostComments.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { unwrapApiBody, request, type ApiResponse } from '~/utils/http/factory'
import { unwrapApiBody, type ApiResponse } from '~/utils/http/factory'
import { useAuthSession } from '~/composables/useAuthSession'
type CommentNode = {
@ -29,6 +29,7 @@ const props = defineProps<{
}>()
const { loggedIn, refresh: refreshAuthSession } = useAuthSession()
const { fetchData } = useClientApi()
const toast = useToast()
onMounted(() => {
@ -95,44 +96,17 @@ function authorLine(node: CommentNode): string {
return '访客'
}
function extractErrorMessage(e: unknown): string {
if (e && typeof e === 'object') {
const fe = e as {
statusMessage?: string
message?: string
data?: { message?: string }
}
if (typeof fe.statusMessage === 'string' && fe.statusMessage.length) {
return fe.statusMessage
}
if (typeof fe.data?.message === 'string' && fe.data.message.length) {
return fe.data.message
}
if (typeof fe.message === 'string' && fe.message.length) {
return fe.message
}
}
if (e instanceof Error) {
return e.message
}
return '操作失败'
}
async function deleteComment(node: CommentNode) {
const postId = data.value?.postId
if (postId == null) {
return
}
try {
const res = await request<ApiResponse<{ ok: boolean }>>(
`/api/me/posts/${postId}/comments/${node.id}`,
{ method: 'DELETE' },
)
unwrapApiBody(res)
await fetchData(`/api/me/posts/${postId}/comments/${node.id}`, { method: 'DELETE' })
await refreshComments()
}
catch (e: unknown) {
toast.add({ title: extractErrorMessage(e), color: 'error' })
catch {
/* fetchData 已 toast */
}
}
@ -164,29 +138,18 @@ async function submitComment() {
...(loggedIn.value ? {} : { guestDisplayName: guestName.value.trim() }),
}
if (loggedIn.value) {
const res = await request<ApiResponse<{ id: number }>>(`${base}/comments`, {
method: 'POST',
body: payload,
})
unwrapApiBody(res)
}
else {
const res = await $fetch<ApiResponse<{ id: number }>>(`${base}/comments`, {
method: 'POST',
body: payload,
credentials: 'include',
})
unwrapApiBody(res)
}
await fetchData<{ id: number }>(`${base}/comments`, {
method: 'POST',
body: payload,
})
draftBody.value = ''
guestName.value = ''
replyToId.value = null
await refreshComments()
}
catch (e: unknown) {
toast.add({ title: extractErrorMessage(e), color: 'error' })
catch {
/* fetchData 已 toast */
}
finally {
submitting.value = false

4
app/pages/index/index.vue

@ -1,8 +1,8 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../utils/http/factory'
import { useAuthSession } from '../../composables/useAuthSession'
const { loggedIn, user, clear } = useAuthSession()
const { fetchData } = useClientApi()
const { allowRegister, siteName } = useGlobalConfig()
const logoutLoading = ref(false)
@ -37,7 +37,7 @@ const features = [
async function logout() {
logoutLoading.value = true
try {
unwrapApiBody(await request<ApiResponse<{ success: boolean }>>('/api/auth/logout', { method: 'POST' }))
await fetchData<{ success: boolean }>('/api/auth/logout', { method: 'POST' })
clear()
await navigateTo('/login')
} finally {

7
app/pages/me/admin/config/index.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../../utils/http/factory'
import { useAuthSession } from '../../../../composables/useAuthSession'
definePageMeta({ title: '应用配置' })
@ -14,6 +13,7 @@ type GlobalConfigPayload = {
}
const { user, refresh } = useAuthSession()
const { fetchData } = useClientApi()
const { refresh: refreshGlobalConfig } = useGlobalConfig()
const loading = ref(true)
@ -33,8 +33,7 @@ async function ensureAdmin() {
async function load() {
loading.value = true
try {
const res = await request<ApiResponse<GlobalConfigPayload>>('/api/config/global')
const cfg = unwrapApiBody(res).config
const { config: cfg } = await fetchData<GlobalConfigPayload>('/api/config/global')
siteName.value = cfg.siteName
allowRegister.value = cfg.allowRegister
mediaOrphanAutoSweepEnabled.value = cfg.mediaOrphanAutoSweepEnabled
@ -50,7 +49,7 @@ onMounted(async () => {
})
async function putKey(key: string, value: unknown) {
await request('/api/config/global', {
await fetchData('/api/config/global', {
method: 'PUT',
body: { key, value },
})

28
app/pages/me/admin/media-storage.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { useAuthSession } from '../../../composables/useAuthSession'
definePageMeta({ title: '媒体存储校验' })
@ -14,6 +13,7 @@ type AuditReport = {
}
const { user, refresh } = useAuthSession()
const { fetchData } = useClientApi()
const toast = useToast()
const loading = ref(false)
@ -28,30 +28,11 @@ async function ensureAdmin() {
}
}
function extractError(e: unknown): string {
if (e && typeof e === 'object') {
const fe = e as { statusMessage?: string; message?: string; data?: { message?: string } }
if (typeof fe.statusMessage === 'string' && fe.statusMessage.length) {
return fe.statusMessage
}
if (typeof fe.data?.message === 'string' && fe.data.message.length) {
return fe.data.message
}
if (typeof fe.message === 'string' && fe.message.length) {
return fe.message
}
}
return '操作失败'
}
async function runAudit() {
loading.value = true
try {
const res = await request<ApiResponse<AuditReport>>('/api/admin/media/storage-audit')
report.value = unwrapApiBody(res)
report.value = await fetchData<AuditReport>('/api/admin/media/storage-audit')
toast.add({ title: '校验完成', color: 'success' })
} catch (e: unknown) {
toast.add({ title: extractError(e), color: 'error' })
} finally {
loading.value = false
}
@ -60,16 +41,13 @@ async function runAudit() {
async function runCleanup() {
cleaning.value = true
try {
const res = await request<ApiResponse<{ removed: number; removedIds: number[] }>>(
const { removed } = await fetchData<{ removed: number; removedIds: number[] }>(
'/api/admin/media/storage-audit-cleanup',
{ method: 'POST', body: {} },
)
const { removed } = unwrapApiBody(res)
toast.add({ title: `已移除 ${removed} 条失效记录(无引用且磁盘无文件)`, color: 'success' })
cleanupOpen.value = false
await runAudit()
} catch (e: unknown) {
toast.add({ title: extractError(e), color: 'error' })
} finally {
cleaning.value = false
}

10
app/pages/me/admin/users/index.vue

@ -1,11 +1,11 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../../utils/http/factory'
import { useAuthSession } from '../../../../composables/useAuthSession'
import { buildPublicProfileAbsoluteUrl } from '../../../../utils/public-profile-url'
definePageMeta({ title: '用户管理' })
const { user, refresh } = useAuthSession()
const { fetchData } = useClientApi()
const toast = useToast()
const rows = ref<
@ -44,8 +44,8 @@ async function ensureAdmin() {
async function load() {
loading.value = true
try {
const res = await request<ApiResponse<{ users: typeof rows.value }>>('/api/admin/users')
rows.value = unwrapApiBody(res).users
const { users } = await fetchData<{ users: typeof rows.value }>('/api/admin/users')
rows.value = users
} finally {
loading.value = false
}
@ -59,7 +59,7 @@ onMounted(async () => {
async function createUser() {
creating.value = true
try {
await request('/api/admin/users', {
await fetchData('/api/admin/users', {
method: 'POST',
body: {
username: form.username,
@ -77,7 +77,7 @@ async function createUser() {
}
async function setStatus(id: number, status: 'active' | 'disabled') {
await request(`/api/admin/users/${id}`, { method: 'PATCH', body: { status } })
await fetchData(`/api/admin/users/${id}`, { method: 'PATCH', body: { status } })
await load()
}
</script>

6
app/pages/me/index.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../utils/http/factory'
import { useAuthSession } from '../../composables/useAuthSession'
definePageMeta({
@ -7,6 +6,7 @@ definePageMeta({
})
const { user } = useAuthSession()
const { fetchData } = useClientApi()
type ProfilePayload = {
profile: { publicSlug: string | null }
@ -16,8 +16,8 @@ const publicSlug = ref<string | null>(null)
onMounted(async () => {
try {
const res = await request<ApiResponse<ProfilePayload>>('/api/me/profile')
publicSlug.value = unwrapApiBody(res).profile.publicSlug
const { profile } = await fetchData<ProfilePayload>('/api/me/profile')
publicSlug.value = profile.publicSlug
} catch {
publicSlug.value = null
}

32
app/pages/me/media/orphans.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { useAuthSession } from '../../../composables/useAuthSession'
definePageMeta({ title: '图片孤儿审查' })
@ -20,6 +19,7 @@ type OrphanItem = {
type Filter = 'all' | 'deletable' | 'cooling'
const toast = useToast()
const { fetchData } = useClientApi()
const { refresh: refreshAuth } = useAuthSession()
const filter = ref<Filter>('all')
@ -57,26 +57,6 @@ const confirmDescription = computed(() => {
return n === 1 ? '确定删除该图片?此操作不可恢复。' : `确定删除选中的 ${n} 张图片?此操作不可恢复。`
})
function extractError(e: unknown): string {
if (e && typeof e === 'object') {
const fe = e as {
statusMessage?: string
message?: string
data?: { message?: string }
}
if (typeof fe.statusMessage === 'string' && fe.statusMessage.length) {
return fe.statusMessage
}
if (typeof fe.data?.message === 'string' && fe.data.message.length) {
return fe.data.message
}
if (typeof fe.message === 'string' && fe.message.length) {
return fe.message
}
}
return '操作失败'
}
function formatBytes(n: number): string {
if (n < 1024) {
return `${n} B`
@ -103,16 +83,13 @@ async function load() {
page: String(page.value),
pageSize: String(pageSize.value),
})
const res = await request<ApiResponse<{ items: OrphanItem[]; total: number }>>(
const body = await fetchData<{ items: OrphanItem[]; total: number }>(
`/api/me/media/orphans?${q.toString()}`,
)
const body = unwrapApiBody(res)
items.value = body.items
total.value = body.total
const allowed = new Set(body.items.filter((i) => i.state === 'deletable').map((i) => i.id))
selectedIds.value = new Set([...selectedIds.value].filter((id) => allowed.has(id)))
} catch (e: unknown) {
toast.add({ title: extractError(e), color: 'error' })
} finally {
loading.value = false
}
@ -170,17 +147,14 @@ function openConfirm(ids: number[]) {
async function executeDelete() {
deleting.value = true
try {
const res = await request<ApiResponse<{ deleted: number }>>('/api/me/media/orphans-delete', {
const { deleted } = await fetchData<{ deleted: number }>('/api/me/media/orphans-delete', {
method: 'POST',
body: { ids: confirmIds.value },
})
const { deleted } = unwrapApiBody(res)
toast.add({ title: `已删除 ${deleted}`, color: 'success' })
confirmOpen.value = false
selectedIds.value = new Set()
await load()
} catch (e: unknown) {
toast.add({ title: extractError(e), color: 'error' })
} finally {
deleting.value = false
}

9
app/pages/me/posts/[id].vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { useAuthSession } from '../../../composables/useAuthSession'
definePageMeta({ title: '编辑文章' })
@ -7,6 +6,7 @@ definePageMeta({ title: '编辑文章' })
const route = useRoute()
const id = computed(() => route.params.id as string)
const { user, refresh: refreshAuth } = useAuthSession()
const { fetchData } = useClientApi()
const state = reactive({
title: '',
@ -30,8 +30,7 @@ const publicPostHref = computed(() => {
async function load() {
loading.value = true
try {
const res = await request<ApiResponse<{ post: typeof state }>>(`/api/me/posts/${id.value}`)
const p = unwrapApiBody(res).post
const { post: p } = await fetchData<{ post: typeof state }>(`/api/me/posts/${id.value}`)
Object.assign(state, {
title: p.title,
slug: p.slug,
@ -57,7 +56,7 @@ watch(id, () => {
async function save() {
saving.value = true
try {
await request(`/api/me/posts/${id.value}`, {
await fetchData(`/api/me/posts/${id.value}`, {
method: 'PUT',
body: {
title: state.title,
@ -74,7 +73,7 @@ async function save() {
}
async function remove() {
await request(`/api/me/posts/${id.value}`, { method: 'DELETE' })
await fetchData(`/api/me/posts/${id.value}`, { method: 'DELETE' })
await navigateTo('/me/posts')
}

6
app/pages/me/posts/index.vue

@ -1,5 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { useAuthSession } from '../../../composables/useAuthSession'
definePageMeta({ title: '文章' })
@ -9,12 +8,13 @@ type Row = { id: number; title: string; slug: string; visibility: string }
const posts = ref<Row[]>([])
const loading = ref(true)
const { user, refresh: refreshAuth } = useAuthSession()
const { fetchData } = useClientApi()
async function load() {
loading.value = true
try {
const res = await request<ApiResponse<{ posts: Row[] }>>('/api/me/posts')
posts.value = unwrapApiBody(res).posts
const { posts: list } = await fetchData<{ posts: Row[] }>('/api/me/posts')
posts.value = list
} finally {
loading.value = false
}

8
app/pages/me/posts/new.vue

@ -1,8 +1,8 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
definePageMeta({ title: '新建文章' })
const { fetchData } = useClientApi()
const state = reactive({
title: '',
slug: '',
@ -15,7 +15,7 @@ const loading = ref(false)
async function submit() {
loading.value = true
try {
const res = await request<ApiResponse<{ post: { id: number } }>>('/api/me/posts', {
const { post } = await fetchData<{ post: { id: number } }>('/api/me/posts', {
method: 'POST',
body: {
title: state.title,
@ -25,7 +25,7 @@ async function submit() {
visibility: state.visibility,
},
})
const id = unwrapApiBody(res).post.id
const id = post.id
await navigateTo(`/me/posts/${id}`)
} finally {
loading.value = false

44
app/pages/me/profile/index.vue

@ -1,10 +1,10 @@
<script setup lang="ts">
import { useAuthSession } from '../../../composables/useAuthSession'
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
definePageMeta({ title: '资料' })
const { refresh: refreshAuthSession } = useAuthSession()
const { fetchData, getApiErrorMessage } = useClientApi()
type ProfileGet = {
profile: {
@ -62,11 +62,11 @@ async function onAvatarFileChange(ev: Event) {
try {
const form = new FormData()
form.append('file', file)
const res = await request<ApiResponse<{ files: { url: string }[] }>>('/api/file/upload', {
const { files } = await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
method: 'POST',
body: form,
notify: false,
})
const { files } = unwrapApiBody(res)
const url = files[0]?.url
if (!url) {
message.value = '上传未返回地址'
@ -75,10 +75,7 @@ async function onAvatarFileChange(ev: Event) {
state.avatar = url
message.value = '头像已上传,请点击下方「保存」写入资料'
} catch (e: unknown) {
message.value =
typeof e === 'object' && e !== null && 'statusMessage' in e
? String((e as { statusMessage: string }).statusMessage)
: '头像上传失败'
message.value = getApiErrorMessage(e)
} finally {
uploadingAvatar.value = false
}
@ -96,11 +93,11 @@ async function onHeaderIconFileChange(ev: Event) {
try {
const form = new FormData()
form.append('file', file)
const res = await request<ApiResponse<{ files: { url: string }[] }>>('/api/file/upload', {
const { files } = await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
method: 'POST',
body: form,
notify: false,
})
const { files } = unwrapApiBody(res)
const url = files[0]?.url
if (!url) {
message.value = '上传未返回地址'
@ -109,10 +106,7 @@ async function onHeaderIconFileChange(ev: Event) {
state.publicHomeHeaderIconUrl = url
message.value = '顶栏图标已上传,请点击下方「保存」写入'
} catch (e: unknown) {
message.value =
typeof e === 'object' && e !== null && 'statusMessage' in e
? String((e as { statusMessage: string }).statusMessage)
: '顶栏图标上传失败'
message.value = getApiErrorMessage(e)
} finally {
uploadingHeaderIcon.value = false
}
@ -121,12 +115,12 @@ async function onHeaderIconFileChange(ev: Event) {
async function load() {
loading.value = true
try {
const [profileRes, meCfgRes] = await Promise.all([
request<ApiResponse<ProfileGet>>('/api/me/profile'),
request<ApiResponse<MeConfigGet>>('/api/config/me'),
const [profilePayload, meCfgPayload] = await Promise.all([
fetchData<ProfileGet>('/api/me/profile'),
fetchData<MeConfigGet>('/api/config/me'),
])
const p = unwrapApiBody(profileRes).profile
const cfg = unwrapApiBody(meCfgRes).config
const p = profilePayload.profile
const cfg = meCfgPayload.config
state.nickname = p.nickname ?? ''
state.avatar = p.avatar ?? ''
state.avatarVisibility = p.avatarVisibility
@ -155,8 +149,9 @@ async function save() {
message.value = '社交链接 JSON 无效'
return
}
await request('/api/me/profile', {
await fetchData('/api/me/profile', {
method: 'PUT',
notify: false,
body: {
nickname: state.nickname || null,
avatar: state.avatar || null,
@ -168,12 +163,14 @@ async function save() {
},
})
await Promise.all([
request('/api/config/me', {
fetchData('/api/config/me', {
method: 'PUT',
notify: false,
body: { key: 'publicHomeHeaderTitle', value: state.publicHomeHeaderTitle },
}),
request('/api/config/me', {
fetchData('/api/config/me', {
method: 'PUT',
notify: false,
body: { key: 'publicHomeHeaderIconUrl', value: state.publicHomeHeaderIconUrl },
}),
])
@ -185,10 +182,7 @@ async function save() {
}
await load()
} catch (e: unknown) {
message.value =
typeof e === 'object' && e !== null && 'statusMessage' in e
? String((e as { statusMessage: string }).statusMessage)
: '保存失败'
message.value = getApiErrorMessage(e)
} finally {
saving.value = false
}

19
app/pages/me/rss/index.vue

@ -1,6 +1,4 @@
<script setup lang="ts">
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
definePageMeta({ title: 'RSS' })
type Feed = { id: number; feedUrl: string; title: string | null; lastError: string | null }
@ -22,6 +20,7 @@ const copiedItemId = ref<number | null>(null)
let copyResetTimer: ReturnType<typeof setTimeout> | undefined
const { user, refresh } = useAuthSession()
const { fetchData } = useClientApi()
const filteredItems = computed(() => {
if (selectedFeedId.value === null) {
@ -57,11 +56,11 @@ async function load() {
loading.value = true
try {
const [f, i] = await Promise.all([
request<ApiResponse<{ feeds: Feed[] }>>('/api/me/rss/feeds'),
request<ApiResponse<{ items: Item[] }>>('/api/me/rss/items'),
fetchData<{ feeds: Feed[] }>('/api/me/rss/feeds'),
fetchData<{ items: Item[] }>('/api/me/rss/items'),
])
feeds.value = unwrapApiBody(f).feeds
items.value = unwrapApiBody(i).items
feeds.value = f.feeds
items.value = i.items
if (selectedFeedId.value !== null && !feeds.value.some((x) => x.id === selectedFeedId.value)) {
selectedFeedId.value = null
}
@ -75,18 +74,18 @@ onMounted(async () => {
})
async function addFeed() {
await request('/api/me/rss/feeds', { method: 'POST', body: { feedUrl: feedUrl.value } })
await fetchData('/api/me/rss/feeds', { method: 'POST', body: { feedUrl: feedUrl.value } })
feedUrl.value = ''
await load()
}
async function syncAll() {
await request('/api/me/rss/sync', { method: 'POST', body: {} })
await fetchData('/api/me/rss/sync', { method: 'POST', body: {} })
await load()
}
async function removeFeed(id: number) {
await request(`/api/me/rss/feeds/${id}`, { method: 'DELETE' })
await fetchData(`/api/me/rss/feeds/${id}`, { method: 'DELETE' })
if (selectedFeedId.value === id) {
selectedFeedId.value = null
}
@ -94,7 +93,7 @@ async function removeFeed(id: number) {
}
async function setItemVis(id: number, visibility: string) {
await request(`/api/me/rss/items/${id}`, { method: 'PATCH', body: { visibility } })
await fetchData(`/api/me/rss/items/${id}`, { method: 'PATCH', body: { visibility } })
await load()
}

15
app/pages/me/timeline/index.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { useAuthSession } from '../../../composables/useAuthSession'
import { request, unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import {
formatOccurredOnDisplay,
occurredOnToIsoAttr,
@ -61,6 +60,8 @@ const visibilitySelectItems = [
{ label: '仅链接(凭分享链接访问)', value: 'unlisted' },
] as const
const { fetchData } = useClientApi()
const events = ref<Ev[]>([])
const loading = ref(true)
const submitLoading = ref(false)
@ -78,8 +79,8 @@ const form = reactive({
async function load() {
loading.value = true
try {
const res = await request<ApiResponse<{ events: Ev[] }>>('/api/me/timeline')
events.value = unwrapApiBody(res).events
const { events: list } = await fetchData<{ events: Ev[] }>('/api/me/timeline')
events.value = list
} finally {
loading.value = false
}
@ -99,7 +100,7 @@ async function add() {
}
submitLoading.value = true
try {
await request('/api/me/timeline', {
await fetchData('/api/me/timeline', {
method: 'POST',
body: {
occurredOn: occurredOnIso,
@ -125,8 +126,7 @@ async function removeEvent(e: Ev) {
}
deletingId.value = e.id
try {
const res = await request<ApiResponse<{ ok: boolean }>>(`/api/me/timeline/${e.id}`, { method: 'DELETE' })
unwrapApiBody(res)
await fetchData(`/api/me/timeline/${e.id}`, { method: 'DELETE' })
await load()
} finally {
deletingId.value = null
@ -159,11 +159,10 @@ async function updateVisibility(e: Ev, visibility: string) {
}
updatingVisibilityId.value = e.id
try {
const res = await request<ApiResponse<{ event: unknown }>>(`/api/me/timeline/${e.id}`, {
await fetchData(`/api/me/timeline/${e.id}`, {
method: 'PUT',
body: { visibility },
})
unwrapApiBody(res)
await load()
} finally {
updatingVisibilityId.value = null

Loading…
Cancel
Save