mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-07 05:32:52 +08:00
✨ Feature(custom): add release note page
This commit is contained in:
@@ -339,7 +339,20 @@
|
||||
"newestVersion": "Newest Version: {version}",
|
||||
"getting": "Getting...",
|
||||
"hasNewVersion": "PicList has been updated, please click OK to restart and trigger the update",
|
||||
"networkError": "Network error, please check your network connection"
|
||||
"networkError": "Network error, please check your network connection",
|
||||
"releaseNotes": "Release Notes",
|
||||
"releaseNotesDescription": "View the latest changes and updates",
|
||||
"latestReleaseNotes": "Latest Release Notes",
|
||||
"refresh": "Refresh",
|
||||
"retry": "Retry",
|
||||
"loadingReleaseNotes": "Loading release notes...",
|
||||
"releaseNotesError": "Failed to load release notes. Please check your network connection.",
|
||||
"noReleaseNotes": "No release notes available",
|
||||
"lastUpdated": "Last updated",
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{minutes} minutes ago",
|
||||
"hoursAgo": "{hours} hours ago",
|
||||
"daysAgo": "{days} days ago"
|
||||
}
|
||||
},
|
||||
"plugin": {
|
||||
|
||||
@@ -334,7 +334,20 @@
|
||||
"newestVersion": "最新版本",
|
||||
"getting": "正在获取中...",
|
||||
"hasNewVersion": "PicList 更新啦,请点击确定重启触发更新",
|
||||
"networkError": "网络错误,请检查网络连接"
|
||||
"networkError": "网络错误,请检查网络连接",
|
||||
"releaseNotes": "版本说明",
|
||||
"releaseNotesDescription": "查看最新的更改和更新内容",
|
||||
"latestReleaseNotes": "最新版本说明",
|
||||
"refresh": "刷新",
|
||||
"retry": "重试",
|
||||
"loadingReleaseNotes": "正在加载版本说明...",
|
||||
"releaseNotesError": "加载版本说明失败,请检查网络连接。",
|
||||
"noReleaseNotes": "暂无版本说明",
|
||||
"lastUpdated": "最后更新",
|
||||
"justNow": "刚刚",
|
||||
"minutesAgo": "{minutes} 分钟前",
|
||||
"hoursAgo": "{hours} 小时前",
|
||||
"daysAgo": "{days} 天前"
|
||||
}
|
||||
},
|
||||
"plugin": {
|
||||
|
||||
@@ -334,7 +334,20 @@
|
||||
"newestVersion": "最新版本",
|
||||
"getting": "正在獲取中...",
|
||||
"hasNewVersion": "PicList 更新啦,請點擊確定重啟觸發更新",
|
||||
"networkError": "網絡錯誤,請檢查網絡連接"
|
||||
"networkError": "網絡錯誤,請檢查網絡連接",
|
||||
"releaseNotes": "版本說明",
|
||||
"releaseNotesDescription": "查看最新的更改和更新內容",
|
||||
"latestReleaseNotes": "最新版本說明",
|
||||
"refresh": "刷新",
|
||||
"retry": "重試",
|
||||
"loadingReleaseNotes": "正在載入版本說明...",
|
||||
"releaseNotesError": "載入版本說明失敗,請檢查網絡連接。",
|
||||
"noReleaseNotes": "暫無版本說明",
|
||||
"lastUpdated": "最後更新",
|
||||
"justNow": "剛剛",
|
||||
"minutesAgo": "{minutes} 分鐘前",
|
||||
"hoursAgo": "{hours} 小時前",
|
||||
"daysAgo": "{days} 天前"
|
||||
}
|
||||
},
|
||||
"plugin": {
|
||||
|
||||
@@ -650,6 +650,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Release Notes Card -->
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('pages.settings.update.releaseNotes') }}</h2>
|
||||
<p>{{ t('pages.settings.update.releaseNotesDescription') }}</p>
|
||||
|
||||
<div class="release-notes-card">
|
||||
<div class="release-notes-header">
|
||||
<h3>{{ t('pages.settings.update.latestReleaseNotes') }}</h3>
|
||||
<div class="release-notes-actions">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm"
|
||||
:disabled="fetchingReleaseNotes"
|
||||
@click="fetchReleaseNotesManually"
|
||||
>
|
||||
<RefreshCw :size="14" :class="{ rotate: fetchingReleaseNotes }" />
|
||||
{{ t('pages.settings.update.refresh') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="release-notes-content">
|
||||
<div v-if="fetchingReleaseNotes" class="release-notes-loading">
|
||||
<RefreshCw :size="16" class="rotate" />
|
||||
{{ t('pages.settings.update.loadingReleaseNotes') }}
|
||||
</div>
|
||||
<div v-else-if="releaseNotes" class="release-notes-text">
|
||||
<pre class="release-notes-pre">{{ releaseNotes }}</pre>
|
||||
</div>
|
||||
<div v-else-if="releaseNotesError" class="release-notes-error">
|
||||
{{ releaseNotesError }}
|
||||
<button class="btn btn-link btn-sm" @click="fetchReleaseNotesManually">
|
||||
{{ t('pages.settings.update.retry') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="release-notes-empty">
|
||||
{{ t('pages.settings.update.noReleaseNotes') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="releaseNotesLastFetch" class="release-notes-footer">
|
||||
<small>
|
||||
{{ t('pages.settings.update.lastUpdated') }}: {{ formatLastFetchTime(releaseNotesLastFetch) }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1401,6 +1448,8 @@ const addWatch = () => {
|
||||
watch(currentLanguage, newVal => {
|
||||
if (newVal) {
|
||||
handleLanguageChange(newVal)
|
||||
// Fetch release notes when language changes
|
||||
fetchReleaseNotes(true)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1531,6 +1580,12 @@ function confirmSyncSetting() {
|
||||
|
||||
const version = pkg.version
|
||||
const latestVersion = ref('')
|
||||
const releaseNotes = ref('')
|
||||
const releaseNotesError = ref('')
|
||||
const releaseNotesLastFetch = ref<Date | null>(null)
|
||||
const fetchingReleaseNotes = ref(false)
|
||||
|
||||
const RELEASE_NOTES_CACHE_DURATION = 30 * 60 * 1000
|
||||
|
||||
const needUpdate = computed(() => {
|
||||
if (latestVersion.value) {
|
||||
@@ -1589,6 +1644,9 @@ async function initData() {
|
||||
formOfSetting.value.logFileSizeLimit = enforceNumber(settings.logFileSizeLimit) || 10
|
||||
addProxyWatch()
|
||||
addWatch()
|
||||
|
||||
// Fetch release notes on initialization
|
||||
fetchReleaseNotes()
|
||||
}
|
||||
|
||||
function initArray(arrayT: string | string[], defaultValue: string[]) {
|
||||
@@ -1703,6 +1761,62 @@ function compareVersion2Update(current: string, latest: string): boolean {
|
||||
return compare(current, latest, '<')
|
||||
}
|
||||
|
||||
function formatLastFetchTime(date: Date): string {
|
||||
const now = new Date()
|
||||
const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60))
|
||||
|
||||
if (diffInMinutes < 1) {
|
||||
return t('pages.settings.update.justNow')
|
||||
} else if (diffInMinutes < 60) {
|
||||
return t('pages.settings.update.minutesAgo', { minutes: diffInMinutes })
|
||||
} else {
|
||||
const hours = Math.floor(diffInMinutes / 60)
|
||||
if (hours < 24) {
|
||||
return t('pages.settings.update.hoursAgo', { hours })
|
||||
} else {
|
||||
const days = Math.floor(hours / 24)
|
||||
return t('pages.settings.update.daysAgo', { days })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchReleaseNotes(forceRefresh = false): Promise<void> {
|
||||
if (!forceRefresh && releaseNotesLastFetch.value) {
|
||||
const timeSinceLastFetch = Date.now() - releaseNotesLastFetch.value.getTime()
|
||||
if (timeSinceLastFetch < RELEASE_NOTES_CACHE_DURATION) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fetchingReleaseNotes.value = true
|
||||
releaseNotesError.value = ''
|
||||
|
||||
const isEnglish = currentLanguage.value === 'en'
|
||||
const fileName = isEnglish ? 'currentVersion_en.md' : 'currentVersion.md'
|
||||
const url = `https://raw.githubusercontent.com/Kuingsmile/piclist/dev/${fileName}`
|
||||
|
||||
const response = await fetch(url)
|
||||
if (response.ok) {
|
||||
const content = await response.text()
|
||||
releaseNotes.value = content
|
||||
releaseNotesLastFetch.value = new Date()
|
||||
releaseNotesError.value = ''
|
||||
} else {
|
||||
throw new Error(`HTTP ${response.status}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch release notes:', error)
|
||||
releaseNotesError.value = t('pages.settings.update.releaseNotesError')
|
||||
} finally {
|
||||
fetchingReleaseNotes.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchReleaseNotesManually(): Promise<void> {
|
||||
await fetchReleaseNotes(true)
|
||||
}
|
||||
|
||||
async function checkUpdate() {
|
||||
checkUpdateVisible.value = true
|
||||
latestVersion.value = (await getLatestVersion()) || t('pages.settings.update.networkError')
|
||||
|
||||
@@ -809,3 +809,162 @@ small {
|
||||
background: var(--color-blue-common);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* Update info and Release Notes Styles */
|
||||
.update-info {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.update-info > div {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.925rem;
|
||||
}
|
||||
|
||||
.update-info > div:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.update-notice {
|
||||
color: var(--color-success) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Release Notes Card */
|
||||
.release-notes-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.release-notes-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 1.5rem;
|
||||
background: var(--color-background);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.release-notes-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.release-notes-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-link:hover {
|
||||
color: var(--color-accent-hover);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.release-notes-content {
|
||||
background: var(--color-background);
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-accent) transparent;
|
||||
}
|
||||
|
||||
.release-notes-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.release-notes-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.release-notes-content::-webkit-scrollbar-thumb {
|
||||
background: var(--color-accent);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.release-notes-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.release-notes-loading,
|
||||
.release-notes-error,
|
||||
.release-notes-empty {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.925rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.release-notes-error {
|
||||
color: var(--color-error);
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.release-notes-text {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.release-notes-pre {
|
||||
margin: 0;
|
||||
padding: 1.5rem;
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
color: var(--color-text-primary);
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.release-notes-footer {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: var(--color-background-secondary);
|
||||
border-top: 1px solid var(--color-border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.release-notes-footer small {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Rotation animation for loading icons */
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rotate {
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user