CI 测试 / go vet + go test (push) Successful in 49s
- 全项目版本号统一: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 文案嵌入更新)
162 lines
6.6 KiB
TypeScript
162 lines
6.6 KiB
TypeScript
/**
|
||
* 站点配置 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
|
||
},
|
||
},
|
||
})
|