26.9:版本号统一 + CI 精简 + 前端产物重建
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 文案嵌入更新)
This commit is contained in:
2026-09-08 03:16:51 +08:00
parent 27432218c4
commit 84df9996cb
122 changed files with 247 additions and 12268 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ export async function request<T>(path: string, opts: RequestOptions = {}): Promi
if (json !== undefined) body = JSON.stringify(json)
else if (formData) body = formData
else if (form) {
// v3.1.1 根因修复:URLSearchParams.toString() 产出的是字符串 body
// 26.9 根因修复:URLSearchParams.toString() 产出的是字符串 body
// fetch 对字符串默认 Content-Type: text/plain —— 后端无法按表单解析,
// 文本分享被静默存成空(或被空文本校验拒绝)。必须显式声明 urlencoded。
body = new URLSearchParams(form).toString()
+1 -1
View File
@@ -24,7 +24,7 @@ function routerBase(): string {
}
export function pickupPageUrl(code: string, base?: string): string {
// v3.1:配置了站点对外域名时用之(内网访问也能生成公网链接),否则当前地址
// 26.9:配置了站点对外域名时用之(内网访问也能生成公网链接),否则当前地址
const origin = (base?.trim() || location.origin).replace(/\/$/, '')
return `${origin}${routerBase()}${encodeURIComponent(code)}`
}
+1 -1
View File
@@ -27,7 +27,7 @@ const link = computed(() => pickupPageUrl(props.code, config.shareLinkBase))
async function copy(value: 'code' | 'link'): Promise<void> {
// 复制链接本身即可:链接已含取件码(/s/<code>),再拼一遍会导致
// 「/s/XVR3H XVR3H」被整串当码查询而 404(v3.1.2 遗留缺陷)
// 「/s/XVR3H XVR3H」被整串当码查询而 404(26.9 遗留缺陷)
const text = value === 'link' ? link.value : props.code
const ok = await copyText(text)
if (ok) {
+2 -2
View File
@@ -96,7 +96,7 @@ export const useConfigStore = defineStore('config', {
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),
/** v3.1:分享链接基础地址——配置了对外域名用之,否则当前访问地址 */
/** 26.9:分享链接基础地址——配置了对外域名用之,否则当前访问地址 */
shareLinkBase: (s): string => (s.siteDomain?.trim() ? s.siteDomain.trim().replace(/\/$/, '') : location.origin),
/** ⑧ 存储策略:max_file_size>0 时优先,否则回落 uploadSize */
effectiveMaxFileSize(): number {
@@ -111,7 +111,7 @@ export const useConfigStore = defineStore('config', {
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)
// v3.1:站点对外域名(空=当前访问地址)
// 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']) ?? '')
+1 -1
View File
@@ -30,7 +30,7 @@ export interface ChunkUploadOptions {
file: File
expireValue: number
expireStyle: string
customCode?: string // v3.1:自定义提取码(4-8 位字母数字)
customCode?: string // 26.9:自定义提取码(4-8 位字母数字)
onProgress?: (percent: number, uploadedBytes: number, totalBytes: number) => void
}
+1 -1
View File
@@ -48,7 +48,7 @@ function errMsg(e: unknown, fallback: string): string {
return fallback
}
// ---- 自定义提取码(v3.1):两个分享 tab 共用 ----
// ---- 自定义提取码(26.9):两个分享 tab 共用 ----
const customCode = ref('')
const CODE_RE = /^[A-Za-z0-9]{4,8}$/
+4 -4
View File
@@ -62,7 +62,7 @@ const form = reactive({
allowed_file_types: '',
uploadCount: 0,
uploadMinute: 0,
// v3.2:上下行带宽(前端友好单位 = MB/s;提交时换算为字节/秒)
// 26.9:上下行带宽(前端友好单位 = MB/s;提交时换算为字节/秒)
uploadRate: 0,
downloadRate: 0,
})
@@ -166,7 +166,7 @@ async function load(): Promise<void> {
form.allowed_file_types = types.join(',')
form.uploadCount = toNum(pick<unknown>(cfg, ['uploadCount', 'upload_count']), 0)
form.uploadMinute = toNum(pick<unknown>(cfg, ['uploadMinute', 'upload_minute']), 0)
// v3.2:带宽(KV 字节/秒 → UI MB/s,0 保留为不限)
// 26.9:带宽(KV 字节/秒 → UI MB/s,0 保留为不限)
form.uploadRate = Math.round(toNum(pick<unknown>(cfg, ['upload_rate', 'uploadRate']), 0) / MB_BYTES)
form.downloadRate = Math.round(toNum(pick<unknown>(cfg, ['download_rate', 'downloadRate']), 0) / MB_BYTES)
// v3:当前引擎(get 的 _engine_hint.storage_backend 或公开 config 的 storage_engine
@@ -236,7 +236,7 @@ async function save(): Promise<void> {
// 上传频率限制(键名对齐参考既有 camelCase 键)
uploadCount: clamp(form.uploadCount || 0, 1, UPLOAD_COUNT_MAX),
uploadMinute: clamp(form.uploadMinute || 0, 1, UPLOAD_MINUTE_MAX),
// v3.2:带宽(MB/s → 字节/秒;负数与 NaN 归 0)
// 26.9:带宽(MB/s → 字节/秒;负数与 NaN 归 0)
upload_rate: clamp(Number(form.uploadRate) || 0, 0, RATE_MAX_MB) * MB_BYTES,
download_rate: clamp(Number(form.downloadRate) || 0, 0, RATE_MAX_MB) * MB_BYTES,
})
@@ -748,7 +748,7 @@ onMounted(load)
<p class="hint">{{ t('admin.settings.uploadMinuteHint', { max: UPLOAD_MINUTE_MAX }) }}</p>
</div>
</div>
<!-- v3.2上下行带宽与上传次数限制并列 -->
<!-- 26.9上下行带宽与上传次数限制并列 -->
<div class="field-row" style="margin-top: 14px">
<div class="field">
<label for="set-upload-rate">{{ t('admin.settings.uploadRate') }}</label>