322 lines
9.5 KiB
Vue
322 lines
9.5 KiB
Vue
<template>
|
|
<div class="art-full-height">
|
|
<div class="box-border flex gap-4 h-full max-md:block max-md:gap-0 max-md:h-auto">
|
|
<div class="flex-shrink-0 w-64 h-full max-md:w-full max-md:h-auto max-md:mb-5">
|
|
<ElCard class="tree-card art-card-xs flex flex-col h-full mt-0" shadow="never">
|
|
<template #header>
|
|
<div class="flex justify-between items-center">
|
|
<b>附件分类</b>
|
|
<SaButton
|
|
v-permission="'core:attachment:edit'"
|
|
type="primary"
|
|
@click="categoryShowDialog('add')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<ElScrollbar>
|
|
<ElTree
|
|
:data="treeData"
|
|
:props="{ children: 'children', label: 'label' }"
|
|
node-key="id"
|
|
default-expand-all
|
|
highlight-current
|
|
:expand-on-click-node="false"
|
|
@node-click="handleNodeClick"
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div class="flex items-center justify-between w-full" v-if="data.id > 1">
|
|
<span>{{ node.label }}</span>
|
|
<div class="tree-node-actions">
|
|
<SaButton
|
|
v-permission="'core:attachment:edit'"
|
|
type="secondary"
|
|
@click="categoryShowDialog('edit', data)"
|
|
/>
|
|
<SaButton
|
|
v-permission="'core:attachment:edit'"
|
|
type="error"
|
|
@click="categoryDeleteRow(data, categoryApi.delete, getCategoryList)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</ElTree>
|
|
</ElScrollbar>
|
|
</ElCard>
|
|
</div>
|
|
|
|
<div class="flex flex-col flex-grow min-w-0">
|
|
<ElCard class="art-table-card !mt-0" shadow="never">
|
|
<!-- 表格头部 -->
|
|
<div class="flex justify-between items-center mb-4">
|
|
<ElSpace wrap>
|
|
<ElUpload
|
|
v-permission="'core:system:uploadImage'"
|
|
class="upload-btn"
|
|
:show-file-list="false"
|
|
:http-request="handleUpload"
|
|
:before-upload="beforeUpload"
|
|
accept="image/*"
|
|
>
|
|
<ElButton :icon="UploadFilled">上传图片</ElButton>
|
|
</ElUpload>
|
|
<ElButton
|
|
v-permission="'core:attachment:edit'"
|
|
:disabled="selectedRows.length === 0"
|
|
@click="deleteSelectedRows(api.delete, refreshData)"
|
|
v-ripple
|
|
>
|
|
<template #icon>
|
|
<ArtSvgIcon icon="ri:delete-bin-5-line" />
|
|
</template>
|
|
删除
|
|
</ElButton>
|
|
<ElButton
|
|
v-permission="'core:attachment:edit'"
|
|
:disabled="selectedRows.length === 0"
|
|
@click="moveDialogVisible = true"
|
|
v-ripple
|
|
>
|
|
<template #icon>
|
|
<ArtSvgIcon icon="ri:swap-box-line" />
|
|
</template>
|
|
移动
|
|
</ElButton>
|
|
</ElSpace>
|
|
<ElSpace wrap>
|
|
<SaSelect
|
|
v-model="searchForm.storage_mode"
|
|
placeholder="请选择存储模式"
|
|
dict="upload_mode"
|
|
@change="handleSearch"
|
|
clearable
|
|
style="width: 160px"
|
|
/>
|
|
<ElInput
|
|
v-model="searchForm.origin_name"
|
|
placeholder="请输入文件名称"
|
|
:suffix-icon="Search"
|
|
@keyup.enter="handleSearch"
|
|
@clear="handleSearch"
|
|
clearable
|
|
style="width: 240px"
|
|
/>
|
|
</ElSpace>
|
|
</div>
|
|
|
|
<!-- 表格 -->
|
|
<ArtTable
|
|
:loading="loading"
|
|
:data="data"
|
|
:columns="columns"
|
|
:pagination="pagination"
|
|
@sort-change="handleSortChange"
|
|
@selection-change="handleSelectionChange"
|
|
@pagination:size-change="handleSizeChange"
|
|
@pagination:current-change="handleCurrentChange"
|
|
>
|
|
<!-- 操作列 -->
|
|
<template #operation="{ row }">
|
|
<div class="flex gap-2">
|
|
<SaButton
|
|
v-permission="'core:attachment:edit'"
|
|
type="secondary"
|
|
@click="showDialog('edit', row)"
|
|
/>
|
|
<SaButton
|
|
v-permission="'core:attachment:edit'"
|
|
type="error"
|
|
@click="deleteRow(row, api.delete, refreshData)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</ArtTable>
|
|
</ElCard>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分类弹窗 -->
|
|
<CategoryDialog
|
|
v-model="categoryDialogVisible"
|
|
:dialog-type="categoryDialogType"
|
|
:data="categoryDialogData"
|
|
@success="getCategoryList"
|
|
/>
|
|
|
|
<!-- 移动弹窗 -->
|
|
<MoveDialog v-model="moveDialogVisible" :selected-rows="selectedRows" @success="refreshData" />
|
|
|
|
<!-- 表单弹窗 -->
|
|
<EditDialog
|
|
v-model="dialogVisible"
|
|
:dialog-type="dialogType"
|
|
:data="dialogData"
|
|
@success="refreshData"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import api from '@/api/safeguard/attachment'
|
|
import categoryApi from '@/api/safeguard/category'
|
|
import { uploadImage } from '@/api/auth'
|
|
import { useTable } from '@/hooks/core/useTable'
|
|
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
|
import { Search, UploadFilled } from '@element-plus/icons-vue'
|
|
import type { UploadRequestOptions, UploadProps } from 'element-plus'
|
|
import EditDialog from './modules/edit-dialog.vue'
|
|
import CategoryDialog from './modules/category-dialog.vue'
|
|
import MoveDialog from './modules/move-dialog.vue'
|
|
|
|
/** 附件分类数据 */
|
|
const treeData = ref([])
|
|
|
|
/** 获取附件分类数据 */
|
|
const getCategoryList = () => {
|
|
categoryApi.list({ tree: true }).then((data: any) => {
|
|
treeData.value = data
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 切换附件分类
|
|
* @param data
|
|
*/
|
|
const handleNodeClick = (data: any) => {
|
|
if (data.id === 1) {
|
|
searchParams.category_id = undefined
|
|
} else {
|
|
searchParams.category_id = data.id
|
|
}
|
|
getData()
|
|
}
|
|
|
|
/** 附件分类弹窗相关 */
|
|
const {
|
|
dialogType: categoryDialogType,
|
|
dialogVisible: categoryDialogVisible,
|
|
dialogData: categoryDialogData,
|
|
showDialog: categoryShowDialog,
|
|
deleteRow: categoryDeleteRow
|
|
} = useSaiAdmin()
|
|
|
|
/** 移动弹窗相关 */
|
|
const moveDialogVisible = ref(false)
|
|
|
|
/** 附件弹窗相关 */
|
|
const {
|
|
dialogType,
|
|
dialogVisible,
|
|
dialogData,
|
|
showDialog,
|
|
selectedRows,
|
|
handleSelectionChange,
|
|
deleteRow,
|
|
deleteSelectedRows
|
|
} = useSaiAdmin()
|
|
|
|
/** 附件搜索表单 */
|
|
const searchForm = ref({
|
|
origin_name: undefined,
|
|
storage_mode: undefined,
|
|
category_id: undefined,
|
|
orderField: 'create_time',
|
|
orderType: 'desc'
|
|
})
|
|
|
|
/** 附件表格相关 */
|
|
const {
|
|
columns,
|
|
data,
|
|
loading,
|
|
pagination,
|
|
getData,
|
|
searchParams,
|
|
handleSortChange,
|
|
handleSizeChange,
|
|
handleCurrentChange,
|
|
refreshData
|
|
} = useTable({
|
|
core: {
|
|
apiFn: api.list,
|
|
apiParams: {
|
|
...searchForm.value
|
|
},
|
|
columnsFactory: () => [
|
|
{ type: 'selection' },
|
|
{ prop: 'url', label: 'page.table.preview', saiType: 'image', width: 80 },
|
|
{ prop: 'origin_name', label: 'page.table.fileName', minWidth: 160, showOverflowTooltip: true },
|
|
{
|
|
prop: 'storage_mode',
|
|
label: 'page.table.storageMode',
|
|
width: 100,
|
|
saiType: 'dict',
|
|
saiDict: 'upload_mode'
|
|
},
|
|
{ prop: 'mime_type', label: 'page.table.fileType', width: 160, showOverflowTooltip: true },
|
|
{ prop: 'size_info', label: 'page.table.fileSize', width: 100 },
|
|
{ prop: 'create_time', label: 'page.table.uploadTime', width: 180, sortable: true },
|
|
{ prop: 'operation', label: 'table.actions.operation', width: 100, fixed: 'right', useSlot: true }
|
|
]
|
|
}
|
|
})
|
|
|
|
/** 附件搜索 */
|
|
const handleSearch = () => {
|
|
Object.assign(searchParams, searchForm.value)
|
|
getData()
|
|
}
|
|
|
|
/** 附件上传前验证 */
|
|
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
|
const isImage = file.type.startsWith('image/')
|
|
if (!isImage) {
|
|
ElMessage.error('只能上传图片文件!')
|
|
return false
|
|
}
|
|
const isLt5M = file.size / 1024 / 1024 < 5
|
|
if (!isLt5M) {
|
|
ElMessage.error('图片大小不能超过 5MB!')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/** 附件处理上传 */
|
|
const handleUpload = async (options: UploadRequestOptions) => {
|
|
const { file } = options
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
await uploadImage(formData)
|
|
ElMessage.success('上传成功')
|
|
refreshData()
|
|
} catch (error: any) {
|
|
console.error('上传失败:', error)
|
|
ElMessage.error(error.message || '上传失败')
|
|
}
|
|
}
|
|
|
|
/** 初始化附件分类数据 */
|
|
onMounted(() => {
|
|
getCategoryList()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.tree-node-actions {
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.el-tree-node__content:hover .tree-node-actions {
|
|
opacity: 1;
|
|
}
|
|
|
|
:deep(.el-tree-node__content) {
|
|
height: 32px;
|
|
}
|
|
</style>
|