Files
dafuweng-saiadmin6.x/saiadmin-artd/src/views/safeguard/attachment/index.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>