FileCodeBox Go 重写版 v2.5.6(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务: - 安全审计全部修复(docs/security-audit-2026-09-05.md): bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、 全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、 管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、 会话默认 7 天、限流缓存故障降级、robots.txt 端点等 - 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固 - Redis 支持库号(FCB_REDIS_DB / redis://…/db URL) - 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、 提码 5 位起、chunk 32MiB 上限、admin 审计动作等) 验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
/** 文件拖拽/点选组件(需求⑧:allowedFileTypes 过滤文件选择器 accept;大小提示对齐 max_file_size) */
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { formatBytes } from '@/utils/format'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: File | null
|
||||
/** 最大字节数,超限提示 */
|
||||
maxSize?: number
|
||||
disabled?: boolean
|
||||
/** 需求⑧:类型白名单(星号表示不限制;含斜杠按 MIME 匹配,否则按扩展名) */
|
||||
acceptTypes?: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [f: File | null]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const dragover = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
/** 需求⑧:把白名单映射为 <input accept> 值("*" → 空 = 不限制) */
|
||||
const acceptAttr = computed(() => {
|
||||
const rules = (props.acceptTypes ?? []).map((r) => r.trim()).filter(Boolean)
|
||||
if (!rules.length || rules.some((r) => r === '*' || r === '*/*')) return ''
|
||||
return rules
|
||||
.map((r) => (r.includes('/') ? r : r.startsWith('.') ? r : `.${r.toLowerCase()}`))
|
||||
.join(',')
|
||||
})
|
||||
|
||||
const acceptHint = computed(() => {
|
||||
const rules = (props.acceptTypes ?? []).filter((r) => r && r !== '*' && r !== '*/*')
|
||||
return rules.length ? t('drop.typeHint', { types: rules.join(', ') }) : ''
|
||||
})
|
||||
|
||||
function accept(f: File | null | undefined): void {
|
||||
error.value = ''
|
||||
if (!f) return
|
||||
if (props.maxSize && f.size > props.maxSize) {
|
||||
error.value = t('drop.tooLarge', { size: formatBytes(f.size), limit: formatBytes(props.maxSize) })
|
||||
emit('update:modelValue', null)
|
||||
return
|
||||
}
|
||||
emit('update:modelValue', f)
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent): void {
|
||||
dragover.value = false
|
||||
if (props.disabled) return
|
||||
accept(e.dataTransfer?.files?.[0])
|
||||
}
|
||||
|
||||
function onPick(e: Event): void {
|
||||
const input = e.target as HTMLInputElement
|
||||
accept(input.files?.[0])
|
||||
input.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!modelValue"
|
||||
class="dropzone"
|
||||
:class="{ dragover, disabled }"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="t('drop.aria')"
|
||||
@click="!disabled && inputRef?.click()"
|
||||
@keydown.enter.prevent="!disabled && inputRef?.click()"
|
||||
@dragover.prevent="dragover = true"
|
||||
@dragleave="dragover = false"
|
||||
@drop.prevent="onDrop"
|
||||
>
|
||||
<div class="dz-icon" aria-hidden="true">📦</div>
|
||||
<div class="dz-main">{{ t('drop.zone') }}</div>
|
||||
<div class="dz-sub">
|
||||
<template v-if="maxSize">{{ t('drop.maxSize', { size: formatBytes(maxSize) }) }}</template>
|
||||
<template v-else>{{ t('drop.noLimit') }}</template>
|
||||
<template v-if="acceptHint"> · {{ acceptHint }}</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="file-chip">
|
||||
<span aria-hidden="true">📄</span>
|
||||
<span class="fc-name">{{ modelValue.name }}</span>
|
||||
<span class="fc-size">{{ formatBytes(modelValue.size) }}</span>
|
||||
<button class="fc-remove" type="button" :title="t('drop.remove')" @click="emit('update:modelValue', null)">✕</button>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="hint" style="color: var(--c-danger)">{{ error }}</p>
|
||||
<input ref="inputRef" type="file" hidden :accept="acceptAttr" @change="onPick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropzone.disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user