Files
FileShare/web/src/stores/config.ts
T
SKYMirror 84df9996cb
CI 测试 / go vet + go test (push) Successful in 49s
26.9:版本号统一 + CI 精简 + 前端产物重建
- 全项目版本号统一:v3.x 迭代号(26.9/26.9/26.9/26.9 及裸 v2/v3)→ 26.9,
  覆盖 Go 注释 / 文档 / openapi.yaml / README×4 / 前端源码(80+ 处)
- v31_test.go 更名 custom_code_test.go;TestV2AccessorDefaults → TestKVAccessorDefaults
- docs/api/00-overview.md 更新日志合并为单条 26.9 条目(修复错位拼接)
- .goreleaser.yaml 头部注释与实际一致(Pro 2.18.1 / GITEA_TOKEN / semver tag 要求)
- CI:release-image.yml → ci.yml,仅保留 vet+test 门禁;
  镜像发布移交 GoReleaser Pro(原 build-push 的 tag 校验与 26.9 版本方案冲突,历史 9 次失败)
- 前端重建:server/web/dist 与 web-embed 同步(docs 文案嵌入更新)
2026-09-08 03:16:51 +08:00

162 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 站点配置 store:公共配置 + Logo/favicon(需求④)+ 背景/页脚/通知(⑤⑥⑦)+ 策略范围(⑧)。
* 远程 URL 默认值已删除:Logo/favicon 使用本地打包资源兜底(config 自定义仍优先)。
*/
import { defineStore } from 'pinia'
import { request } from '@/api/http'
import { paths } from '@/api/paths'
import { pick } from '@/utils/format'
import logoLocal from '@/assets/brand/logo.svg'
import faviconLocal from '@/assets/brand/favicon.png'
/** 需求④:默认 Logo(导航栏)与 favicon(浏览器标签)——本地打包资源,无远程依赖 */
export const DEFAULT_LOGO_URL = logoLocal
export const DEFAULT_FAVICON_URL = faviconLocal
export const DEFAULT_SITE_NAME = '文件快传'
export interface ConfigState {
loaded: boolean
loading: boolean
siteName: string
siteDomain: string
description: string
explain: string
uploadSize: number
allowedFileTypes: string[]
expireStyle: string[]
enableChunk: boolean
openUpload: boolean
notifyEnabled: boolean
notifyTitle: string
notifyContent: string
logoUrl: string
faviconUrl: string
backgroundUrl: string
footerText: string
footerBeian: string
maxFileSize: number
maxSaveSeconds: number
maxSaveCount: number
uploadCount: number
uploadMinute: number
}
function toBool(v: unknown, fallback = true): boolean {
if (v === undefined || v === null) return fallback
if (typeof v === 'boolean') return v
if (typeof v === 'number') return v !== 0
return String(v) !== '0' && String(v) !== 'false' && String(v) !== ''
}
function toList(v: unknown): string[] {
if (Array.isArray(v)) return v.map((x) => String(x).trim()).filter(Boolean)
if (typeof v === 'string') {
return v
.split(',')
.map((x) => x.trim())
.filter(Boolean)
}
return []
}
function toNum(v: unknown, fallback: number): number {
const n = Number(v)
return Number.isFinite(n) ? n : fallback
}
export const useConfigStore = defineStore('config', {
state: (): ConfigState => ({
loaded: false,
loading: false,
siteName: DEFAULT_SITE_NAME,
siteDomain: '',
description: '',
explain: '',
uploadSize: 10 * 1024 * 1024,
allowedFileTypes: [],
expireStyle: ['day', 'hour', 'minute', 'forever', 'count'],
enableChunk: false,
openUpload: true,
notifyEnabled: false,
notifyTitle: '',
notifyContent: '',
logoUrl: '',
faviconUrl: '',
backgroundUrl: '',
footerText: '',
footerBeian: '',
maxFileSize: 0,
maxSaveSeconds: 0,
maxSaveCount: 0,
uploadCount: 0,
uploadMinute: 0,
}),
getters: {
/** 需求④:自定义优先,本地打包资源兜底 */
displayLogoUrl: (s) => (s.logoUrl?.trim() ? s.logoUrl : DEFAULT_LOGO_URL),
displayFaviconUrl: (s) => (s.faviconUrl?.trim() ? s.faviconUrl : DEFAULT_FAVICON_URL),
displayName: (s) => (s.siteName?.trim() ? s.siteName : DEFAULT_SITE_NAME),
/** 26.9:分享链接基础地址——配置了对外域名用之,否则当前访问地址 */
shareLinkBase: (s): string => (s.siteDomain?.trim() ? s.siteDomain.trim().replace(/\/$/, '') : location.origin),
/** ⑧ 存储策略:max_file_size>0 时优先,否则回落 uploadSize */
effectiveMaxFileSize(): number {
return this.maxFileSize > 0 ? this.maxFileSize : this.uploadSize
},
},
actions: {
/** 拉取公共配置;失败静默使用默认值(未部署后端时前端仍可浏览) */
async load(): Promise<void> {
this.loading = true
try {
const raw = await request<Record<string, unknown>>(paths.publicConfig, { timeout: 8000 })
const cfg = (pick<Record<string, unknown>>(raw, ['config']) ?? raw) as Record<string, unknown>
this.siteName = String(pick<string>(cfg, ['name', 'site_name', 'siteName']) ?? DEFAULT_SITE_NAME)
// 26.9:站点对外域名(空=当前访问地址)
this.siteDomain = String(pick<string>(cfg, ['site_domain', 'siteDomain']) ?? '').trim()
this.description = String(pick<string>(cfg, ['description']) ?? '')
this.explain = String(pick<string>(cfg, ['explain', 'page_explain']) ?? '')
this.uploadSize = toNum(pick<unknown>(cfg, ['uploadSize', 'upload_size']), 10 * 1024 * 1024)
this.allowedFileTypes = toList(pick<unknown>(cfg, ['allowedFileTypes', 'allowed_file_types']))
const styles = toList(pick<unknown>(cfg, ['expireStyle', 'expire_style']))
if (styles.length) this.expireStyle = styles
this.enableChunk = toBool(pick<unknown>(cfg, ['enableChunk', 'enable_chunk']), false)
this.openUpload = toBool(pick<unknown>(cfg, ['openUpload', 'open_upload']), true)
this.notifyTitle = String(pick<string>(cfg, ['notify_title', 'notifyTitle']) ?? '')
this.notifyContent = String(pick<string>(cfg, ['notify_content', 'notifyContent']) ?? '')
// 需求⑦:通知开关(1/0
this.notifyEnabled = toBool(pick<unknown>(cfg, ['notify_enabled', 'notifyEnabled']), false)
// 需求⑤:背景图
this.backgroundUrl = String(pick<string>(cfg, ['background_url', 'backgroundUrl']) ?? '').trim()
// 需求⑥:页脚
this.footerText = String(pick<string>(cfg, ['footer_text', 'footerText']) ?? '')
this.footerBeian = String(pick<string>(cfg, ['footer_beian', 'footerBeian']) ?? '')
// 需求⑧:保存策略与上传频率
this.maxFileSize = toNum(pick<unknown>(cfg, ['max_file_size', 'maxFileSize', 'maxFileSize']), 0)
this.maxSaveSeconds = toNum(pick<unknown>(cfg, ['max_save_seconds', 'maxSaveSeconds']), 0)
this.maxSaveCount = toNum(pick<unknown>(cfg, ['max_save_count', 'maxSaveCount']), 0)
this.uploadCount = toNum(pick<unknown>(cfg, ['uploadCount', 'upload_count']), 0)
this.uploadMinute = toNum(pick<unknown>(cfg, ['uploadMinute', 'upload_minute']), 0)
// 需求④:运行时优先读 config 的 logo_url/favicon_url,空值回退本地默认
this.logoUrl = String(pick<string>(cfg, ['logo_url', 'logoUrl']) ?? '').trim()
this.faviconUrl = String(pick<string>(cfg, ['favicon_url', 'faviconUrl']) ?? '').trim()
this.loaded = true
this.applyToDocument()
} catch {
// 保持默认值
} finally {
this.loading = false
}
},
/** 将站点名与 favicon 应用到 document(管理端保存配置后调用即可全站生效) */
applyToDocument(): void {
let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
if (!link) {
link = document.createElement('link')
link.rel = 'icon'
document.head.appendChild(link)
}
link.href = this.displayFaviconUrl
},
},
})