This repository has been archived on 2024-04-28. You can view files and clone it, but cannot push or open issues or pull requests.
Files
continew-admin-ui-arco/src/views/system/file/main/FileMain/index.vue
Charles7c b56f029e68 refactor: 引入 unplugin-auto-import,减少重复性 Vue 函数引入
避免在每个 Vue 组件中都重复性的去声明 ref 等函数
2024-01-07 23:54:43 +08:00

805 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="file-main">
<a-row justify="space-between" class="row-operate">
<!-- 左侧区域 -->
<a-space wrap>
<a-form ref="queryRef" :model="queryParams" layout="inline">
<a-form-item hide-label>
<a-upload
v-permission="['system:file:upload']"
:show-file-list="false"
:custom-request="handleUpload"
>
<template #upload-button>
<a-button type="primary" shape="round">
<template #icon><icon-upload /></template>
<template #default>上传</template>
</a-button>
</template>
</a-upload>
</a-form-item>
<a-form-item field="name" hide-label>
<a-input
v-model="queryParams.name"
placeholder="输入文件名称搜索"
allow-clear
@press-enter="handleQuery"
/>
</a-form-item>
<a-form-item hide-label>
<a-space>
<a-button type="primary" @click="handleQuery">
<template #icon><icon-search /></template>查询
</a-button>
<a-button @click="resetQuery">
<template #icon><icon-refresh /></template>重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</a-space>
<!-- 右侧区域 -->
<a-space wrap>
<a-button
v-if="isBatchMode"
:disabled="!fileStore.selectedFileIds.length"
type="primary"
status="danger"
@click="handleMulDelete"
><template #icon><icon-delete /></template
></a-button>
<a-button
v-permission="['system:file:delete']"
type="primary"
@click="isBatchMode = !isBatchMode"
>
<template #icon><icon-select-all /></template>
<template #default>{{
isBatchMode ? '取消批量' : '批量操作'
}}</template>
</a-button>
<a-button-group>
<a-tooltip content="视图" position="bottom">
<a-button @click="fileStore.changeViewMode">
<template #icon>
<icon-apps v-if="fileStore.viewMode === 'grid'" />
<icon-list v-else />
</template>
</a-button>
</a-tooltip>
<a-tooltip content="配置存储库" position="bottom">
<a-button
v-permission="['system:storage:list']"
@click="handleConfig"
>
<template #icon>
<icon-settings />
</template>
</a-button>
</a-tooltip>
</a-button-group>
</a-space>
</a-row>
<!-- 文件列表-宫格模式 -->
<a-spin class="file-wrap" :loading="loading">
<FileGrid
v-show="fileList.length && fileStore.viewMode == 'grid'"
:data="fileList"
:is-batch-mode="isBatchMode"
:selected-file-ids="fileStore.selectedFileIds"
@click="handleClickFile"
@check="handleCheckFile"
@right-menu-click="handleRightMenuClick"
></FileGrid>
<!-- 文件列表-列表模式 -->
<FileList
v-show="fileList.length && fileStore.viewMode == 'list'"
:data="fileList"
:is-batch-mode="isBatchMode"
@click="handleClickFile"
@right-menu-click="handleRightMenuClick"
></FileList>
<a-empty v-show="!fileList.length"></a-empty>
</a-spin>
<!-- 配置存储库 -->
<a-drawer
title="配置存储库"
:visible="storageVisible"
:width="1070"
:mask-closable="false"
:esc-to-close="false"
unmount-on-close
render-to-body
:footer="false"
@cancel="handleCancelConfig"
>
<!-- 操作栏 -->
<div class="header-operation" style="margin-bottom: 16px">
<a-row>
<a-col :span="12">
<a-space>
<a-button
v-permission="['system:storage:add']"
type="primary"
@click="toAddStorage"
>
<template #icon><icon-plus /></template>新增
</a-button>
<a-button
v-permission="['system:storage:export']"
:loading="exportStorageLoading"
type="primary"
status="warning"
@click="handleStorageExport"
>
<template #icon><icon-download /></template>导出
</a-button>
</a-space>
</a-col>
</a-row>
</div>
<a-table
row-key="id"
:data="storageList"
:loading="storageLoading"
:bordered="false"
:pagination="{
showTotal: true,
showPageSize: true,
total: totalStorage,
current: storageQueryParams.page,
}"
size="large"
column-resizable
stripe
@page-change="handleStoragePageChange"
@page-size-change="handleStoragePageSizeChange"
>
<template #columns>
<a-table-column title="名称" :width="135">
<template #cell="{ record }">
{{ record.name }}
<a-tag v-if="record.isDefault" color="arcoblue">默认</a-tag>
</template>
</a-table-column>
<a-table-column title="编码" data-index="code" />
<a-table-column title="类型" align="center">
<template #cell="{ record }">
<dict-tag :value="record.type" :dict="storage_type_enum" />
</template>
</a-table-column>
<a-table-column
title="访问密钥"
data-index="accessKey"
ellipsis
tooltip
/>
<a-table-column
title="终端节点"
data-index="endpoint"
ellipsis
tooltip
/>
<a-table-column
title="桶名称"
data-index="bucketName"
ellipsis
tooltip
/>
<a-table-column title="域名" data-index="domain" ellipsis tooltip />
<a-table-column title="状态" align="center">
<template #cell="{ record }">
<a-switch
v-model="record.status"
:checked-value="1"
:unchecked-value="2"
:disabled="!checkPermission(['system:storage:update'])"
@change="handleStorageChangeStatus(record)"
/>
</template>
</a-table-column>
<a-table-column
title="描述"
data-index="description"
ellipsis
tooltip
/>
<a-table-column
v-if="
checkPermission([
'system:storage:update',
'system:storage:delete',
])
"
title="操作"
align="center"
fixed="right"
:width="90"
>
<template #cell="{ record }">
<a-button
v-permission="['system:storage:update']"
type="text"
size="small"
title="修改"
@click="toUpdateStorage(record.id)"
>
<template #icon><icon-edit /></template>
</a-button>
<a-popconfirm
content="是否确定删除该数据?"
type="warning"
@ok="handleDeleteStorage([record.id])"
>
<a-button
v-permission="['system:storage:delete']"
type="text"
size="small"
:title="record.isDefault ? '默认存储库不能删除' : '删除'"
:disabled="record.disabled"
>
<template #icon><icon-delete /></template>
</a-button>
</a-popconfirm>
</template>
</a-table-column>
</template>
</a-table>
</a-drawer>
<!-- 表单区域 -->
<a-modal
:title="storageFormTitle"
:visible="storageFormVisible"
:width="580"
:mask-closable="false"
:esc-to-close="false"
unmount-on-close
render-to-body
@ok="handleStorageFormOk"
@cancel="handleStorageFormCancel"
>
<a-form
ref="storageFormRef"
:model="storageForm"
:rules="storageRules"
size="large"
:label-col-style="{ width: '84px' }"
layout="inline"
>
<a-form-item label="名称" field="name">
<a-input
v-model="storageForm.name"
placeholder="请输入名称"
style="width: 162px"
/>
</a-form-item>
<a-form-item label="编码" field="code">
<a-input
v-model="storageForm.code"
placeholder="请输入编码"
style="width: 162px"
/>
</a-form-item>
<a-form-item label="类型" field="type">
<a-select
v-model="storageForm.type"
:options="storage_type_enum"
placeholder="请选择存储类型"
style="width: 416px"
/>
</a-form-item>
<a-form-item
v-if="storageForm.type === 1"
label="访问密钥"
field="accessKey"
>
<a-input
v-model="storageForm.accessKey"
placeholder="请输入访问密钥"
style="width: 416px"
/>
</a-form-item>
<a-form-item
v-if="storageForm.type === 1"
label="私有密钥"
field="secretKey"
>
<a-input
v-model="storageForm.secretKey"
placeholder="请输入私有密钥"
style="width: 416px"
/>
</a-form-item>
<a-form-item
v-if="storageForm.type === 1"
label="终端节点"
field="endpoint"
>
<a-input
v-model="storageForm.endpoint"
placeholder="请输入终端节点"
style="width: 416px"
/>
</a-form-item>
<a-form-item label="桶名称" field="bucketName">
<a-input
v-model="storageForm.bucketName"
placeholder="请输入桶名称"
style="width: 416px"
/>
</a-form-item>
<a-form-item v-if="storageForm.type === 1" label="域名" field="domain">
<a-input
v-model="storageForm.domain"
placeholder="请输入域名"
style="width: 416px"
/>
</a-form-item>
<a-form-item
v-if="storageForm.type === 2"
label="域名"
field="domain"
:rules="[
{
required: true,
message: '请输入域名',
},
]"
>
<a-input
v-model="storageForm.domain"
placeholder="请输入域名"
style="width: 416px"
/>
</a-form-item>
<a-form-item label="排序" field="sort">
<a-input-number
v-model="storageForm.sort"
placeholder="请输入排序"
style="width: 416px"
:min="1"
mode="button"
/>
</a-form-item>
<a-form-item label="描述" field="description">
<a-textarea
v-model="storageForm.description"
:max-length="200"
placeholder="请输入描述"
style="width: 416px"
:auto-size="{
minRows: 3,
}"
show-word-limit
/>
</a-form-item>
<a-form-item label="默认存储" field="isDefault">
<a-switch v-model="storageForm.isDefault" type="round" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { Modal, RequestOption } from '@arco-design/web-vue';
import checkPermission from '@/utils/permission';
import { api as viewerApi } from 'v-viewer';
import { imageTypeList } from '@/constant/file';
import { useFileStore } from '@/store/modules/file';
import type { ListParam, FileItem } from '@/api/system/file';
import { list, del } from '@/api/system/file';
import type { DataRecord as StorageDataRecord } from '@/api/system/storage';
import {
list as listStorage,
get as getStorage,
add as addStorage,
update as updateStorage,
del as delStorage,
} from '@/api/system/storage';
import { upload } from '@/api/common';
import FileGrid from './FileGrid.vue';
import FileList from './FileList.vue';
import {
openFileRenameModal,
openFileDetailModal,
previewFileVideoModal,
previewFileAudioModal,
} from '../../components/index';
import 'viewerjs/dist/viewer.css';
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const fileStore = useFileStore();
const loading = ref(false);
// 文件列表数据
const fileList = ref<FileItem[]>([]);
// 批量操作
const isBatchMode = ref(false);
const storageVisible = ref(false);
const storageLoading = ref(false);
const exportStorageLoading = ref(false);
const storageList = ref<StorageDataRecord[]>([]);
const totalStorage = ref(0);
const storageFormTitle = ref();
const storageFormVisible = ref(false);
const { storage_type_enum } = proxy.useDict('storage_type_enum');
const data = reactive({
// 查询参数
queryParams: {
name: undefined,
type: route.query.type?.toString() || undefined,
sort: ['updateTime,desc'],
},
storageQueryParams: {
page: 1,
size: 10,
sort: ['updateTime,desc'],
},
storageForm: {} as StorageDataRecord,
storageRules: {
name: [{ required: true, message: '请输入名称' }],
code: [{ required: true, message: '请输入编码' }],
type: [{ required: true, message: '请选择类型' }],
accessKey: [{ required: true, message: '请输入访问密钥' }],
secretKey: [{ required: true, message: '请输入私有密钥' }],
endpoint: [{ required: true, message: '请输入终端节点' }],
bucketName: [{ required: true, message: '请输入桶名称' }],
},
});
const { queryParams, storageQueryParams, storageForm, storageRules } =
toRefs(data);
const getList = async (params: ListParam = { ...queryParams.value }) => {
try {
loading.value = true;
isBatchMode.value = false;
params.type = params.type === '0' ? undefined : params.type;
const res = await list(params);
fileList.value = res.data;
} finally {
loading.value = false;
}
};
onMounted(() => {
getList();
});
onBeforeRouteUpdate((to) => {
if (!to.query.type) return;
queryParams.value.type = to.query.type?.toString();
getList();
});
// 点击文件
const handleClickFile = (item: FileItem) => {
if (imageTypeList.includes(item.extension)) {
if (item.url) {
const imgList: string[] = fileList.value
.filter((i) => imageTypeList.includes(i.extension))
.map((a) => a.url || '');
const index = imgList.findIndex((i) => i === item.url);
if (imgList.length) {
viewerApi({
options: {
initialViewIndex: index,
},
images: imgList,
});
}
}
}
if (item.extension === 'mp4') {
previewFileVideoModal(item);
}
if (item.extension === 'mp3') {
previewFileAudioModal(item);
}
};
// 勾选文件
const handleCheckFile = (item: FileItem) => {
fileStore.addSelectedFileItem(item);
};
// 鼠标右键
const handleRightMenuClick = (mode: string, fileInfo: FileItem) => {
if (mode === 'delete') {
Modal.warning({
title: '提示',
content: `是否确定删除文件 [${fileInfo.name}]`,
hideCancel: false,
onOk: () => {
del(fileInfo.id).then((res) => {
proxy.$message.success(res.msg);
getList();
});
},
});
}
if (mode === 'download') {
const link = document.createElement('a');
link.href = fileInfo.url;
link.download = fileInfo.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
if (mode === 'rename') {
openFileRenameModal(fileInfo);
}
if (mode === 'detail') {
openFileDetailModal(fileInfo);
}
};
// 批量删除
const handleMulDelete = () => {
Modal.warning({
title: '提示',
content: `是否确定删除所选的${fileStore.selectedFileIds.length}个文件?`,
hideCancel: false,
onOk: () => {
del(fileStore.selectedFileIds).then((res) => {
proxy.$message.success(res.msg);
getList();
});
},
});
};
/**
* 上传
*
* @param options /
*/
const handleUpload = (options: RequestOption) => {
const controller = new AbortController();
(async function requestWrap() {
const {
onProgress,
onError,
onSuccess,
fileItem,
name = 'file',
} = options;
onProgress(20);
const formData = new FormData();
formData.append(name as string, fileItem.file as Blob);
upload(formData)
.then((res) => {
onSuccess(res);
getList();
proxy.$message.success(res.msg);
})
.catch((error) => {
onError(error);
});
})();
return {
abort() {
controller.abort();
},
};
};
/**
* 查询
*/
const handleQuery = () => {
getList();
};
/**
* 重置
*/
const resetQuery = () => {
proxy.$refs.queryRef.resetFields();
handleQuery();
};
/**
* 查询存储库列表
*
* @param params 参数
*/
const getStorageList = async (
params: ListParam = { ...storageQueryParams.value },
) => {
try {
storageLoading.value = true;
const res = await listStorage(params);
storageList.value = res.data.list;
totalStorage.value = res.data.total;
} finally {
storageLoading.value = false;
}
};
/**
* 重置表单
*/
const resetStorage = () => {
storageForm.value = {
type: 1,
sort: 999,
isDefault: false,
};
proxy.$refs.storageFormRef?.resetFields();
};
/**
* 打开修改对话框
*
* @param id ID
*/
const toUpdateStorage = (id: number) => {
resetStorage();
getStorage(id).then((res) => {
storageForm.value = res.data;
storageFormTitle.value = '修改存储库';
storageFormVisible.value = true;
});
};
/**
* 打开新增对话框
*/
const toAddStorage = () => {
resetStorage();
storageFormTitle.value = '新增存储库';
storageFormVisible.value = true;
};
/**
* 删除
*
* @param ids ID 列表
*/
const handleDeleteStorage = (ids: Array<number>) => {
delStorage(ids).then((res) => {
proxy.$message.success(res.msg);
getStorageList();
});
};
/**
* 配置
*/
const handleConfig = () => {
getStorageList();
storageVisible.value = true;
};
/**
* 取消配置
*/
const handleCancelConfig = () => {
storageVisible.value = false;
storageList.value = [];
};
/**
* 切换页码
*
* @param current 页码
*/
const handleStoragePageChange = (current: number) => {
storageQueryParams.value.page = current;
getStorageList();
};
/**
* 切换每页条数
*
* @param pageSize 每页条数
*/
const handleStoragePageSizeChange = (pageSize: number) => {
storageQueryParams.value.size = pageSize;
getStorageList();
};
/**
* 修改状态
*
* @param record 记录信息
*/
const handleStorageChangeStatus = (record: StorageDataRecord) => {
const { id } = record;
if (id) {
const tip = record.status === 1 ? '启用' : '禁用';
getStorage(id)
.then((res) => {
res.data.status = record.status;
updateStorage(res.data, id)
.then(() => {
proxy.$message.success(`${tip}成功`);
})
.catch(() => {
record.status = record.status === 1 ? 2 : 1;
});
})
.catch(() => {
record.status = record.status === 1 ? 2 : 1;
});
}
};
/**
* 取消
*/
const handleStorageFormCancel = () => {
storageFormVisible.value = false;
proxy.$refs.storageFormRef?.resetFields();
};
/**
* 确定
*/
const handleStorageFormOk = () => {
proxy.$refs.storageFormRef.validate((valid: any) => {
if (!valid) {
if (storageForm.value.id !== undefined) {
updateStorage(storageForm.value, storageForm.value.id).then((res) => {
handleStorageFormCancel();
getStorageList();
proxy.$message.success(res.msg);
});
} else {
addStorage(storageForm.value).then((res) => {
handleStorageFormCancel();
getStorageList();
proxy.$message.success(res.msg);
});
}
}
});
};
/**
* 导出
*/
const handleStorageExport = () => {
if (exportStorageLoading.value) return;
exportStorageLoading.value = true;
proxy
.download('/system/storage/export', { ...storageQueryParams.value }, '存储库数据')
.finally(() => {
exportStorageLoading.value = false;
});
};
</script>
<style lang="less" scoped>
.file-main {
height: 100%;
background: var(--color-bg-1);
border-radius: 2px;
display: flex;
flex-direction: column;
overflow: hidden;
.row-operate {
border-bottom: 1px dashed var(--color-border-3);
margin: 20px 16px 0;
}
.file-wrap {
flex: 1;
padding: 0 16px 16px;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
}
}
:deep(.arco-form-item-layout-inline) {
margin-right: 8px;
}
</style>