|
|
@@ -1,471 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="file-picker-main">
|
|
|
- <div class="file-picker-body">
|
|
|
- <div class="file-picker-toolbar">
|
|
|
- <ElUpload
|
|
|
- action=""
|
|
|
- :accept="accept"
|
|
|
- :showFileList="false"
|
|
|
- :beforeUpload="handleUpload"
|
|
|
- >
|
|
|
- <ElButton type="primary" class="ele-btn-icon" :icon="UploadOutlined">
|
|
|
- 上传
|
|
|
- </ElButton>
|
|
|
- </ElUpload>
|
|
|
- <div class="file-picker-search">
|
|
|
- <ElInput
|
|
|
- :clearable="true"
|
|
|
- v-model="searchKeyword"
|
|
|
- placeholder="请输入文件名"
|
|
|
- @clear="handleSearch"
|
|
|
- @change="handleSearch"
|
|
|
- />
|
|
|
- <ElButton type="primary" @click="handleSearch">搜索</ElButton>
|
|
|
- </div>
|
|
|
- <EleSegmented v-model="isGridMode" :items="modeSegmentedItems" />
|
|
|
- </div>
|
|
|
- <template v-if="fileData.length">
|
|
|
- <div class="file-picker-file-list" @scroll="handleFileListScroll">
|
|
|
- <EleFileList
|
|
|
- :boxChoose="true"
|
|
|
- :icons="localIcons"
|
|
|
- :smallIcons="localSmallIcons"
|
|
|
- :contextMenuProps="contextMenuProps"
|
|
|
- v-bind="fileListProps || {}"
|
|
|
- :data="fileData"
|
|
|
- :grid="isGridMode === 1"
|
|
|
- :selectionType="limit === 1 ? 'radio' : 'checkbox'"
|
|
|
- :selections="fileSelections"
|
|
|
- v-model:current="fileCurrent"
|
|
|
- :contextMenus="fileContextMenus"
|
|
|
- :class="[{ 'is-ping-top': isPingTop }]"
|
|
|
- @itemClick="handleFileItemClick"
|
|
|
- @itemContextMenu="handleFileCtxMenuClick"
|
|
|
- @update:selections="updateSelections"
|
|
|
- @itemContextOpen="handleFileCtxMenuOpen"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <ElePagination
|
|
|
- size="small"
|
|
|
- :teleported="false"
|
|
|
- :pageSizes="[18, 24, 30, 36, 40, 42]"
|
|
|
- layout="total,prev,pager,next,sizes"
|
|
|
- v-bind="paginationProps || {}"
|
|
|
- :currentPage="currentPage"
|
|
|
- :pageSize="pageSize"
|
|
|
- :total="total"
|
|
|
- @update:currentPage="handleCurrentPageChange"
|
|
|
- @update:pageSize="handlePageSizeChange"
|
|
|
- />
|
|
|
- </template>
|
|
|
- <ElEmpty
|
|
|
- v-else
|
|
|
- :imageSize="80"
|
|
|
- description="无数据"
|
|
|
- v-bind="emptyProps || {}"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
- import { ref, reactive, watch, markRaw } from 'vue';
|
|
|
- import {
|
|
|
- localIcons,
|
|
|
- localSmallIcons
|
|
|
- } from 'ele-admin-plus/es/ele-file-list/icons';
|
|
|
- import {
|
|
|
- UploadOutlined,
|
|
|
- MenuOutlined,
|
|
|
- AppstoreOutlined,
|
|
|
- EditOutlined,
|
|
|
- DeleteOutlined
|
|
|
- } from '@/components/icons';
|
|
|
- import { uploadFile, fetchSourcePageList } from '@/api/system/file';
|
|
|
- import request from '@/utils/request';
|
|
|
-
|
|
|
- const props = defineProps({
|
|
|
- /** 最大选择数量 */
|
|
|
- limit: Number,
|
|
|
- /** 文件大小限制, 单位MB */
|
|
|
- fileLimit: Number,
|
|
|
- /** 接受上传的文件类型 */
|
|
|
- accept: String,
|
|
|
- /** 接口查询参数 */
|
|
|
- params: Object,
|
|
|
- /** 文件列表自定义属性 */
|
|
|
- fileListProps: Object,
|
|
|
- /** 空组件属性 */
|
|
|
- emptyProps: Object,
|
|
|
- /** 分页组件属性 */
|
|
|
- paginationProps: Object,
|
|
|
- /** 统一设置层级 */
|
|
|
- baseIndex: Number,
|
|
|
- /** 消息提示组件 */
|
|
|
- messageIns: [Object, Function],
|
|
|
- /** 文件分类 */
|
|
|
- sourceCate: String,
|
|
|
- /** 文件类型 */
|
|
|
- sourceType: String
|
|
|
- });
|
|
|
-
|
|
|
- const emit = defineEmits([
|
|
|
- 'queryStart',
|
|
|
- 'queryDone',
|
|
|
- 'renameFile',
|
|
|
- 'moveFile',
|
|
|
- 'removeFile',
|
|
|
- 'fileItemContextOpen'
|
|
|
- ]);
|
|
|
-
|
|
|
- /** 当前分组id */
|
|
|
- const fileParentId = ref();
|
|
|
-
|
|
|
- /** 文件数据 */
|
|
|
- const fileData = ref([]);
|
|
|
-
|
|
|
- /** 选中的文件数据 */
|
|
|
- const fileSelections = ref([]);
|
|
|
-
|
|
|
- /** 单选选中的文件数据 */
|
|
|
- const fileCurrent = ref();
|
|
|
-
|
|
|
- /** 是否网格模式 */
|
|
|
- const isGridMode = ref(1);
|
|
|
-
|
|
|
- /** 搜索关键字 */
|
|
|
- const searchKeyword = ref('');
|
|
|
-
|
|
|
- /** 当前页码 */
|
|
|
- const currentPage = ref(1);
|
|
|
-
|
|
|
- /** 每页显示数量 */
|
|
|
- const pageSize = ref(40);
|
|
|
-
|
|
|
- /** 总数量 */
|
|
|
- const total = ref(0);
|
|
|
-
|
|
|
- /** 文件列表是否固定表头 */
|
|
|
- const isPingTop = ref(false);
|
|
|
-
|
|
|
- /** 视图模式分段器数据 */
|
|
|
- const modeSegmentedItems = [
|
|
|
- { icon: MenuOutlined, value: 0, iconStyle: { transform: 'scale(0.9)' } },
|
|
|
- { icon: AppstoreOutlined, value: 1 }
|
|
|
- ];
|
|
|
-
|
|
|
- /** 文件列表右键菜单属性 */
|
|
|
- const contextMenuProps = reactive({
|
|
|
- menuStyle: { minWidth: '120px' },
|
|
|
- iconProps: { size: 15 },
|
|
|
- popperOptions: { strategy: 'fixed' },
|
|
|
- zIndex: props.baseIndex
|
|
|
- });
|
|
|
-
|
|
|
- /** 校验最大选择数量 */
|
|
|
- const checkLimit = (selections, isAdd) => {
|
|
|
- if (
|
|
|
- props.limit &&
|
|
|
- props.limit > 1 &&
|
|
|
- (isAdd
|
|
|
- ? selections.length >= props.limit
|
|
|
- : selections.length > props.limit)
|
|
|
- ) {
|
|
|
- props.messageIns?.error?.(`最多只能选择 ${props.limit} 个`);
|
|
|
- return false;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 更新选中数据 */
|
|
|
- const updateSelections = (selections) => {
|
|
|
- if (checkLimit(selections) !== false) {
|
|
|
- fileSelections.value = selections;
|
|
|
- return;
|
|
|
- }
|
|
|
- if (props.limit && props.limit > 1) {
|
|
|
- fileSelections.value = selections.slice(0, props.limit);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 清空选中 */
|
|
|
- const clearSelections = () => {
|
|
|
- fileSelections.value = [];
|
|
|
- fileCurrent.value = void 0;
|
|
|
- };
|
|
|
-
|
|
|
- /** 搜索 */
|
|
|
- const handleSearch = () => {
|
|
|
- queryData({ pageNum: 1 });
|
|
|
- };
|
|
|
-
|
|
|
- /** 切换当前页码 */
|
|
|
- const handleCurrentPageChange = (page) => {
|
|
|
- if (currentPage.value !== page) {
|
|
|
- currentPage.value = page;
|
|
|
- queryData();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 切换每页显示数量 */
|
|
|
- const handlePageSizeChange = (limit) => {
|
|
|
- if (pageSize.value !== limit) {
|
|
|
- currentPage.value = 1;
|
|
|
- pageSize.value = limit;
|
|
|
- const maxPage = Math.ceil(total.value / limit);
|
|
|
- if (maxPage && currentPage.value > maxPage) {
|
|
|
- currentPage.value = maxPage;
|
|
|
- }
|
|
|
- queryData();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件列表点击事件 */
|
|
|
- const handleFileItemClick = (item) => {
|
|
|
- if (props.limit === 1) {
|
|
|
- fileCurrent.value = item;
|
|
|
- return;
|
|
|
- }
|
|
|
- const index = fileSelections.value.findIndex((d) => d.key === item.key);
|
|
|
- if (index !== -1) {
|
|
|
- fileSelections.value.splice(index, 1);
|
|
|
- return;
|
|
|
- }
|
|
|
- if (checkLimit(fileSelections.value, true) !== false) {
|
|
|
- fileSelections.value.push(item);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件列表右键菜单点击事件 */
|
|
|
- const handleFileCtxMenuClick = (option) => {
|
|
|
- const { key, item } = option;
|
|
|
- const userFileItem = item.userFile;
|
|
|
- if (key === 'preview') {
|
|
|
- if (!item.thumbnail) {
|
|
|
- window.open(item.url);
|
|
|
- return;
|
|
|
- }
|
|
|
- const data = fileData.value.filter((d) => !!d.thumbnail);
|
|
|
- const urls = data.map((d) => d.url);
|
|
|
- const index = data.indexOf(item);
|
|
|
- if (index !== -1) {
|
|
|
- window.open(item.url);
|
|
|
- }
|
|
|
- } else if (key === 'rename') {
|
|
|
- emit('renameFile', userFileItem, true);
|
|
|
- } else if (key === 'move') {
|
|
|
- emit('moveFile', userFileItem, true);
|
|
|
- } else if (key === 'remove') {
|
|
|
- emit('removeFile', userFileItem, true);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件列表右键菜单数据 */
|
|
|
- const fileContextMenus = (item) => {
|
|
|
- const menus = [
|
|
|
- {
|
|
|
- title: '重命名',
|
|
|
- command: 'rename',
|
|
|
- icon: markRaw(EditOutlined)
|
|
|
- },
|
|
|
- {
|
|
|
- title: '删除',
|
|
|
- command: 'remove',
|
|
|
- icon: markRaw(DeleteOutlined),
|
|
|
- divided: true,
|
|
|
- danger: true
|
|
|
- }
|
|
|
- ];
|
|
|
- if (item.thumbnail) {
|
|
|
- menus[0].divided = true;
|
|
|
- menus.unshift({ title: '预览', command: 'preview' });
|
|
|
- } else {
|
|
|
- menus[0].divided = true;
|
|
|
- menus.unshift({ title: '打开', command: 'preview' });
|
|
|
- }
|
|
|
- return menus;
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件列表右键菜单打开事件 */
|
|
|
- const handleFileCtxMenuOpen = () => {
|
|
|
- emit('fileItemContextOpen');
|
|
|
- };
|
|
|
-
|
|
|
- /** 校验选择的文件 */
|
|
|
- const checkFile = (file) => {
|
|
|
- if (!file) {
|
|
|
- return;
|
|
|
- }
|
|
|
- if (props.accept === 'image/*') {
|
|
|
- if (!file.type.startsWith('image')) {
|
|
|
- props.messageIns?.error?.('只能选择图片');
|
|
|
- return;
|
|
|
- }
|
|
|
- } else if (props.accept === '.xls,.xlsx') {
|
|
|
- if (
|
|
|
- ![
|
|
|
- 'application/vnd.ms-excel',
|
|
|
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
- ].includes(file.type)
|
|
|
- ) {
|
|
|
- props.messageIns?.error?.('只能选择 excel 文件');
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- if (props.fileLimit && file.size / 1024 / 1024 > props.fileLimit) {
|
|
|
- props.messageIns?.error?.(`大小不能超过 ${props.fileLimit}MB`);
|
|
|
- return;
|
|
|
- }
|
|
|
- return true;
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件上传事件 */
|
|
|
- const handleUpload = (file) => {
|
|
|
- if (checkFile(file)) {
|
|
|
- const loading = props.messageIns?.loading?.({
|
|
|
- message: '上传中..',
|
|
|
- plain: true,
|
|
|
- mask: true
|
|
|
- });
|
|
|
- uploadFile(file).then((res) => {
|
|
|
- if (res.code === 200) {
|
|
|
- file.url = res.url;
|
|
|
- addFileToDir(file);
|
|
|
- props.messageIns?.success?.('上传成功');
|
|
|
- } else {
|
|
|
- props.messageIns?.error?.(res.msg);
|
|
|
- }
|
|
|
- }).finally(() => {
|
|
|
- loading?.close?.();
|
|
|
- });
|
|
|
- }
|
|
|
- return false;
|
|
|
- };
|
|
|
- //新增文件到指定目录
|
|
|
- const addFileToDir = (file) => {
|
|
|
- let data = {
|
|
|
- sourceName: file.name,
|
|
|
- sourceUrl: file.url,
|
|
|
- sourceType: props.sourceType,
|
|
|
- sourceCate: props.sourceCate
|
|
|
- };
|
|
|
- request
|
|
|
- .post('/baseinfo/source/add', data)
|
|
|
- .then((res) => {
|
|
|
- if (res.data.code === 200) {
|
|
|
- props.messageIns?.success?.('上传成功');
|
|
|
- queryData();
|
|
|
- } else {
|
|
|
- props.messageIns?.error?.(res.data.msg);
|
|
|
- }
|
|
|
- })
|
|
|
- };
|
|
|
-
|
|
|
- /** 文件列表滚动事件 */
|
|
|
- const handleFileListScroll = (e) => {
|
|
|
- const wrapEl = e.currentTarget;
|
|
|
- const scrollTop = wrapEl.scrollTop;
|
|
|
- isPingTop.value = scrollTop > 1;
|
|
|
- };
|
|
|
-
|
|
|
- /** 触发文件数据请求完成 */
|
|
|
- const handleQueryDone = () => {
|
|
|
- emit('queryDone');
|
|
|
- };
|
|
|
-
|
|
|
- /** 判断是否是图片文件 */
|
|
|
- const isImageFile = (item) => {
|
|
|
- // 根据sourceType判断是否为图片,1为图片
|
|
|
- if (item.sourceType === '1') {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return (
|
|
|
- typeof item.contentType === 'string' &&
|
|
|
- item.contentType.startsWith('image/') &&
|
|
|
- item.sourceUrl
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- /** 格式化文件大小 */
|
|
|
- const formatLength = (length) => {
|
|
|
- if (length == null) {
|
|
|
- return '-';
|
|
|
- }
|
|
|
- if (length < 1024) {
|
|
|
- return length + 'B';
|
|
|
- } else if (length < 1024 * 1024) {
|
|
|
- return (length / 1024).toFixed(1) + 'KB';
|
|
|
- } else if (length < 1024 * 1024 * 1024) {
|
|
|
- return (length / 1024 / 1024).toFixed(1) + 'M';
|
|
|
- } else {
|
|
|
- return (length / 1024 / 1024 / 1024).toFixed(1) + 'G';
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- /** 查询文件数据 */
|
|
|
- const queryData = (params) => {
|
|
|
- emit('queryStart');
|
|
|
-
|
|
|
- // 构建请求参数
|
|
|
- const requestParams = {
|
|
|
- sourceNameLike: searchKeyword.value,
|
|
|
- sourceType: props.sourceType,
|
|
|
- sourceCate: props.sourceCate,
|
|
|
- pageNum: currentPage.value,
|
|
|
- pageSize: pageSize.value,
|
|
|
- };
|
|
|
-
|
|
|
- // 使用新的API
|
|
|
- fetchSourcePageList(requestParams)
|
|
|
- .then((result) => {
|
|
|
- console.log('result:', result);
|
|
|
- if (!result?.rows?.length && result?.total) {
|
|
|
- const maxPage = Math.ceil(result.total / pageSize.value);
|
|
|
- if (maxPage && currentPage.value > maxPage) {
|
|
|
- queryData({ pageNum: maxPage });
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- total.value = result?.total || 0;
|
|
|
- fileData.value =
|
|
|
- result?.rows?.map?.((d) => {
|
|
|
- return {
|
|
|
- key: d.id,
|
|
|
- name: d.sourceName,
|
|
|
- url: d.sourceUrl,
|
|
|
- thumbnail: isImageFile(d) ? d.sourceUrl : void 0,
|
|
|
- length: formatLength(d.length),
|
|
|
- updateTime: d.createTime,
|
|
|
- isDirectory: false,
|
|
|
- userFile: {
|
|
|
- ...d,
|
|
|
- name: d.sourceName,
|
|
|
- url: d.sourceUrl
|
|
|
- }
|
|
|
- };
|
|
|
- }) || [];
|
|
|
- console.log('fileData:', fileData.value);
|
|
|
- handleQueryDone();
|
|
|
- })
|
|
|
- .catch((e) => {
|
|
|
- fileData.value = [];
|
|
|
- props.messageIns?.error?.(e.message);
|
|
|
- handleQueryDone();
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- watch(
|
|
|
- () => props.limit,
|
|
|
- () => {
|
|
|
- clearSelections();
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- watch(
|
|
|
- () => props.baseIndex,
|
|
|
- (baseIndex) => {
|
|
|
- contextMenuProps.zIndex = baseIndex;
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- defineExpose({ queryData, clearSelections });
|
|
|
-</script>
|