!217 上传组件增加 modelValue 参数适配,同时支持 modelValue 和 value 参数(适配 FormCreate)
Merge pull request !217 from puhui999/dev-form-create
This commit is contained in:
@@ -4,17 +4,7 @@
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
||||
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
|
||||
import {
|
||||
computed,
|
||||
defineEmits,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
h,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
} from 'vue';
|
||||
import { computed, h, onBeforeUnmount, onMounted, provide, ref } from 'vue';
|
||||
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
@@ -655,7 +645,7 @@ onBeforeUnmount(() => {
|
||||
type="file"
|
||||
id="files"
|
||||
ref="refFile"
|
||||
style="display: none"
|
||||
class="hidden"
|
||||
accept=".xml, .bpmn"
|
||||
@change="importLocalFile"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { h, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
||||
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { defineOptions, defineProps, ref, watch } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { CustomConfigMap } from './data';
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
inject,
|
||||
nextTick,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import { inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Divider,
|
||||
|
||||
@@ -153,11 +153,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form
|
||||
:model="flowConditionForm"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form :model="flowConditionForm">
|
||||
<Form.Item label="流转类型">
|
||||
<Select v-model:value="flowConditionForm.type" @change="updateFlowType">
|
||||
<Select.Option value="normal">普通流转路径</Select.Option>
|
||||
|
||||
@@ -305,7 +305,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ style: { width: '80px' } }">
|
||||
<Form>
|
||||
<FormItem label="流程表单">
|
||||
<!-- <Input v-model:value="formKey" @change="updateElementFormKey" />-->
|
||||
<Select
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon, PlusOutlined } from '@vben/icons';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import {
|
||||
@@ -290,7 +290,7 @@ watch(
|
||||
<div class="element-drawer__button">
|
||||
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
添加监听器
|
||||
</Button>
|
||||
@@ -309,12 +309,7 @@ watch(
|
||||
:width="width as any"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
ref="listenerFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form :model="listenerForm" ref="listenerFormRef">
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
@@ -462,20 +457,23 @@ watch(
|
||||
</template>
|
||||
</Form>
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><IconifyIcon icon="ep:menu" />注入字段:</span>
|
||||
<Button type="primary" @click="openListenerFieldForm(null, -1)">
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="flex items-center">
|
||||
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||
注入字段
|
||||
</span>
|
||||
<Button
|
||||
type="primary"
|
||||
title="添加字段"
|
||||
@click="openListenerFieldForm(null, -1)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
<Table
|
||||
:data-source="fieldsListOfListener"
|
||||
size="small"
|
||||
:scroll="{ y: 240 }"
|
||||
:pagination="false"
|
||||
bordered
|
||||
style="flex: none"
|
||||
>
|
||||
</div>
|
||||
<Table :data-source="fieldsListOfListener" size="small" bordered>
|
||||
<TableColumn title="序号" width="50px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
@@ -492,12 +490,12 @@ watch(
|
||||
/>
|
||||
<TableColumn
|
||||
title="字段值/表达式"
|
||||
width="100px"
|
||||
width="120px"
|
||||
:custom-render="
|
||||
({ record }: any) => record.string || record.expression
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="130px">
|
||||
<TableColumn title="操作" width="80px" fixed="right">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
@@ -532,13 +530,7 @@ watch(
|
||||
width="600px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
ref="listenerFieldFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
style="height: 136px"
|
||||
>
|
||||
<Form :model="listenerFieldForm" ref="listenerFieldFormRef">
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
|
||||
@@ -89,7 +89,7 @@ const select = async (row: BpmProcessListenerApi.ProcessListener) => {
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column title="值" align="center" data-index="value" />
|
||||
<Table.Column title="操作" align="center">
|
||||
<Table.Column title="操作" align="center" fixed="right">
|
||||
<template #default="{ record }">
|
||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { MenuOutlined, PlusOutlined, SelectOutlined } from '@vben/icons';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import {
|
||||
@@ -300,11 +300,11 @@ watch(
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button size="small" type="primary" @click="openListenerForm(null)">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon> <IconifyIcon icon="ep:plus" /></template>
|
||||
添加监听器
|
||||
</Button>
|
||||
<Button size="small" @click="openProcessListenerDialog">
|
||||
<template #icon><SelectOutlined /></template>
|
||||
<template #icon> <IconifyIcon icon="ep:select" /></template>
|
||||
选择监听器
|
||||
</Button>
|
||||
</div>
|
||||
@@ -316,12 +316,7 @@ watch(
|
||||
:width="width"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFormRef"
|
||||
>
|
||||
<Form :model="listenerForm" ref="listenerFormRef">
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
@@ -458,16 +453,22 @@ watch(
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><MenuOutlined />注入字段:</span>
|
||||
<div class="mb-2 flex justify-between">
|
||||
<span class="flex items-center">
|
||||
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||
注入字段
|
||||
</span>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
title="添加字段"
|
||||
@click="openListenerFieldForm(null)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
<Table
|
||||
:data="fieldsListOfListener"
|
||||
size="small"
|
||||
@@ -533,13 +534,7 @@ watch(
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFieldFormRef"
|
||||
style="height: 136px"
|
||||
>
|
||||
<Form :model="listenerFieldForm" ref="listenerFieldFormRef">
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
|
||||
@@ -421,7 +421,7 @@ watch(
|
||||
</RadioGroup>
|
||||
<div v-else>除了UserTask以外节点的多实例待实现</div>
|
||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||
<Form :label-col="{ span: 6 }" style="display: none">
|
||||
<Form class="hidden">
|
||||
<FormItem label="快捷配置">
|
||||
<Button size="small" @click="() => changeConfig('依次审批')">
|
||||
依次审批
|
||||
@@ -467,7 +467,7 @@ watch(
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="元素变量" key="elementVariable" style="display: none">
|
||||
<FormItem label="元素变量" key="elementVariable" class="hidden">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.elementVariable"
|
||||
allow-clear
|
||||
@@ -485,7 +485,7 @@ watch(
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步状态" key="async" style="display: none">
|
||||
<FormItem label="异步状态" key="async" class="hidden">
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.asyncBefore"
|
||||
@change="() => updateLoopAsync('asyncBefore')"
|
||||
|
||||
@@ -161,25 +161,15 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table :data="elementPropertyList" :scroll="{ y: 240 }" bordered>
|
||||
<Table :data="elementPropertyList" size="small" bordered>
|
||||
<TableColumn title="序号" width="50">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="属性名"
|
||||
data-index="name"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="属性值"
|
||||
data-index="value"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn title="操作" width="110">
|
||||
<TableColumn title="属性名" data-index="name" />
|
||||
<TableColumn title="属性值" data-index="value" />
|
||||
<TableColumn title="操作">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@@ -215,11 +205,7 @@ watch(
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="propertyForm"
|
||||
ref="attributeFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
>
|
||||
<Form :model="propertyForm" ref="attributeFormRef">
|
||||
<FormItem label="属性名:" name="name">
|
||||
<Input v-model:value="propertyForm.name" allow-clear />
|
||||
</FormItem>
|
||||
|
||||
@@ -84,8 +84,8 @@ onMounted(() => {
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="panel-tab__content--title">
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555" />
|
||||
<span class="flex items-center">
|
||||
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||
消息列表
|
||||
</span>
|
||||
<Button type="primary" title="创建新消息" @click="openModel('message')">
|
||||
@@ -95,33 +95,19 @@ onMounted(() => {
|
||||
创建新消息
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="messageList" :bordered="true" :pagination="false">
|
||||
<Table :data-source="messageList" size="small" bordered>
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="消息ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="消息名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn title="消息ID" data-index="id" />
|
||||
<TableColumn title="消息名称" data-index="name" />
|
||||
</Table>
|
||||
<div
|
||||
class="panel-tab__content--title"
|
||||
style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eee"
|
||||
>
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555">
|
||||
信号列表
|
||||
</IconifyIcon>
|
||||
<div class="panel-tab__content--title mt-2 border-t border-gray-200 pt-2">
|
||||
<span class="flex items-center">
|
||||
<IconifyIcon icon="ep:menu" class="mr-2 text-gray-600" />
|
||||
信号列表
|
||||
</span>
|
||||
<Button type="primary" title="创建新信号" @click="openModel('signal')">
|
||||
<template #icon>
|
||||
@@ -130,24 +116,14 @@ onMounted(() => {
|
||||
创建新信号
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="signalList" :bordered="true" :pagination="false">
|
||||
<Table :data-source="signalList" size="small" bordered>
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="信号ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="信号名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn title="信号ID" data-index="id" />
|
||||
<TableColumn title="信号名称" data-index="name" />
|
||||
</Table>
|
||||
|
||||
<Modal
|
||||
@@ -157,11 +133,7 @@ onMounted(() => {
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="modelObjectForm"
|
||||
:label-col="{ span: 9 }"
|
||||
:wrapper-col="{ span: 15 }"
|
||||
>
|
||||
<Form :model="modelObjectForm">
|
||||
<FormItem :label="modelConfig.idLabel">
|
||||
<Input v-model:value="modelObjectForm.id" allow-clear />
|
||||
</FormItem>
|
||||
|
||||
@@ -63,9 +63,9 @@ watch(
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
|
||||
<Form>
|
||||
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步延续" style="display: none">
|
||||
<FormItem label="异步延续" class="hidden">
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.asyncBefore"
|
||||
@change="changeTaskAsync"
|
||||
|
||||
@@ -180,7 +180,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<Form>
|
||||
<FormItem label="实例名称">
|
||||
<Input
|
||||
v-model:value="formData.processInstanceName"
|
||||
@@ -341,12 +341,7 @@ watch(
|
||||
@ok="saveVariable"
|
||||
@cancel="variableDialogVisible = false"
|
||||
>
|
||||
<Form
|
||||
:model="varialbeFormData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
ref="varialbeFormRef"
|
||||
>
|
||||
<Form :model="varialbeFormData" ref="varialbeFormRef">
|
||||
<FormItem label="源:" name="source">
|
||||
<Input v-model:value="varialbeFormData.source" allow-clear />
|
||||
</FormItem>
|
||||
|
||||
@@ -143,7 +143,7 @@ watch(
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form :model="newMessageForm" size="small" :label-col="{ span: 6 }">
|
||||
<Form :model="newMessageForm" size="small">
|
||||
<Form.Item label="消息ID">
|
||||
<Input v-model:value="newMessageForm.id" allow-clear />
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
FormItem,
|
||||
|
||||
@@ -344,7 +344,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<Form>
|
||||
<FormItem label="规则类型" name="candidateStrategy">
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateStrategy"
|
||||
|
||||
@@ -3,12 +3,7 @@ import type { Ref } from 'vue';
|
||||
|
||||
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
IconifyIcon,
|
||||
QuestionCircleFilled,
|
||||
} from '@vben/icons';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
||||
|
||||
@@ -240,7 +235,11 @@ watch(
|
||||
循环
|
||||
</Button>
|
||||
</Button.Group>
|
||||
<CheckCircleFilled v-if="valid" style="color: green; margin-left: 8px" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-filled"
|
||||
v-if="valid"
|
||||
style="color: green; margin-left: 8px"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
||||
<span>条件:</span>
|
||||
@@ -254,11 +253,15 @@ watch(
|
||||
>
|
||||
<template #suffix>
|
||||
<Tooltip v-if="!valid" title="格式错误" placement="top">
|
||||
<ExclamationCircleFilled style="color: orange" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:exclamation-circle-filled"
|
||||
class="text-orange-400"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip :title="helpText" placement="top">
|
||||
<QuestionCircleFilled
|
||||
style="color: #409eff; cursor: pointer"
|
||||
<IconifyIcon
|
||||
icon="ant-design:question-circle-filled"
|
||||
class="cursor-pointer text-[#409eff]"
|
||||
@click="showHelp = true"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -351,7 +354,3 @@ watch(
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 相关样式 */
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FileUploadProps } from './typing';
|
||||
|
||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||
|
||||
import { ref, toRefs, watch } from 'vue';
|
||||
import { computed, ref, toRefs, watch } from 'vue';
|
||||
|
||||
import { CloudUpload } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
@@ -22,8 +22,10 @@ defineOptions({ name: 'FileUpload', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||
value: () => [],
|
||||
modelValue: undefined,
|
||||
directory: undefined,
|
||||
disabled: false,
|
||||
drag: false,
|
||||
helpText: '',
|
||||
maxSize: 2,
|
||||
maxNumber: 1,
|
||||
@@ -33,7 +35,14 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||
resultField: '',
|
||||
showDescription: false,
|
||||
});
|
||||
const emit = defineEmits(['change', 'update:value', 'delete', 'returnText']);
|
||||
const emit = defineEmits([
|
||||
'change',
|
||||
'update:value',
|
||||
'update:modelValue',
|
||||
'delete',
|
||||
'returnText',
|
||||
'preview',
|
||||
]);
|
||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||
const isInnerOperate = ref<boolean>(false);
|
||||
const { getStringAccept } = useUploadType({
|
||||
@@ -43,13 +52,25 @@ const { getStringAccept } = useUploadType({
|
||||
maxSizeRef: maxSize,
|
||||
});
|
||||
|
||||
// 计算当前绑定的值,优先使用 modelValue
|
||||
const currentValue = computed(() => {
|
||||
return props.modelValue === undefined ? props.value : props.modelValue;
|
||||
});
|
||||
|
||||
// 判断是否使用 modelValue
|
||||
const isUsingModelValue = computed(() => {
|
||||
return props.modelValue !== undefined;
|
||||
});
|
||||
|
||||
const fileList = ref<UploadProps['fileList']>([]);
|
||||
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||
const uploadNumber = ref<number>(0); // 上传文件计数器
|
||||
const uploadList = ref<any[]>([]); // 临时上传列表
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
currentValue,
|
||||
(v) => {
|
||||
if (isInnerOperate.value) {
|
||||
isInnerOperate.value = false;
|
||||
@@ -94,15 +115,40 @@ async function handleRemove(file: UploadFile) {
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
emit('delete', file);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件预览
|
||||
function handlePreview(file: UploadFile) {
|
||||
emit('preview', file);
|
||||
}
|
||||
|
||||
// 处理文件数量超限
|
||||
function handleExceed() {
|
||||
message.error($t('ui.upload.maxNumber', [maxNumber.value]));
|
||||
}
|
||||
|
||||
// 处理上传错误
|
||||
function handleUploadError(error: any) {
|
||||
console.error('上传错误:', error);
|
||||
message.error($t('ui.upload.uploadError'));
|
||||
// 上传失败时减少计数器
|
||||
uploadNumber.value = Math.max(0, uploadNumber.value - 1);
|
||||
}
|
||||
|
||||
async function beforeUpload(file: File) {
|
||||
const fileContent = await file.text();
|
||||
emit('returnText', fileContent);
|
||||
|
||||
// 检查文件数量限制
|
||||
if (fileList.value!.length >= props.maxNumber) {
|
||||
message.error($t('ui.upload.maxNumber', [props.maxNumber]));
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
const { maxSize, accept } = props;
|
||||
const isAct = checkFileType(file, accept);
|
||||
if (!isAct) {
|
||||
@@ -110,6 +156,7 @@ async function beforeUpload(file: File) {
|
||||
isActMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isActMsg.value = true), 1000);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||
if (isLt) {
|
||||
@@ -117,8 +164,12 @@ async function beforeUpload(file: File) {
|
||||
isLtMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||
|
||||
// 只有在验证通过后才增加计数器
|
||||
uploadNumber.value++;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function customRequest(info: UploadRequestOption<any>) {
|
||||
@@ -133,17 +184,48 @@ async function customRequest(info: UploadRequestOption<any>) {
|
||||
info.onProgress!({ percent });
|
||||
};
|
||||
const res = await api?.(info.file as File, progressEvent);
|
||||
|
||||
// 处理上传成功后的逻辑
|
||||
handleUploadSuccess(res, info.file as File);
|
||||
|
||||
info.onSuccess!(res);
|
||||
message.success($t('ui.upload.uploadSuccess'));
|
||||
|
||||
// 更新文件
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
info.onError!(error);
|
||||
handleUploadError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理上传成功
|
||||
function handleUploadSuccess(res: any, file: File) {
|
||||
// 删除临时文件
|
||||
const index = fileList.value?.findIndex((item) => item.name === file.name);
|
||||
if (index !== -1) {
|
||||
fileList.value?.splice(index!, 1);
|
||||
}
|
||||
|
||||
// 添加到临时上传列表
|
||||
const fileUrl = res?.url || res?.data || res;
|
||||
uploadList.value.push({
|
||||
name: file.name,
|
||||
url: fileUrl,
|
||||
status: UploadResultStatus.DONE,
|
||||
uid: file.name + Date.now(),
|
||||
});
|
||||
|
||||
// 检查是否所有文件都上传完成
|
||||
if (uploadList.value.length >= uploadNumber.value) {
|
||||
fileList.value?.push(...uploadList.value);
|
||||
uploadList.value = [];
|
||||
uploadNumber.value = 0;
|
||||
|
||||
// 更新值
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,11 +238,26 @@ function getValue() {
|
||||
}
|
||||
return item?.url || item?.response?.url || item?.response;
|
||||
});
|
||||
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
||||
|
||||
// 单个文件的情况,根据输入参数类型决定返回格式
|
||||
if (props.maxNumber === 1) {
|
||||
return list.length > 0 ? list[0] : '';
|
||||
const singleValue = list.length > 0 ? list[0] : '';
|
||||
// 如果原始值是字符串或 modelValue 是字符串,返回字符串
|
||||
if (
|
||||
isString(props.value) ||
|
||||
(isUsingModelValue.value && isString(props.modelValue))
|
||||
) {
|
||||
return singleValue;
|
||||
}
|
||||
return singleValue;
|
||||
}
|
||||
return list;
|
||||
|
||||
// 多文件情况,根据输入参数类型决定返回格式
|
||||
if (isUsingModelValue.value) {
|
||||
return Array.isArray(props.modelValue) ? list : list.join(',');
|
||||
}
|
||||
|
||||
return Array.isArray(props.value) ? list : list.join(',');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -177,15 +274,34 @@ function getValue() {
|
||||
:multiple="multiple"
|
||||
list-type="text"
|
||||
:progress="{ showInfo: true }"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: true,
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: true,
|
||||
}"
|
||||
@remove="handleRemove"
|
||||
@preview="handlePreview"
|
||||
@reject="handleExceed"
|
||||
>
|
||||
<div v-if="fileList && fileList.length < maxNumber">
|
||||
<div v-if="drag" class="upload-drag-area">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<CloudUpload />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p class="ant-upload-hint">
|
||||
支持{{ accept.join('/') }}格式文件,不超过{{ maxSize }}MB
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="fileList && fileList.length < maxNumber">
|
||||
<Button>
|
||||
<CloudUpload />
|
||||
{{ $t('ui.upload.upload') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="showDescription" class="mt-2 flex flex-wrap items-center">
|
||||
<div
|
||||
v-if="showDescription && !drag"
|
||||
class="mt-2 flex flex-wrap items-center"
|
||||
>
|
||||
请上传不超过
|
||||
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
|
||||
的
|
||||
@@ -195,3 +311,35 @@ function getValue() {
|
||||
</Upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.upload-drag-area {
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background-color: #fafafa;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.upload-drag-area:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
.ant-upload-drag-icon {
|
||||
font-size: 48px;
|
||||
color: #d9d9d9;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ant-upload-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ant-upload-hint {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FileUploadProps } from './typing';
|
||||
|
||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||
|
||||
import { ref, toRefs, watch } from 'vue';
|
||||
import { computed, ref, toRefs, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
@@ -22,6 +22,7 @@ defineOptions({ name: 'ImageUpload', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||
value: () => [],
|
||||
modelValue: undefined,
|
||||
directory: undefined,
|
||||
disabled: false,
|
||||
listType: 'picture-card',
|
||||
@@ -34,7 +35,12 @@ const props = withDefaults(defineProps<FileUploadProps>(), {
|
||||
resultField: '',
|
||||
showDescription: true,
|
||||
});
|
||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||
const emit = defineEmits([
|
||||
'change',
|
||||
'update:value',
|
||||
'update:modelValue',
|
||||
'delete',
|
||||
]);
|
||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||
const isInnerOperate = ref<boolean>(false);
|
||||
const { getStringAccept } = useUploadType({
|
||||
@@ -43,6 +49,16 @@ const { getStringAccept } = useUploadType({
|
||||
maxNumberRef: maxNumber,
|
||||
maxSizeRef: maxSize,
|
||||
});
|
||||
|
||||
// 计算当前绑定的值,优先使用 modelValue
|
||||
const currentValue = computed(() => {
|
||||
return props.modelValue === undefined ? props.value : props.modelValue;
|
||||
});
|
||||
|
||||
// 判断是否使用 modelValue
|
||||
const isUsingModelValue = computed(() => {
|
||||
return props.modelValue !== undefined;
|
||||
});
|
||||
const previewOpen = ref<boolean>(false); // 是否展示预览
|
||||
const previewImage = ref<string>(''); // 预览图片
|
||||
const previewTitle = ref<string>(''); // 预览标题
|
||||
@@ -51,9 +67,11 @@ const fileList = ref<UploadProps['fileList']>([]);
|
||||
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||
const uploadNumber = ref<number>(0); // 上传文件计数器
|
||||
const uploadList = ref<any[]>([]); // 临时上传列表
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
currentValue,
|
||||
async (v) => {
|
||||
if (isInnerOperate.value) {
|
||||
isInnerOperate.value = false;
|
||||
@@ -122,6 +140,7 @@ async function handleRemove(file: UploadFile) {
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
emit('delete', file);
|
||||
}
|
||||
@@ -133,6 +152,12 @@ function handleCancel() {
|
||||
}
|
||||
|
||||
async function beforeUpload(file: File) {
|
||||
// 检查文件数量限制
|
||||
if (fileList.value!.length >= props.maxNumber) {
|
||||
message.error($t('ui.upload.maxNumber', [props.maxNumber]));
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
const { maxSize, accept } = props;
|
||||
const isAct = checkImgType(file, accept);
|
||||
if (!isAct) {
|
||||
@@ -140,6 +165,7 @@ async function beforeUpload(file: File) {
|
||||
isActMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isActMsg.value = true), 1000);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||
if (isLt) {
|
||||
@@ -147,8 +173,12 @@ async function beforeUpload(file: File) {
|
||||
isLtMsg.value = false;
|
||||
// 防止弹出多个错误提示
|
||||
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||
|
||||
// 只有在验证通过后才增加计数器
|
||||
uploadNumber.value++;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function customRequest(info: UploadRequestOption<any>) {
|
||||
@@ -163,20 +193,59 @@ async function customRequest(info: UploadRequestOption<any>) {
|
||||
info.onProgress!({ percent });
|
||||
};
|
||||
const res = await api?.(info.file as File, progressEvent);
|
||||
|
||||
// 处理上传成功后的逻辑
|
||||
handleUploadSuccess(res, info.file as File);
|
||||
|
||||
info.onSuccess!(res);
|
||||
message.success($t('ui.upload.uploadSuccess'));
|
||||
|
||||
// 更新文件
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('change', value);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
info.onError!(error);
|
||||
handleUploadError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理上传成功
|
||||
function handleUploadSuccess(res: any, file: File) {
|
||||
// 删除临时文件
|
||||
const index = fileList.value?.findIndex((item) => item.name === file.name);
|
||||
if (index !== -1) {
|
||||
fileList.value?.splice(index!, 1);
|
||||
}
|
||||
|
||||
// 添加到临时上传列表
|
||||
const fileUrl = res?.url || res?.data || res;
|
||||
uploadList.value.push({
|
||||
name: file.name,
|
||||
url: fileUrl,
|
||||
status: UploadResultStatus.DONE,
|
||||
uid: file.name + Date.now(),
|
||||
});
|
||||
|
||||
// 检查是否所有文件都上传完成
|
||||
if (uploadList.value.length >= uploadNumber.value) {
|
||||
fileList.value?.push(...uploadList.value);
|
||||
uploadList.value = [];
|
||||
uploadNumber.value = 0;
|
||||
|
||||
// 更新值
|
||||
const value = getValue();
|
||||
isInnerOperate.value = true;
|
||||
emit('update:value', value);
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理上传错误
|
||||
function handleUploadError(error: any) {
|
||||
console.error('上传错误:', error);
|
||||
message.error($t('ui.upload.uploadError'));
|
||||
// 上传失败时减少计数器
|
||||
uploadNumber.value = Math.max(0, uploadNumber.value - 1);
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
const list = (fileList.value || [])
|
||||
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||
@@ -186,11 +255,26 @@ function getValue() {
|
||||
}
|
||||
return item?.url || item?.response?.url || item?.response;
|
||||
});
|
||||
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
||||
|
||||
// 单个文件的情况,根据输入参数类型决定返回格式
|
||||
if (props.maxNumber === 1) {
|
||||
return list.length > 0 ? list[0] : '';
|
||||
const singleValue = list.length > 0 ? list[0] : '';
|
||||
// 如果原始值是字符串或 modelValue 是字符串,返回字符串
|
||||
if (
|
||||
isString(props.value) ||
|
||||
(isUsingModelValue.value && isString(props.modelValue))
|
||||
) {
|
||||
return singleValue;
|
||||
}
|
||||
return singleValue;
|
||||
}
|
||||
return list;
|
||||
|
||||
// 多文件情况,根据输入参数类型决定返回格式
|
||||
if (isUsingModelValue.value) {
|
||||
return Array.isArray(props.modelValue) ? list : list.join(',');
|
||||
}
|
||||
|
||||
return Array.isArray(props.value) ? list : list.join(',');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,10 +21,12 @@ export interface FileUploadProps {
|
||||
// 上传的目录
|
||||
directory?: string;
|
||||
disabled?: boolean;
|
||||
drag?: boolean; // 是否支持拖拽上传
|
||||
helpText?: string;
|
||||
listType?: UploadListType;
|
||||
// 最大数量的文件,Infinity不限制
|
||||
maxNumber?: number;
|
||||
modelValue?: string | string[]; // v-model 支持
|
||||
// 文件最大多少MB
|
||||
maxSize?: number;
|
||||
// 是否支持多选
|
||||
|
||||
@@ -61,15 +61,3 @@ export const MenuOutlined = createIconifyIcon('ant-design:menu-outlined');
|
||||
export const PlusOutlined = createIconifyIcon('ant-design:plus-outlined');
|
||||
|
||||
export const SelectOutlined = createIconifyIcon('ant-design:select-outlined');
|
||||
|
||||
export const CheckCircleFilled = createIconifyIcon(
|
||||
'ant-design:check-circle-filled',
|
||||
);
|
||||
|
||||
export const ExclamationCircleFilled = createIconifyIcon(
|
||||
'ant-design:exclamation-circle-filled',
|
||||
);
|
||||
|
||||
export const QuestionCircleFilled = createIconifyIcon(
|
||||
'ant-design:question-circle-filled',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user