Lan
2025-08-01 13:24:22 +08:00
parent 8c8456d76d
commit 4061605c57
8 changed files with 1476 additions and 1350 deletions
-15
View File
@@ -1,15 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}
+20
View File
@@ -0,0 +1,20 @@
import js from '@eslint/js'
import vue from 'eslint-plugin-vue'
import typescript from '@vue/eslint-config-typescript'
import prettier from '@vue/eslint-config-prettier/skip-formatting'
export default [
js.configs.recommended,
...vue.configs['flat/essential'],
...typescript(),
prettier,
{
files: ['**/*.{js,mjs,cjs,vue,ts}'],
rules: {
// 你可以在这里添加自定义规则
}
},
{
ignores: ['dist/**', 'node_modules/**']
}
]
+35 -33
View File
@@ -9,46 +9,48 @@
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
"type-check": "vue-tsc --build --force", "type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "lint": "eslint . --fix",
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.16",
"axios": "^1.7.7", "axios": "^1.11.0",
"dompurify": "^3.2.6",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lru-cache": "^11.0.1", "lru-cache": "^11.1.0",
"lucide-vue-next": "^0.445.0", "lucide-vue-next": "^0.535.0",
"marked": "^14.1.2", "marked": "^16.1.1",
"pinia": "^2.2.2", "pinia": "^3.0.3",
"qrcode.vue": "^3.4.1", "qrcode.vue": "^3.6.0",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"vue": "^3.5.8", "vue": "^3.5.18",
"vue-router": "^4.4.5" "vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/config-array": "^0.18.0", "@eslint/config-array": "^0.21.0",
"@eslint/object-schema": "^2.1.4", "@eslint/object-schema": "^2.1.6",
"@rushstack/eslint-patch": "^1.10.4", "@rushstack/eslint-patch": "^1.12.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.6",
"@types/node": "^20.16.7", "@types/file-saver": "^2.0.7",
"@types/spark-md5": "^3.0.4", "@types/node": "^24.1.0",
"@vitejs/plugin-vue": "^5.1.4", "@types/spark-md5": "^3.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^9.0.0", "@vitejs/plugin-vue-jsx": "^5.0.1",
"@vue/eslint-config-typescript": "^13.0.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/tsconfig": "^0.5.1", "@vue/eslint-config-typescript": "^14.6.0",
"autoprefixer": "^10.4.20", "@vue/tsconfig": "^0.7.0",
"eslint": "^8.57.1", "autoprefixer": "^10.4.21",
"eslint-plugin-vue": "^9.28.0", "eslint": "^9.32.0",
"glob": "^11.0.0", "eslint-plugin-vue": "^10.4.0",
"npm-run-all2": "^6.2.3", "glob": "^11.0.3",
"postcss": "^8.4.47", "npm-run-all2": "^8.0.4",
"prettier": "^3.3.3", "postcss": "^8.5.6",
"prettier": "^3.6.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.17",
"typescript": "~5.4.5", "typescript": "~5.8.3",
"vite": "^5.4.7", "vite": "^7.0.6",
"vite-plugin-vue-devtools": "^7.4.6", "vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^2.1.6" "vue-tsc": "^3.0.4"
} }
} }
+1332 -1269
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -19,7 +19,7 @@
<component :is="alertIcons[alert.type]" class="h-6 w-6 text-white" /> <component :is="alertIcons[alert.type]" class="h-6 w-6 text-white" />
</div> </div>
<div class="ml-3 flex-1 pt-0.5"> <div class="ml-3 flex-1 pt-0.5">
<p class="text-sm font-medium text-white" v-html="alert.message"></p> <p class="text-sm font-medium text-white">{{ alert.message }}</p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">
<button <button
+83 -27
View File
@@ -257,8 +257,35 @@
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref, inject, onMounted, watch, computed } from 'vue' import { ref, inject, onMounted, watch, computed } from 'vue'
// 定义数据接口
interface FileRecord {
id: number
code: string
filename: string
size: string
downloadUrl: string | null
content: string | null
date: string
}
interface InputStatus {
readonly: boolean
loading: boolean
}
interface ApiResponse {
code: number
message?: string
detail?: {
code: string
name: string
text: string
size: number
}
}
import { import {
BoxIcon, BoxIcon,
EyeIcon, EyeIcon,
@@ -280,6 +307,7 @@ import { storeToRefs } from 'pinia'
import api from '@/utils/api' import api from '@/utils/api'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { marked } from 'marked' import { marked } from 'marked'
import DOMPurify from 'dompurify'
import { useAlertStore } from '@/stores/alertStore' import { useAlertStore } from '@/stores/alertStore'
import { copyToClipboard } from '@/utils/clipboard' import { copyToClipboard } from '@/utils/clipboard'
@@ -291,25 +319,27 @@ const isDarkMode = inject('isDarkMode')
const fileStore = useFileDataStore() const fileStore = useFileDataStore()
const { receiveData } = storeToRefs(fileStore) const { receiveData } = storeToRefs(fileStore)
const code = ref('') const code = ref('')
const inputStatus = ref({ const inputStatus = ref<InputStatus>({
readonly: false, readonly: false,
loading: false loading: false
}) })
const isInputFocused = ref(false) const isInputFocused = ref(false)
const error = ref('') const error = ref('')
const selectedRecord = ref(null) const selectedRecord = ref<FileRecord | null>(null)
const showDrawer = ref(false) const showDrawer = ref(false)
const route = useRoute() const route = useRoute()
// 使用 receiveData 替代原来的 records // 使用 receiveData 替代原来的 records
const records = receiveData const records = receiveData
const config = JSON.parse(localStorage.getItem('config') || '{}') const config = JSON.parse(localStorage.getItem('config') || '{}')
const codeInput = ref(null) const codeInput = ref<HTMLInputElement | null>(null)
onMounted(() => { onMounted(() => {
codeInput.value && codeInput.value.focus() if (codeInput.value) {
codeInput.value.focus()
}
const query_code = route.query.code const query_code = route.query.code
if (query_code) { if (query_code && typeof query_code === 'string') {
code.value = query_code code.value = query_code
} }
}) })
@@ -319,12 +349,15 @@ watch(code, (newVal) => {
} }
}) })
const getDownloadUrl = (record) => { const getDownloadUrl = (record: FileRecord) => {
if (record.downloadUrl.startsWith('http')) { if (record.downloadUrl) {
return record.downloadUrl if (record.downloadUrl.startsWith('http')) {
} else { return record.downloadUrl
return `${baseUrl}${record.downloadUrl}` } else {
return `${baseUrl}${record.downloadUrl}`
}
} }
return ''
} }
// 在其他代码后添加复制功能 // 在其他代码后添加复制功能
const copyContent = async () => { const copyContent = async () => {
@@ -345,11 +378,12 @@ const handleSubmit = async () => {
inputStatus.value.loading = true inputStatus.value.loading = true
try { try {
const res = await api.post('/share/select/', { const response = await api.post('/share/select/', {
code: code.value code: code.value
}) })
const res = (response.data || response) as ApiResponse
if (res.code === 200) { if (res && res.code === 200) {
if (res.detail) { if (res.detail) {
const isFile = res.detail.text.startsWith('/share/download') || res.detail.name !== 'Text' const isFile = res.detail.text.startsWith('/share/download') || res.detail.name !== 'Text'
const newFileData = { const newFileData = {
@@ -362,7 +396,7 @@ const handleSubmit = async () => {
date: new Date().toLocaleString() date: new Date().toLocaleString()
} }
let flag = true let flag = true
fileStore.receiveData.forEach((file) => { fileStore.receiveData.forEach((file: FileRecord) => {
if (file.code === newFileData.code) { if (file.code === newFileData.code) {
flag = false flag = false
} }
@@ -381,7 +415,8 @@ const handleSubmit = async () => {
alertStore.showAlert('无效的取件码', 'error') alertStore.showAlert('无效的取件码', 'error')
} }
} else { } else {
alertStore.showAlert(res.detail || '获取文件失败', 'error') alertStore.showAlert('获取文件失败', 'error')
console.log(res)
} }
} catch (err) { } catch (err) {
console.error('取件失败:', err) console.error('取件失败:', err)
@@ -393,7 +428,7 @@ const handleSubmit = async () => {
} }
} }
const formatFileSize = (bytes) => { const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes' if (bytes === 0) return '0 Bytes'
const k = 1024 const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
@@ -401,12 +436,12 @@ const formatFileSize = (bytes) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
} }
const viewDetails = (record) => { const viewDetails = (record: FileRecord) => {
selectedRecord.value = record selectedRecord.value = record
} }
const deleteRecord = (id) => { const deleteRecord = (id: number) => {
const index = records.value.findIndex((record) => record.id === id) const index = records.value.findIndex((record: FileRecord) => record.id === id)
if (index !== -1) { if (index !== -1) {
fileStore.deleteReceiveData(index) fileStore.deleteReceiveData(index)
} }
@@ -420,7 +455,7 @@ const toSend = () => {
router.push('/send') router.push('/send')
} }
const getQRCodeValue = (record) => { const getQRCodeValue = (record: FileRecord) => {
if (record.downloadUrl) { if (record.downloadUrl) {
return `${baseUrl}${record.downloadUrl}` return `${baseUrl}${record.downloadUrl}`
} else { } else {
@@ -428,7 +463,7 @@ const getQRCodeValue = (record) => {
} }
} }
const downloadRecord = (record) => { const downloadRecord = (record: FileRecord) => {
console.log(record) console.log(record)
if (record.downloadUrl) { if (record.downloadUrl) {
@@ -445,12 +480,33 @@ const downloadRecord = (record) => {
} }
const showPreview = ref(false) const showPreview = ref(false)
const renderedContent = computed(() => { const renderedContent = ref('')
if (selectedRecord.value && selectedRecord.value.content) {
return marked(selectedRecord.value.content) // 监听selectedRecord变化,异步渲染内容
} watch(
return '' () => selectedRecord.value?.content,
}) async (content) => {
if (content) {
try {
// 使用 marked 解析 Markdown,然后用 DOMPurify 清理 HTML 防止 XSS
const rawHtml = await marked(content)
renderedContent.value = DOMPurify.sanitize(rawHtml, {
// 允许的标签和属性
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'code', 'pre', 'a', 'img'],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class'],
// 禁用危险的协议
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.-]+(?:[^a-z+.-:]|$))/i
})
} catch (error) {
console.error('Markdown 渲染失败:', error)
renderedContent.value = content // fallback 到原始内容
}
} else {
renderedContent.value = ''
}
},
{ immediate: true }
)
const showContentPreview = () => { const showContentPreview = () => {
showPreview.value = true showPreview.value = true
+3 -3
View File
@@ -568,14 +568,14 @@ const displayedPages = computed(() => {
const total = totalPages.value const total = totalPages.value
const delta = 2 // 当前页码前后显示的页码数 const delta = 2 // 当前页码前后显示的页码数
let pages: (number | string)[] = [] const pages: (number | string)[] = []
// 始终显示第一页 // 始终显示第一页
pages.push(1) pages.push(1)
// 计算显示范围 // 计算显示范围
let left = Math.max(2, current - delta) const left = Math.max(2, current - delta)
let right = Math.min(total - 1, current + delta) const right = Math.min(total - 1, current + delta)
// 添加省略号和页码 // 添加省略号和页码
if (left > 2) { if (left > 2) {
+2 -2
View File
@@ -108,7 +108,7 @@ const refreshData = () => {
config.value = res.detail config.value = res.detail
// 将字节转换为合适的单位 // 将字节转换为合适的单位
let size = config.value.uploadSize const size = config.value.uploadSize
if (size >= 1024 * 1024 * 1024) { if (size >= 1024 * 1024 * 1024) {
fileSize.value = Math.round(size / (1024 * 1024 * 1024)) fileSize.value = Math.round(size / (1024 * 1024 * 1024))
sizeUnit.value = 'GB' sizeUnit.value = 'GB'
@@ -121,7 +121,7 @@ const refreshData = () => {
} }
// 时间单位转换逻辑 // 时间单位转换逻辑
let seconds = config.value.max_save_seconds const seconds = config.value.max_save_seconds
if (seconds === 0) { if (seconds === 0) {
// 如果是0,显示为7天 // 如果是0,显示为7天
saveTime.value = 7 saveTime.value = 7