feat(api)!: 适配新版后端接口

切换到 Cookie 会话和新版 DTO,并迁移画廊、地图、个人主页、百科及上传流程。停用后端尚未实现的账号与徽章功能,同时纳入新版 API 契约文档。

BREAKING CHANGE: 前端不再兼容旧 /api/v1 接口和 refresh token 流程。
This commit is contained in:
2026-08-16 00:34:04 +08:00
parent cd70f89a28
commit c78b2f8ca5
31 changed files with 1750 additions and 2184 deletions
+12 -30
View File
@@ -91,9 +91,7 @@ function handleRemove(id: string) {
function onCategoryChange(event: Event) {
if (!activeItem.value) return
const value = (event.target as HTMLSelectElement).value
if (value === 'other') {
activeItem.value.cloudCategoryId = 'other'
} else if (value === '') {
if (value === '') {
activeItem.value.cloudCategoryId = null
} else {
activeItem.value.cloudCategoryId = Number(value)
@@ -102,6 +100,7 @@ function onCategoryChange(event: Event) {
}
function formatDatetimeLocal(iso: string): string {
if (!iso) return ''
const date = new Date(iso)
const pad = (value: number) => String(value).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`
@@ -110,8 +109,7 @@ function formatDatetimeLocal(iso: string): string {
function onCapturedAtChange(event: Event) {
if (!activeItem.value) return
const value = (event.target as HTMLInputElement).value
if (!value) return
activeItem.value.capturedAt = new Date(value).toISOString()
activeItem.value.capturedAt = value ? new Date(value).toISOString() : ''
delete activeItem.value.errors.capturedAt
}
@@ -169,7 +167,7 @@ async function handleSubmit() {
const result = await uploadAll()
if (!result.ok) {
errorMsg.value = '上传失败,请稍后重试'
errorMsg.value = result.errorMessage || '上传失败,请稍后重试'
return
}
@@ -217,7 +215,7 @@ onBeforeUnmount(() => {
</div>
<div class="max-h-[72vh] overflow-y-auto px-5 py-5">
<input ref="fileInput" type="file" accept="image/*" class="hidden" @change="handleFileSelect" />
<input ref="fileInput" type="file" accept="image/jpeg,image/png,image/webp" class="hidden" @change="handleFileSelect" />
<NAlert v-if="successMsg" type="success" :bordered="false" title="已提交审核" class="mb-4">
图片审核通过后会出现在画廊和地图中。
@@ -248,7 +246,7 @@ onBeforeUnmount(() => {
>
<NIcon size="42" class="text-sky-600"><CloudUpload /></NIcon>
<p class="mt-3 text-base font-semibold text-slate-800">选择一张云图</p>
<p class="oc-field-help">支持 JPG、PNG,可点击或拖拽上传。</p>
<p class="oc-field-help">支持 JPEG、PNG、WebP,图片不超过 20MB。</p>
</div>
<template v-else-if="activeItem">
@@ -275,21 +273,13 @@ onBeforeUnmount(() => {
<option v-for="cloudType in cloudsStore.cloudTypes" :key="cloudType.id" :value="cloudType.id">
{{ cloudType.name }}({{ cloudType.name_en }})
</option>
<option value="other">其他</option>
</select>
<input
v-if="activeItem.cloudCategoryId === 'other'"
v-model="activeItem.customCloudType"
type="text"
placeholder="输入云的类型"
class="oc-field-control mt-2"
@input="activeItem.errors = {}"
/>
<p v-if="activeItem.errors.cloudCategory" class="oc-field-error">{{ activeItem.errors.cloudCategory }}</p>
<p v-if="activeItem.errors.file" class="oc-field-error">{{ activeItem.errors.file }}</p>
</div>
<div>
<label class="oc-field-label">拍摄时间 <span class="oc-field-required">*</span></label>
<label class="oc-field-label">拍摄时间</label>
<input
type="datetime-local"
:value="formatDatetimeLocal(activeItem.capturedAt)"
@@ -300,7 +290,7 @@ onBeforeUnmount(() => {
</div>
<div>
<label class="oc-field-label">经纬度</label>
<label class="oc-field-label">经纬度 <span class="oc-field-required">*</span></label>
<div class="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_40px] gap-2">
<div>
<input
@@ -336,17 +326,7 @@ onBeforeUnmount(() => {
</NIcon>
</button>
</div>
<p class="mt-1 text-xs text-slate-400">选填,可手动输入,也可以点击右侧地图按钮选点。</p>
</div>
<div>
<label class="oc-field-label">位置名称</label>
<input
v-model="activeItem.locationName"
type="text"
placeholder="如:北京、成都"
class="oc-field-control"
/>
<p class="mt-1 text-xs text-slate-400">必填,可手动输入,也可以点击右侧地图按钮选点。</p>
</div>
<div>
@@ -354,9 +334,11 @@ onBeforeUnmount(() => {
<textarea
v-model="activeItem.description"
rows="3"
maxlength="128"
placeholder="描述一下这张图片..."
class="oc-field-control resize-none"
></textarea>
<p v-if="activeItem.errors.description" class="oc-field-error">{{ activeItem.errors.description }}</p>
</div>
<label class="flex items-center justify-between gap-4 border border-slate-200 bg-slate-50 px-4 py-3">