更新若干功能
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { call, on } from './api'
|
||||
import { call, on, onTasksChanged } from './api'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
@@ -8,13 +8,29 @@ export const useAppStore = defineStore('app', {
|
||||
projectGroups: [],
|
||||
selectedProjectGroupId: Number(localStorage.getItem('cc-project-group-id') || 0),
|
||||
dashboard: { projects: 0, totalLines: 0, commits: 0 },
|
||||
settings: { theme: 'dark', locale: 'zh-CN', glassOpacity: 55, gitScope: 'current', autoRefresh: true, loadingStyle: 'fullscreen-orbit' },
|
||||
settings: { theme: 'dark', locale: 'zh-CN', glassOpacity: 55, gitScope: 'current', autoRefresh: true, loadingStyle: 'fullscreen-orbit', imageMode: 'base64' },
|
||||
tasks: {},
|
||||
batchTaskIds: [],
|
||||
batchSummary: null,
|
||||
pendingAction: '',
|
||||
favorites: [],
|
||||
unreadMessages: 0,
|
||||
// 左侧导航徽标(消息未读/待办/工单/今日到期/日历红点/团队指派)
|
||||
badges: { unread: 0, todosOpen: 0, ticketsActive: 0, todayDue: 0, calendarDot: false, teamAssigned: 0 },
|
||||
toast: null,
|
||||
toastTimer: null,
|
||||
toastLeaveTimer: null,
|
||||
loading: false
|
||||
loading: false,
|
||||
syncStatus: { configured: false, loggedIn: false, userId: 0, username: '', online: false, syncing: false },
|
||||
avatarSrc: '',
|
||||
loginOpen: false,
|
||||
paletteOpen: false,
|
||||
// 日历页"每日心语"入口:设为 'YYYY-MM-DD' 时 DailyCard 打开对应日期的卡片
|
||||
dailyCardDate: '',
|
||||
// 节日名 → { mode: 'photo'|'art', image: dataURL }(管理员上传,随同步分发)
|
||||
festivalImages: {},
|
||||
// 探测到加载失败的节日图(key → true),日历格自动回退动态插画
|
||||
festBroken: {}
|
||||
}),
|
||||
actions: {
|
||||
async boot() {
|
||||
@@ -22,9 +38,22 @@ export const useAppStore = defineStore('app', {
|
||||
if (this.bootstrap.state === 'ready') {
|
||||
this.settings = await call('GetSettings')
|
||||
this.applyAppearance(this.settings)
|
||||
try { this.syncStatus = await call('GetSyncStatus') } catch {}
|
||||
this.loadFestivalImages()
|
||||
await this.refresh()
|
||||
}
|
||||
},
|
||||
async loadFestivalImages() {
|
||||
try { this.festivalImages = (await call('ListFestivalImages')) || {} } catch { return }
|
||||
// 预探测 photo 模式的图片:解码失败的标记为 broken,展示时回退动态插画
|
||||
for (const [key, it] of Object.entries(this.festivalImages)) {
|
||||
if (it.mode !== 'photo' || !it.image) continue
|
||||
const probe = new Image()
|
||||
probe.onerror = () => { this.festBroken = { ...this.festBroken, [key]: true } }
|
||||
probe.onload = () => { if (this.festBroken[key]) { const n = { ...this.festBroken }; delete n[key]; this.festBroken = n } }
|
||||
probe.src = it.image
|
||||
}
|
||||
},
|
||||
applyAppearance(settings) {
|
||||
this.settings = { ...this.settings, ...settings }
|
||||
let theme = this.settings.theme || 'dark'
|
||||
@@ -32,6 +61,25 @@ export const useAppStore = defineStore('app', {
|
||||
document.documentElement.dataset.theme = theme
|
||||
document.documentElement.style.setProperty('--glass-user-opacity', String((this.settings.glassOpacity || 55) / 100))
|
||||
localStorage.setItem('cc-settings', JSON.stringify(this.settings))
|
||||
this.resolveAvatar()
|
||||
},
|
||||
// resolveAvatar 把设置中的头像解析为可显示的 <img> 源:path 模式需经后端读文件转 dataURL。
|
||||
async resolveAvatar() {
|
||||
const { avatarMode, avatarValue } = this.settings
|
||||
if (!avatarMode || !avatarValue) { this.avatarSrc = ''; return }
|
||||
if (avatarMode === 'path') {
|
||||
try { this.avatarSrc = await call('ReadImageAsDataURL', avatarValue) } catch { this.avatarSrc = '' }
|
||||
return
|
||||
}
|
||||
this.avatarSrc = avatarValue
|
||||
},
|
||||
async refreshSyncStatus() {
|
||||
try { this.syncStatus = await call('GetSyncStatus') } catch {}
|
||||
},
|
||||
// openAccount 是所有登录入口的统一行为:未登录弹全屏登录层,已登录进个人主页。
|
||||
openAccount(router) {
|
||||
if (this.syncStatus.loggedIn) router?.push('/profile')
|
||||
else this.loginOpen = true
|
||||
},
|
||||
async saveSettings(patch) {
|
||||
const next = { ...this.settings, ...patch }
|
||||
@@ -63,20 +111,35 @@ export const useAppStore = defineStore('app', {
|
||||
},
|
||||
async refresh() {
|
||||
this.loading = true
|
||||
this.refreshSyncStatus()
|
||||
try {
|
||||
this.projectGroups = await call('ListProjectGroups')
|
||||
if (this.selectedProjectGroupId && !this.projectGroups.some(g => g.id === this.selectedProjectGroupId)) {
|
||||
this.setProjectGroup(0)
|
||||
}
|
||||
const groupId = this.selectedProjectGroupId || 0
|
||||
;[this.projects, this.dashboard] = await Promise.all([
|
||||
;[this.projects, this.dashboard, this.favorites, this.unreadMessages] = await Promise.all([
|
||||
groupId ? call('ListProjectsByGroup', groupId) : call('ListProjects'),
|
||||
groupId ? call('GetDashboardByGroup', groupId) : call('GetDashboard')
|
||||
groupId ? call('GetDashboardByGroup', groupId) : call('GetDashboard'),
|
||||
call('ListFavorites').catch(() => []),
|
||||
call('UnreadMessageCount').catch(() => 0)
|
||||
])
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async toggleFavorite(projectId) {
|
||||
const fav = await call('ToggleFavorite', projectId)
|
||||
this.favorites = fav ? [projectId, ...this.favorites.filter(x => x !== projectId)] : this.favorites.filter(x => x !== projectId)
|
||||
return fav
|
||||
},
|
||||
async refreshUnread() {
|
||||
try { this.unreadMessages = await call('UnreadMessageCount') } catch {}
|
||||
this.refreshBadges()
|
||||
},
|
||||
async refreshBadges() {
|
||||
try { this.badges = await call('GetNavBadges') } catch {}
|
||||
},
|
||||
async changeProjectGroup(groupId) {
|
||||
this.setProjectGroup(groupId)
|
||||
await this.refresh()
|
||||
@@ -86,7 +149,7 @@ export const useAppStore = defineStore('app', {
|
||||
if (persist) localStorage.setItem('cc-project-group-id', String(this.selectedProjectGroupId))
|
||||
},
|
||||
listen() {
|
||||
return on('analysis:progress', e => {
|
||||
const offProgress = on('analysis:progress', e => {
|
||||
delete this.tasks.__batch_pending__
|
||||
this.tasks[e.taskId] = e
|
||||
if (['completed', 'error', 'cancelled'].includes(e.stage)) {
|
||||
@@ -100,6 +163,30 @@ export const useAppStore = defineStore('app', {
|
||||
this.refresh()
|
||||
}
|
||||
})
|
||||
const offBatch = on('batch:done', s => {
|
||||
this.batchSummary = s
|
||||
this.batchTaskIds = []
|
||||
delete this.tasks.__batch_pending__
|
||||
this.showToast({ type: s.failed ? 'error' : 'success', key: 'batchDoneToast', params: { completed: s.completed, total: s.total } })
|
||||
this.refresh()
|
||||
})
|
||||
const offMessage = on('message:new', () => { this.unreadMessages += 1; this.refreshBadges() })
|
||||
const offSync = on('sync:done', async st => {
|
||||
this.syncStatus = st || this.syncStatus
|
||||
this.refreshBadges()
|
||||
// 拉取可能更新了设置(头像 / API Key),刷新本地副本
|
||||
try { this.applyAppearance(await call('GetSettings')) } catch {}
|
||||
// 拉取可能带来新项目/分组/收藏,刷新项目列表
|
||||
if (st?.pulled > 0) {
|
||||
this.refresh().catch(() => {})
|
||||
this.loadFestivalImages()
|
||||
}
|
||||
})
|
||||
// 徽标:启动即取,任务变更事件 + 60s 轮询兜底
|
||||
this.refreshBadges()
|
||||
const offTasks = onTasksChanged(() => this.refreshBadges())
|
||||
const badgeTimer = setInterval(() => this.refreshBadges(), 60000)
|
||||
return () => { offProgress?.(); offBatch?.(); offMessage?.(); offSync?.(); offTasks?.(); clearInterval(badgeTimer) }
|
||||
},
|
||||
async analyze(id, kind = 'all') {
|
||||
const task = await call('StartAnalysis', id, kind)
|
||||
|
||||
Reference in New Issue
Block a user