test(component): 测试修改popover

This commit is contained in:
何嘉悦 2025-05-30 17:12:35 +08:00
parent d2275d5932
commit d976d489a2
12 changed files with 609 additions and 457 deletions

View File

@ -13,6 +13,9 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@floating-ui/core": "^1.7.0",
"@floating-ui/dom": "^1.7.0",
"@floating-ui/vue": "^1.1.6",
"@jsonlee_12138/enum": "^1.0.4", "@jsonlee_12138/enum": "^1.0.4",
"@vueuse/core": "^13.2.0", "@vueuse/core": "^13.2.0",
"@vueuse/integrations": "^13.2.0", "@vueuse/integrations": "^13.2.0",

40
pnpm-lock.yaml generated
View File

@ -11,6 +11,15 @@ importers:
'@element-plus/icons-vue': '@element-plus/icons-vue':
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.1(vue@3.5.14(typescript@5.8.3)) version: 2.3.1(vue@3.5.14(typescript@5.8.3))
'@floating-ui/core':
specifier: ^1.7.0
version: 1.7.0
'@floating-ui/dom':
specifier: ^1.7.0
version: 1.7.0
'@floating-ui/vue':
specifier: ^1.1.6
version: 1.1.6(vue@3.5.14(typescript@5.8.3))
'@jsonlee_12138/enum': '@jsonlee_12138/enum':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4 version: 1.0.4
@ -599,6 +608,9 @@ packages:
'@floating-ui/utils@0.2.9': '@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
'@floating-ui/vue@1.1.6':
resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==}
'@humanfs/core@0.19.1': '@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
@ -725,67 +737,56 @@ packages:
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.41.0': '@rollup/rollup-linux-arm-musleabihf@4.41.0':
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.41.0': '@rollup/rollup-linux-arm64-gnu@4.41.0':
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.41.0': '@rollup/rollup-linux-arm64-musl@4.41.0':
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.41.0': '@rollup/rollup-linux-loongarch64-gnu@4.41.0':
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0': '@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.41.0': '@rollup/rollup-linux-riscv64-gnu@4.41.0':
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.41.0': '@rollup/rollup-linux-riscv64-musl@4.41.0':
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.41.0': '@rollup/rollup-linux-s390x-gnu@4.41.0':
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.41.0': '@rollup/rollup-linux-x64-gnu@4.41.0':
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.41.0': '@rollup/rollup-linux-x64-musl@4.41.0':
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.41.0': '@rollup/rollup-win32-arm64-msvc@4.41.0':
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==}
@ -1124,49 +1125,41 @@ packages:
resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-arm64-musl@1.7.2': '@unrs/resolver-binding-linux-arm64-musl@1.7.2':
resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2':
resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2':
resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-riscv64-musl@1.7.2': '@unrs/resolver-binding-linux-riscv64-musl@1.7.2':
resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-linux-s390x-gnu@1.7.2': '@unrs/resolver-binding-linux-s390x-gnu@1.7.2':
resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-gnu@1.7.2': '@unrs/resolver-binding-linux-x64-gnu@1.7.2':
resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@unrs/resolver-binding-linux-x64-musl@1.7.2': '@unrs/resolver-binding-linux-x64-musl@1.7.2':
resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@unrs/resolver-binding-wasm32-wasi@1.7.2': '@unrs/resolver-binding-wasm32-wasi@1.7.2':
resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==}
@ -5266,6 +5259,15 @@ snapshots:
'@floating-ui/utils@0.2.9': {} '@floating-ui/utils@0.2.9': {}
'@floating-ui/vue@1.1.6(vue@3.5.14(typescript@5.8.3))':
dependencies:
'@floating-ui/dom': 1.7.0
'@floating-ui/utils': 0.2.9
vue-demi: 0.14.10(vue@3.5.14(typescript@5.8.3))
transitivePeerDependencies:
- '@vue/composition-api'
- vue
'@humanfs/core@0.19.1': {} '@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6': '@humanfs/node@0.16.6':

View File

@ -0,0 +1,34 @@
<!-- 深度思考按钮 -->
<script setup lang="ts">
import { useChatStore } from '@/stores/modules/chat';
const chatStore = useChatStore();
const isDeepThinking = computed(() => chatStore.isDeepThinking);
//
function setIsDeepThinking() {
chatStore.setDeepThinking(!chatStore.isDeepThinking);
}
</script>
<template>
<div
:class="{ 'is-select': isDeepThinking }"
class="deep-thinking-btn flex items-center p-10px rounded-10px rounded-15px cursor-pointer font-size-12px border-1px border-[rgba(0,0,0,0.08)] border-solid hover:bg-[rgba(0,0,0,.04)]"
@click="setIsDeepThinking"
>
<el-icon>
<ElementPlus />
</el-icon>
<span>深度思考</span>
</div>
</template>
<style scoped lang="scss">
.deep-thinking-btn.is-select {
color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 15px;
}
</style>

View File

@ -49,7 +49,7 @@ function handleClick(item: GetSessionListVO) {
<Popover <Popover
ref="popoverRef" ref="popoverRef"
position="top-start" position="top-start"
:offset="[-14, 14]" :offset="[-14, 10]"
:trigger-style="{ cursor: 'pointer' }" :trigger-style="{ cursor: 'pointer' }"
popover-class="popover-content" popover-class="popover-content"
:popover-style="popoverStyle" :popover-style="popoverStyle"
@ -58,7 +58,7 @@ function handleClick(item: GetSessionListVO) {
<!-- 触发元素插槽 --> <!-- 触发元素插槽 -->
<template #trigger> <template #trigger>
<div <div
class="model-switch-box select-none flex items-center gap-4px px-10px py-8px rounded-15px cursor-pointer font-size-12px" class="model-switch-box select-none flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-12px border-[rgba()]"
> >
<div class="model-switch-box-icon"> <div class="model-switch-box-icon">
<SvgIcon name="models" size="12" /> <SvgIcon name="models" size="12" />
@ -70,7 +70,11 @@ function handleClick(item: GetSessionListVO) {
</template> </template>
<div class="popover-content-box"> <div class="popover-content-box">
<div v-for="item in popoverList" :key="item.id" class="popover-content-box-items h-full"> <div
v-for="item in popoverList"
:key="item.id"
class="popover-content-box-items flex rounded-8px select-none transition-all transition-duration-300 flex items-center gap-8px p-4px hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
>
<el-tooltip <el-tooltip
popper-class="rounded-tooltip" popper-class="rounded-tooltip"
effect="dark" effect="dark"
@ -78,6 +82,7 @@ function handleClick(item: GetSessionListVO) {
trigger="hover" trigger="hover"
:offset="10" :offset="10"
:show-arrow="false" :show-arrow="false"
transition="zoom-fade"
> >
<template #content> <template #content>
<div class="popover-content-box-item-text text-wrap max-w-200px rounded-lg"> <div class="popover-content-box-item-text text-wrap max-w-200px rounded-lg">
@ -85,16 +90,12 @@ function handleClick(item: GetSessionListVO) {
</div> </div>
</template> </template>
<div <div
class="popover-content-box-item select-none transition-all transition-duration-300 flex items-center h-full gap-8px p-8px pl-10px pr-12px rounded-lg hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]" class="popover-content-box-item font-size-12px text-overflow w-full line-height-16px"
:class="{ 'bg-[rgba(0,0,0,.04)] is-select': item.modelName === currentModelName }" :class="{ 'bg-[rgba(0,0,0,.04)] is-select': item.modelName === currentModelName }"
@click="handleClick(item)" @click="handleClick(item)"
>
<div
class="popover-content-box-item-text font-size-12px text-overflow max-h-120px line-height-snug"
> >
{{ item.modelName }} {{ item.modelName }}
</div> </div>
</div>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
@ -106,11 +107,32 @@ function handleClick(item: GetSessionListVO) {
.model-switch-box { .model-switch-box {
color: var(--el-color-primary, #409eff); color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff); border: 1px solid var(--el-color-primary, #409eff);
border-radius: 15px; border-radius: 10px;
} }
.popover-content-box-item.is-select { .popover-content-box-item.is-select {
color: var(--el-color-primary, #409eff); color: var(--el-color-primary, #409eff);
font-weight: 700; font-weight: 700;
} }
.popover-content-box {
// background-color: red;
height: 200px;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
//
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: #f5f5f5;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 4px;
}
}
</style> </style>

View File

@ -0,0 +1,431 @@
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { onClickOutside, useEventListener } from '@vueuse/core';
type PopoverPosition =
| 'top'
| 'top-start'
| 'top-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
| 'right'
| 'right-start'
| 'right-end';
type Offset = [number, number];
const props = withDefaults(defineProps<PopoverProps>(), {
position: 'bottom',
offset: () => [8, 8],
boundary: 'viewport',
closeOnContentClick: false,
closeOnTriggerClick: false,
triggerStyle: () => ({}),
popoverStyle: () => ({}),
popoverClass: '',
});
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
(e: 'positionChange', pos: PopoverPosition): void;
}>();
const VIEWPORT_PADDING = 16;
interface PopoverProps {
position?: PopoverPosition;
offset?: Offset;
triggerStyle?: CSSProperties;
popoverStyle?: CSSProperties;
popoverClass?: string;
boundary?: 'viewport' | HTMLElement;
closeOnContentClick?: boolean;
closeOnTriggerClick?: boolean;
}
const triggerRef = ref<HTMLElement | null>(null);
const popoverRef = ref<HTMLElement | null>(null);
const showPoperContent = ref(false);
const currentPosition = ref<PopoverPosition>(props.position);
let resizeObserver: ResizeObserver | null = null;
let mutationObserver: MutationObserver | null = null;
let updatePositionTimeout: number | null = null;
//
function initObservers() {
if (!popoverRef.value)
return;
//
resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(updatePosition);
});
resizeObserver.observe(popoverRef.value);
// /
mutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
updatePosition();
}
}
requestAnimationFrame(updatePosition);
});
mutationObserver.observe(popoverRef.value, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class'],
});
}
//
function destroyObservers() {
resizeObserver?.disconnect();
mutationObserver?.disconnect();
resizeObserver = null;
mutationObserver = null;
}
// DOM
function updatePosition() {
if (!triggerRef.value || !popoverRef.value)
return;
//
triggerRef.value.getBoundingClientRect();
popoverRef.value.getBoundingClientRect();
const triggerRect = triggerRef.value.getBoundingClientRect();
const popoverRect = popoverRef.value.getBoundingClientRect();
const boundaryRect = getBoundaryRect();
const adjustedPos = adjustPosition(triggerRect, popoverRect, boundaryRect);
currentPosition.value = adjustedPos;
emits('positionChange', adjustedPos);
const { top, left, origin } = calculatePosition(triggerRect, popoverRect, adjustedPos);
popoverRef.value.style.top = `${top}px`;
popoverRef.value.style.left = `${left}px`;
popoverRef.value.style.transformOrigin = origin;
}
//
useEventListener('resize', () => {
if (updatePositionTimeout)
clearTimeout(updatePositionTimeout);
updatePositionTimeout = setTimeout(updatePosition, 50);
});
// /
watch(
showPoperContent,
(newVal) => {
if (newVal) {
nextTick(() => {
initObservers();
updatePosition();
});
}
else {
destroyObservers();
}
},
{ immediate: true },
);
//
function getBoundaryRect(): DOMRect {
if (props.boundary === 'viewport') {
return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
}
return (props.boundary as HTMLElement).getBoundingClientRect();
}
//
function calculatePosition(triggerRect: DOMRect, popoverRect: DOMRect, position: PopoverPosition) {
const [offsetX, offsetY] = props.offset!;
const { width: tW, height: tH } = triggerRect;
const { width: pW, height: pH } = popoverRect;
const positionMap: Record<PopoverPosition, { top: number; left: number; origin: string }> = {
'top': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + tW / 2 - pW / 2 + offsetX,
origin: 'bottom center',
},
'top-start': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + offsetX,
origin: 'bottom left',
},
'top-end': {
top: triggerRect.top - pH - offsetY,
left: triggerRect.left + tW - pW + offsetX,
origin: 'bottom right',
},
'bottom': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + tW / 2 - pW / 2 + offsetX,
origin: 'top center',
},
'bottom-start': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + offsetX,
origin: 'top left',
},
'bottom-end': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + tW - pW + offsetX,
origin: 'top right',
},
'left': {
top: triggerRect.top + tH / 2 - pH / 2 + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right center',
},
'left-start': {
top: triggerRect.top + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right top',
},
'left-end': {
top: triggerRect.top + tH - pH + offsetY,
left: triggerRect.left - pW - offsetX,
origin: 'right bottom',
},
'right': {
top: triggerRect.top + tH / 2 - pH / 2 + offsetY,
left: triggerRect.right + offsetX,
origin: 'left center',
},
'right-start': {
top: triggerRect.top + offsetY,
left: triggerRect.right + offsetX,
origin: 'left top',
},
'right-end': {
top: triggerRect.top + tH - pH + offsetY,
left: triggerRect.right + offsetX,
origin: 'left bottom',
},
};
return positionMap[position];
}
//
function adjustPosition(
triggerRect: DOMRect,
popoverRect: DOMRect,
boundaryRect: DOMRect,
): PopoverPosition {
const allPositions: PopoverPosition[] = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
];
const candidatePositions = [props.position, ...allPositions.filter(p => p !== props.position)];
for (const pos of candidatePositions) {
const { top, left } = calculatePosition(triggerRect, popoverRect, pos);
if (
top >= boundaryRect.top + VIEWPORT_PADDING
&& left >= boundaryRect.left + VIEWPORT_PADDING
&& top + popoverRect.height <= boundaryRect.bottom - VIEWPORT_PADDING
&& left + popoverRect.width <= boundaryRect.right - VIEWPORT_PADDING
) {
return pos;
}
}
return props.position;
}
//
function handleTriggerClick() {
if (showPoperContent.value) {
props.closeOnTriggerClick && hidePopover();
}
else {
showPoperContent.value = true;
nextTick(() => emits('show'));
}
}
function handleContentClick(e: MouseEvent) {
props.closeOnContentClick && hidePopover();
e.stopPropagation();
}
function hidePopover() {
showPoperContent.value = false;
emits('hide');
}
onClickOutside(popoverRef, () => !props.closeOnTriggerClick && hidePopover(), {
ignore: [triggerRef],
});
onUnmounted(() => {
destroyObservers();
updatePositionTimeout && clearTimeout(updatePositionTimeout);
});
defineExpose({ show: () => (showPoperContent.value = true), hide: hidePopover });
</script>
<template>
<div
ref="triggerRef"
:style="props.triggerStyle"
role="button"
aria-haspopup="true"
:aria-expanded="showPoperContent"
@click.stop="handleTriggerClick"
>
<slot name="trigger" />
</div>
<Teleport to="body">
<Transition name="popover-fade" @before-enter="updatePosition">
<div
v-if="showPoperContent"
ref="popoverRef"
class="popover-content"
:style="props.popoverStyle"
:class="[props.popoverClass]"
role="dialog"
aria-modal="false"
:data-popper-placement="currentPosition"
@click="handleContentClick"
>
<slot name="header" />
<slot />
<slot name="footer" />
</div>
</Transition>
</Teleport>
</template>
<style lang="scss">
.popover-fade-enter-active,
.popover-fade-leave-active {
transition:
opacity 0.2s ease,
transform 0.2s ease;
will-change: transform, opacity;
}
.popover-fade-enter-from,
.popover-fade-leave-to {
opacity: 0;
transform: scale(0.95);
}
.popover-fade-enter-to,
.popover-fade-leave-from {
opacity: 1;
transform: scale(1);
}
.popover-content {
position: fixed;
min-width: 120px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 12px 16px;
z-index: 1000;
&::before {
content: "";
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
}
[data-popper-placement^="top"]::before {
top: 100%;
border-top-color: #e5e7eb;
}
[data-popper-placement="top"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="top-start"]::before {
left: 16px;
}
[data-popper-placement="top-end"]::before {
right: 16px;
}
[data-popper-placement^="bottom"]::before {
bottom: 100%;
border-bottom-color: #e5e7eb;
}
[data-popper-placement="bottom"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="bottom-start"]::before {
left: 16px;
}
[data-popper-placement="bottom-end"]::before {
right: 16px;
}
[data-popper-placement^="left"]::before {
left: 100%;
border-left-color: #e5e7eb;
}
[data-popper-placement="left"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="left-start"]::before {
top: 16px;
}
[data-popper-placement="left-end"]::before {
bottom: 16px;
}
[data-popper-placement^="right"]::before {
right: 100%;
border-right-color: #e5e7eb;
}
[data-popper-placement="right"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="right-start"]::before {
top: 16px;
}
[data-popper-placement="right-end"]::before {
bottom: 16px;
}
}
</style>

View File

@ -1,6 +1,29 @@
<!-- Popover 弹框 -->
<script setup lang="ts"> <script setup lang="ts">
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { onClickOutside } from '@vueuse/core'; import { flip, offset, shift, useFloating } from '@floating-ui/vue';
const props = withDefaults(defineProps<PopoverProps>(), {
position: 'bottom',
offset: () => [8, 8],
boundary: 'viewport',
closeOnContentClick: false,
closeOnTriggerClick: false,
triggerStyle: () => ({}),
popoverStyle: () => ({}),
popoverClass: '',
});
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
(e: 'positionChange', pos: PopoverPosition): void;
}>();
const reference = ref();
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
placement: props.position,
middleware: [offset(props.offset[0]), flip(), shift()],
});
type PopoverPosition = type PopoverPosition =
| 'top' | 'top'
@ -17,24 +40,6 @@ type PopoverPosition =
| 'right-end'; | 'right-end';
type Offset = [number, number]; type Offset = [number, number];
const props = withDefaults(defineProps<PopoverProps>(), {
position: 'bottom',
offset: () => [8, 8],
boundary: 'viewport',
closeOnContentClick: false,
closeOnTriggerClick: false,
triggerStyle: () => ({}),
popoverStyle: () => ({}),
popoverClass: '',
});
const emits = defineEmits<{
(e: 'show'): void;
(e: 'hide'): void;
(e: 'positionChange', pos: PopoverPosition): void;
}>();
const VIEWPORT_PADDING = 16;
interface PopoverProps { interface PopoverProps {
position?: PopoverPosition; position?: PopoverPosition;
offset?: Offset; offset?: Offset;
@ -46,362 +51,42 @@ interface PopoverProps {
closeOnTriggerClick?: boolean; closeOnTriggerClick?: boolean;
} }
const triggerRef = ref<HTMLElement | null>(null);
const popoverRef = ref<HTMLElement | null>(null);
const showPoperContent = ref(false); const showPoperContent = ref(false);
const currentPosition = ref<PopoverPosition>(props.position);
//
function beforeEnter() {
updatePosition();
}
function getBoundaryRect() {
if (props.boundary === 'viewport') {
return {
top: 0,
left: 0,
right: window.innerWidth,
bottom: window.innerHeight,
width: window.innerWidth,
height: window.innerHeight,
x: 0,
y: 0,
toJSON: () => ({}),
};
}
return (props.boundary as HTMLElement).getBoundingClientRect();
}
function calculatePosition(
triggerRect: DOMRect,
popoverRect: DOMRect,
position: PopoverPosition,
): { top: number; left: number; origin: string } {
const [offsetX, offsetY] = props.offset!; // X/Y
const { width: tWidth, height: tHeight } = triggerRect;
const { width: pWidth, height: pHeight } = popoverRect;
const positionMap: Record<PopoverPosition, { top: number; left: number; origin: string }> = {
// top/bottomYoffsetYXoffsetX
'top': {
top: triggerRect.top - pHeight - offsetY, // Y - Y
left: triggerRect.left + tWidth / 2 - pWidth / 2 + offsetX, // X + X
origin: 'bottom center',
},
'top-start': {
top: triggerRect.top - pHeight - offsetY,
left: triggerRect.left + offsetX, // X + X
origin: 'bottom left',
},
'top-end': {
top: triggerRect.top - pHeight - offsetY,
left: triggerRect.left + tWidth - pWidth + offsetX, // X - + X
origin: 'bottom right',
},
'bottom': {
top: triggerRect.bottom + offsetY, // Y + Y
left: triggerRect.left + tWidth / 2 - pWidth / 2 + offsetX, // X + X
origin: 'top center',
},
'bottom-start': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + offsetX, // X + X
origin: 'top left',
},
'bottom-end': {
top: triggerRect.bottom + offsetY,
left: triggerRect.left + tWidth - pWidth + offsetX, // X - + X
origin: 'top right',
},
// left/rightXoffsetXYoffsetY
'left': {
top: triggerRect.top + tHeight / 2 - pHeight / 2 + offsetY, // Y + Y
left: triggerRect.left - pWidth - offsetX, // X - - X
origin: 'right center',
},
'left-start': {
top: triggerRect.top + offsetY, // Y + Y
left: triggerRect.left - pWidth - offsetX,
origin: 'right top',
},
'left-end': {
top: triggerRect.top + tHeight - pHeight + offsetY, // Y - + Y
left: triggerRect.left - pWidth - offsetX,
origin: 'right bottom',
},
'right': {
top: triggerRect.top + tHeight / 2 - pHeight / 2 + offsetY, // Y + Y
left: triggerRect.right + offsetX, // X + X
origin: 'left center',
},
'right-start': {
top: triggerRect.top + offsetY, // Y + Y
left: triggerRect.right + offsetX,
origin: 'left top',
},
'right-end': {
top: triggerRect.top + tHeight - pHeight + offsetY, // Y - + Y
left: triggerRect.right + offsetX,
origin: 'left bottom',
},
};
return positionMap[position];
}
function adjustPosition(
triggerRect: DOMRect,
popoverRect: DOMRect,
boundaryRect: DOMRect,
): PopoverPosition {
const allPositions: PopoverPosition[] = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
];
const candidatePositions = [props.position, ...allPositions.filter(p => p !== props.position)];
for (const pos of candidatePositions) {
const { top, left } = calculatePosition(triggerRect, popoverRect, pos);
if (
top >= boundaryRect.top + VIEWPORT_PADDING
&& left >= boundaryRect.left + VIEWPORT_PADDING
&& top + popoverRect.height <= boundaryRect.bottom - VIEWPORT_PADDING
&& left + popoverRect.width <= boundaryRect.right - VIEWPORT_PADDING
) {
return pos;
}
}
return props.position;
}
function updatePosition() {
if (!triggerRef.value || !popoverRef.value)
return;
//
const triggerRect = triggerRef.value.getBoundingClientRect();
const popoverRect = popoverRef.value.getBoundingClientRect();
const boundaryRect = getBoundaryRect();
const adjustedPos = adjustPosition(triggerRect, popoverRect, boundaryRect);
currentPosition.value = adjustedPos;
emits('positionChange', adjustedPos);
const { top, left, origin } = calculatePosition(triggerRect, popoverRect, adjustedPos);
popoverRef.value.style.top = `${top}px`;
popoverRef.value.style.left = `${left}px`;
popoverRef.value.style.transformOrigin = origin;
}
watch(
[() => showPoperContent.value, () => props.position],
async ([newShow]) => {
if (newShow) {
//
await nextTick();
updatePosition(); // requestAnimationFrame
}
},
{ immediate: true },
);
onMounted(() => {
window.addEventListener('resize', updatePosition);
});
onUnmounted(() => {
window.removeEventListener('resize', updatePosition);
});
function handleTriggerClick() { function handleTriggerClick() {
showPoperContent.value = !showPoperContent.value;
if (showPoperContent.value) { if (showPoperContent.value) {
if (props.closeOnTriggerClick) emits('show');
hidePopover();
} }
else { else {
showPoperContent.value = true; emits('hide');
nextTick(() => emits('show'));
} }
} }
function handleContentClick(e: MouseEvent) {
if (props.closeOnContentClick)
hidePopover();
e.stopPropagation();
}
function hidePopover() {
showPoperContent.value = false;
emits('hide');
}
onClickOutside(popoverRef, () => !props.closeOnTriggerClick && hidePopover(), {
ignore: [triggerRef],
});
defineExpose({
show: () => (showPoperContent.value = true),
hide: hidePopover,
});
</script> </script>
<template> <template>
<div <div>
ref="triggerRef" <div ref="reference" :style="props.triggerStyle" @click.stop="handleTriggerClick">
:style="props.triggerStyle"
role="button"
aria-haspopup="true"
:aria-expanded="showPoperContent"
@click.stop="handleTriggerClick"
>
<slot name="trigger" /> <slot name="trigger" />
</div> </div>
<Teleport to="body"> <Transition name="popover-fade">
<Transition name="popover-fade" @before-enter="beforeEnter">
<div <div
v-if="showPoperContent" v-if="showPoperContent"
ref="popoverRef" ref="floating"
class="popover-content" class="popover-content"
:style="props.popoverStyle" :style="{
...floatingStyles,
...props.popoverStyle,
}"
:class="[props.popoverClass]" :class="[props.popoverClass]"
role="dialog"
aria-modal="false"
:data-popper-placement="currentPosition"
@click="handleContentClick"
> >
<slot name="header" /> <slot name="header" />
<slot /> <slot />
<slot name="footer" /> <slot name="footer" />
</div> </div>
</Transition> </Transition>
</Teleport> </div>
</template> </template>
<style lang="scss"> <style scoped lang="scss"></style>
/* 动画样式保持不变 */
.popover-fade-enter-active,
.popover-fade-leave-active {
transition:
opacity 0.2s ease,
transform 0.2s ease;
will-change: transform, opacity;
}
.popover-fade-enter-from,
.popover-fade-leave-to {
opacity: 0;
transform: scale(0.95);
}
.popover-fade-enter-to,
.popover-fade-leave-from {
opacity: 1;
transform: scale(1);
}
.popover-content {
position: fixed;
min-width: 120px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 12px 16px;
z-index: 1000;
&::before {
content: "";
position: absolute;
width: 0;
height: 0;
border: 6px solid transparent;
}
[data-popper-placement^="top"]::before {
top: 100%;
border-top-color: #e5e7eb;
}
[data-popper-placement="top"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="top-start"]::before {
left: 16px;
}
[data-popper-placement="top-end"]::before {
right: 16px;
}
[data-popper-placement^="bottom"]::before {
bottom: 100%;
border-bottom-color: #e5e7eb;
}
[data-popper-placement="bottom"]::before {
left: 50%;
transform: translateX(-50%);
}
[data-popper-placement="bottom-start"]::before {
left: 16px;
}
[data-popper-placement="bottom-end"]::before {
right: 16px;
}
[data-popper-placement^="left"]::before {
left: 100%;
border-left-color: #e5e7eb;
}
[data-popper-placement="left"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="left-start"]::before {
top: 16px;
}
[data-popper-placement="left-end"]::before {
bottom: 16px;
}
[data-popper-placement^="right"]::before {
right: 100%;
border-right-color: #e5e7eb;
}
[data-popper-placement="right"]::before {
top: 50%;
transform: translateY(-50%);
}
[data-popper-placement="right-start"]::before {
top: 16px;
}
[data-popper-placement="right-end"]::before {
bottom: 16px;
}
}
</style>

View File

@ -95,7 +95,7 @@ function handleClick(item: any) {
<Popover <Popover
ref="popoverRef" ref="popoverRef"
position="bottom-end" position="bottom-end"
:offset="[-10, 8]" :offset="[0, 8]"
:trigger-style="{ cursor: 'pointer' }" :trigger-style="{ cursor: 'pointer' }"
popover-class="popover-content" popover-class="popover-content"
:popover-style="popoverStyle" :popover-style="popoverStyle"

View File

@ -66,24 +66,24 @@ provide('refresh', refreshMainPage);
} }
/* 带id聊天页面中间缩放动画 */ /* 带id聊天页面中间缩放动画 */
.zoom-fade-enter-from { // .zoom-fade-enter-from {
transform: scale(0.8); /* 进入前:缩小隐藏 */ // transform: scale(0.9); /* */
opacity: 0; // opacity: 0;
} // }
.zoom-fade-enter-active, // .zoom-fade-enter-active,
.zoom-fade-leave-active { // .zoom-fade-leave-active {
transition: all 0.3s; /* 缓入动画 */ // transition: all 0.3s; /* */
} // }
.zoom-fade-enter-to { // .zoom-fade-enter-to {
transform: scale(1); /* 进入后:正常大小 */ // transform: scale(1); /* */
opacity: 1; // opacity: 1;
} // }
.zoom-fade-leave-from { // .zoom-fade-leave-from {
transform: scale(1); /* 离开前:正常大小 */ // transform: scale(1); /* */
opacity: 1; // opacity: 1;
} // }
.zoom-fade-leave-to { // .zoom-fade-leave-to {
transform: scale(0.8); /* 离开后:缩小隐藏 */ // transform: scale(0.9); /* */
opacity: 0; // opacity: 0;
} // }
</style> </style>

View File

@ -3,16 +3,12 @@
import ModelSelect from '@/components/ModelSelect/index.vue'; import ModelSelect from '@/components/ModelSelect/index.vue';
import WelecomeText from '@/components/WelecomeText/index.vue'; import WelecomeText from '@/components/WelecomeText/index.vue';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat';
import { useSessionStore } from '@/stores/modules/session'; import { useSessionStore } from '@/stores/modules/session';
const userStore = useUserStore(); const userStore = useUserStore();
const chatStore = useChatStore();
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
const senderValue = ref(''); const senderValue = ref('');
const isDeepThinking = computed(() => chatStore.isDeepThinking);
async function handleSend() { async function handleSend() {
localStorage.setItem('chatContent', senderValue.value); localStorage.setItem('chatContent', senderValue.value);
await sessionStore.createSessionList({ await sessionStore.createSessionList({
@ -22,11 +18,6 @@ async function handleSend() {
remark: senderValue.value.slice(0, 10), remark: senderValue.value.slice(0, 10),
}); });
} }
//
function setIsDeepThinking() {
chatStore.setDeepThinking(!chatStore.isDeepThinking);
}
</script> </script>
<template> <template>
@ -47,25 +38,13 @@ function setIsDeepThinking() {
<template #prefix> <template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden"> <div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<ModelSelect /> <ModelSelect />
<div <div
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]" class="flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px border-1px border-[rgba(0,0,0,0.08)] border-solid hover:bg-[rgba(0,0,0,.04)]"
> >
<el-icon> <el-icon>
<Paperclip /> <Paperclip />
</el-icon> </el-icon>
</div> </div>
<div
:class="{ 'is-select': isDeepThinking }"
class="flex items-center gap-4px px-10px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
@click="setIsDeepThinking"
>
<el-icon>
<ElementPlus />
</el-icon>
<span>深度思考</span>
</div>
</div> </div>
</template> </template>
</Sender> </Sender>
@ -85,11 +64,5 @@ function setIsDeepThinking() {
.chat-defaul-sender { .chat-defaul-sender {
width: 100%; width: 100%;
} }
.is-select {
color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 15px;
}
} }
</style> </style>

View File

@ -28,7 +28,6 @@ const { startStream: _, cancel, data, error, isLoading } = useXStream();
const route = useRoute(); const route = useRoute();
const chatStore = useChatStore(); const chatStore = useChatStore();
const modelStore = useModelStore(); const modelStore = useModelStore();
const isDeepThinking = computed(() => chatStore.isDeepThinking);
const inputValue = ref('帮我写一篇小米手机介绍'); const inputValue = ref('帮我写一篇小米手机介绍');
const senderRef = ref<any>(null); const senderRef = ref<any>(null);
const bubbleItems = ref<MessageItem[]>([]); const bubbleItems = ref<MessageItem[]>([]);
@ -75,10 +74,6 @@ watch(
{ immediate: true, deep: true }, { immediate: true, deep: true },
); );
//
function setIsDeepThinking() {
chatStore.setDeepThinking(!chatStore.isDeepThinking);
}
// //
function handleDataChunk(chunk: string) { function handleDataChunk(chunk: string) {
if (chunk === ' [DONE]') { if (chunk === ' [DONE]') {
@ -253,23 +248,12 @@ function handleChange(payload: { value: boolean; status: ThinkingStatus }) {
<template #prefix> <template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden"> <div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<div <div
class="flex items-center gap-4px px-12px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]" class="flex items-center gap-4px p-10px rounded-10px cursor-pointer font-size-14px border-1px border-[rgba(0,0,0,0.08)] border-solid hover:bg-[rgba(0,0,0,.04)]"
> >
<el-icon> <el-icon>
<Paperclip /> <Paperclip />
</el-icon> </el-icon>
</div> </div>
<div
:class="{ 'is-select': isDeepThinking }"
class="flex items-center gap-4px px-10px py-8px rounded-15px cursor-pointer font-size-12px border-1px border-gray border-solid hover:bg-[rgba(0,0,0,.04)]"
@click="setIsDeepThinking"
>
<el-icon>
<ElementPlus />
</el-icon>
<span>深度思考</span>
</div>
</div> </div>
</template> </template>
@ -338,11 +322,5 @@ function handleChange(payload: { value: boolean; status: ThinkingStatus }) {
margin-bottom: 22px; margin-bottom: 22px;
width: 100%; width: 100%;
} }
.is-select {
color: var(--el-color-primary, #409eff);
border: 1px solid var(--el-color-primary, #409eff);
border-radius: 15px;
}
} }
</style> </style>

View File

@ -20,10 +20,31 @@
} }
} }
// rounded-tooltip // rounded-tooltip 提示框样式
.rounded-tooltip { .rounded-tooltip {
border-radius: 10px !important; border-radius: 10px !important;
display: flex; display: flex;
align-items: center; align-items: center;
height: fit-content; height: fit-content;
} }
.zoom-fade-enter-from {
transform: scale(0.9); /* 进入前:缩小隐藏 */
opacity: 0;
}
.zoom-fade-enter-active,
.zoom-fade-leave-active {
transition: transform 0.3s, opacity 0.3s; /* 缓入动画 */
}
.zoom-fade-enter-to {
transform: scale(1); /* 进入后:正常大小 */
opacity: 1;
}
.zoom-fade-leave-from {
transform: scale(1); /* 离开前:正常大小 */
opacity: 1;
}
.zoom-fade-leave-to {
transform: scale(0.9); /* 离开后:缩小隐藏 */
opacity: 0;
}

View File

@ -3,12 +3,14 @@
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable // biome-ignore lint: disable
export {}; export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default'] AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
copy: typeof import('./../src/components/Popover/index copy.vue')['default']
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
@ -23,6 +25,7 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default'] IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
'Index copy': typeof import('./../src/components/Popover/index copy.vue')['default']
LoginDialog: typeof import('./../src/components/LoginDialog/index.vue')['default'] LoginDialog: typeof import('./../src/components/LoginDialog/index.vue')['default']
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default'] ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
Popover: typeof import('./../src/components/Popover/index.vue')['default'] Popover: typeof import('./../src/components/Popover/index.vue')['default']