feat: 钉钉、企业微信webbook、企业微信应用api,问题:交互
This commit is contained in:
@@ -42,6 +42,14 @@ const pageSize = ref(24);
|
||||
const pageSizeOptions = ['12', '24', '48'];
|
||||
const selected = ref<string[]>([]);
|
||||
|
||||
/** 按 acceptTypes 隔离 localStorage,避免图片/视频选择器互相污染 */
|
||||
const storageKey = computed(
|
||||
() =>
|
||||
`file-picker-type-${
|
||||
props.acceptTypes?.length ? props.acceptTypes.join('-') : 'all'
|
||||
}`,
|
||||
);
|
||||
|
||||
const {
|
||||
activeType,
|
||||
keyword,
|
||||
@@ -50,12 +58,10 @@ const {
|
||||
buildListParams,
|
||||
fileTypes,
|
||||
typesLoading,
|
||||
} = useFileGalleryFilter('file-picker-active-type');
|
||||
} = useFileGalleryFilter(() => storageKey.value);
|
||||
|
||||
/**
|
||||
* 实际渲染的 tabs(按 acceptTypes 过滤)
|
||||
* - acceptTypes 为空:返回 tabs 原始值(含「全部」+ 所有类型)
|
||||
* - acceptTypes 非空:只保留匹配的类型 tab(不含「全部」,强制选具体类型)
|
||||
*/
|
||||
const visibleTabs = computed(() => {
|
||||
if (!props.acceptTypes || props.acceptTypes.length === 0) {
|
||||
@@ -67,30 +73,40 @@ const visibleTabs = computed(() => {
|
||||
});
|
||||
|
||||
/**
|
||||
* acceptTypes 变化时锁定到第一个匹配的类型(避免停留在「全部」导致看到非目标类型文件)
|
||||
* 强制锁定到 acceptTypes 首项(打开弹窗 / 类型字典就绪时调用)
|
||||
*/
|
||||
watch(
|
||||
() => props.acceptTypes,
|
||||
(arr) => {
|
||||
if (!arr || arr.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (arr.includes(activeType.value)) {
|
||||
return;
|
||||
}
|
||||
function lockAcceptType() {
|
||||
const arr = props.acceptTypes;
|
||||
if (!arr || arr.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (activeType.value !== arr[0]) {
|
||||
setActiveType(arr[0]!);
|
||||
page.value = 1;
|
||||
void load();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
/**
|
||||
* 是否允许拉列表:有 acceptTypes 时需等类型字典加载完且 visibleTabs 非空
|
||||
*/
|
||||
function canLoadList() {
|
||||
if (typesLoading.value) {
|
||||
return false;
|
||||
}
|
||||
if (props.acceptTypes && props.acceptTypes.length > 0) {
|
||||
return visibleTabs.value.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!canLoadList()) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getFileGalleryList(buildListParams(page.value, pageSize.value));
|
||||
const res = await getFileGalleryList(
|
||||
buildListParams(page.value, pageSize.value),
|
||||
);
|
||||
const data = (res as any)?.data ?? res;
|
||||
items.value = data?.items ?? [];
|
||||
total.value = data?.total ?? 0;
|
||||
@@ -99,21 +115,37 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.acceptTypes,
|
||||
(arr) => {
|
||||
if (!arr || arr.length === 0) {
|
||||
return;
|
||||
}
|
||||
lockAcceptType();
|
||||
page.value = 1;
|
||||
void load();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(val) => {
|
||||
if (val) {
|
||||
selected.value = [];
|
||||
page.value = 1;
|
||||
if (!typesLoading.value) {
|
||||
void load();
|
||||
}
|
||||
// 打开时再次按 acceptTypes 重锁,避免沿用其它选择器的视频 tab
|
||||
lockAcceptType();
|
||||
void load();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(typesLoading, (val) => {
|
||||
if (!val && props.open) {
|
||||
lockAcceptType();
|
||||
void load();
|
||||
}
|
||||
});
|
||||
@@ -184,6 +216,11 @@ function onSearch(value: string) {
|
||||
function itemIcon(item: FileGalleryItem) {
|
||||
return item.type_icon || resolveTypeIcon(item.type, fileTypes.value);
|
||||
}
|
||||
|
||||
/** 图片类型展示缩略图(xk_file_type:1=图片) */
|
||||
function isImageItem(item: FileGalleryItem) {
|
||||
return Number(item.type) === 1 || Number(item.type) === 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -217,7 +254,7 @@ function itemIcon(item: FileGalleryItem) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Spin :spinning="loading">
|
||||
<Spin :spinning="loading || typesLoading">
|
||||
<div v-if="items.length" class="gallery-grid">
|
||||
<div
|
||||
v-for="item in items"
|
||||
@@ -226,10 +263,17 @@ function itemIcon(item: FileGalleryItem) {
|
||||
:class="{ active: isSelected(item.url) }"
|
||||
@click="toggleSelect(item.url)"
|
||||
>
|
||||
<img v-if="item.type === 0" :src="item.url" alt="" class="gallery-thumb" />
|
||||
<img
|
||||
v-if="isImageItem(item)"
|
||||
:src="item.url"
|
||||
alt=""
|
||||
class="gallery-thumb"
|
||||
/>
|
||||
<div v-else class="gallery-file-icon">
|
||||
<MIcon :icon="itemIcon(item)" size="28" />
|
||||
<span class="file-name">{{ item.file_name || item.original_name || '未命名' }}</span>
|
||||
<span class="file-name">{{
|
||||
item.file_name || item.original_name || '未命名'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="gallery-meta">
|
||||
<div class="file-title">
|
||||
|
||||
@@ -24,12 +24,17 @@ interface Props {
|
||||
modelValue: string[];
|
||||
multiple?: boolean;
|
||||
maxCount?: number;
|
||||
/**
|
||||
* 素材库可选类型(透传 GalleryPickLink),默认仅图片
|
||||
*/
|
||||
acceptTypes?: number[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: () => [],
|
||||
multiple: true,
|
||||
maxCount: 9,
|
||||
acceptTypes: () => [1],
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -184,6 +189,7 @@ const galleryRemainCount = computed(() => Math.max(props.maxCount - fileList.val
|
||||
v-if="showUploadButton"
|
||||
:multiple="multiple"
|
||||
:max-count="galleryRemainCount"
|
||||
:accept-types="acceptTypes"
|
||||
@select="onGallerySelect"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* OSS 文件上传(单文件 URL 字符串)
|
||||
* 可选:从素材库选择(acceptTypes 过滤,如文件=6、语音=3)
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { Button, Upload } from 'ant-design-vue';
|
||||
|
||||
import GalleryPickLink from '#/components/form/components/gallery-pick-link.vue';
|
||||
import { Icon } from '#/components/icon';
|
||||
import { uploadFile } from '#/api/core/upload';
|
||||
import { uploadToOss } from '#/utils/oss-upload';
|
||||
@@ -17,11 +23,17 @@ interface FileItem {
|
||||
interface Props {
|
||||
modelValue?: string;
|
||||
maxCount?: number;
|
||||
/** 素材库类型过滤,如 [6] 文件、[3] 语音 */
|
||||
acceptTypes?: number[];
|
||||
/** input accept,如 .pdf,.doc 或 audio/* */
|
||||
accept?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
maxCount: 1,
|
||||
acceptTypes: () => [6],
|
||||
accept: '.pdf,.doc,.docx,.zip,.rar,.txt,application/pdf,audio/*,*/*',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -131,26 +143,50 @@ function handleRemove() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function onGallerySelect(urls: string[]) {
|
||||
const url = urls[0] || '';
|
||||
if (!url) return;
|
||||
syncFileListFromValue(url);
|
||||
updateModelValue();
|
||||
}
|
||||
|
||||
const showUploadButton = computed(
|
||||
() => fileList.value.filter((item) => item.status !== 'error').length === 0,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Upload
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
:max-count="maxCount"
|
||||
list-type="text"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,image/*,application/pdf"
|
||||
@remove="handleRemove"
|
||||
>
|
||||
<Button v-if="showUploadButton">
|
||||
上传文件
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:cloud-upload-outlined" />
|
||||
</template>
|
||||
</Button>
|
||||
</Upload>
|
||||
<div class="upload-oss-file-wrap">
|
||||
<Upload
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
:max-count="maxCount"
|
||||
list-type="text"
|
||||
:accept="accept"
|
||||
@remove="handleRemove"
|
||||
>
|
||||
<Button v-if="showUploadButton">
|
||||
上传文件
|
||||
<template #icon>
|
||||
<Icon icon="ant-design:cloud-upload-outlined" />
|
||||
</template>
|
||||
</Button>
|
||||
</Upload>
|
||||
<GalleryPickLink
|
||||
v-if="showUploadButton"
|
||||
:max-count="1"
|
||||
:accept-types="acceptTypes"
|
||||
@select="onGallerySelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.upload-oss-file-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user