This commit is contained in:
Lan
2024-11-23 21:50:46 +08:00
parent 9693f3d329
commit a247dd163d
7 changed files with 166 additions and 140 deletions
+1 -1
View File
@@ -1 +1 @@
VITE_API_BASE_URL_PROD=https://share.lanol.cn VITE_API_BASE_URL_PROD=
+2 -2
View File
@@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '/', path: '/',
+1 -1
View File
@@ -51,7 +51,7 @@ api.interceptors.response.use(
case 401: case 401:
console.error('未授权,请重新登录') console.error('未授权,请重新登录')
localStorage.clear() localStorage.clear()
window.location.href = '/login' window.location.href = '/#/login'
break break
case 403: case 403:
// 禁止访问 // 禁止访问
+21 -13
View File
@@ -45,10 +45,11 @@
id="code" id="code"
v-model="code" v-model="code"
type="text" type="text"
class="w-full px-4 py-3 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-300 pr-10" class="w-full px-4 py-3 rounded-lg placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition duration-300 pr-10"
:class="[ :class="[
isDarkMode ? 'bg-gray-700 bg-opacity-50' : 'bg-gray-100', isDarkMode ? 'bg-gray-700 bg-opacity-50' : 'bg-gray-100',
{ 'ring-2 ring-red-500': error } { 'ring-2 ring-red-500': error },
isDarkMode ? 'text-gray-300' : 'text-gray-800'
]" ]"
placeholder="请输入5位取件码" placeholder="请输入5位取件码"
required required
@@ -592,19 +593,26 @@ const showContentPreview = () => {
/* 添加 Markdown 样式 */ /* 添加 Markdown 样式 */
:deep(.prose) { :deep(.prose) {
@apply text-left; text-align: left;
} }
:deep(.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6) { :deep(.prose h1),
@apply text-indigo-600 dark:text-indigo-400; :deep(.prose h2),
:deep(.prose h3),
:deep(.prose h4),
:deep(.prose h5),
:deep(.prose h6) {
color: rgb(79, 70, 229); /* text-indigo-600 */
} }
:deep(.prose a) {
@apply text-blue-600 dark:text-blue-400; @media (prefers-color-scheme: dark) {
} :deep(.prose h1),
:deep(.prose code) { :deep(.prose h2),
@apply bg-gray-100 dark:bg-gray-700 rounded px-1; :deep(.prose h3),
} :deep(.prose h4),
:deep(.prose pre) { :deep(.prose h5),
@apply bg-gray-100 dark:bg-gray-700 rounded p-4 overflow-x-auto; :deep(.prose h6) {
color: rgb(129, 140, 248); /* text-indigo-400 */
}
} }
/* 添加新的宽度类 */ /* 添加新的宽度类 */
+1 -1
View File
@@ -424,7 +424,7 @@ const selectedFile = ref<File | null>(null)
const textContent = ref('') const textContent = ref('')
const fileInput = ref<HTMLInputElement | null>(null) const fileInput = ref<HTMLInputElement | null>(null)
const expirationMethod = ref('day') const expirationMethod = ref('day')
const expirationValue = ref('') const expirationValue = ref('1')
const uploadProgress = ref(0) const uploadProgress = ref(0)
const showDrawer = ref(false) const showDrawer = ref(false)
const selectedRecord = ref<any>(null) const selectedRecord = ref<any>(null)
+136 -120
View File
@@ -11,7 +11,7 @@
<div class="relative flex-1"> <div class="relative flex-1">
<input <input
type="text" type="text"
v-model="fileSearchQuery" v-model="params.keyword"
:class="[ :class="[
isDarkMode isDarkMode
? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400' ? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400'
@@ -25,53 +25,20 @@
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']" :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"
/> />
</div> </div>
<select
v-model="fileTypeFilter"
:class="[
isDarkMode
? 'bg-gray-700 border-gray-600 text-white'
: 'bg-white border-gray-300 text-gray-900'
]"
class="border rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<option value="">所有类型</option>
<option value="PDF">PDF</option>
<option value="Image">图片</option>
<option value="Video">视频</option>
<option value="Document">文档</option>
</select>
</div> </div>
<!-- 上传按钮 --> <!-- 搜索按钮 -->
<div class="flex gap-4"> <div class="flex gap-4">
<button <button
@click="$refs.fileInput.click()" @click="handleSearch"
class="flex items-center px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition-colors duration-200" class="flex items-center px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition-colors duration-200"
> >
<UploadIcon class="w-5 h-5 mr-2" /> <SearchIcon class="w-5 h-5 mr-2" />
上传文件 搜索
</button> </button>
<input ref="fileInput" type="file" multiple class="hidden" @change="handleFileUpload" />
</div> </div>
</div> </div>
<!-- 拖拽上传区域 -->
<div
v-if="showDropZone"
class="mb-6 border-2 border-dashed rounded-lg p-8 text-center"
:class="[isDarkMode ? 'border-gray-600 bg-gray-800/50' : 'border-gray-300 bg-gray-50']"
@drop.prevent="handleFileDrop"
@dragover.prevent="showDropZone = true"
@dragleave.prevent="showDropZone = false"
>
<UploadCloudIcon
class="mx-auto w-12 h-12 mb-4"
:class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"
/>
<p :class="[isDarkMode ? 'text-gray-300' : 'text-gray-700']">拖拽文件到此处上传</p>
</div>
<!-- File List --> <!-- File List -->
<div <div
class="rounded-lg shadow-md overflow-hidden transition-colors duration-300" class="rounded-lg shadow-md overflow-hidden transition-colors duration-300"
@@ -106,7 +73,14 @@
: 'bg-white divide-y divide-gray-200' : 'bg-white divide-y divide-gray-200'
]" ]"
> >
<tr v-for="file in files" :key="file.id"> <tr v-for="file in tableData" :key="file.id">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<span class="font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
{{ file.code }}
</span>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center"> <div class="flex items-center">
<FileIcon <FileIcon
@@ -114,28 +88,29 @@
:class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-500']" :class="[isDarkMode ? 'text-indigo-400' : 'text-indigo-500']"
/> />
<span class="font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-900']"> <span class="font-medium" :class="[isDarkMode ? 'text-white' : 'text-gray-900']">
{{ file.name }} {{ file.prefix }}
</span> </span>
</div> </div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"> <span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']">
{{ file.size }} {{ Math.round((file.size / 1024 / 1024) * 100) / 100 }}MB
</span> </span>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"> <span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']">
{{ file.type }} {{ file.text }}
</span> </span>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']"> <span :class="[isDarkMode ? 'text-gray-400' : 'text-gray-500']">
{{ file.lastModified }} {{ file.expired_at ? formatTimestamp(file.expired_at) : '永久' }}
</span> </span>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button <!-- <button
@click="downloadFile(file)" v-if="file.file_path"
@click="downloadFile(file.id)"
class="mr-3 transition-colors duration-200" class="mr-3 transition-colors duration-200"
:class="[ :class="[
isDarkMode isDarkMode
@@ -144,9 +119,9 @@
]" ]"
> >
下载 下载
</button> </button> -->
<button <button
@click="deleteFile(file)" @click="deleteFile(file.id)"
class="transition-colors duration-200" class="transition-colors duration-200"
:class="[ :class="[
isDarkMode isDarkMode
@@ -244,75 +219,128 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject, ref, computed } from 'vue' import { inject, ref, computed } from 'vue'
import api from '@/utils/api'
import { FileIcon, SearchIcon } from 'lucide-vue-next'
import { useAlertStore } from '@/stores/alertStore'
const alertStore = useAlertStore()
function formatTimestamp(timestamp: string): string {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const isDarkMode = inject('isDarkMode') const isDarkMode = inject('isDarkMode')
const tableData: any = ref([])
// 新增的响应式变量 // 修改文件表头
const fileSearchQuery = ref('') const fileTableHeaders = ['取件码', '名称', '大小', '描述', '过期时间', '操作']
const fileTypeFilter = ref('')
const showDropZone = ref(false)
const handleFileUpload = (event: Event) => {
const files = (event.target as HTMLInputElement).files
if (files && files.length > 0) {
console.log('Uploading files:', files)
}
}
interface File {
id: number
name: string
size: string
type: string
lastModified: string
}
const fileTableHeaders = ['名称', '大小', '类型', '最后修改', '操作']
const files: File[] = [
{ id: 1, name: 'document.pdf', size: '2.5 MB', type: 'PDF', lastModified: '2024-01-15' },
{ id: 2, name: 'image.jpg', size: '1.8 MB', type: 'Image', lastModified: '2024-01-14' },
{
id: 3,
name: 'spreadsheet.xlsx',
size: '3.2 MB',
type: 'Spreadsheet',
lastModified: '2024-01-13'
},
{
id: 4,
name: 'presentation.pptx',
size: '5.1 MB',
type: 'Presentation',
lastModified: '2024-01-12'
},
{ id: 5, name: 'video.mp4', size: '15.7 MB', type: 'Video', lastModified: '2024-01-11' }
]
const handleFileDrop = (event: any) => {
const files = (event.dataTransfer as DataTransfer).files
if (files && files.length > 0) {
console.log('Dropped files:', files)
}
}
// 分页参数 // 分页参数
const params = ref({ const params = ref({
page: 1, page: 1,
size: 10, size: 10,
total: 0 total: 0,
keyword: ''
}) })
// 下载文件处理
const downloadFile = async (id: number) => {
try {
const response = await api({
url: '/admin/file/download',
method: 'get',
params: { id },
responseType: 'blob'
})
const contentDisposition = response.headers['content-disposition']
let filename = 'file'
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (filenameMatch != null && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '')
}
// @ts-ignore
if (window.showSaveFilePicker) {
await saveFileByWebApi(response.data, filename)
} else {
await saveFileByElementA(response.data, filename)
}
} catch (error) {
console.error('下载失败:', error)
}
}
// 删除文件处理
const deleteFile = async (id: number) => {
try {
await api({
url: '/admin/file/delete',
method: 'delete',
data: { id }
})
await loadFiles()
} catch (error) {
console.error('删除失败:', error)
}
}
// 文件保存辅助函数
async function saveFileByElementA(fileBlob: Blob, filename: string) {
const downloadUrl = window.URL.createObjectURL(fileBlob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = filename
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(downloadUrl)
document.body.removeChild(link)
}
async function saveFileByWebApi(fileBlob: Blob, filename: string) {
// @ts-ignore
const newHandle = await window.showSaveFilePicker({
suggestedName: filename
})
const writableStream = await newHandle.createWritable()
await writableStream.write(fileBlob)
await writableStream.close()
}
// 加载文件列表
const loadFiles = async () => {
try {
const res: any = await api({
url: '/admin/file/list',
method: 'get',
params: params.value
})
tableData.value = res.detail.data
params.value.total = res.detail.total
alertStore.showAlert('加载成功', 'success')
} catch (error) {
console.error('加载文件列表失败:', error)
}
}
// 页码改变处理函数
const handlePageChange = async (page: any) => {
if (page < 1 || page > totalPages.value) return
params.value.page = page
await loadFiles()
}
// 初始加载
loadFiles()
// 计算总页数 // 计算总页数
const totalPages = computed(() => Math.ceil(params.value.total / params.value.size)) const totalPages = computed(() => Math.ceil(params.value.total / params.value.size))
const downloadFile = (file: File) => {
console.log('Downloading file:', file.name)
}
const deleteFile = (file: File) => {
console.log('Deleting file:', file.name)
}
// 计算要显示的页码 // 计算要显示的页码
const displayedPages = computed(() => { const displayedPages = computed(() => {
const current = params.value.page const current = params.value.page
@@ -349,21 +377,9 @@ const displayedPages = computed(() => {
return pages return pages
}) })
// 页码改变处理函数 // 添加搜索处理函数
const handlePageChange = async (page: any) => { const handleSearch = async () => {
if (page < 1 || page > totalPages.value) return params.value.page = 1 // 重置页码到第一页
params.value.page = page await loadFiles()
await loadFiles() // 重新加载文件列表
}
// 加载文件列表
const loadFiles = async () => {
try {
params.value.total = 85
// 更新文件列表数据...
} catch (error) {
console.error('加载文件列表失败:', error)
// 处理错误...
}
} }
</script> </script>
+4 -2
View File
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject, ref } from 'vue' import { inject, ref } from 'vue'
import api from '@/utils/api' import api from '@/utils/api'
import { useAlertStore } from '@/stores/alertStore'
const isDarkMode = inject('isDarkMode') const isDarkMode = inject('isDarkMode')
interface ConfigState { interface ConfigState {
@@ -124,6 +125,7 @@ const refreshData = () => {
} }
}) })
} }
const alertStore = useAlertStore()
// 转换文件大小为字节 // 转换文件大小为字节
const convertToBytes = (size: number, unit: string): number => { const convertToBytes = (size: number, unit: string): number => {
const units = { const units = {
@@ -151,9 +153,9 @@ const submitSave = () => {
data: formData data: formData
}).then((res: any) => { }).then((res: any) => {
if (res.code == 200) { if (res.code == 200) {
alert('保存成功') alertStore.showAlert('保存成功', 'success')
} else { } else {
alert(res.message) alertStore.showAlert(res.message, 'error')
} }
}) })
} }