refactor: extract EncyclopediaProgressCard component and limit map pitch
- Create EncyclopediaProgressCard with dynamic progress color, reuse in encyclopedia and profile pages to unify styling - Add skyColor and maxPitch to AMap 3D view to reduce empty tile area when tilting - Extend AMap type declarations for skyColor, maxPitch, getPitch, getRotation
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { NCard, NProgress } from 'naive-ui'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
unlockedCount: number
|
||||||
|
totalCount: number
|
||||||
|
percent: number
|
||||||
|
isLoggedIn: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function lerpHex(a: string, b: string, t: number) {
|
||||||
|
const ah = parseInt(a.slice(1), 16)
|
||||||
|
const bh = parseInt(b.slice(1), 16)
|
||||||
|
const r = Math.round(((ah >> 16) & 0xff) + (((bh >> 16) & 0xff) - ((ah >> 16) & 0xff)) * t)
|
||||||
|
const g = Math.round(((ah >> 8) & 0xff) + (((bh >> 8) & 0xff) - ((ah >> 8) & 0xff)) * t)
|
||||||
|
const bv = Math.round((ah & 0xff) + ((bh & 0xff) - (ah & 0xff)) * t)
|
||||||
|
return `#${((r << 16) | (g << 8) | bv).toString(16).padStart(6, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function progressColor(pct: number) {
|
||||||
|
const t = Math.min(1, Math.max(0, pct / 100))
|
||||||
|
if (t <= 0.5) return lerpHex('#0ea5e9', '#f59e0b', t * 2)
|
||||||
|
return lerpHex('#f59e0b', '#ef4444', (t - 0.5) * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const progressText = computed(() => `${props.unlockedCount}/${props.totalCount}`)
|
||||||
|
const currentColor = computed(() => progressColor(props.isLoggedIn ? props.percent : 0))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard class="border border-white/80 bg-white/85 shadow-sm backdrop-blur" :bordered="false">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-slate-500">当前进度</p>
|
||||||
|
<p class="mt-1 text-3xl font-bold" :style="{ color: currentColor }">{{ progressText }}</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex h-16 w-16 items-center justify-center border text-xl font-semibold text-white transition-colors duration-500"
|
||||||
|
:style="{ backgroundColor: currentColor, borderColor: currentColor }"
|
||||||
|
>
|
||||||
|
{{ isLoggedIn ? percent : 0 }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NProgress
|
||||||
|
class="mt-5"
|
||||||
|
type="line"
|
||||||
|
:show-indicator="false"
|
||||||
|
:percentage="isLoggedIn ? percent : 0"
|
||||||
|
:color="{ stops: ['#0ea5e9', currentColor] }"
|
||||||
|
:height="12"
|
||||||
|
rail-color="#e2e8f0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p class="mt-4 text-sm text-slate-500">
|
||||||
|
<template v-if="isLoggedIn">
|
||||||
|
已解锁 {{ unlockedCount }} 枚徽章,还差 {{ Math.max(totalCount - unlockedCount, 0) }} 枚。
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
登录后可同步你的个人图鉴进度。
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
Vendored
+4
@@ -8,7 +8,9 @@ declare namespace AMap {
|
|||||||
setCenter(center: [number, number]): void
|
setCenter(center: [number, number]): void
|
||||||
setZoom(zoom: number): void
|
setZoom(zoom: number): void
|
||||||
setPitch(pitch: number): void
|
setPitch(pitch: number): void
|
||||||
|
getPitch(): number
|
||||||
setRotation(rotation: number): void
|
setRotation(rotation: number): void
|
||||||
|
getRotation(): number
|
||||||
on(event: string, callback: (...args: unknown[]) => void): void
|
on(event: string, callback: (...args: unknown[]) => void): void
|
||||||
off(event: string, callback: (...args: unknown[]) => void): void
|
off(event: string, callback: (...args: unknown[]) => void): void
|
||||||
getCenter(): { lng: number; lat: number }
|
getCenter(): { lng: number; lat: number }
|
||||||
@@ -28,6 +30,8 @@ declare namespace AMap {
|
|||||||
mapStyle?: string
|
mapStyle?: string
|
||||||
features?: string[]
|
features?: string[]
|
||||||
layers?: unknown[]
|
layers?: unknown[]
|
||||||
|
skyColor?: string
|
||||||
|
maxPitch?: number
|
||||||
resizeEnable?: boolean
|
resizeEnable?: boolean
|
||||||
dragEnable?: boolean
|
dragEnable?: boolean
|
||||||
zoomEnable?: boolean
|
zoomEnable?: boolean
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { NAlert, NCard, NEmpty, NIcon, NProgress, NSkeleton } from 'naive-ui'
|
import { NAlert, NCard, NEmpty, NIcon, NSkeleton } from 'naive-ui'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useEncyclopediaStore } from '@/stores/encyclopedia'
|
import { useEncyclopediaStore } from '@/stores/encyclopedia'
|
||||||
|
import EncyclopediaProgressCard from '@/components/cloud/EncyclopediaProgressCard.vue'
|
||||||
import { Lock } from '@vicons/tabler'
|
import { Lock } from '@vicons/tabler'
|
||||||
import type { CloudType } from '@/types/database'
|
import type { CloudType } from '@/types/database'
|
||||||
|
|
||||||
@@ -20,26 +21,6 @@ const rarityMeta = {
|
|||||||
rare: { label: '罕见', chip: 'bg-rose-100 text-rose-700 border-rose-200', glow: 'from-rose-100 to-white' },
|
rare: { label: '罕见', chip: 'bg-rose-100 text-rose-700 border-rose-200', glow: 'from-rose-100 to-white' },
|
||||||
} satisfies Record<CloudType['rarity'], { label: string; chip: string; glow: string }>
|
} satisfies Record<CloudType['rarity'], { label: string; chip: string; glow: string }>
|
||||||
|
|
||||||
function lerpHex(a: string, b: string, t: number) {
|
|
||||||
const ah = parseInt(a.slice(1), 16)
|
|
||||||
const bh = parseInt(b.slice(1), 16)
|
|
||||||
const r = Math.round(((ah >> 16) & 0xff) + (((bh >> 16) & 0xff) - ((ah >> 16) & 0xff)) * t)
|
|
||||||
const g = Math.round(((ah >> 8) & 0xff) + (((bh >> 8) & 0xff) - ((ah >> 8) & 0xff)) * t)
|
|
||||||
const bv = Math.round((ah & 0xff) + ((bh & 0xff) - (ah & 0xff)) * t)
|
|
||||||
return `#${((r << 16) | (g << 8) | bv).toString(16).padStart(6, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function progressColor(percent: number) {
|
|
||||||
const t = Math.min(1, Math.max(0, percent / 100))
|
|
||||||
if (t <= 0.5) return lerpHex('#0ea5e9', '#f59e0b', t * 2)
|
|
||||||
return lerpHex('#f59e0b', '#ef4444', (t - 0.5) * 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTypes = computed(() => encyclopediaStore.cloudTypes.length || 10)
|
|
||||||
const unlockedCount = computed(() => (authStore.isLoggedIn ? encyclopediaStore.unlockedCount : 0))
|
|
||||||
const progressText = computed(() => `${unlockedCount.value}/${totalTypes.value}`)
|
|
||||||
const currentProgressColor = computed(() => progressColor(authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0))
|
|
||||||
|
|
||||||
function isUnlocked(cloudTypeId: number) {
|
function isUnlocked(cloudTypeId: number) {
|
||||||
return authStore.isLoggedIn && encyclopediaStore.isUnlocked(cloudTypeId)
|
return authStore.isLoggedIn && encyclopediaStore.isUnlocked(cloudTypeId)
|
||||||
}
|
}
|
||||||
@@ -92,39 +73,12 @@ onMounted(async () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NCard class="border border-white/80 bg-white/85 shadow-sm backdrop-blur" :bordered="false">
|
<EncyclopediaProgressCard
|
||||||
<div class="flex items-center justify-between">
|
:unlocked-count="authStore.isLoggedIn ? encyclopediaStore.unlockedCount : 0"
|
||||||
<div>
|
:total-count="encyclopediaStore.cloudTypes.length || 10"
|
||||||
<p class="text-sm text-slate-500">当前进度</p>
|
:percent="encyclopediaStore.unlockPercent"
|
||||||
<p class="mt-1 text-3xl font-bold" :style="{ color: currentProgressColor }">{{ progressText }}</p>
|
:is-logged-in="authStore.isLoggedIn"
|
||||||
</div>
|
/>
|
||||||
<div
|
|
||||||
class="flex h-16 w-16 items-center justify-center border text-xl font-semibold text-white transition-colors duration-500"
|
|
||||||
:style="{ backgroundColor: currentProgressColor, borderColor: currentProgressColor }"
|
|
||||||
>
|
|
||||||
{{ authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0 }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NProgress
|
|
||||||
class="mt-5"
|
|
||||||
type="line"
|
|
||||||
:show-indicator="false"
|
|
||||||
:percentage="authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0"
|
|
||||||
:color="{ stops: ['#0ea5e9', currentProgressColor] }"
|
|
||||||
:height="12"
|
|
||||||
rail-color="#e2e8f0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p class="mt-4 text-sm text-slate-500">
|
|
||||||
<template v-if="authStore.isLoggedIn">
|
|
||||||
已解锁 {{ unlockedCount }} 枚徽章,还差 {{ Math.max(totalTypes - unlockedCount, 0) }} 枚。
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
登录后可同步你的个人图鉴进度。
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
</NCard>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -409,6 +409,8 @@ onMounted(async () => {
|
|||||||
mapStyle: 'amap://styles/normal',
|
mapStyle: 'amap://styles/normal',
|
||||||
features: ['bg', 'road', 'building', 'point'],
|
features: ['bg', 'road', 'building', 'point'],
|
||||||
resizeEnable: true,
|
resizeEnable: true,
|
||||||
|
skyColor: '#c8dce8',
|
||||||
|
maxPitch: 70,
|
||||||
} as AMap.MapOptions)
|
} as AMap.MapOptions)
|
||||||
|
|
||||||
mapInst.addControl(new AMapLib.Scale())
|
mapInst.addControl(new AMapLib.Scale())
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import { RouterLink, useRoute } from 'vue-router'
|
import { RouterLink, useRoute } from 'vue-router'
|
||||||
import { NAlert, NButton, NCard, NDropdown, NEmpty, NIcon, NProgress, NSkeleton, NTag, useMessage } from 'naive-ui'
|
import { NAlert, NButton, NCard, NDropdown, NEmpty, NIcon, NSkeleton, NTag, useMessage } from 'naive-ui'
|
||||||
|
import EncyclopediaProgressCard from '@/components/cloud/EncyclopediaProgressCard.vue'
|
||||||
import { Settings } from '@vicons/tabler'
|
import { Settings } from '@vicons/tabler'
|
||||||
import CloudEditModal, { type CloudEditFormValue } from '@/components/cloud/CloudEditModal.vue'
|
import CloudEditModal, { type CloudEditFormValue } from '@/components/cloud/CloudEditModal.vue'
|
||||||
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
|
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
|
||||||
@@ -536,33 +537,18 @@ watch(selectedUploadDate, async newValue => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NCard class="border border-white/80 bg-white/85 shadow-sm backdrop-blur" :bordered="false">
|
<EncyclopediaProgressCard
|
||||||
<template v-if="isOwnProfile">
|
v-if="isOwnProfile"
|
||||||
<p class="text-sm text-slate-500">图鉴进度</p>
|
:unlocked-count="encyclopediaStore.unlockedCount"
|
||||||
<div class="mt-3 flex items-end justify-between gap-4">
|
:total-count="encyclopediaStore.cloudTypes.length || 10"
|
||||||
<div>
|
:percent="encyclopediaStore.unlockPercent"
|
||||||
<p class="text-3xl font-bold text-slate-900">{{ encyclopediaStore.unlockProgress }}</p>
|
:is-logged-in="true"
|
||||||
<p class="mt-2 text-sm text-slate-500">已解锁 {{ encyclopediaStore.unlockedCount }} 种基础云型</p>
|
/>
|
||||||
</div>
|
|
||||||
<div class="border border-slate-900 bg-slate-900 px-4 py-3 text-xl font-semibold text-white">
|
|
||||||
{{ encyclopediaStore.unlockPercent }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NProgress
|
|
||||||
class="mt-5"
|
|
||||||
type="line"
|
|
||||||
:show-indicator="false"
|
|
||||||
:percentage="encyclopediaStore.unlockPercent"
|
|
||||||
color="{stops:['#0ea5e9','#f59e0b']}"
|
|
||||||
:height="12"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
<NCard v-else class="border border-white/80 bg-white/85 shadow-sm backdrop-blur" :bordered="false">
|
||||||
<p class="text-sm text-slate-500">公开贡献</p>
|
<p class="text-sm text-slate-500">公开贡献</p>
|
||||||
<p class="mt-3 text-3xl font-bold text-slate-900">{{ approvedShots }}</p>
|
<p class="mt-3 text-3xl font-bold text-slate-900">{{ approvedShots }}</p>
|
||||||
<p class="mt-2 text-sm text-slate-500">当前公开可见的云图数量</p>
|
<p class="mt-2 text-sm text-slate-500">当前公开可见的云图数量</p>
|
||||||
</template>
|
|
||||||
</NCard>
|
</NCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user