fix: https://github.com/vastsa/FileCodeBox/issues/376 https://github.com/vastsa/FileCodeBox/issues/351
This commit is contained in:
@@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1332
-1269
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user