DownloadView.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <script setup lang="ts">
  2. // P16 下载页
  3. import { onMounted, computed, watch } from 'vue'
  4. import { storeToRefs } from 'pinia'
  5. import { useAppVersionStore } from '@/stores/appVersion'
  6. import { useI18nStore } from '@/stores/i18n'
  7. import { useI18n } from 'vue-i18n'
  8. import heroVisual from '@/assets/download/hero-visual.png'
  9. import iconApple from '@/assets/download/icon-apple.png'
  10. import iconAndroid from '@/assets/download/icon-android.png'
  11. const { t } = useI18n()
  12. const i18nStore = useI18nStore()
  13. const { current: locale } = storeToRefs(i18nStore)
  14. const versionStore = useAppVersionStore()
  15. const { android, ios, loading } = storeToRefs(versionStore)
  16. onMounted(() => {
  17. versionStore.fetchAll()
  18. })
  19. watch(locale, () => {
  20. versionStore.fetchAll()
  21. })
  22. const iosUrl = computed(() => ios.value?.downloadUrl || '')
  23. const androidUrl = computed(() => android.value?.downloadUrl || '')
  24. const latestInfo = computed(() => android.value ?? ios.value)
  25. function handleDownload(e: MouseEvent, url: string) {
  26. if (!url) e.preventDefault()
  27. }
  28. </script>
  29. <template>
  30. <main class="page-main">
  31. <section class="download-hero">
  32. <div class="page-container hero-inner">
  33. <div class="hero-content">
  34. <h1 class="hero-title" v-html="t('download.heroTitle').replace('\n', '<br>')"></h1>
  35. <p class="hero-desc">{{ t('download.appDesc') }}</p>
  36. <p v-if="latestInfo?.newVersion" class="version-tag">
  37. {{ t('download.latestVersion') }} v{{ latestInfo.newVersion }}
  38. <span v-if="latestInfo.publishTime" class="version-time">· {{ latestInfo.publishTime }}</span>
  39. </p>
  40. <div class="download-buttons">
  41. <a
  42. :href="iosUrl || '#'"
  43. :class="['dl-btn', 'dark-card', { 'is-disabled': !iosUrl }]"
  44. :target="iosUrl ? '_blank' : undefined"
  45. rel="noopener"
  46. @click="handleDownload($event, iosUrl)"
  47. >
  48. <img :src="iconApple" alt="iOS" class="dl-icon" />
  49. <span class="dl-text">{{ loading ? t('common.loading') : (iosUrl ? t('download.ios') : t('download.iosNotReleased')) }}</span>
  50. </a>
  51. <a
  52. :href="androidUrl || '#'"
  53. :class="['dl-btn', 'dark-card', { 'is-disabled': !androidUrl }]"
  54. :target="androidUrl ? '_blank' : undefined"
  55. rel="noopener"
  56. @click="handleDownload($event, androidUrl)"
  57. >
  58. <img :src="iconAndroid" alt="Android" class="dl-icon" />
  59. <span class="dl-text">{{ loading ? t('common.loading') : (androidUrl ? t('download.android') : t('download.androidNotReleased')) }}</span>
  60. </a>
  61. </div>
  62. <div v-if="latestInfo?.updateDescription" class="update-notes">
  63. <h3 class="update-title">{{ t('download.updateNotes') }}</h3>
  64. <p class="update-text">{{ latestInfo.updateDescription }}</p>
  65. </div>
  66. </div>
  67. <div class="hero-visual">
  68. <img :src="heroVisual" alt="iBit APP" class="hero-img" />
  69. </div>
  70. </div>
  71. </section>
  72. </main>
  73. </template>
  74. <style scoped>
  75. .download-hero {
  76. min-height: calc(100vh - var(--header-height));
  77. display: flex;
  78. align-items: center;
  79. background: var(--color-bg-primary);
  80. position: relative;
  81. overflow: hidden;
  82. }
  83. .download-hero::before {
  84. content: '';
  85. position: absolute;
  86. right: -10%;
  87. top: 50%;
  88. transform: translateY(-50%);
  89. width: 720px;
  90. height: 720px;
  91. border-radius: 50%;
  92. background: radial-gradient(circle, rgba(240,185,11,0.12) 0%, rgba(240,185,11,0.02) 45%, transparent 70%);
  93. pointer-events: none;
  94. }
  95. .hero-inner {
  96. display: grid;
  97. grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  98. gap: var(--space-12);
  99. align-items: center;
  100. padding-top: var(--space-12);
  101. padding-bottom: var(--space-12);
  102. position: relative;
  103. z-index: 1;
  104. }
  105. .hero-title {
  106. font-size: 48px;
  107. font-weight: var(--fw-bold);
  108. color: var(--color-text-primary);
  109. line-height: 1.25;
  110. margin-bottom: var(--space-3);
  111. }
  112. .hero-desc {
  113. font-size: var(--text-body);
  114. color: var(--color-text-secondary);
  115. margin-bottom: var(--space-10);
  116. }
  117. .download-buttons {
  118. display: flex;
  119. gap: var(--space-4);
  120. flex-wrap: wrap;
  121. }
  122. .dl-btn {
  123. display: inline-flex;
  124. align-items: center;
  125. gap: var(--space-3);
  126. padding: var(--space-4) var(--space-8);
  127. border-radius: var(--radius-md);
  128. min-width: 180px;
  129. cursor: pointer;
  130. transition: border-color var(--transition), transform var(--transition);
  131. }
  132. .dl-btn:hover {
  133. border-color: var(--color-primary);
  134. transform: translateY(-2px);
  135. }
  136. .dl-icon {
  137. width: 24px;
  138. height: 24px;
  139. object-fit: contain;
  140. }
  141. .dl-text {
  142. font-size: var(--text-body);
  143. font-weight: var(--fw-semibold);
  144. color: var(--color-text-primary);
  145. }
  146. .dl-btn.is-disabled {
  147. opacity: 0.5;
  148. cursor: not-allowed;
  149. pointer-events: auto;
  150. }
  151. .dl-btn.is-disabled:hover {
  152. border-color: inherit;
  153. transform: none;
  154. }
  155. .version-tag {
  156. display: inline-flex;
  157. align-items: center;
  158. gap: var(--space-2);
  159. padding: var(--space-1) var(--space-3);
  160. margin-bottom: var(--space-4);
  161. border-radius: var(--radius-sm);
  162. background: rgba(240, 185, 11, 0.12);
  163. color: var(--color-primary);
  164. font-size: var(--text-caption);
  165. font-weight: var(--fw-semibold);
  166. }
  167. .version-time {
  168. color: var(--color-text-secondary);
  169. font-weight: var(--fw-regular);
  170. }
  171. .update-notes {
  172. margin-top: var(--space-8);
  173. padding: var(--space-5) var(--space-6);
  174. border-radius: var(--radius-md);
  175. background: var(--color-bg-secondary);
  176. max-width: 520px;
  177. }
  178. .update-title {
  179. font-size: var(--text-body);
  180. font-weight: var(--fw-semibold);
  181. color: var(--color-text-primary);
  182. margin-bottom: var(--space-2);
  183. }
  184. .update-text {
  185. font-size: var(--text-caption);
  186. color: var(--color-text-secondary);
  187. line-height: 1.6;
  188. white-space: pre-line;
  189. }
  190. /* Hero visual */
  191. .hero-visual {
  192. display: flex;
  193. justify-content: center;
  194. align-items: center;
  195. }
  196. .hero-img {
  197. width: 100%;
  198. max-width: 640px;
  199. object-fit: contain;
  200. filter: drop-shadow(0 20px 80px rgba(240,185,11,0.2));
  201. }
  202. /* ── 移动端 ── */
  203. @media (max-width: 960px) {
  204. .hero-title { font-size: 36px; }
  205. }
  206. @media (max-width: 768px) {
  207. .download-hero { min-height: unset; padding: var(--space-10) 0; }
  208. .hero-inner {
  209. grid-template-columns: 1fr;
  210. padding-top: var(--space-6);
  211. padding-bottom: var(--space-6);
  212. gap: var(--space-6);
  213. }
  214. .hero-title { font-size: 28px; }
  215. .download-buttons { flex-wrap: wrap; }
  216. .dl-btn { flex: 1; min-width: 140px; }
  217. .hero-img { max-width: 380px; }
  218. }
  219. </style>