文件选择器
This commit is contained in:
@@ -413,6 +413,13 @@ class FileList(CoreModel):
|
|||||||
(1, '文件选择器上传'),
|
(1, '文件选择器上传'),
|
||||||
)
|
)
|
||||||
upload_method = models.SmallIntegerField(default=0, blank=True, null=True, choices=UPLOAD_METHOD_CHOIDES, verbose_name='上传方式', help_text='上传方式')
|
upload_method = models.SmallIntegerField(default=0, blank=True, null=True, choices=UPLOAD_METHOD_CHOIDES, verbose_name='上传方式', help_text='上传方式')
|
||||||
|
FILE_TYPE_CHOIDES = (
|
||||||
|
(0, '图片'),
|
||||||
|
(1, '视频'),
|
||||||
|
(2, '音频'),
|
||||||
|
(3, '其他'),
|
||||||
|
)
|
||||||
|
file_type = models.SmallIntegerField(default=3, choices=FILE_TYPE_CHOIDES, blank=True, null=True, verbose_name='文件类型', help_text='文件类型')
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.md5sum: # file is new
|
if not self.md5sum: # file is new
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
validated_data['md5sum'] = md5.hexdigest()
|
validated_data['md5sum'] = md5.hexdigest()
|
||||||
validated_data['engine'] = file_engine
|
validated_data['engine'] = file_engine
|
||||||
validated_data['mime_type'] = file.content_type
|
validated_data['mime_type'] = file.content_type
|
||||||
|
ft = {'image':0,'video':1,'audio':2}.get(file.content_type.split('/')[0], None)
|
||||||
|
validated_data['file_type'] = 3 if ft is None else ft
|
||||||
if file_backup:
|
if file_backup:
|
||||||
validated_data['url'] = file
|
validated_data['url'] = file
|
||||||
if file_engine == 'oss':
|
if file_engine == 'oss':
|
||||||
@@ -65,13 +67,20 @@ class FileSerializer(CustomModelSerializer):
|
|||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
|
||||||
|
class FileAllSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FileList
|
||||||
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
class FileFilter(django_filters.FilterSet):
|
class FileFilter(django_filters.FilterSet):
|
||||||
name = django_filters.CharFilter(field_name="name", lookup_expr="icontains", help_text="文件名")
|
name = django_filters.CharFilter(field_name="name", lookup_expr="icontains", help_text="文件名")
|
||||||
mime_type = django_filters.CharFilter(field_name="mime_type", lookup_expr="icontains", help_text="文件类型")
|
mime_type = django_filters.CharFilter(field_name="mime_type", lookup_expr="icontains", help_text="文件类型")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FileList
|
model = FileList
|
||||||
fields = ['name', 'mime_type', 'upload_method']
|
fields = ['name', 'mime_type', 'upload_method', 'file_type']
|
||||||
|
|
||||||
|
|
||||||
class FileViewSet(CustomModelViewSet):
|
class FileViewSet(CustomModelViewSet):
|
||||||
@@ -86,4 +95,8 @@ class FileViewSet(CustomModelViewSet):
|
|||||||
queryset = FileList.objects.all()
|
queryset = FileList.objects.all()
|
||||||
serializer_class = FileSerializer
|
serializer_class = FileSerializer
|
||||||
filter_class = FileFilter
|
filter_class = FileFilter
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
|
|
||||||
|
@action(methods=['GET'], detail=False)
|
||||||
|
def get_all(self, request):
|
||||||
|
return DetailResponse(data=self.get_serializer(self.get_queryset(), many=True).data)
|
||||||
|
|||||||
67
web/src/components/fileSelector/fileItem.vue
Normal file
67
web/src/components/fileSelector/fileItem.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="itemRef" class="file-item" :title="data.name">
|
||||||
|
<div class="file-name">{{ data.name }}</div>
|
||||||
|
<component :is="FileTypes[data.file_type].tag" v-bind="FileTypes[data.file_type].attr" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineComponent, nextTick } from 'vue';
|
||||||
|
import { ref, defineProps, PropType, watch, onMounted, h } from 'vue';
|
||||||
|
const props = defineProps({
|
||||||
|
fileData: { type: Object as PropType<any>, required: true },
|
||||||
|
});
|
||||||
|
const _OtherFileComponent = defineComponent({
|
||||||
|
template: '<el-icon><Files /></el-icon>'
|
||||||
|
})
|
||||||
|
const FileTypes = [
|
||||||
|
{ tag: 'img', attr: { src: props.fileData.url, draggable: false } },
|
||||||
|
{ tag: 'video', attr: { src: props.fileData.url, controls: false, autoplay: true, muted: true, loop: true } },
|
||||||
|
{ tag: 'audio', attr: { src: props.fileData.url, controls: true, autoplay: false, muted: false, loop: false, volume: 0 } },
|
||||||
|
{ tag: _OtherFileComponent, attr: { style: { fontSize: '2rem' } } },
|
||||||
|
];
|
||||||
|
const itemRef = ref<HTMLDivElement | null>(null);
|
||||||
|
const data = ref<any>(null);
|
||||||
|
watch(props.fileData, (nVal) => data.value = nVal, { immediate: true, deep: true });
|
||||||
|
defineExpose({});
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
itemRef.value?.addEventListener('mouseenter', () => {
|
||||||
|
itemRef.value?.querySelector('.file-name')?.classList.add('show');
|
||||||
|
});
|
||||||
|
itemRef.value?.addEventListener('mouseleave', () => {
|
||||||
|
itemRef.value?.querySelector('.file-name')?.classList.remove('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.file-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item>* {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 0 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
222
web/src/components/fileSelector/index.vue
Normal file
222
web/src/components/fileSelector/index.vue
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<template>
|
||||||
|
<el-select v-model="data" suffix-icon="arrow-down" clearable :multiple="props.multiple" placeholder="请选择文件"
|
||||||
|
@click="selectVisiable = true" @clear="selectedInit" @remove-tag="selectedInit">
|
||||||
|
<el-option v-for="item, index in listAllData" :key="index" :value="item.id" :label="item.name" />
|
||||||
|
</el-select>
|
||||||
|
<el-dialog v-model="selectVisiable" :draggable="false" width="50%" :align-center="false"
|
||||||
|
@open="if (listData.length === 0) listRequest();">
|
||||||
|
<template #header>
|
||||||
|
<span class="el-dialog__title">文件选择</span>
|
||||||
|
<el-divider style="margin: 0;" />
|
||||||
|
</template>
|
||||||
|
<div style="padding: 4px;">
|
||||||
|
<el-tabs v-model="tabsActived" :type="props.tabsType" :stretch="true" @tab-change="handleTabChange">
|
||||||
|
<el-tab-pane v-if="props.tabsShow & SHOW.IMAGE" :name="0" label="图片" />
|
||||||
|
<el-tab-pane v-if="props.tabsShow & SHOW.VIDEO" :name="1" label="视频" />
|
||||||
|
<el-tab-pane v-if="props.tabsShow & SHOW.AUDIO" :name="2" label="音频" />
|
||||||
|
<el-tab-pane v-if="props.tabsShow & SHOW.OTHER" :name="3" label="其他" />
|
||||||
|
</el-tabs>
|
||||||
|
<el-row justify="space-between" class="headerBar">
|
||||||
|
<el-span :span="16">
|
||||||
|
<el-input v-model="filterForm.name" :placeholder="`请输入${TypeLabel[tabsActived]}名`" prefix-icon="search"
|
||||||
|
clearable @change="listRequest" />
|
||||||
|
<div>
|
||||||
|
<el-tag v-if="props.multiple" type="primary" effect="light">
|
||||||
|
一共选中 {{ data?.length || 0 }} 个文件
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</el-span>
|
||||||
|
<el-span :span="8">
|
||||||
|
<el-button type="default" circle icon="refresh" @click="listRequest" />
|
||||||
|
<!-- 这里 show-file-list 和 clearFiles 一起使用确保不会显示上传列表 -->
|
||||||
|
<el-upload ref="uploadRef" :action="getBaseURL() + 'api/system/file/'" :multiple="false"
|
||||||
|
:data="{ upload_method: 1 }" :drag="false" :show-file-list="false" :accept="AcceptList[tabsActived]"
|
||||||
|
:on-success="() => { listRequest(); listRequestAll(); uploadRef.clearFiles(); }">
|
||||||
|
<el-button type="primary" icon="plus">上传{{ TypeLabel[tabsActived] }}</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</el-span>
|
||||||
|
</el-row>
|
||||||
|
<el-empty v-if="!listData.length" description="无内容,请上传"
|
||||||
|
style="width: 100%; height: calc(50vh); margin-top: 24px; padding: 4px;" />
|
||||||
|
<div ref="listContainerRef" class="listContainer" v-else>
|
||||||
|
<div v-for="item in listData" :style="{ width: (props.itemSize || 100) + 'px' }" :key="item.id"
|
||||||
|
@click="onItemClick($event)" :data-id="item.id">
|
||||||
|
<FileItem :fileData="item" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="listPaginator">
|
||||||
|
<el-pagination background size="small" layout="total, sizes, prev, pager, next" :total="pageForm.total"
|
||||||
|
v-model:page-size="pageForm.limit" :page-sizes="[10, 20, 30, 40, 50]" v-model:current-page="pageForm.page"
|
||||||
|
:hide-on-single-page="false" @change="handlePageChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="default" @click="selectVisiable = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="selectVisiable = false">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useUi, UserPageQuery, AddReq, EditReq, DelReq } from '@fast-crud/fast-crud';
|
||||||
|
import { ref, reactive, defineProps, PropType, watch, onMounted, nextTick } from 'vue';
|
||||||
|
import { getBaseURL } from '/@/utils/baseUrl';
|
||||||
|
import { request } from '/@/utils/service';
|
||||||
|
import { SHOW } from './types';
|
||||||
|
import FileItem from './fileItem.vue';
|
||||||
|
|
||||||
|
const TypeLabel = ['图片', '视频', '音频', '文件']
|
||||||
|
const AcceptList = ['image/*', 'video/*', 'audio/*', ''];
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {},
|
||||||
|
tabsType: { type: Object as PropType<'' | 'card' | 'border-card'>, default: '' },
|
||||||
|
// 1000图片 100视频 10音频 1 其他 控制tabs的显示
|
||||||
|
tabsShow: { type: Number, default: SHOW.ALL },
|
||||||
|
itemSize: { type: Number, default: 100 },
|
||||||
|
multiple: { type: Boolean, default: false },
|
||||||
|
} as any);
|
||||||
|
const selectVisiable = ref<boolean>(false);
|
||||||
|
const tabsActived = ref<number>(0);
|
||||||
|
const fileApiPrefix = '/api/system/file/';
|
||||||
|
const fileApi = {
|
||||||
|
GetList: (query: UserPageQuery) => request({ url: fileApiPrefix, method: 'get', params: query }),
|
||||||
|
GetAll: () => request({ url: fileApiPrefix + 'get_all/' }),
|
||||||
|
AddObj: (obj: AddReq) => request({ url: fileApiPrefix, method: 'post', data: obj }),
|
||||||
|
UpdateObj: (obj: EditReq) => request({ url: fileApiPrefix + obj.id + '/', method: 'put', data: obj }),
|
||||||
|
DelObj: (id: DelReq) => request({ url: fileApiPrefix + id + '/', method: 'delete', data: { id } }),
|
||||||
|
};
|
||||||
|
// 过滤表单
|
||||||
|
const filterForm = reactive({ name: '' });
|
||||||
|
// 分页表单
|
||||||
|
const pageForm = reactive({ page: 1, limit: 10, total: 0 });
|
||||||
|
// 展示的数据列表
|
||||||
|
const listData = ref<any[]>([]);
|
||||||
|
const listAllData = ref<any[]>([]);
|
||||||
|
const listRequest = async () => {
|
||||||
|
let res = await fileApi.GetList({ page: pageForm.page, limit: pageForm.limit, file_type: tabsActived.value, ...filterForm });
|
||||||
|
listData.value = res.data;
|
||||||
|
pageForm.total = res.total;
|
||||||
|
pageForm.page = res.page;
|
||||||
|
pageForm.limit = res.limit;
|
||||||
|
selectedInit();
|
||||||
|
};
|
||||||
|
const listRequestAll = async () => { let res = await fileApi.GetAll(); listAllData.value = res.data; };
|
||||||
|
// tab改变时触发
|
||||||
|
const handleTabChange = (name: string) => { pageForm.page = 1; listRequest(); };
|
||||||
|
// 分页器改变时触发
|
||||||
|
const handlePageChange = (currentPage: number, pageSize: number) => { pageForm.page = currentPage; pageForm.limit = pageSize; listRequest(); };
|
||||||
|
// 选择的行为
|
||||||
|
const listContainerRef = ref<any>();
|
||||||
|
const onItemClick = async (e: MouseEvent) => {
|
||||||
|
let target = e.target as HTMLElement;
|
||||||
|
let flat = 0; // -1删除 0不变 1添加
|
||||||
|
while (!target.dataset.id) target = target.parentElement as HTMLElement;
|
||||||
|
let fileId = Number(target.dataset.id);
|
||||||
|
if (props.multiple) {
|
||||||
|
if (target.classList.contains('active')) {
|
||||||
|
target.classList.remove('active');
|
||||||
|
flat = -1;
|
||||||
|
} else {
|
||||||
|
target.classList.add('active');
|
||||||
|
flat = 1;
|
||||||
|
}
|
||||||
|
if (data.value) {
|
||||||
|
if (flat === 1) data.value.push(fileId);
|
||||||
|
else data.value.splice(data.value.indexOf(fileId), 1);
|
||||||
|
}
|
||||||
|
else data.value = [fileId];
|
||||||
|
// 去重排序,<降序,>升序
|
||||||
|
data.value = Array.from(new Set(data.value)).sort();
|
||||||
|
} else {
|
||||||
|
for (let i of listContainerRef.value?.children) {
|
||||||
|
(i as HTMLElement).classList.remove('active');
|
||||||
|
}
|
||||||
|
target.classList.add('active');
|
||||||
|
data.value = fileId;
|
||||||
|
}
|
||||||
|
onDataChange(data.value);
|
||||||
|
};
|
||||||
|
// 每次列表刷新都得更新一下选择状态,因为所有标签页共享列表
|
||||||
|
const selectedInit = async () => {
|
||||||
|
await nextTick();
|
||||||
|
for (let i of (listContainerRef.value?.children || [])) {
|
||||||
|
i.classList.remove('active');
|
||||||
|
let fid = Number((i as HTMLElement).dataset.id);
|
||||||
|
if (props.multiple) {
|
||||||
|
if (data.value?.includes(fid)) i.classList.add('active');
|
||||||
|
} else {
|
||||||
|
if (fid === data.value) i.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const uploadRef = ref<any>();
|
||||||
|
// 清空状态
|
||||||
|
const clear = () => {
|
||||||
|
filterForm.name = '';
|
||||||
|
pageForm.page = 1;
|
||||||
|
pageForm.limit = 10;
|
||||||
|
pageForm.total = 0;
|
||||||
|
listData.value = [];
|
||||||
|
// all数据不能清,因为all只会在挂载的时候赋值一次
|
||||||
|
// listAllData.value = [];
|
||||||
|
data.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// fs-crud部分
|
||||||
|
const data = ref<any>();
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
watch(() => props.modelValue, (val) => { data.value = val; }, { immediate: true });
|
||||||
|
const { ui } = useUi();
|
||||||
|
const formValidator = ui.formItem.injectFormItemContext();
|
||||||
|
const onDataChange = (value: any) => {
|
||||||
|
emit('update:modelValue', value);
|
||||||
|
formValidator.onChange();
|
||||||
|
formValidator.onBlur();
|
||||||
|
};
|
||||||
|
defineExpose({ data, onDataChange, selectVisiable, clear });
|
||||||
|
|
||||||
|
onMounted(() => listRequestAll());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.headerBar>* {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listContainer {
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
grid-gap: 36px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 8px;
|
||||||
|
height: calc(50vh);
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listContainer>* {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
/* border: 1px solid rgba(0, 0, 0, .1); */
|
||||||
|
/* div阴影,2px范围,均匀投影,0偏移 */
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, .2);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
box-shadow: 0 0 8px var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.listPaginator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
justify-items: center;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
web/src/components/fileSelector/types.ts
Normal file
7
web/src/components/fileSelector/types.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const SHOW = {
|
||||||
|
IMAGE: 0b1000, // 图片
|
||||||
|
VIDEO: 0b0100, // 视频
|
||||||
|
AUDIO: 0b0010, // 音频
|
||||||
|
OTHER: 0b0001, // 其他
|
||||||
|
ALL: 0b1111, // 全部
|
||||||
|
};
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { UserPageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
|
import {
|
||||||
|
UserPageQuery,
|
||||||
|
AddReq,
|
||||||
|
DelReq,
|
||||||
|
EditReq,
|
||||||
|
CrudExpose,
|
||||||
|
CrudOptions,
|
||||||
|
CreateCrudOptionsProps,
|
||||||
|
CreateCrudOptionsRet,
|
||||||
|
dict
|
||||||
|
} from '@fast-crud/fast-crud';
|
||||||
|
|
||||||
export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const pageRequest = async (query: UserPageQuery) => {
|
const pageRequest = async (query: UserPageQuery) => {
|
||||||
return await api.GetList(query);
|
return await api.GetList(query);
|
||||||
};
|
};
|
||||||
@@ -20,7 +30,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
actionbar: {
|
actionbar: {
|
||||||
buttons: {
|
buttons: {
|
||||||
add: {
|
add: {
|
||||||
show: false,
|
show: true,
|
||||||
|
click: () => context.openAddHandle?.()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -34,7 +45,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
//固定右侧
|
//固定右侧
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 200,
|
width: 200,
|
||||||
show:false,
|
show: false,
|
||||||
buttons: {
|
buttons: {
|
||||||
view: {
|
view: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -95,12 +106,13 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
type: 'input',
|
type: 'input',
|
||||||
column:{
|
column: {
|
||||||
minWidth: 120,
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
component: {
|
component: {
|
||||||
placeholder: '请输入文件名称',
|
placeholder: '请输入文件名称',
|
||||||
|
clearable: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -110,8 +122,8 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
search: {
|
search: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
column:{
|
column: {
|
||||||
minWidth: 200,
|
minWidth: 360,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
md5sum: {
|
md5sum: {
|
||||||
@@ -119,13 +131,76 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
|
|||||||
search: {
|
search: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
column:{
|
column: {
|
||||||
minWidth: 120,
|
minWidth: 300,
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
disabled: false,
|
disabled: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mime_type: {
|
||||||
|
title: '文件类型',
|
||||||
|
type: 'input',
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
component: {
|
||||||
|
placeholder: '请输入文件名称',
|
||||||
|
clearable: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
minWidth: 160
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file_type: {
|
||||||
|
title: '文件类型',
|
||||||
|
type: 'dict-select',
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: '图片', value: 0, color: 'success' },
|
||||||
|
{ label: '视频', value: 1, color: 'warning' },
|
||||||
|
{ label: '音频', value: 2, color: 'danger' },
|
||||||
|
{ label: '其他', value: 3, color: 'primary' },
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false,
|
||||||
|
component: {
|
||||||
|
placeholder: '请选择文件类型'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
title: '文件大小',
|
||||||
|
column: {
|
||||||
|
minWidth: 120
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload_method: {
|
||||||
|
title: '上传方式',
|
||||||
|
type: 'dict-select',
|
||||||
|
dict: dict({
|
||||||
|
data: [
|
||||||
|
{ label: '默认上传', value: 0, color: 'primary' },
|
||||||
|
{ label: '文件选择器上传', value: 1, color: 'warning' },
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
column: {
|
||||||
|
minWidth: 140
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<fs-page>
|
<fs-page>
|
||||||
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
|
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||||
|
<template #actionbar-left="scope">
|
||||||
|
<el-upload :action="getBaseURL() + 'api/system/file/'" :multiple="false"
|
||||||
|
:on-success="() => crudExpose.doRefresh()" :drag="false" :show-file-list="false">
|
||||||
|
<el-button type="primary" icon="plus">上传</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</template>
|
||||||
|
<template #cell_size="scope">
|
||||||
|
<span>{{ scope.row.size ? getSizeDisplay(scope.row.size) : '0b' }}</span>
|
||||||
|
</template>
|
||||||
|
</fs-crud>
|
||||||
|
<el-dialog v-model="selectorVisiable" @closed="fileSelectorRef.clear();">
|
||||||
|
<el-form-item label="文件">
|
||||||
|
<FileSelector ref="fileSelectorRef" :tabsShow="SHOW.ALL" :itemSize="120" :multiple="true" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-dialog>
|
||||||
</fs-page>
|
</fs-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, nextTick } from 'vue';
|
||||||
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
import { useExpose, useCrud } from '@fast-crud/fast-crud';
|
||||||
import { createCrudOptions } from './crud';
|
import { createCrudOptions } from './crud';
|
||||||
|
import { getBaseURL } from '/@/utils/baseUrl';
|
||||||
|
import FileSelector from '/@/components/fileSelector/index.vue';
|
||||||
|
import { SHOW } from '/@/components/fileSelector/types';
|
||||||
|
|
||||||
|
const selectorVisiable = ref(false);
|
||||||
|
const fileSelectorRef = ref<any>(null);
|
||||||
|
const getSizeDisplay = (n: number) => n < 1024 ? n + 'b' : (n < 1024 * 1024 ? (n / 1024).toFixed(2) + 'Kb' : (n / (1024 * 1024)).toFixed(2) + 'Mb');
|
||||||
|
|
||||||
|
const openAddHandle = async () => {
|
||||||
|
selectorVisiable.value = true;
|
||||||
|
await nextTick();
|
||||||
|
}
|
||||||
// crud组件的ref
|
// crud组件的ref
|
||||||
const crudRef = ref();
|
const crudRef = ref();
|
||||||
// crud 配置的ref
|
// crud 配置的ref
|
||||||
@@ -15,7 +42,7 @@ const crudBinding = ref();
|
|||||||
// 暴露的方法
|
// 暴露的方法
|
||||||
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
const { crudExpose } = useExpose({ crudRef, crudBinding });
|
||||||
// 你的crud配置
|
// 你的crud配置
|
||||||
const { crudOptions } = createCrudOptions({ crudExpose });
|
const { crudOptions } = createCrudOptions({ crudExpose, context: { openAddHandle } });
|
||||||
// 初始化crud配置
|
// 初始化crud配置
|
||||||
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
||||||
|
|
||||||
@@ -23,4 +50,4 @@ const { resetCrudOptions } = useCrud({ crudExpose, crudOptions });
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
crudExpose.doRefresh();
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -31,7 +31,7 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
|
|||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: env.VITE_PORT as unknown as number,
|
port: env.VITE_PORT as unknown as number,
|
||||||
open: true,
|
open: false,
|
||||||
hmr: true,
|
hmr: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/gitee': {
|
'/gitee': {
|
||||||
|
|||||||
Reference in New Issue
Block a user