Merge remote-tracking branch 'origin/main' into feature/0909_主agent优化
# Conflicts: # src/views/home/components/history-conversation-drawer/index.vue
This commit is contained in:
@ -1,2 +0,0 @@
|
||||
export { default as TabBar } from './tab-bar/index.vue';
|
||||
export { default as ModalSimple } from './modal/index.vue';
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Component, DefineComponent } from 'vue';
|
||||
|
||||
import IconHover from '@arco-design/web-vue/es/_components/icon-hover';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
content?: string | (() => DefineComponent | Component);
|
||||
}>();
|
||||
|
||||
defineEmits(['close']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot name="header">
|
||||
<div class="flex justify-end mb7">
|
||||
<slot name="close">
|
||||
<icon-hover @click="$emit('close')">
|
||||
<icon-close />
|
||||
</icon-hover>
|
||||
</slot>
|
||||
</div>
|
||||
</slot>
|
||||
<slot>
|
||||
<div class="flex flex-col text-center">
|
||||
<div v-if="title" class="mb4 text-lg font-600">{{ title }}</div>
|
||||
<template v-else />
|
||||
<component :is="content" v-if="typeof content === 'function'" />
|
||||
<div v-else>{{ content }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
@ -1,83 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
import { listenerRouteChange, removeRouteListener } from '@/utils/route-listener';
|
||||
import { useAppStore, useTabBarStore } from '@/stores';
|
||||
import TabItem from './tab-item.vue';
|
||||
const appStore = useAppStore();
|
||||
const tabBarStore = useTabBarStore();
|
||||
const affixRef = ref();
|
||||
const tagList = computed(() => {
|
||||
return tabBarStore.getTabList;
|
||||
});
|
||||
const offsetTop = computed(() => {
|
||||
return appStore.navbar ? 60 : 0;
|
||||
});
|
||||
watch(
|
||||
() => appStore.navbar,
|
||||
() => {
|
||||
affixRef.value.updatePosition();
|
||||
},
|
||||
);
|
||||
listenerRouteChange((route: RouteLocationNormalized) => {
|
||||
if (!route.meta.noAffix && !tagList.value.some((tag) => tag.fullPath === route.fullPath)) {
|
||||
tabBarStore.updateTabList(route);
|
||||
}
|
||||
}, true);
|
||||
onUnmounted(() => {
|
||||
removeRouteListener();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-bar-container">
|
||||
<a-affix ref="affixRef" :offset-top="offsetTop">
|
||||
<div class="tab-bar-box">
|
||||
<div class="tab-bar-scroll">
|
||||
<div class="tags-wrap">
|
||||
<tab-item v-for="(tag, index) in tagList" :key="tag.fullPath" :index="index" :item-data="tag" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag-bar-operation"></div>
|
||||
</div>
|
||||
</a-affix>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tab-bar-container {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-2);
|
||||
.tab-bar-box {
|
||||
display: flex;
|
||||
padding: 0 0 0 20px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
.tab-bar-scroll {
|
||||
height: 32px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.tags-wrap {
|
||||
padding: 4px 0;
|
||||
height: 48px;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
:deep(.arco-tag) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
&:first-child {
|
||||
.arco-tag-close-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tag-bar-operation {
|
||||
width: 100px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,177 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import type { TagProps } from '@/stores/modules/tab-bar/types';
|
||||
|
||||
import { useTabBarStore } from '@/stores';
|
||||
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
const props = defineProps({
|
||||
itemData: {
|
||||
type: Object as PropType<TagProps>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-shadow
|
||||
enum Eaction {
|
||||
reload = 'reload',
|
||||
current = 'current',
|
||||
left = 'left',
|
||||
right = 'right',
|
||||
others = 'others',
|
||||
all = 'all',
|
||||
}
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const tabBarStore = useTabBarStore();
|
||||
const goto = (tag: TagProps) => {
|
||||
router.push({ ...tag });
|
||||
};
|
||||
const tagList = computed(() => {
|
||||
return tabBarStore.getTabList;
|
||||
});
|
||||
const disabledReload = computed(() => {
|
||||
return props.itemData.fullPath !== route.fullPath;
|
||||
});
|
||||
const disabledCurrent = computed(() => {
|
||||
return props.index === 0;
|
||||
});
|
||||
const disabledLeft = computed(() => {
|
||||
return [0, 1].includes(props.index);
|
||||
});
|
||||
const disabledRight = computed(() => {
|
||||
return props.index === tagList.value.length - 1;
|
||||
});
|
||||
const tagClose = (tag: TagProps, idx: number) => {
|
||||
tabBarStore.deleteTag(idx, tag);
|
||||
if (props.itemData.fullPath === route.fullPath) {
|
||||
const latest = tagList.value[idx - 1]; // 获取队列的前一个tab
|
||||
router.push({ name: latest.name });
|
||||
}
|
||||
};
|
||||
const findCurrentRouteIndex = () => {
|
||||
return tagList.value.findIndex((el) => el.fullPath === route.fullPath);
|
||||
};
|
||||
const actionSelect = async (value: any) => {
|
||||
const { itemData, index } = props;
|
||||
const copyTagList = [...tagList.value];
|
||||
if (value === Eaction.current) {
|
||||
tagClose(itemData, index);
|
||||
} else if (value === Eaction.left) {
|
||||
const currentRouteIdx = findCurrentRouteIndex();
|
||||
copyTagList.splice(1, props.index - 1);
|
||||
tabBarStore.freshTabList(copyTagList);
|
||||
if (currentRouteIdx < index) {
|
||||
router.push({ name: itemData.name });
|
||||
}
|
||||
} else if (value === Eaction.right) {
|
||||
const currentRouteIdx = findCurrentRouteIndex();
|
||||
copyTagList.splice(props.index + 1);
|
||||
tabBarStore.freshTabList(copyTagList);
|
||||
if (currentRouteIdx > index) {
|
||||
router.push({ name: itemData.name });
|
||||
}
|
||||
} else if (value === Eaction.others) {
|
||||
const filterList = tagList.value.filter((el, idx) => {
|
||||
return idx === 0 || idx === props.index;
|
||||
});
|
||||
tabBarStore.freshTabList(filterList);
|
||||
router.push({ name: itemData.name });
|
||||
} else if (value === Eaction.reload) {
|
||||
tabBarStore.deleteCache(itemData);
|
||||
await router.push({
|
||||
name: REDIRECT_ROUTE_NAME,
|
||||
params: {
|
||||
path: route.fullPath,
|
||||
},
|
||||
});
|
||||
tabBarStore.addCache(itemData.name);
|
||||
} else {
|
||||
tabBarStore.resetTabList();
|
||||
router.push({ name: DEFAULT_ROUTE_NAME });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-dropdown trigger="contextMenu" :popup-max-height="false" @select="actionSelect">
|
||||
<span
|
||||
:class="[
|
||||
'arco-tag arco-tag-size-medium arco-tag-checked',
|
||||
{ 'link-activated': itemData.fullPath === $route.fullPath },
|
||||
]"
|
||||
@click="goto(itemData)"
|
||||
>
|
||||
<span class="tag-link">{{ itemData.title }}</span>
|
||||
<span
|
||||
class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
|
||||
@click.stop="tagClose(itemData, index)"
|
||||
>
|
||||
<icon-close />
|
||||
</span>
|
||||
</span>
|
||||
<template #content>
|
||||
<a-doption :disabled="disabledReload" :value="Eaction.reload">
|
||||
<icon-refresh />
|
||||
<span>重新加载</span>
|
||||
</a-doption>
|
||||
<a-doption class="sperate-line" :disabled="disabledCurrent" :value="Eaction.current">
|
||||
<icon-close />
|
||||
<span>关闭当前标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :disabled="disabledLeft" :value="Eaction.left">
|
||||
<icon-to-left />
|
||||
<span>关闭左侧标签页</span>
|
||||
</a-doption>
|
||||
<a-doption class="sperate-line" :disabled="disabledRight" :value="Eaction.right">
|
||||
<icon-to-right />
|
||||
<span>关闭右侧标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :value="Eaction.others">
|
||||
<icon-swap />
|
||||
<span>关闭其它标签页</span>
|
||||
</a-doption>
|
||||
<a-doption :value="Eaction.all">
|
||||
<icon-folder-delete />
|
||||
<span>关闭全部标签页</span>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tag-link {
|
||||
color: var(--color-text-2);
|
||||
text-decoration: none;
|
||||
}
|
||||
.link-activated {
|
||||
color: rgb(var(--link-6));
|
||||
.tag-link {
|
||||
color: rgb(var(--link-6));
|
||||
}
|
||||
& + .arco-tag-close-btn {
|
||||
color: rgb(var(--link-6));
|
||||
}
|
||||
}
|
||||
:deep(.arco-dropdown-option-content) {
|
||||
span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.arco-dropdown-open {
|
||||
.tag-link {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
.arco-tag-close-btn {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
.sperate-line {
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
}
|
||||
</style>
|
||||
@ -3,23 +3,26 @@
|
||||
* @Date: 2025-06-25 14:02:40
|
||||
-->
|
||||
<template>
|
||||
<a-select
|
||||
v-model="selectedValues"
|
||||
:multiple="multiple"
|
||||
size="medium"
|
||||
<Select
|
||||
v-model:value="selectedValues"
|
||||
:mode="multiple ? 'multiple' : undefined"
|
||||
size="middle"
|
||||
:placeholder="placeholder"
|
||||
:allow-clear="allClear"
|
||||
:allow-search="allowSearch"
|
||||
:max-tag-count="maxTagCount"
|
||||
:allowClear="allClear"
|
||||
:showSearch="allowSearch"
|
||||
showArrow
|
||||
:maxTagCount="maxTagCount"
|
||||
@change="handleChange"
|
||||
>
|
||||
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
|
||||
<Option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Select } from 'ant-design-vue';
|
||||
const { Option } = Select;
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@ -3,14 +3,15 @@
|
||||
* @Date: 2025-06-30 10:54:49
|
||||
-->
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
<Modal
|
||||
v-model:open="visible"
|
||||
title="自定义列"
|
||||
width="960px"
|
||||
unmountOnClose
|
||||
titleAlign="start"
|
||||
class="custom-table-column-modal"
|
||||
@close="close"
|
||||
centered
|
||||
wrapClassName="custom-table-column-modal"
|
||||
@cancel="close"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<!-- 左侧分组 -->
|
||||
@ -20,16 +21,16 @@
|
||||
<span class="text">{{ group.label }}</span>
|
||||
</div>
|
||||
<div class="fields">
|
||||
<a-checkbox
|
||||
<Checkbox
|
||||
v-for="option in group.columns"
|
||||
:key="option.value"
|
||||
:model-value="isCheck(option)"
|
||||
:checked="isCheck(option)"
|
||||
:value="option.value"
|
||||
:disabled="option.is_require === ENUM_STATUS.NO"
|
||||
@change="(checked) => onCheckChange(checked, option)"
|
||||
@change="(e) => onCheckChange(e.target.checked, option)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</a-checkbox>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -64,15 +65,16 @@
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div style="text-align: right">
|
||||
<a-button class="mr-8px" size="medium" @click="close">取消</a-button>
|
||||
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button>
|
||||
<div class="flex">
|
||||
<Button @click="close">取消</Button>
|
||||
<Button type="primary" @click="onSubmit">确定</Button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Checkbox, Modal, Button } from 'ant-design-vue';
|
||||
import { ref, defineExpose } from 'vue';
|
||||
import { VueDraggable } from 'vue-draggable-plus';
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
.custom-table-column-modal {
|
||||
.arco-modal-body {
|
||||
.ant-modal-body {
|
||||
.modal-body {
|
||||
height: 504px;
|
||||
border-radius: 8px;
|
||||
|
||||
@ -1,13 +1,31 @@
|
||||
<template>
|
||||
<a-modal modal-class="delete-modal" body-class="body" cancel-text="返回" ok-text="确定删除" v-bind="$attrs">
|
||||
<Modal wrapClassName="delete-modal" body-class="body" v-bind="$attrs" centered title="删除账号">
|
||||
<h2 class="delete-modal-title flex item-center">
|
||||
<img src="@/assets/warning.svg" alt="" />
|
||||
{{ $attrs.title }}
|
||||
{{ $attrs.content }}
|
||||
</h2>
|
||||
<slot></slot>
|
||||
</a-modal>
|
||||
<p class="delete-modal-content">删除后,该账号将无法登录您的企业。</p>
|
||||
<template #footer>
|
||||
<div style="text-align: right">
|
||||
<Button @click="close">返回</Button>
|
||||
<Button type="primary" danger @click="onSubmit">确定删除</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { Modal, Button } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['close', 'ok']);
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
emit('ok');
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
:deep(.arco-btn-status-danger) {
|
||||
background-color: red !important;
|
||||
@ -18,7 +36,7 @@
|
||||
display: none;
|
||||
}
|
||||
.delete-modal-title {
|
||||
margin-top: 24px;
|
||||
// margin-top: 24px;
|
||||
font-family: $font-family-medium;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
@ -29,6 +47,14 @@
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
.delete-modal-content {
|
||||
margin-left: 34px;
|
||||
margin-top: 16px;
|
||||
font-family: $font-family-medium;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--Text-2, rgba(60, 64, 67, 1));
|
||||
}
|
||||
.arco-modal-footer {
|
||||
border-top: none;
|
||||
:first-child {
|
||||
@ -53,5 +79,6 @@
|
||||
}
|
||||
.body {
|
||||
padding: 0 24px;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,32 +3,45 @@
|
||||
* @Date: 2025-08-11 22:15:35
|
||||
-->
|
||||
<template>
|
||||
<a-popover
|
||||
:trigger="'hover'"
|
||||
class="hover-big-image-preview-popover"
|
||||
:position="props.position"
|
||||
:mouse-enter-delay="props.enterDelay"
|
||||
:mouse-leave-delay="props.leaveDelay"
|
||||
:disabled="!props.src"
|
||||
<Popover
|
||||
trigger="hover"
|
||||
:placement="props.position"
|
||||
:mouseEnterDelay="props.enterDelay / 1000"
|
||||
:mouseLeaveDelay="props.leaveDelay / 1000"
|
||||
:open="props.src ? undefined : false"
|
||||
overlayClassName="hover-big-image-preview-popover"
|
||||
>
|
||||
<template #content>
|
||||
<div class="preview-container">
|
||||
<img :src="props.src" alt="preview" class="preview-image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<slot />
|
||||
</a-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Popover } from 'ant-design-vue';
|
||||
// import { computed, onMounted, ref, watch } from 'vue';
|
||||
// import type { ImageOrientation } from '@/utils/tools';
|
||||
// import { getImageOrientationByUrl } from '@/utils/tools';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
position?: 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb';
|
||||
position?:
|
||||
| 'top'
|
||||
| 'topLeft'
|
||||
| 'topRight'
|
||||
| 'bottom'
|
||||
| 'bottomLeft'
|
||||
| 'bottomRight'
|
||||
| 'left'
|
||||
| 'leftTop'
|
||||
| 'leftBottom'
|
||||
| 'right'
|
||||
| 'rightTop'
|
||||
| 'rightBottom';
|
||||
enterDelay?: number;
|
||||
leaveDelay?: number;
|
||||
}
|
||||
@ -37,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
position: 'right',
|
||||
enterDelay: 100,
|
||||
leaveDelay: 200,
|
||||
src: '',
|
||||
});
|
||||
|
||||
// const orientation = ref<ImageOrientation>('landscape');
|
||||
@ -67,7 +81,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
<style lang="scss">
|
||||
.hover-big-image-preview-popover {
|
||||
.arco-popover-popup-content {
|
||||
.ant-popover-content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-popover-inner {
|
||||
padding: 16px !important;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
90
src/components/img-lazy-load/index.vue
Normal file
90
src/components/img-lazy-load/index.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="img-lazy" v-lazy:background-image="imgSrc" :key="src" :class="imgClass" :style="style" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, computed } from 'vue';
|
||||
const emit = defineEmits(['click']);
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: [String, Number],
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
},
|
||||
loadingSize: {
|
||||
type: [String],
|
||||
default: '5',
|
||||
},
|
||||
errorSize: {
|
||||
type: [String],
|
||||
default: '5',
|
||||
},
|
||||
fit: {
|
||||
type: [String],
|
||||
default: 'cover',
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
},
|
||||
customImg: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
return {
|
||||
'background-size': props.fit,
|
||||
width: props.width ? parseInt(props.width) + 'px' : undefined,
|
||||
height: props.height ? parseInt(props.height) + 'px' : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
const imgClass = computed(() => {
|
||||
return {
|
||||
['loading-size-' + props.loadingSize]: true,
|
||||
};
|
||||
});
|
||||
|
||||
const imgSrc = computed(() => {
|
||||
return props.innerSrc || props.src;
|
||||
});
|
||||
const innerSrc = ref('');
|
||||
|
||||
watch(
|
||||
() => props.customImg,
|
||||
() => {
|
||||
innerSrc.value = '';
|
||||
if (props.customImg) {
|
||||
const img = new Image();
|
||||
img.src = props.src;
|
||||
img.onerror = () => {
|
||||
innerSrc.value = props.customImg;
|
||||
};
|
||||
img.onload = () => {
|
||||
innerSrc.value = props.src;
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.img-lazy.block {
|
||||
display: block;
|
||||
}
|
||||
.img-lazy {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
background-size: 100% 100%;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@for $i from 0 to 10 {
|
||||
&.loading-size-#{$i}[lazy='loading'] {
|
||||
background-size: #{$i * 10 + '%'} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -9,6 +9,7 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from '@components/modal.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getQueryParam } from '@/utils/helper';
|
||||
|
||||
import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all';
|
||||
@ -26,7 +27,7 @@ async function getEnterprise() {
|
||||
|
||||
async function handleJoin() {
|
||||
await joinEnterpriseByInviteCode(inviteCode.value);
|
||||
AMessage.success('加入成功');
|
||||
message.success('加入成功');
|
||||
}
|
||||
// onMounted(() => {
|
||||
// getEnterprise();
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<a-modal title-align="start" modal-class="modal" body-class="body" v-bind="$attrs">
|
||||
<Modal title-align="start" wrapClassName="modal" cancelText="取消" okText="确定" body-class="body" v-bind="$attrs" centered>
|
||||
<slot></slot>
|
||||
</a-modal>
|
||||
</Modal>
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { Modal } from 'ant-design-vue';
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.modal {
|
||||
.arco-modal-header {
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<a-upload
|
||||
:custom-request="customRequest"
|
||||
<Upload
|
||||
:customRequest="customRequest"
|
||||
action="/"
|
||||
:limit="limit"
|
||||
:maxCount="limit"
|
||||
:fileList="fileList"
|
||||
@change="onChange"
|
||||
@success="handleSuccess"
|
||||
@error="handleError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { Upload, message } from 'ant-design-vue';
|
||||
import { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
|
||||
import axios from 'axios';
|
||||
|
||||
@ -70,8 +68,10 @@ watch(
|
||||
);
|
||||
|
||||
let previousFileListLength = 0;
|
||||
//删除图片
|
||||
const onChange = (fileList) => {
|
||||
const onChange = (info) => {
|
||||
const { fileList } = info;
|
||||
|
||||
// 如果删除了文件
|
||||
if (fileList.length < previousFileListLength) {
|
||||
if (props.limit === 1) {
|
||||
if (fileList.length === 0) {
|
||||
@ -86,28 +86,35 @@ const onChange = (fileList) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理上传成功的文件
|
||||
if (info.file.status === 'done' && info.file.response) {
|
||||
handleSuccess(info.file);
|
||||
} else if (info.file.status === 'error') {
|
||||
handleError(info.file.error);
|
||||
}
|
||||
|
||||
previousFileListLength = fileList.length;
|
||||
};
|
||||
|
||||
const beforeUpload = (file, files) => {
|
||||
if (props.limit > 0 && files.length >= props.limit) {
|
||||
Message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||
message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||
return false; // 阻止上传
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
Message.error('上传失败');
|
||||
message.error('上传失败');
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
const customRequest = async (option) => {
|
||||
const { onProgress, onError, onSuccess, fileItem, name } = option;
|
||||
const { onProgress, onError, onSuccess, file, name } = option;
|
||||
try {
|
||||
// 1. 获取预签名上传URL
|
||||
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) });
|
||||
const response = await fetchUploadFile({ suffix: getFileExtension(file.name) });
|
||||
const preSignedUrl = response?.data?.upload_url;
|
||||
|
||||
if (!preSignedUrl) {
|
||||
@ -115,9 +122,9 @@ const customRequest = async (option) => {
|
||||
}
|
||||
console.log('preSignedUrl', preSignedUrl);
|
||||
// 2. 使用预签名URL上传文件
|
||||
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
|
||||
const blob = new Blob([file], { type: file.type });
|
||||
await axios.put(preSignedUrl, blob, {
|
||||
headers: { 'Content-Type': fileItem.file.type },
|
||||
headers: { 'Content-Type': file.type },
|
||||
});
|
||||
|
||||
onSuccess(JSON.stringify(response));
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
<template>
|
||||
<a-upload
|
||||
:custom-request="customRequest"
|
||||
list-type="picture-card"
|
||||
<Upload
|
||||
:customRequest="customRequest"
|
||||
listType="picture-card"
|
||||
action="/"
|
||||
:limit="limit"
|
||||
:maxCount="limit"
|
||||
:fileList="fileList"
|
||||
image-preview
|
||||
:showUploadList="{ showPreviewIcon: true, showRemoveIcon: true }"
|
||||
@change="onChange"
|
||||
@success="handleSuccess"
|
||||
@error="handleError"
|
||||
@preview="handlePreview"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { Upload, message } from 'ant-design-vue';
|
||||
import { fetchImageUploadFile } from '@/api/all';
|
||||
import axios from 'axios';
|
||||
|
||||
@ -72,8 +71,14 @@ watch(
|
||||
);
|
||||
|
||||
let previousFileListLength = 0;
|
||||
//删除图片
|
||||
const onChange = (fileList) => {
|
||||
const handlePreview = (file) => {
|
||||
console.log('Preview file:', file);
|
||||
};
|
||||
|
||||
const onChange = (info) => {
|
||||
const { fileList } = info;
|
||||
|
||||
// 如果删除了文件
|
||||
if (fileList.length < previousFileListLength) {
|
||||
if (props.limit === 1) {
|
||||
if (fileList.length === 0) {
|
||||
@ -88,28 +93,35 @@ const onChange = (fileList) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理上传成功的文件
|
||||
if (info.file.status === 'done' && info.file.response) {
|
||||
handleSuccess(info.file);
|
||||
} else if (info.file.status === 'error') {
|
||||
handleError(info.file.error);
|
||||
}
|
||||
|
||||
previousFileListLength = fileList.length;
|
||||
};
|
||||
|
||||
const beforeUpload = (file, files) => {
|
||||
if (props.limit > 0 && files.length >= props.limit) {
|
||||
Message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||
message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||
return false; // 阻止上传
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
Message.error('上传失败');
|
||||
message.error('上传失败');
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
const customRequest = async (option) => {
|
||||
const { onProgress, onError, onSuccess, fileItem, name } = option;
|
||||
const { onProgress, onError, onSuccess, file, name } = option;
|
||||
try {
|
||||
// 1. 获取预签名上传URL
|
||||
const response = await fetchImageUploadFile({ suffix: getFileExtension(fileItem.file.name) });
|
||||
const response = await fetchImageUploadFile({ suffix: getFileExtension(file.name) });
|
||||
const preSignedUrl = response?.data?.upload_url;
|
||||
|
||||
if (!preSignedUrl) {
|
||||
@ -117,9 +129,9 @@ const customRequest = async (option) => {
|
||||
}
|
||||
console.log('preSignedUrl', preSignedUrl);
|
||||
// 2. 使用预签名URL上传文件
|
||||
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
|
||||
const blob = new Blob([file], { type: file.type });
|
||||
await axios.put(preSignedUrl, blob, {
|
||||
headers: { 'Content-Type': fileItem.file.type },
|
||||
headers: { 'Content-Type': file.type },
|
||||
});
|
||||
|
||||
onSuccess(JSON.stringify(response));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script lang="tsx">
|
||||
import { Button } from '@arco-design/web-vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { Bubble } from '@/components/xt-chat/xt-bubble';
|
||||
|
||||
import Http from '@/api';
|
||||
@ -79,8 +79,8 @@ export default {
|
||||
<header class="header flex justify-end items-center mb-16px px-16px">
|
||||
{hasMediaCenter.value && (
|
||||
<Button
|
||||
type="outline"
|
||||
size="medium"
|
||||
type="primary"
|
||||
ghost
|
||||
class="mr-16px"
|
||||
v-slots={{ icon: () => <icon-plus size="14" /> }}
|
||||
onClick={onAddMediaCenter}
|
||||
@ -90,8 +90,8 @@ export default {
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="outline"
|
||||
size="medium"
|
||||
type="primary"
|
||||
ghost
|
||||
class="mr-16px"
|
||||
v-slots={{ icon: () => <icon-plus size="14" /> }}
|
||||
onClick={onAddTaskManage}
|
||||
|
||||
Reference in New Issue
Block a user