Merge pull request 'feature/linzhijun_扣子智能体_0710' (#24) from feature/linzhijun_扣子智能体_0710 into main
Reviewed-on: ai-team/lingji-work-fe#24
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div class="right-wrap">
|
||||
<div class="relative mr-12px" @click="setUnread">
|
||||
<!-- 灵机空间入口 -->
|
||||
<div class="agent-entry" :class="isAgentRoute ? 'agent' : ''" @click="handleAgentClick"></div>
|
||||
|
||||
<!-- 任务中心 -->
|
||||
<div class="relative mx-16px" @click="setUnread">
|
||||
<SvgIcon
|
||||
name="svg-taskCenter"
|
||||
size="16"
|
||||
@ -10,6 +14,7 @@
|
||||
<div class="w-4px h-4px rounded-50% bg-#F64B31 absolute top-1px right-1px" v-if="hasUnreadInfo"></div>
|
||||
</div>
|
||||
|
||||
<!-- 头像设置 -->
|
||||
<a-dropdown trigger="click" class="layout-avatar-dropdown">
|
||||
<a-avatar class="cursor-pointer" :size="32">
|
||||
<img alt="avatar" src="@/assets/avatar.svg" />
|
||||
@ -83,9 +88,17 @@ import icon1 from '@/assets/option.svg';
|
||||
import icon2 from '@/assets/exit.svg';
|
||||
import icon3 from '@/assets/change.svg';
|
||||
|
||||
const props = defineProps({
|
||||
isAgentRoute: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const enterpriseStore = useEnterpriseStore();
|
||||
const userStore = useUserStore();
|
||||
const sideBarStore = useSidebarStore();
|
||||
const route = useRoute();
|
||||
|
||||
const hasUnreadInfo = computed(() => sideBarStore.unreadInfo.length);
|
||||
|
||||
@ -117,15 +130,13 @@ const setUnread = () => {
|
||||
sideBarStore.removeTaskUnreadInfo();
|
||||
}
|
||||
};
|
||||
const handleAgentClick = () => {
|
||||
router.push({ name: props.isAgentRoute ? 'Home' : 'AgentIndex' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.right-wrap {
|
||||
display: flex;
|
||||
padding-right: 20px;
|
||||
list-style: none;
|
||||
align-items: center;
|
||||
}
|
||||
@import './style.scss';
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.layout-avatar-dropdown,
|
||||
|
||||
27
src/components/_base/navbar/components/right-side/style.scss
Normal file
27
src/components/_base/navbar/components/right-side/style.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.right-wrap {
|
||||
display: flex;
|
||||
padding-right: 20px;
|
||||
list-style: none;
|
||||
align-items: center;
|
||||
.agent-entry {
|
||||
width: 100px;
|
||||
height: 32px;
|
||||
background: url('@/assets/img/agent/icon-entry.png') 100% 100%;
|
||||
background-size: cover;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: url('@/assets/img/agent/icon-entry-hover.png') 100% 100%;
|
||||
background-size: cover;
|
||||
}
|
||||
&.agent {
|
||||
background: url('@/assets/img/agent/icon-home.png') 100% 100%;
|
||||
background-size: cover;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: url('@/assets/img/agent/icon-home-hover.png') 100% 100%;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,9 @@
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<NavbarMenu />
|
||||
<NavbarMenu v-if="!isAgentRoute"/>
|
||||
</div>
|
||||
<RightSide />
|
||||
<RightSide :isAgentRoute="isAgentRoute" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -17,6 +17,12 @@ import NavbarMenu from './components/navbar-menu';
|
||||
import RightSide from './components/right-side';
|
||||
|
||||
import router from '@/router';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const isAgentRoute = computed(() => {
|
||||
return route.meta?.isAgentRoute;
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.navbar-wrap {
|
||||
|
||||
141
src/components/text-over-tips/index.vue
Normal file
141
src/components/text-over-tips/index.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<a-tooltip :disabled="isShowBtn || (!isShowBtn && disabled)" :placement="props.placement">
|
||||
<template #content>
|
||||
<div :style="contentStyle" class="tip-content">{{ props.context }}</div>
|
||||
</template>
|
||||
<div class="overflow-hidden">
|
||||
<div v-bind="$attrs" ref="Text" :class="`${isShow ? '' : `line-${props.line}`} `" class="overflow-text">
|
||||
{{ props.context }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isShowBtn && !disabled"
|
||||
class="color-#8C8C8C flex items-center cursor-pointer mt-2px"
|
||||
@click="
|
||||
() => {
|
||||
isShow = !isShow;
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ isShow ? '收起' : '展开' }}
|
||||
<icon-up size="16" :class="{ active: isShow }" class="ml-2px color-#8C8C8C" />
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, toRefs, onBeforeMount, onMounted, watchEffect, computed, watch, nextTick, defineProps } from 'vue';
|
||||
import elementResizeDetectorMaker from 'element-resize-detector';
|
||||
|
||||
const props = defineProps({
|
||||
context: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
},
|
||||
line: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
maxHeight: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
maxWidth: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
isShowBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const data = reactive({});
|
||||
const isShow = ref(false);
|
||||
const contentStyle = computed(() => {
|
||||
let style = {
|
||||
'max-height': props.maxHeight + 'px',
|
||||
'max-width': props.maxWidth + 'px',
|
||||
overflow: 'auto',
|
||||
padding: '1px 0',
|
||||
};
|
||||
return props.maxHeight || props.maxWidth ? style : {};
|
||||
});
|
||||
const disabled = ref(true);
|
||||
const Text = ref(null);
|
||||
const textWidth = ref();
|
||||
watch(
|
||||
[() => props.context, textWidth],
|
||||
async () => {
|
||||
if (props.context) {
|
||||
await nextTick();
|
||||
nextTick(() => {
|
||||
if (props.line < 2) {
|
||||
if (Text.value?.clientWidth < Text.value?.scrollWidth) {
|
||||
disabled.value = false;
|
||||
} else {
|
||||
disabled.value = true;
|
||||
}
|
||||
} else {
|
||||
if (Text.value?.clientHeight < Text.value?.scrollHeight) {
|
||||
disabled.value = false;
|
||||
} else {
|
||||
disabled.value = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
onBeforeMount(() => {});
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
const erd = elementResizeDetectorMaker();
|
||||
if (Text.value) {
|
||||
erd.listenTo(Text.value, () => {
|
||||
const _width = Text.value.getBoundingClientRect().width;
|
||||
textWidth.value = _width;
|
||||
});
|
||||
}
|
||||
});
|
||||
watchEffect(() => {});
|
||||
defineExpose({
|
||||
...toRefs(data),
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.overflow-text {
|
||||
display: inline-block;
|
||||
font-family: $font-family-regular;
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
font-style: normal;
|
||||
box-sizing: border-box;
|
||||
white-space: pre-line;
|
||||
&.line-1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&.line-2 {
|
||||
@include multi-ellipsis(2);
|
||||
}
|
||||
&.line-3 {
|
||||
@include multi-ellipsis(3);
|
||||
}
|
||||
}
|
||||
.tip-content {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.active {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
</style>
|
||||
137
src/components/upload/FileUpload.vue
Normal file
137
src/components/upload/FileUpload.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<a-upload
|
||||
:custom-request="customRequest"
|
||||
action="/"
|
||||
:limit="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 { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
|
||||
import axios from 'axios';
|
||||
|
||||
const fileList = ref([]);
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 0, // 0 表示不限制
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const handleSuccess = (fileItem) => {
|
||||
let response = fileItem.response;
|
||||
response = JSON.parse(response);
|
||||
if (response && response.data.file_url) {
|
||||
if (props.limit === 1) {
|
||||
emit('update:modelValue', response.data.file_url);
|
||||
} else {
|
||||
emit('update:modelValue', [...props.modelValue, response.data.file_url]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (value) => {
|
||||
console.log(value, 'value');
|
||||
if (value) {
|
||||
fileList.value =
|
||||
props.limit == 1
|
||||
? [
|
||||
{
|
||||
name: '',
|
||||
url: props.modelValue as string,
|
||||
},
|
||||
]
|
||||
: (props.modelValue as string[]).map((item) => {
|
||||
return {
|
||||
name: '',
|
||||
url: item,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
fileList.value = [];
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
let previousFileListLength = 0;
|
||||
//删除图片
|
||||
const onChange = (fileList) => {
|
||||
if (fileList.length < previousFileListLength) {
|
||||
if (props.limit === 1) {
|
||||
if (fileList.length === 0) {
|
||||
emit('update:modelValue', '');
|
||||
}
|
||||
} else {
|
||||
if (fileList.length === 0) {
|
||||
emit('update:modelValue', []);
|
||||
} else {
|
||||
let image_data = fileList.map((item) => item.url);
|
||||
emit('update:modelValue', image_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousFileListLength = fileList.length;
|
||||
};
|
||||
|
||||
const beforeUpload = (file, files) => {
|
||||
if (props.limit > 0 && files.length >= props.limit) {
|
||||
Message.warning(`最多只能上传 ${props.limit} 张图片`);
|
||||
return false; // 阻止上传
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
Message.error('上传失败');
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
const customRequest = async (option) => {
|
||||
const { onProgress, onError, onSuccess, fileItem, name } = option;
|
||||
try {
|
||||
// 1. 获取预签名上传URL
|
||||
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) });
|
||||
const preSignedUrl = response?.data?.upload_url;
|
||||
|
||||
if (!preSignedUrl) {
|
||||
throw new Error('未能获取有效的预签名上传地址');
|
||||
}
|
||||
console.log('preSignedUrl', preSignedUrl);
|
||||
// 2. 使用预签名URL上传文件
|
||||
const blob = new Blob([fileItem.file], { type: fileItem.file.type });
|
||||
await axios.put(preSignedUrl, blob, {
|
||||
headers: { 'Content-Type': fileItem.file.type },
|
||||
});
|
||||
|
||||
onSuccess(JSON.stringify(response));
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
};
|
||||
|
||||
function getFileExtension(filename: string): string {
|
||||
const match = filename.match(/\.([^.]+)$/);
|
||||
return match ? match[1].toLowerCase() : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 添加一些样式 */
|
||||
</style>
|
||||
Reference in New Issue
Block a user