refactor(styles): 建立统一的生产级样式体系 (#4)
## 变更内容 - 将全局样式拆分为设计令牌、基础规则和语义组件层 - 集中管理 Naive UI 主题配置 - 迁移页面容器、表单、信息面板和按钮等重复样式 - 增加样式体系文档与自动契约检查 ## 验证 - `npm run check:styles` - `npm run build`Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -528,7 +528,7 @@ onMounted(loadAdminData)
|
||||
<div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-slate-950">待审核队列</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">共 {{ pendingImages.length }} 张待处理图片。</p>
|
||||
<p class="oc-field-help">共 {{ pendingImages.length }} 张待处理图片。</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" @click="toggleAllPendingSelection">
|
||||
@@ -568,7 +568,7 @@ onMounted(loadAdminData)
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-lg font-semibold text-slate-950">{{ item.cloudTypeName }}</p>
|
||||
<p class="mt-1 text-sm text-slate-500">上传者:{{ item.username }}</p>
|
||||
<p class="oc-field-help">上传者:{{ item.username }}</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -654,7 +654,7 @@ onMounted(loadAdminData)
|
||||
<div class="mb-4 flex flex-col gap-3 border border-slate-200 bg-white p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-slate-950">图片管理</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">最近 {{ images.length }} 张图片,可调整审核状态、可见性或删除。</p>
|
||||
<p class="oc-field-help">最近 {{ images.length }} 张图片,可调整审核状态、可见性或删除。</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<select
|
||||
@@ -792,22 +792,22 @@ onMounted(loadAdminData)
|
||||
@close="selectedImage = null"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedImage.created_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedImage.captured_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">状态</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">状态</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
{{ statusMeta[selectedImage.status].label }} / {{ selectedImage.is_hidden ? '隐藏' : '公开' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedImage.latitude) }} / 经度 {{ formatCoordinate(selectedImage.longitude) }}
|
||||
</p>
|
||||
@@ -815,7 +815,7 @@ onMounted(loadAdminData)
|
||||
</div>
|
||||
|
||||
<div class="mt-5 border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="oc-section-label">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedImage.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
|
||||
@@ -85,14 +85,14 @@ onUnmounted(() => {
|
||||
</p>
|
||||
<div class="mt-8 grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Step 1</div>
|
||||
<div class="oc-stat-label">Step 1</div>
|
||||
<div class="mt-2 text-lg font-bold text-slate-900">发送邮件</div>
|
||||
<div class="mt-1 text-sm text-slate-500">使用注册邮箱请求重置链接</div>
|
||||
<div class="oc-field-help">使用注册邮箱请求重置链接</div>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Step 2</div>
|
||||
<div class="oc-stat-label">Step 2</div>
|
||||
<div class="mt-2 text-lg font-bold text-slate-900">设置新密码</div>
|
||||
<div class="mt-1 text-sm text-slate-500">在专用页面完成新密码更新</div>
|
||||
<div class="oc-field-help">在专用页面完成新密码更新</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -44,14 +44,14 @@ async function handleLogin() {
|
||||
</div>
|
||||
<div class="mt-8 flex gap-4">
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Collection</div>
|
||||
<div class="mt-2 text-2xl font-bold text-slate-900">10</div>
|
||||
<div class="mt-1 text-sm text-slate-500">基础云属待收集</div>
|
||||
<div class="oc-stat-label">Collection</div>
|
||||
<div class="oc-stat-value">10</div>
|
||||
<div class="oc-field-help">基础云属待收集</div>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Mode</div>
|
||||
<div class="mt-2 text-2xl font-bold text-slate-900">Atlas</div>
|
||||
<div class="mt-1 text-sm text-slate-500">地图、图鉴、画廊一体</div>
|
||||
<div class="oc-stat-label">Mode</div>
|
||||
<div class="oc-stat-value">Atlas</div>
|
||||
<div class="oc-field-help">地图、图鉴、画廊一体</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -61,14 +61,14 @@ async function handleRegister() {
|
||||
</div>
|
||||
<div class="mt-8 flex gap-4">
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Review</div>
|
||||
<div class="mt-2 text-2xl font-bold text-slate-900">Queue</div>
|
||||
<div class="mt-1 text-sm text-slate-500">上传后进入审核队列</div>
|
||||
<div class="oc-stat-label">Review</div>
|
||||
<div class="oc-stat-value">Queue</div>
|
||||
<div class="oc-field-help">上传后进入审核队列</div>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-white px-4 py-3">
|
||||
<div class="text-xs uppercase tracking-[0.2em] text-slate-500">Profile</div>
|
||||
<div class="mt-2 text-2xl font-bold text-slate-900">Journal</div>
|
||||
<div class="mt-1 text-sm text-slate-500">自动生成个人天空日志</div>
|
||||
<div class="oc-stat-label">Profile</div>
|
||||
<div class="oc-stat-value">Journal</div>
|
||||
<div class="oc-field-help">自动生成个人天空日志</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -200,11 +200,11 @@ watch(() => route.params.id, () => {
|
||||
<NCard class="mt-8 border border-slate-200">
|
||||
<p class="text-sm text-slate-500">你的收集状态</p>
|
||||
<template v-if="isUnlocked && collectionEntry">
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">已解锁</p>
|
||||
<p class="oc-stat-value">已解锁</p>
|
||||
<p class="mt-2 text-sm text-slate-600">首次解锁于 {{ formatDate(collectionEntry.unlocked_at) }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">尚未解锁</p>
|
||||
<p class="oc-stat-value">尚未解锁</p>
|
||||
<p class="mt-2 text-sm text-slate-600">拍到并上传这种云朵后,这枚徽章就会被点亮。</p>
|
||||
</template>
|
||||
</NCard>
|
||||
@@ -215,11 +215,11 @@ watch(() => route.params.id, () => {
|
||||
<section class="mt-6 grid gap-4 md:grid-cols-3">
|
||||
<NCard class="border border-slate-200 shadow-sm">
|
||||
<p class="text-sm text-slate-500">稀有度</p>
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">{{ rarityMeta[cloudType.rarity].label }}</p>
|
||||
<p class="oc-stat-value">{{ rarityMeta[cloudType.rarity].label }}</p>
|
||||
</NCard>
|
||||
<NCard class="border border-slate-200 shadow-sm">
|
||||
<p class="text-sm text-slate-500">公开云图</p>
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">{{ publicCount }}</p>
|
||||
<p class="oc-stat-value">{{ publicCount }}</p>
|
||||
</NCard>
|
||||
<NCard class="border border-slate-200 shadow-sm">
|
||||
<p class="text-sm text-slate-500">首次解锁</p>
|
||||
@@ -233,7 +233,7 @@ watch(() => route.params.id, () => {
|
||||
<div class="mb-4 flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-900">该类型云图画廊</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">来自社区公开可见的已通过云图。</p>
|
||||
<p class="oc-field-help">来自社区公开可见的已通过云图。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -281,20 +281,20 @@ watch(() => route.params.id, () => {
|
||||
@close="selectedGalleryItem = null"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedGalleryItem.created_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedGalleryItem.captured_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ selectedGalleryItem.location_name || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedGalleryItem.latitude) }} / 经度 {{ formatCoordinate(selectedGalleryItem.longitude) }}
|
||||
</p>
|
||||
@@ -302,7 +302,7 @@ watch(() => route.params.id, () => {
|
||||
</div>
|
||||
|
||||
<div class="mt-5 border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="oc-section-label">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedGalleryItem.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
|
||||
@@ -62,13 +62,13 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="bg-[linear-gradient(180deg,#e0f2fe_0%,#f8fafc_100%)] border-b border-sky-100">
|
||||
<div class="max-w-6xl mx-auto px-4 py-10">
|
||||
<div class="oc-page-hero">
|
||||
<div class="oc-container oc-container--content oc-page-hero__inner">
|
||||
<div class="grid gap-6 lg:grid-cols-[1.4fr_0.9fr] lg:items-start">
|
||||
<div>
|
||||
<p class="text-sm font-medium tracking-[0.24em] text-sky-700 uppercase">Cloud Encyclopedia</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">云朵图鉴</h1>
|
||||
<p class="mt-4 max-w-2xl text-slate-600 leading-7">
|
||||
<p class="oc-page-eyebrow">Cloud Encyclopedia</p>
|
||||
<h1 class="oc-page-title">云朵图鉴</h1>
|
||||
<p class="oc-page-description">
|
||||
收集 10 种基础云属。每拍到一种新的云型,图鉴里就会点亮一枚徽章,并记录你第一次遇见它的时间。
|
||||
</p>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-8">
|
||||
<div class="oc-container oc-container--content oc-page-content">
|
||||
<NAlert
|
||||
v-if="encyclopediaStore.collectionError && authStore.isLoggedIn"
|
||||
class="mb-6"
|
||||
|
||||
@@ -341,17 +341,17 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<section class="border-b border-sky-100 bg-[linear-gradient(180deg,#e0f2fe_0%,#f8fafc_100%)]">
|
||||
<div class="max-w-7xl mx-auto px-4 py-10">
|
||||
<p class="text-sm font-medium uppercase tracking-[0.24em] text-sky-700">Community Gallery</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">云图画廊</h1>
|
||||
<p class="mt-4 max-w-2xl text-sm leading-7 text-slate-600">
|
||||
<section class="oc-page-hero">
|
||||
<div class="oc-container oc-container--wide oc-page-hero__inner">
|
||||
<p class="oc-page-eyebrow">Community Gallery</p>
|
||||
<h1 class="oc-page-title">云图画廊</h1>
|
||||
<p class="oc-page-description">
|
||||
按上传时间倒序浏览社区云图。瀑布流会尽量保留图片原始比例,悬停即可快速查看基本信息,点开可看大图和详细记录。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="oc-container oc-container--wide oc-page-content">
|
||||
<section>
|
||||
<div class="mb-4 border border-slate-200 bg-white p-3 shadow-[6px_6px_0_0_rgba(15,23,42,0.05)]">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -534,20 +534,20 @@ onUnmounted(() => {
|
||||
@close="closeDetail"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatUploadTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatCapturedTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ selectedCloud.location_name || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedCloud.latitude) }} / 经度 {{ formatCoordinate(selectedCloud.longitude) }}
|
||||
</p>
|
||||
@@ -555,7 +555,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="mt-5 border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="oc-section-label">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedCloud.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
|
||||
+12
-12
@@ -442,7 +442,7 @@ onUnmounted(() => {
|
||||
<button
|
||||
type="button"
|
||||
class="w-10 h-10 rounded-lg shadow-md flex items-center justify-center"
|
||||
:class="authStore.loading || !authStore.isLoggedIn ? 'cursor-not-allowed bg-slate-100 text-slate-300 shadow-none' : 'bg-white hover:bg-gray-50'"
|
||||
:class="authStore.loading || !authStore.isLoggedIn ? 'cursor-not-allowed bg-slate-100 text-slate-300 shadow-none' : 'bg-white hover:bg-slate-50'"
|
||||
:title="authStore.isLoggedIn ? '快速上传图片' : '请先登录后上传图片'"
|
||||
aria-label="快速上传图片"
|
||||
:disabled="authStore.loading || !authStore.isLoggedIn"
|
||||
@@ -452,14 +452,14 @@ onUnmounted(() => {
|
||||
<CloudUpload />
|
||||
</NIcon>
|
||||
</button>
|
||||
<button @click="refresh" class="w-10 h-10 bg-white rounded-lg shadow-md flex items-center justify-center hover:bg-gray-50" title="刷新">
|
||||
<button @click="refresh" class="oc-map-button" title="刷新">
|
||||
<span class="text-lg">
|
||||
<NIcon>
|
||||
<Refresh/>
|
||||
</NIcon>
|
||||
</span>
|
||||
</button>
|
||||
<button @click="toggleSat" class="w-10 h-10 bg-white rounded-lg shadow-md flex items-center justify-center hover:bg-gray-50" :title="satelliteOn ? '切换普通视图' : '切换卫星视图'">
|
||||
<button @click="toggleSat" class="oc-map-button" :title="satelliteOn ? '切换普通视图' : '切换卫星视图'">
|
||||
<NIcon size="20" style="display: inline-flex; vertical-align: middle;">
|
||||
<Map v-if="satelliteOn" />
|
||||
<Satellite v-else />
|
||||
@@ -467,7 +467,7 @@ onUnmounted(() => {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="w-10 h-10 bg-white rounded-lg shadow-md flex items-center justify-center hover:bg-gray-50"
|
||||
class="oc-map-button"
|
||||
:title="timelineControlsOpen ? '收起时间控件' : '展开时间控件'"
|
||||
:aria-label="timelineControlsOpen ? '收起时间控件' : '展开时间控件'"
|
||||
@click="toggleTimelineControls"
|
||||
@@ -519,7 +519,7 @@ onUnmounted(() => {
|
||||
<div class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="w-10 h-10 bg-white rounded-lg shadow-md flex items-center justify-center hover:bg-gray-50"
|
||||
class="oc-map-button"
|
||||
title="按日期或月份查看"
|
||||
aria-label="按日期或月份查看"
|
||||
@click="archivePanelOpen = !archivePanelOpen"
|
||||
@@ -562,13 +562,13 @@ onUnmounted(() => {
|
||||
v-if="archiveKind === 'day'"
|
||||
v-model="archiveDay"
|
||||
type="date"
|
||||
class="mt-3 w-full border border-slate-300 px-3 py-2 text-sm text-slate-800 outline-none focus:border-sky-500 focus:ring-2 focus:ring-sky-100"
|
||||
class="oc-field-control mt-3"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="archiveMonth"
|
||||
type="month"
|
||||
class="mt-3 w-full border border-slate-300 px-3 py-2 text-sm text-slate-800 outline-none focus:border-sky-500 focus:ring-2 focus:ring-sky-100"
|
||||
class="oc-field-control mt-3"
|
||||
/>
|
||||
|
||||
<button
|
||||
@@ -599,19 +599,19 @@ onUnmounted(() => {
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<p class="oc-section-label">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(previewCloud.createdAt) }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<p class="oc-section-label">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(previewCloud.capturedAt) }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<p class="oc-section-label">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ previewCloud.locationName || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<p class="oc-section-label">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(previewCloud.latitude) }} / 经度 {{ formatCoordinate(previewCloud.longitude) }}
|
||||
</p>
|
||||
@@ -619,7 +619,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="mt-5 rounded-[28px] border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="oc-section-label">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ previewCloud.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
|
||||
@@ -86,9 +86,9 @@ async function savePassword() {
|
||||
</RouterLink>
|
||||
|
||||
<div class="mt-6">
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.24em] text-sky-700">Account Settings</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">个人资料设置</h1>
|
||||
<p class="mt-4 max-w-2xl text-sm leading-7 text-slate-600">
|
||||
<p class="oc-page-eyebrow">Account Settings</p>
|
||||
<h1 class="oc-page-title">个人资料设置</h1>
|
||||
<p class="oc-page-description">
|
||||
修改公开昵称和登录密码。昵称会显示在画廊、图鉴和地图中,且不能与其他用户重复。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -473,8 +473,8 @@ watch(selectedUploadDate, async newValue => {
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div class="border-b border-sky-100 bg-[linear-gradient(180deg,#e0f2fe_0%,#f8fafc_100%)]">
|
||||
<div class="max-w-6xl mx-auto px-4 py-10">
|
||||
<div class="oc-page-hero">
|
||||
<div class="oc-container oc-container--content oc-page-hero__inner">
|
||||
<div v-if="loading" class="grid gap-6 lg:grid-cols-[1.25fr_0.95fr]">
|
||||
<NCard class="oc-panel-card border border-slate-200 shadow-sm" :bordered="false">
|
||||
<NSkeleton class="h-6 w-1/3" />
|
||||
@@ -505,8 +505,8 @@ watch(selectedUploadDate, async newValue => {
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium uppercase tracking-[0.24em] text-sky-700">Sky Journal</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">
|
||||
<p class="oc-page-eyebrow">Sky Journal</p>
|
||||
<h1 class="oc-page-title">
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<div class="mt-3 flex flex-wrap items-center gap-3">
|
||||
@@ -554,7 +554,7 @@ watch(selectedUploadDate, async newValue => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-8">
|
||||
<div class="oc-container oc-container--content oc-page-content">
|
||||
<NAlert
|
||||
v-if="errorMsg"
|
||||
type="error"
|
||||
@@ -592,7 +592,7 @@ watch(selectedUploadDate, async newValue => {
|
||||
|
||||
<NCard class="oc-panel-card border border-slate-200 shadow-sm" :bordered="false">
|
||||
<p class="text-sm text-slate-500">最常拍云型</p>
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">{{ mostCommonCloudType.name }}</p>
|
||||
<p class="oc-stat-value">{{ mostCommonCloudType.name }}</p>
|
||||
<p class="mt-2 text-sm text-slate-500" v-if="mostCommonCloudType.count">
|
||||
共 {{ mostCommonCloudType.count }} 张
|
||||
</p>
|
||||
@@ -605,7 +605,7 @@ watch(selectedUploadDate, async newValue => {
|
||||
|
||||
<NCard class="oc-panel-card border border-slate-200 shadow-sm" :bordered="false">
|
||||
<p class="text-sm text-slate-500">{{ isOwnProfile ? '已通过 / 待审核' : '公开通过数' }}</p>
|
||||
<p class="mt-2 text-2xl font-bold text-slate-900">
|
||||
<p class="oc-stat-value">
|
||||
<template v-if="isOwnProfile">{{ approvedShots }} / {{ pendingShots }}</template>
|
||||
<template v-else>{{ approvedShots }}</template>
|
||||
</p>
|
||||
@@ -630,7 +630,7 @@ watch(selectedUploadDate, async newValue => {
|
||||
<div class="mb-5 flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-900">天空日志</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
<p class="oc-field-help">
|
||||
按拍摄时间倒序展示,并按月份分组。热力图统计按上传日期计算。
|
||||
</p>
|
||||
</div>
|
||||
@@ -794,26 +794,26 @@ watch(selectedUploadDate, async newValue => {
|
||||
@close="selectedCloud = null"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedCloud.created_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatDateTime(selectedCloud.captured_at) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ selectedCloud.location_name || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedCloud.latitude) }} / 经度 {{ formatCoordinate(selectedCloud.longitude) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">{{ isOwnProfile ? '审核状态' : '可见性' }}</p>
|
||||
<div class="oc-inset-panel">
|
||||
<p class="oc-section-label">{{ isOwnProfile ? '审核状态' : '可见性' }}</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
<template v-if="isOwnProfile">
|
||||
{{ statusMeta[selectedCloud.status].label }} / {{ selectedCloud.is_hidden ? '私密' : '公开' }}
|
||||
@@ -824,7 +824,7 @@ watch(selectedUploadDate, async newValue => {
|
||||
</div>
|
||||
|
||||
<div class="mt-5 border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="oc-section-label">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedCloud.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
|
||||
@@ -28,12 +28,12 @@ const description = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-[calc(100vh-4rem)] items-center justify-center px-4 py-10">
|
||||
<div class="w-full max-w-2xl border border-slate-200 bg-white px-8 py-14 text-center shadow-sm">
|
||||
<div class="oc-system-page">
|
||||
<div class="oc-system-card">
|
||||
<div class="text-7xl leading-none">🔐</div>
|
||||
<p class="mt-6 text-sm font-medium uppercase tracking-[0.24em] text-sky-600">401 Login Required</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">{{ title }}</h1>
|
||||
<p class="mx-auto mt-4 max-w-xl text-sm leading-7 text-slate-600">
|
||||
<h1 class="oc-page-title">{{ title }}</h1>
|
||||
<p class="oc-page-description mx-auto max-w-xl">
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import { RouterLink } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-[calc(100vh-4rem)] items-center justify-center px-4 py-10">
|
||||
<div class="w-full max-w-2xl border border-slate-200 bg-white px-8 py-14 text-center shadow-sm">
|
||||
<div class="oc-system-page">
|
||||
<div class="oc-system-card">
|
||||
<div class="text-7xl leading-none">🖐️</div>
|
||||
<p class="mt-6 text-sm font-medium uppercase tracking-[0.24em] text-rose-600">403 Forbidden</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">这里不让进</h1>
|
||||
<p class="mx-auto mt-4 max-w-xl text-sm leading-7 text-slate-600">
|
||||
<h1 class="oc-page-title">这里不让进</h1>
|
||||
<p class="oc-page-description mx-auto max-w-xl">
|
||||
你当前没有访问这个页面的权限。通常是因为这个区域只对管理员开放。
|
||||
</p>
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import { RouterLink } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-[calc(100vh-4rem)] items-center justify-center px-4 py-10">
|
||||
<div class="w-full max-w-2xl border border-slate-200 bg-white px-8 py-14 text-center shadow-sm">
|
||||
<div class="oc-system-page">
|
||||
<div class="oc-system-card">
|
||||
<div class="text-7xl leading-none">🤔</div>
|
||||
<p class="mt-6 text-sm font-medium uppercase tracking-[0.24em] text-amber-600">404 Not Found</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">我也没想明白</h1>
|
||||
<p class="mx-auto mt-4 max-w-xl text-sm leading-7 text-slate-600">
|
||||
<h1 class="oc-page-title">我也没想明白</h1>
|
||||
<p class="oc-page-description mx-auto max-w-xl">
|
||||
这个页面不存在,或者它已经被移动到了别的地方。换个入口再试一次。
|
||||
</p>
|
||||
|
||||
|
||||
@@ -203,17 +203,17 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<section class="border-b border-sky-100 bg-[linear-gradient(180deg,#e0f2fe_0%,#f8fafc_100%)]">
|
||||
<div class="max-w-6xl mx-auto px-4 py-10">
|
||||
<p class="text-sm font-medium uppercase tracking-[0.24em] text-sky-700">Cloud Upload</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">上传云图</h1>
|
||||
<p class="mt-4 max-w-2xl text-sm leading-7 text-slate-600">
|
||||
<section class="oc-page-hero">
|
||||
<div class="oc-container oc-container--content oc-page-hero__inner">
|
||||
<p class="oc-page-eyebrow">Cloud Upload</p>
|
||||
<h1 class="oc-page-title">上传云图</h1>
|
||||
<p class="oc-page-description">
|
||||
批量整理你的云图记录。类别和拍摄时间必填,经纬度既可以手动输入,也可以在迷你地图里点选回填。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-4 py-8">
|
||||
<div class="oc-container oc-container--content oc-page-content">
|
||||
<input ref="fileInput" type="file" accept="image/*" multiple class="hidden" @change="handleFileSelect" />
|
||||
|
||||
<div v-if="successMsg" class="flex items-center justify-center min-h-[60vh]">
|
||||
@@ -231,13 +231,13 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-2xl font-bold text-gray-900">{{ badge.cloudName }}</h3>
|
||||
<h3 class="text-2xl font-bold text-slate-900">{{ badge.cloudName }}</h3>
|
||||
<NTag :bordered="false" type="warning">
|
||||
{{ rarityLabel(badge.rarity) }}
|
||||
</NTag>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">{{ badge.cloudNameEn }}</p>
|
||||
<p class="mt-4 text-sm text-gray-600">解锁时间:{{ formatUnlockedAt(badge.unlockedAt) }}</p>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ badge.cloudNameEn }}</p>
|
||||
<p class="mt-4 text-sm text-slate-600">解锁时间:{{ formatUnlockedAt(badge.unlockedAt) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex gap-3">
|
||||
@@ -258,7 +258,7 @@ onUnmounted(() => {
|
||||
<template v-else>
|
||||
<div v-if="uploading" class="mb-6 border border-slate-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-700">正在上传 {{ currentItemIndex }} / {{ totalItems }}...</span>
|
||||
<span class="text-sm font-medium text-slate-700">正在上传 {{ currentItemIndex }} / {{ totalItems }}...</span>
|
||||
<span class="text-sm font-medium text-sky-600">{{ overallProgress }}%</span>
|
||||
</div>
|
||||
<NProgress
|
||||
@@ -278,17 +278,17 @@ onUnmounted(() => {
|
||||
@drop.prevent="handleDrop"
|
||||
@click="fileInput?.click()"
|
||||
class="flex flex-col items-center justify-center border-2 border-dashed py-20 cursor-pointer transition-colors bg-white shadow-sm"
|
||||
:class="dragOver ? 'border-sky-400 bg-sky-50' : 'border-gray-300 hover:border-sky-400 hover:bg-gray-50'"
|
||||
:class="dragOver ? 'border-sky-400 bg-sky-50' : 'border-slate-300 hover:border-sky-400 hover:bg-slate-50'"
|
||||
>
|
||||
<span class="text-5xl mb-4">☁️</span>
|
||||
<p class="text-lg font-medium text-gray-700 mb-1">点击或拖拽图片到此处</p>
|
||||
<p class="text-sm text-gray-400">支持 JPG、PNG 格式,可一次选择多张</p>
|
||||
<p class="mb-1 text-lg font-medium text-slate-700">点击或拖拽图片到此处</p>
|
||||
<p class="text-sm text-slate-400">支持 JPG、PNG 格式,可一次选择多张</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="flex gap-6">
|
||||
<div class="flex-shrink-0 w-[480px]">
|
||||
<div v-if="activeItem" class="bg-gray-900 overflow-hidden mb-3 border border-slate-900">
|
||||
<div v-if="activeItem" class="mb-3 overflow-hidden border border-slate-900 bg-slate-900">
|
||||
<img :src="activeItem.preview" alt="预览" class="w-full h-[360px] object-contain" />
|
||||
</div>
|
||||
<div class="flex gap-2 overflow-x-auto pb-2">
|
||||
@@ -297,7 +297,7 @@ onUnmounted(() => {
|
||||
:key="item.id"
|
||||
@click="selectItem(item.id)"
|
||||
class="relative flex-shrink-0 w-16 h-16 overflow-hidden cursor-pointer border-2 transition-colors bg-white"
|
||||
:class="activeId === item.id ? 'border-sky-500' : 'border-transparent hover:border-gray-300'"
|
||||
:class="activeId === item.id ? 'border-sky-500' : 'border-transparent hover:border-slate-300'"
|
||||
>
|
||||
<img :src="item.preview" alt="" class="w-full h-full object-cover" />
|
||||
<div v-if="Object.keys(item.errors).length > 0" class="absolute top-0 right-0 w-2.5 h-2.5 bg-red-500"></div>
|
||||
@@ -309,30 +309,30 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div
|
||||
@click="fileInput?.click()"
|
||||
class="flex-shrink-0 w-16 h-16 border-2 border-dashed border-gray-300 flex items-center justify-center cursor-pointer bg-white hover:border-sky-400 hover:bg-gray-50 transition-colors"
|
||||
class="flex h-16 w-16 flex-shrink-0 cursor-pointer items-center justify-center border-2 border-dashed border-slate-300 bg-white transition-colors hover:border-sky-400 hover:bg-slate-50"
|
||||
>
|
||||
<span class="text-gray-400 text-xl">+</span>
|
||||
<span class="text-xl text-slate-400">+</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0" v-if="activeItem">
|
||||
<NCard class="border border-gray-200 shadow-sm">
|
||||
<NCard class="border border-slate-200 shadow-sm">
|
||||
<div class="space-y-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-gray-900">图片信息</h2>
|
||||
<span class="text-sm text-gray-400">{{ items.findIndex(i => i.id === activeId) + 1 }} / {{ items.length }}</span>
|
||||
<h2 class="text-lg font-semibold text-slate-900">图片信息</h2>
|
||||
<span class="text-sm text-slate-400">{{ items.findIndex(i => i.id === activeId) + 1 }} / {{ items.length }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 类别 * -->
|
||||
<div :id="`field-${activeItem.id}-cloudCategory`">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
类别 <span class="text-red-500">*</span>
|
||||
<label class="oc-field-label">
|
||||
类别 <span class="oc-field-required">*</span>
|
||||
</label>
|
||||
<select
|
||||
:value="activeItem.cloudCategoryId ?? ''"
|
||||
@change="onCategoryChange"
|
||||
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors bg-white"
|
||||
class="oc-field-control"
|
||||
>
|
||||
<option value="" disabled>请选择</option>
|
||||
<option v-for="ct in cloudsStore.cloudTypes" :key="ct.id" :value="ct.id">
|
||||
@@ -346,35 +346,35 @@ onUnmounted(() => {
|
||||
type="text"
|
||||
placeholder="输入云的类型"
|
||||
@input="activeItem.errors = {}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors"
|
||||
class="oc-field-control"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="activeItem.errors.cloudCategory" class="text-sm text-red-500 mt-1">{{ activeItem.errors.cloudCategory }}</p>
|
||||
<p v-if="activeItem.errors.cloudCategory" class="oc-field-error">{{ activeItem.errors.cloudCategory }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 拍摄时间 * -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||
拍摄时间 <span class="text-red-500">*</span>
|
||||
<label class="oc-field-label">
|
||||
拍摄时间 <span class="oc-field-required">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
:value="formatDatetimeLocal(activeItem.capturedAt)"
|
||||
@change="onCapturedAtChange"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors"
|
||||
class="oc-field-control"
|
||||
/>
|
||||
<p v-if="activeItem.errors.capturedAt" class="text-sm text-red-500 mt-1">{{ activeItem.errors.capturedAt }}</p>
|
||||
<p v-if="activeItem.errors.capturedAt" class="oc-field-error">{{ activeItem.errors.capturedAt }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 经纬度 -->
|
||||
<div>
|
||||
<div class="mb-1 flex items-center justify-between gap-3">
|
||||
<label class="block text-sm font-medium text-gray-700">经纬度</label>
|
||||
<label class="oc-field-label mb-0">经纬度</label>
|
||||
<button
|
||||
v-if="activeItem.latitude !== null || activeItem.longitude !== null"
|
||||
type="button"
|
||||
@click="clearCoordinates"
|
||||
class="text-xs font-medium text-gray-400 transition-colors hover:text-gray-600"
|
||||
class="text-xs font-medium text-slate-400 transition-colors hover:text-slate-600"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
@@ -387,9 +387,9 @@ onUnmounted(() => {
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="纬度(如 39.90)"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors"
|
||||
class="oc-field-control"
|
||||
/>
|
||||
<p v-if="activeItem.errors.latitude" class="text-sm text-red-500 mt-1">{{ activeItem.errors.latitude }}</p>
|
||||
<p v-if="activeItem.errors.latitude" class="oc-field-error">{{ activeItem.errors.latitude }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
@@ -398,9 +398,9 @@ onUnmounted(() => {
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="经度(如 116.40)"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors"
|
||||
class="oc-field-control"
|
||||
/>
|
||||
<p v-if="activeItem.errors.longitude" class="text-sm text-red-500 mt-1">{{ activeItem.errors.longitude }}</p>
|
||||
<p v-if="activeItem.errors.longitude" class="oc-field-error">{{ activeItem.errors.longitude }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
@@ -414,28 +414,28 @@ onUnmounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">选填,可手动输入,或点击右侧小地图图标选择</p>
|
||||
<p class="mt-1 text-xs text-slate-400">选填,可手动输入,或点击右侧小地图图标选择</p>
|
||||
</div>
|
||||
|
||||
<!-- 位置名称 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">位置名称</label>
|
||||
<label class="oc-field-label">位置名称</label>
|
||||
<input
|
||||
v-model="activeItem.locationName"
|
||||
type="text"
|
||||
placeholder="如:北京、成都"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors"
|
||||
class="oc-field-control"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 图片描述 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">图片描述</label>
|
||||
<label class="oc-field-label">图片描述</label>
|
||||
<textarea
|
||||
v-model="activeItem.description"
|
||||
rows="3"
|
||||
placeholder="描述一下这张图片..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-transparent transition-colors resize-none"
|
||||
class="oc-field-control resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
@@ -443,14 +443,14 @@ onUnmounted(() => {
|
||||
<div class="border border-slate-200 bg-slate-50 px-4 py-3">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-800">公开展示</p>
|
||||
<p class="mt-1 text-xs text-gray-500">关闭后不会出现在画廊、地图和公开主页中。</p>
|
||||
<p class="text-sm font-medium text-slate-800">公开展示</p>
|
||||
<p class="mt-1 text-xs text-slate-500">关闭后不会出现在画廊、地图和公开主页中。</p>
|
||||
</div>
|
||||
<input
|
||||
:checked="!activeItem.isHidden"
|
||||
type="checkbox"
|
||||
:id="'isPublic-' + activeItem.id"
|
||||
class="h-5 w-5 border-gray-300 text-sky-500 focus:ring-sky-500"
|
||||
class="h-5 w-5 border-slate-300 text-sky-500 focus:ring-sky-500"
|
||||
@change="updateActivePublicState(($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
</div>
|
||||
@@ -461,7 +461,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500">共 {{ items.length }} 张图片</p>
|
||||
<p class="text-sm text-slate-500">共 {{ items.length }} 张图片</p>
|
||||
<div class="flex gap-3">
|
||||
<NButton secondary strong type="default" class="oc-panel-button oc-panel-button--neutral" @click="clearAll()" :disabled="uploading">清空</NButton>
|
||||
<NButton type="default" secondary strong class="oc-panel-button oc-panel-button--sky" @click="handleSubmit" :disabled="uploading">
|
||||
|
||||
Reference in New Issue
Block a user