解决所有bug问题

This commit is contained in:
RISE
2026-02-09 20:03:33 +08:00
parent 707545f51e
commit 2e2c257743
22 changed files with 3405 additions and 44 deletions

View File

@@ -0,0 +1,144 @@
<template>
<el-dialog
:title="form.id ? '编辑文件夹' : '新建文件夹'"
v-model="visible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="文件夹名称" prop="classification">
<el-input
v-model="form.classification"
placeholder="请输入文件夹名称"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="FileManagerFolderDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { editFile, getDetail } from '/@/api/stuwork/filemanager'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
// 提交表单数据
const form = reactive({
id: '',
classification: '',
parentId: null as string | null
})
// 定义校验规则
const dataRules = {
classification: [
{ required: true, message: '请输入文件夹名称', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classification = ''
form.parentId = row?.parentId ?? null
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classification = row.classification || ''
form.parentId = row.parentId || null
// 如果需要获取详情
if (row.id && !row.classification) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.classification = res.data.classification || ''
form.parentId = res.data.parentId || form.parentId
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单(新增文件夹需要通过新增文件接口,但只传 classification 和 parentId
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
if (operType.value === 'add') {
// 新增文件夹:使用 addObj但只传 classification 和 parentId
const { addObj } = await import('/@/api/stuwork/filemanager')
await addObj({
classification: form.classification,
parentId: form.parentId || '-1',
level: '0' // 文件夹层级为 0
})
useMessage().success('新建成功')
} else {
// 编辑文件夹:使用 editFile 接口
await editFile({
id: form.id,
parentId: form.parentId || '-1',
classification: form.classification
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
if (!err?._messageShown) {
useMessage().error(err?.msg || (operType.value === 'add' ? '新建失败' : '编辑失败'))
}
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<el-dialog
:title="form.id ? '编辑文件' : '上传文件'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="文件名称" prop="fileName">
<el-input
v-model="form.fileName"
placeholder="请输入文件名称"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="文件上传" prop="fileUrl">
<Upload
v-model="form.fileUrl"
:limit="1"
uploadFileUrl="/stuwork/file/upload"
type="default" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="FileManagerFormDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/filemanager'
import Upload from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add') // add 或 edit
// 提交表单数据
const form = reactive({
id: '',
fileName: '',
fileUrl: '',
remarks: '',
parentId: null as string | null,
level: '1'
})
// 定义校验规则
const dataRules = {
fileName: [
{ required: true, message: '请输入文件名称', trigger: 'blur' }
],
fileUrl: [
{ required: true, message: '请上传文件', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.fileName = ''
form.fileUrl = ''
form.remarks = ''
form.parentId = row?.parentId ?? null
form.level = row?.level || '1'
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.fileName = row.fileName || ''
form.fileUrl = row.fileUrl || ''
form.remarks = row.remarks || ''
form.parentId = row.parentId || null
form.level = row.level || '1'
// 如果需要获取详情
if (row.id && !row.fileUrl) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.fileName = res.data.fileName || form.fileName
form.fileUrl = res.data.fileUrl || ''
form.remarks = res.data.remarks || ''
form.parentId = res.data.parentId || form.parentId
form.level = res.data.level || form.level
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
fileName: form.fileName,
fileUrl: form.fileUrl,
remarks: form.remarks,
parentId: form.parentId || '-1',
level: form.level
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
if (!err?._messageShown) {
useMessage().error(err?.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
}
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,345 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
学工文件管理
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="handleAddFolder">
新建文件夹
</el-button>
<el-button
icon="FolderAdd"
type="success"
class="ml10"
@click="handleAddFile">
上传文件
</el-button>
<el-button
icon="Refresh"
class="ml10"
@click="getDataList">
刷新
</el-button>
</div>
</div>
</template>
<!-- 面包屑导航 -->
<div class="breadcrumb-wrapper mb16" v-if="breadcrumbList.length > 0">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<el-button link @click="handleBreadcrumbClick(null)">
<el-icon><Folder /></el-icon>
根目录
</el-button>
</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.id">
<el-button
link
@click="handleBreadcrumbClick(item)"
:disabled="index === breadcrumbList.length - 1">
{{ item.classification || item.fileName }}
</el-button>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="classification" label="文件夹名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><Folder /></el-icon>
<span style="margin-left: 4px">文件夹名称</span>
</template>
<template #default="scope">
<span v-if="scope.row.classification">{{ scope.row.classification }}</span>
<span v-else class="text-gray-400">-</span>
</template>
</el-table-column>
<el-table-column prop="fileName" label="文件名称" min-width="200" show-overflow-tooltip>
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">文件名称</span>
</template>
<template #default="scope">
<span v-if="scope.row.fileName">{{ scope.row.fileName }}</span>
<span v-else class="text-gray-400">-</span>
</template>
</el-table-column>
<el-table-column prop="level" label="层级" width="80" align="center">
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">层级</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" min-width="150" show-overflow-tooltip>
<template #header>
<el-icon><EditPen /></el-icon>
<span style="margin-left: 4px">备注</span>
</template>
<template #default="scope">
<span v-if="scope.row.remarks">{{ scope.row.remarks }}</span>
<span v-else class="text-gray-400">-</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #header>
<el-icon><Setting /></el-icon>
<span style="margin-left: 4px">操作</span>
</template>
<template #default="scope">
<!-- 文件夹进入编辑删除 -->
<template v-if="scope.row.classification">
<el-button
icon="FolderOpened"
link
type="primary"
@click="handleEnterFolder(scope.row)">
进入
</el-button>
<el-button
icon="Edit"
link
type="primary"
@click="handleEditFolder(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
<!-- 文件下载编辑删除 -->
<template v-else>
<el-button
icon="Download"
link
type="primary"
@click="handleDownload(scope.row)">
下载
</el-button>
<el-button
icon="Edit"
link
type="primary"
@click="handleEditFile(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 新增/编辑文件弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 新增/编辑文件夹弹窗 -->
<folder-dialog ref="folderDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="FileManager">
import { reactive, ref, onMounted, computed } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/filemanager";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import FolderDialog from './folder.vue'
import { List, Document, Folder, Grid, EditPen, Clock, Setting, FolderOpened, Download } from '@element-plus/icons-vue'
// 定义变量内容
const formDialogRef = ref()
const folderDialogRef = ref()
const currentParentId = ref<string | null>(null)
const breadcrumbList = ref<any[]>([])
// 配置 useTable接口返回数组非分页结构
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
parentId: null as string | null
},
pageList: async (params: any) => {
const res = await fetchList(params)
// 数据链是 data.data.records需要正确解析
let list: any[] = []
if (res?.data) {
// 如果 res.data 是数组,直接使用
if (Array.isArray(res.data)) {
list = res.data
}
// 如果 res.data.data 存在且是对象,尝试取 records
else if (res.data.data && res.data.data.records && Array.isArray(res.data.data.records)) {
list = res.data.data.records
}
// 如果 res.data.records 存在且是数组,直接使用
else if (res.data.records && Array.isArray(res.data.records)) {
list = res.data.records
}
}
return {
data: list // isPage: false 时,直接返回数组
}
},
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true,
isPage: false // 不分页
})
// table hook
const {
getDataList,
tableStyle
} = useTable(state)
// 进入文件夹
const handleEnterFolder = (row: any) => {
currentParentId.value = row.id
breadcrumbList.value.push(row)
state.queryForm.parentId = row.id
getDataList()
}
// 面包屑点击
const handleBreadcrumbClick = (item: any | null) => {
if (item === null) {
// 返回根目录
currentParentId.value = null
breadcrumbList.value = []
state.queryForm.parentId = null
} else {
// 返回指定目录
const index = breadcrumbList.value.findIndex(b => b.id === item.id)
if (index >= 0) {
breadcrumbList.value = breadcrumbList.value.slice(0, index + 1)
currentParentId.value = item.id
state.queryForm.parentId = item.id
}
}
getDataList()
}
// 新增文件夹
const handleAddFolder = () => {
folderDialogRef.value?.openDialog('add', { parentId: currentParentId.value })
}
// 新增文件
const handleAddFile = () => {
formDialogRef.value?.openDialog('add', { parentId: currentParentId.value })
}
// 编辑文件夹
const handleEditFolder = (row: any) => {
folderDialogRef.value?.openDialog('edit', row)
}
// 编辑文件
const handleEditFile = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 下载文件
const handleDownload = (row: any) => {
if (!row.fileUrl) {
useMessage().warning('文件地址无效')
return
}
// fileUrl 可能是完整 URL 或相对路径
if (row.fileUrl.startsWith('http://') || row.fileUrl.startsWith('https://')) {
window.open(row.fileUrl, '_blank')
} else {
// 相对路径,需要拼接 baseURL
const baseURL = import.meta.env.VITE_API_URL || ''
const url = baseURL + (row.fileUrl.startsWith('/') ? row.fileUrl : '/' + row.fileUrl)
window.open(url, '_blank')
}
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
const name = row.classification || row.fileName || '该项'
await confirm(`确定要删除${name}吗?`)
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err?.msg || '删除失败')
}
}
}
// 初始化
onMounted(() => {
getDataList()
})
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.breadcrumb-wrapper {
padding: 12px 16px;
background: #f5f7fa;
border-radius: 4px;
:deep(.el-breadcrumb) {
.el-breadcrumb__item {
.el-button {
padding: 0;
font-size: 14px;
}
}
}
}
.mb16 {
margin-bottom: 16px;
}
</style>