添加多个样式

This commit is contained in:
王佑琳 2026-03-09 18:41:01 +08:00
parent d9d7ff22b6
commit 1dd8a4ad61
26 changed files with 491 additions and 428 deletions

View File

@ -3,16 +3,16 @@ VITE_BASE = '/'
# 主服务
VITE_API_PREFIX = '/api'
VITE_API_BASE_URL = 'http://43.248.131.153:8001' # http://huanda.xueai.art http://106.54.11.219/api 43.248.131.153:8003
VITE_API_WS_URL = 'ws://huanda.xueai.art/api'
VITE_API_BASE_URL = 'http://test.xueai.art/api' # http://huanda.xueai.art http://106.54.11.219/api 43.248.131.153:8003
VITE_API_WS_URL = 'ws://test.xueai.art/api'
# 支付服务
VITE_API_PAY_PREFIX = '/pay'
VITE_API_PAY_TARGET = 'http://43.248.131.153:8091' # http://43.248.133.202 test.xueai.art
VITE_API_PAY_TARGET = 'http://test.xueai.art' # http://43.248.133.202 test.xueai.art
# AI Music后端服务
VITE_API_WORKFLOW_UPLOAD = 'http://43.248.131.153:8060/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_WORKFLOW_WS = 'ws://43.248.97.19:4000/huanda' # 43.248.131.153 43.248.131.153:8084
# 任务处理模块
VITE_API_WORKFLOW_UPLOAD = 'http://aipaint.xueai.art/aigc/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_WORKFLOW_WS = 'wss://aipaint.xueai.art/testworkflow'
# 是否开启开发者工具
VITE_OPEN_DEVTOOLS = false

View File

@ -6,16 +6,16 @@ VITE_BUILD_MOCK = false
# 主服务
VITE_API_PREFIX = '/api'
VITE_API_BASE_URL = 'https://huanda.xueai.art/api'
VITE_API_WS_URL = 'wss://huanda.xueai.art/api'
VITE_API_BASE_URL = 'https://sxwz.xueai.art/api'
VITE_API_WS_URL = 'wss://sxwz.xueai.art/api'
# 支付服务
VITE_API_PAY_PREFIX = '/pay'
VITE_API_PAY_TARGET = 'https://huanda.xueai.art' # http://43.248.133.202
VITE_API_PAY_TARGET = 'https://sxwz.xueai.art' # http://43.248.133.202
# 任务处理模块
VITE_API_WORKFLOW_UPLOAD = 'https://huanda.xueai.art/huandaCallback/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_WORKFLOW_WS = 'wss://huanda.xueai.art/task'
VITE_API_WORKFLOW_UPLOAD = 'https://aipaint.xueai.art/aigc/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_WORKFLOW_WS = 'wss://aipaint.xueai.art/testworkflow'
# 是否开启KKFileView
FILE_OPEN_PREVIEW = false

2
components.d.ts vendored
View File

@ -43,6 +43,7 @@ declare module 'vue' {
IEpArrow: typeof import('~icons/ep/arrow')['default']
IEpArrowDown: typeof import('~icons/ep/arrow-down')['default']
IEpBack: typeof import('~icons/ep/back')['default']
IEpCalendar: typeof import('~icons/ep/calendar')['default']
IEpCirclePlusFilled: typeof import('~icons/ep/circle-plus-filled')['default']
IEpClose: typeof import('~icons/ep/close')['default']
IEpDelete: typeof import('~icons/ep/delete')['default']
@ -54,6 +55,7 @@ declare module 'vue' {
IEpRefresh: typeof import('~icons/ep/refresh')['default']
IEpRefreshRight: typeof import('~icons/ep/refresh-right')['default']
IEpRight: typeof import('~icons/ep/right')['default']
IEpStar: typeof import('~icons/ep/star')['default']
IEpStarFilled: typeof import('~icons/ep/star-filled')['default']
IEpTop: typeof import('~icons/ep/top')['default']
IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default']

View File

@ -1,6 +1,8 @@
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
export const autoImportConfig = AutoImport({
imports: [
@ -9,11 +11,11 @@ export const autoImportConfig = AutoImport({
'pinia'
],
dts: './auto-imports.d.ts',
resolvers: [ElementPlusResolver()]
resolvers: [ElementPlusResolver(), IconsResolver({ prefix: 'i' })]
})
export const componentsConfig = Components({
dts: './components.d.ts',
dirs: ['src/components'],
resolvers: [ElementPlusResolver()]
resolvers: [ElementPlusResolver(), IconsResolver({ enabledCollections: 'ep' })]
})

View File

@ -7,6 +7,7 @@
"preview": "vite preview"
},
"devDependencies": {
"@iconify-json/ep": "^1.2.4",
"@vitejs/plugin-vue": "^6.0.4",
"eslint": "^10.0.3",
"less": "^4.5.1",
@ -16,6 +17,7 @@
"vite": "^7.3.1"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.13.6",
"element-plus": "^2.13.5",
"pinia": "^3.0.4",

View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.29)
axios:
specifier: ^1.13.6
version: 1.13.6
@ -30,6 +33,9 @@ importers:
specifier: 2.0.0-beta.8
version: 2.0.0-beta.8(vue@3.5.29)
devDependencies:
'@iconify-json/ep':
specifier: ^1.2.4
version: 1.2.4
'@vitejs/plugin-vue':
specifier: ^6.0.4
version: 6.0.4(vite@7.3.1(less@4.5.1)(yaml@2.8.2))(vue@3.5.29)
@ -316,6 +322,9 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@iconify-json/ep@1.2.4':
resolution: {integrity: sha512-dtKOueYp1CRxAUW+GayIiuHqOlELfO0W3QiHOH4NjvKtBn2uxZbByIKQ+7B1Hj+AgZrhWTie3QKYkZn+VQVZrw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@ -2386,6 +2395,10 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@iconify-json/ep@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
'@iconify/utils@3.1.0':

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,5 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="10" fill="#F8F9FA"/>
<path d="M20.25 15H22.5H24C25.6569 15 27 16.3431 27 18C27 19.6569 25.6569 21 24 21H22.5H20.25M15.75 15H13.5H12C10.3431 15 9 16.3431 9 18C9 19.6569 10.3431 21 12 21H13.5H15.75" stroke="#666666" stroke-width="1.5" stroke-linecap="round"/>
<path d="M16 18H20" stroke="#666666" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 475 B

View File

@ -0,0 +1,3 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.5 2.52002L7.5 0.52002L14.5 2.52002M0.5 2.52002L7.5 5.52002M0.5 2.52002V11.52L7.5 14.52M7.5 5.52002L14.5 2.52002M7.5 5.52002V14.52M14.5 2.52002V11.52L7.5 14.52" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="13" height="13" rx="1.5" stroke="#333333"/>
<path d="M7 3H10C10.5523 3 11 3.44772 11 4V7M7 11H4C3.44772 11 3 10.5523 3 10V7" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5H14M5 2H13" stroke="#333333" stroke-linecap="round"/>
<rect x="2.5" y="8.5" width="13" height="7" rx="1.5" stroke="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3124 8.5069C11.3769 8.33278 11.6231 8.33278 11.6876 8.5069L11.807 8.82972C12.2122 9.92459 13.0754 10.7878 14.1703 11.193L14.4931 11.3124C14.6672 11.3769 14.6672 11.6231 14.4931 11.6876L14.1703 11.807C13.0754 12.2122 12.2122 13.0754 11.807 14.1703L11.6876 14.4931C11.6231 14.6672 11.3769 14.6672 11.3124 14.4931L11.193 14.1703C10.7878 13.0754 9.92459 12.2122 8.82972 11.807L8.5069 11.6876C8.33278 11.6231 8.33278 11.3769 8.5069 11.3124L8.82972 11.193C9.92459 10.7878 10.7878 9.92459 11.193 8.82972L11.3124 8.5069Z" fill="#000F33"/>
<path d="M13 7C13 3.68629 10.3137 1 7 1C3.68629 1 1 3.68629 1 7M13 7L11 6M13 7L14.5 5.5M7 13C4.77915 13 2.84012 11.7934 1.80269 10M1.80269 10H4M1.80269 10L1 12" stroke="#000F33" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 874 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6V13C3 14.1046 3.89543 15 5 15H11C12.1046 15 13 14.1046 13 13V6M6.5 7V12M9.5 7V12M2 4H14" stroke="#000F33" stroke-linecap="round"/>
<path d="M5 4V2C5 1.44772 5.44772 1 6 1H10C10.5523 1 11 1.44772 11 2V4" stroke="#000F33"/>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.91669 3.1875C4.19251 1.84034 5.99812 1 8 1C11.866 1 15 4.13401 15 8C15 8.92829 14.8193 9.81437 14.4912 10.625L13.25 8.875M12.977 12.9224C11.7081 14.2052 9.94688 15 8 15C4.13401 15 1 11.866 1 8C1 7.07171 1.18069 6.18563 1.50883 5.375L2.75 7.125" stroke="#000F33" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.30007 11H5.2C5.08954 11 5 10.9105 5 10.8V9.69993C5 9.43471 5.10536 9.18036 5.29289 8.99282L8.57861 5.70711C8.96913 5.31658 9.6023 5.31658 9.99282 5.70711L10.2929 6.00718C10.6834 6.3977 10.6834 7.03087 10.2929 7.42139L7.00718 10.7071C6.81964 10.8946 6.56529 11 6.30007 11Z" fill="#000F33"/>
</svg>

After

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="6.5" cy="6.5" r="5" stroke="#333333"/>
<path d="M12.6464 13.3536C12.8417 13.5488 13.1583 13.5488 13.3536 13.3536C13.5488 13.1583 13.5488 12.8417 13.3536 12.6464L13 13L12.6464 13.3536ZM13 13L13.3536 12.6464L10.3536 9.64645L10 10L9.64645 10.3536L12.6464 13.3536L13 13Z" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@ -9,7 +9,7 @@
>
<slot name="prefix" />
<span class="select-text">{{ selectedOption || placeholder }}</span>
<div v-if="props.isArrow" class="arrow-icon" :class="{ rotate: isOpen }"><i-ep-ArrowDown /></div>
<!-- <div v-if="props.isArrow" class="arrow-icon" :class="{ rotate: isOpen }"><i-ep-ArrowDown /></div> -->
</div>
<div
@ -18,6 +18,7 @@
class="dropdown-menu"
:class="position"
>
<slot name="header" />
<div
v-for="(option, index) in options"
:key="option.value || index"
@ -26,7 +27,10 @@
@click.stop="selectOption(option)"
>
<slot name="option" :option="option" :selected="option.value === selectedValue">
{{ option.label }}
<div class="option-content">
<span class="option-label">{{ option.label }}</span>
<span v-if="option.value === selectedValue" class="option-check"></span>
</div>
</slot>
</div>
</div>
@ -154,7 +158,7 @@ onBeforeUnmount(() => {
.select-header {
width: v-bind(width);
height: 36px;
height: 28px;
background-color: v-bind(background);
border-radius: 10px;
display: flex;
@ -178,7 +182,7 @@ onBeforeUnmount(() => {
.select-text {
font-family: 'Microsoft YaHei';
font-size: 14px;
font-size: 12px;
color: #333333;
white-space: nowrap;
overflow: hidden;
@ -202,16 +206,30 @@ onBeforeUnmount(() => {
.dropdown-menu {
position: absolute;
background-color: #FFFFFF;
border-radius: 20px;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.1);
padding: 20px 0;
border-radius: 16px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
padding: 10px 8px;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
left: 50%;
transform: translateX(-50%);
min-width: 50px;
width: max-content;
border: 1px solid #e8e8e8;
animation: fadeIn 0.2s ease;
gap: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(-50%) translateY(-8px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
.dropdown-menu.bottom {
@ -243,32 +261,60 @@ onBeforeUnmount(() => {
.dropdown-item {
font-family: 'Microsoft YaHei';
font-size: 14px;
color: #666666;
color: #999999;
cursor: pointer;
transition: all 0.3s ease;
transition: all 0.2s ease;
position: relative;
padding: 0 20px;
min-width: 80px;
text-align: center;
padding: 0px 20px;
min-width: 120px;
text-align: left;
white-space: nowrap;
width: 100%;
box-sizing: border-box;
border-radius: 5px;
height: 36px;
display: flex;
align-items: center;
}
.dropdown-item:hover {
color: #000F33;
color: #333333;
}
.dropdown-item.selected {
color: #000F33;
color: #333333;
font-weight: 400;
background-color: #F8F9FA;
border-radius: 10px;
}
.dropdown-item:not(:last-child)::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 28px;
height: 1px;
background-color: rgba(0, 0, 0, 0.1);
.option-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.option-label {
flex: 1;
}
.option-check {
color: #333333;
font-weight: bold;
font-size: 16px;
margin-left: 12px;
animation: checkIn 0.2s ease;
}
@keyframes checkIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
</style>

View File

@ -1,7 +1,10 @@
<template>
<Transition name="slide-up">
<div class="input-container" :class="{ generate : !props.isGenerate}">
<div class="input-container" :class="{ generate : !props.isGenerate }" @click="handleContainerClick">
<div v-if="!props.isGenerate" class="title">AI绘画2026</div>
<div v-if="useDisplay.Sender_variant === 'default'" class="scroll-to-bottom-btn" @click.stop="handleScrollToBottom">
<div class="scroll-to-bottom-text">回到底部</div>
</div>
<Sender :key="useDisplay.Sender_variant" v-model="prompt" :variant="useDisplay.Sender_variant" :placeholder="promptPlaceholder" :submit-btn-disabled="isgerenate.value" :auto-size="autoSizeConfig">
<template #prefix>
<div v-show="useDisplay.Sender_variant !== 'default'" class="prefix-self-wrap">
@ -25,7 +28,7 @@
<el-button v-if="isgerenate" round color="#626aef" style="animation: spin 1s linear infinite;">
<!-- <i-ep-loading /> -->
</el-button>
<div v-else class="gerenate" :class="{ isprompt: prompt }" @click="generate">
<div v-else class="gerenate" :class="{ isprompt: prompt }" @click="handleStart">
<img v-if="!prompt" src="@/assets/dialog/darkArrow.svg" alt="" />
<img v-else src="@/assets/dialog/writerArrow.svg" alt="" />
<div v-show="useDisplay.Sender_variant !== 'default'">生成</div>
@ -57,7 +60,7 @@ import Quantity from './quantity/index.vue'
import Model from './model/index.vue'
import { Sender } from 'vue-element-plus-x'
import { useDisplayStore, useParamStore } from '@/stores'
import { generateSubImage } from '@/utils/websocket'
import { generate } from '@/utils/websocket'
import { useRouter } from 'vue-router'
const props = defineProps({
@ -80,7 +83,7 @@ const model = ref('flux')
const proportion = ref('9:16')
const quantity = ref(1)
const promptPlaceholder = '结合图片,描述你想生成的画面和动作。例如:电影感剧照,氛围温聲。柔和色调,略带胶片颗粒感,连贯摆出棚拍的动作。'
const promptPlaceholder = '描述你想生成的画面和动作。'
const prompt = ref('')
const imageurl = ref('')
const imageurlShow = ref('')
@ -88,7 +91,7 @@ const isgerenate = ref(false)
const autoSizeConfig = computed(() => {
if (useDisplay.Sender_variant !== 'default') {
return { minRows: 3, maxRows: 3 }
return { minRows: 5, maxRows: 9 }
} else {
return { minRows: 1, maxRows: 1 }
}
@ -149,22 +152,37 @@ const Errors = (error) => {
imageurlShow.value = ''
}
const generate = async () => {
const handleStart = async () => {
if (!props.isGenerate) {
router.push({ name: 'home', query: { loading: false, Generate: true } })
}
if (!imageurl.value && !prompt.value) {
if (!prompt.value) {
// eslint-disable-next-line no-undef
ElMessage.error('请输入提示词')
return
}
isgerenate.value = true
useDisplay.isSubGerenate = true
console.log('生成开始', isgerenate.value)
await generateSubImage(3, { videoImg: imageurl.value, text: prompt.value, file_type: 'video', parentCreateTime: parentTime.value, parentIndex: parentIndex.value, parentTaskId: parentTaskId.value }, '生成视频')
const data = {
videoImg: imageurl.value,
text: prompt.value,
file_type: 'video',
}
await generate(data, 1, 1)
console.log('生成中', isgerenate.value)
}
const handleContainerClick = () => {
if (useDisplay.Sender_variant === 'default') {
useDisplay.Sender_variant = 'updown'
}
}
const handleScrollToBottom = () => {
console.log('点击回到底部按钮')
useDisplay.scrollToBottom()
}
watch(() => useDisplay.isSubGerenate, (newValue) => {
console.log('生成状态', newValue)
if (!newValue) {
@ -202,7 +220,34 @@ onMounted(() => {
left: 50%;
transform: translateX(-50%);
border-radius: 10px;
transition: all 0.3s ease;
}
.scroll-to-bottom-btn {
width: 100%;
display: flex;
justify-content: flex-end;
cursor: pointer;
transition: all 0.3s ease;
z-index: 101;
margin-bottom: 10px;
.scroll-to-bottom-text {
padding: 8px;
width: auto;
height: auto;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
background-color: rgba(0, 15, 51, 0.836);
color: #ffffff;
font-size: 12px;
&:active {
transform: scale(0.95);
}
&:hover {
background-color: rgb(0, 15, 51);
}
}
}
.generate{
height: 100%;
@ -215,7 +260,6 @@ onMounted(() => {
box-shadow: none;
:deep(.el-sender){
background-color: #F8F9FA;
border: none;
box-shadow: none;
}
@ -244,13 +288,13 @@ onMounted(() => {
line-height: normal;
}
:deep(.el-sender){
background-color: #ffffff;
background-color: #F8F9FA;
border: none;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.10);
border-radius: 20px;
}
:deep(.el-sender:focus-within){
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.10);
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1);
}
//

View File

@ -42,7 +42,7 @@
</div>
<template #reference>
<div class="choice-btn">
<img src="@/assets/dialog/originalImage.svg" alt="" style="width: 16px;">
<img src="@/assets/dialog/model.svg" alt="" style="width: 16px;">
<span>{{ getModelLabel(model) }}</span>
</div>
</template>

View File

@ -39,7 +39,7 @@
<label>W</label>
<input type="number" v-model.number="width" @input="updateWidth">
</div>
<div class="lock-icon">🔒</div>
<div class="lock-icon"><img src="@/assets/dialog/lock.svg" alt=""></div>
<div class="input-group">
<label>H</label>
<input type="number" v-model.number="height" @input="updateHeight">
@ -49,7 +49,7 @@
</div>
<template #reference>
<div class="choice-btn">
<img src="@/assets/dialog/originalImage.svg" alt="" style="width: 16px;">
<img src="@/assets/dialog/proportion.svg" alt="" style="width: 16px;">
<span>{{ proportion }}</span>
</div>
</template>

View File

@ -5,7 +5,7 @@
</div>
<template #reference>
<div class="choice-btn">
<img src="@/assets/dialog/originalImage.svg" alt="" style="width: 16px;">
<img src="@/assets/dialog/quantity.svg" alt="" style="width: 16px;">
<span>{{ quantity }} </span>
</div>
</template>

View File

@ -1,6 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useDisplayStore, useUserStore } from '@/stores'
import { getToken } from '@/utils/auth'
import { getToken, setToken } from '@/utils/auth'
const routes = [
{
@ -29,45 +29,47 @@ const router = createRouter({
routes
})
// router.beforeEach(async (to, from, next) => {
// // 白名单路径(不需要验证 token 的路径)
// const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
if(to.query.token){
setToken(to.query.token)
} else {
// 检查是否有 token
const token = getToken()
if (!token) {
// 没有 token重定向到登录页
return next('/login')
}
}
// // 如果访问的是白名单路径,直接放行
// if (whiteList.includes(to.path)) {
// return next()
// }
// 白名单路径(不需要验证 token 的路径)
const whiteList = ['/login']
// 获取用户 store 实例
const userStore = useUserStore()
// 如果访问的是白名单路径,直接放行
if (whiteList.includes(to.path)) {
return next()
}
// // 检查是否有 token
// const token = getToken()
// if (!token) {
// // 没有 token重定向到登录页
// return next('/login')
// }
// useDisplayStore().sidebarShow = to.path.split('/')[1]
// // 获取用户 store 实例
// const userStore = useUserStore()
// // 检查 token 是否有效
// try {
// const isTokenValid = await userStore.checkTokenValid()
// console.log(isTokenValid)
// if (isTokenValid) {
// // token 有效,允许访问
// if (!userStore.userInfo.id) {
// // 如果用户信息不存在,则从服务器获取
// await userStore.getInfo()
// }
// next()
// } else {
// // token 无效,重定向到登录页
// next('/login')
// }
// } catch (error) {
// // 验证过程中出错,重定向到登录页
// console.error('验证 token 时出错:', error)
// next('/login')
// }
// })
// 检查 token 是否有效
try {
const isTokenValid = await userStore.checkTokenValid()
console.log(isTokenValid)
if (isTokenValid) {
// token 有效,允许访问
if (!userStore.userInfo.id) {
// 如果用户信息不存在,则从服务器获取
await userStore.getInfo()
}
next()
} else {
// token 无效,重定向到登录页
next('/login')
}
} catch (error) {
// 验证过程中出错,重定向到登录页
console.error('验证 token 时出错:', error)
next('/login')
}
})
export default router

View File

@ -1,7 +1,54 @@
const DisplayStoreSetup = () => {
const Sender_variant = ref('updown')
const scrollerRef = ref(null)
const scrollToBottom = () => {
console.log('store - 尝试滚动到底部')
const refValue = scrollerRef.value
console.log('store - scrollerRef.value:', refValue)
if (refValue) {
console.log('store - scrollerRef.value.$el:', refValue.$el)
try {
if (typeof refValue.scrollToItem === 'function') {
console.log('store - 使用 scrollToItem 方法')
refValue.scrollToItem(refValue.items?.length - 1)
} else {
console.log('store - scrollToItem 方法不存在')
const scrollerEl = refValue.$el
if (!scrollerEl) {
console.log('store - scrollerEl 不存在')
return
}
console.log('store - scrollerEl:', scrollerEl)
const viewport = scrollerEl.querySelector('.vue-recycle-scroller__viewport')
if (viewport) {
console.log('store - 找到 viewport 元素')
viewport.scrollTop = viewport.scrollHeight
console.log('store - viewport.scrollTop:', viewport.scrollTop)
console.log('store - viewport.scrollHeight:', viewport.scrollHeight)
setTimeout(() => {
viewport.scrollTop = viewport.scrollHeight
}, 50)
}
}
} catch (error) {
console.error('store - 滚动出错:', error)
}
} else {
console.log('store - scrollerRef 不存在')
}
}
return {
Sender_variant
Sender_variant,
scrollerRef,
scrollToBottom
}
}

View File

@ -32,7 +32,6 @@ const storeSetup = () => {
const permissions = ref([]) // 当前角色权限标识集合
const dept = ref({}) // 当前用户所在部门集合
const schoolURL = ref({ school: '', college: '' })
const isLogin = ref(false)
// 重置token
@ -58,7 +57,7 @@ const storeSetup = () => {
const getInfo = async () => {
const res = await getUserInfoApi()
Object.assign(userInfo, res.data)
userInfo.avatar = getAvatar(res.data.avatar, res.data.gender)
// userInfo.avatar = getAvatar(res.data.avatar, res.data.gender)
userInfo.username = res.data.username
if (typeof res.data.routers === 'string' && res.data.routers.trim() !== '') {
userInfo.routers = res.data.routers.split(',').map((item) => item.trim()) // 补充trim处理更完善

View File

@ -47,248 +47,7 @@ export function websocketSuccess() {
})
}
export async function generateRootImage(aspectRatio, prompt) {
const progress_text = ref('')
const message = ref('')
const useDisplay = useDisplayStore()
const useParams = useParamStore()
const useUser = useUserStore()
const newTaskRootId = crypto.randomUUID()
const taskId = crypto.randomUUID()
const time = await getFormattedTime()
const newRootTaskImage = useParams.rootTaskImage
const token = getToken()
const result = await createTask(1, { prompt, aspectRatio, time, taskRootId: newTaskRootId, taskId, token })
// const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjY0NDEwODAyMjk1OTgzNzIzMCwicm5TdHIiOiJiWkVwS2JLWFJyZmRIaFFHWXZKTkdzOGdGM0JSRmxQOCJ9.5eQ2GtVdrDntQDe2tnF8vl_DhTfd2uW-KNqzvl1imc0'
const wsURL = `${import.meta.env.VITE_API_WORKFLOW_WS}/?token=${token}`
const socket = new WebSocket(wsURL)
console.log('WebSocket连接已建立')
// 心跳机制相关变量
let heartbeatInterval = null
const heartbeatIntervalTime = 20000 // 30秒发送一次心跳
try {
// 接收服务器消息
socket.onmessage = async (event) => {
// 处理pong响应
if (event.data === 'pong') {
console.log('收到心跳响应')
return
} else if (event.data === 'please give me taskId') {
socket.send(`setTaskId:${taskId}`)
progress_text.value = '信息提交中...'
return
} else if (event.data === 'OK! Please continue. ') {
// 发送消息
useParams.newTaskRootId = newTaskRootId
useParams.taskRootId = newTaskRootId
useDisplay.taskRootId = newTaskRootId
socket.send(JSON.stringify({
type: 'generate',
data: result
}))
return
} else if (event.data === '任务提交成功,正在排队中...') {
progress_text.value = '生成中...'
useParams.setRootTask = { id: taskId, type: 'image', status: 'generate', name: '模特展示图', time, files: [] }
return
}
message.value = event.data
}
// 处理链接错误
socket.onerror = (error) => {
console.error('WebSocket链接出错:', error)
// 清理心跳定时器
if (heartbeatInterval) {
console.log('清理心跳定时器')
clearInterval(heartbeatInterval)
}
}
// 处理链接关闭
socket.onclose = async (event) => {
console.log('WebSocket已关闭:', event)
useDisplay.isRootGerenate = false
// 清理心跳定时器
if (heartbeatInterval) {
console.log('清理心跳定时器')
clearInterval(heartbeatInterval)
}
if (event.code === 1006) {
console.error('用户身份验证失败')
userError()
return
}
if (event.code === 1000 && event.reason === 'success') {
const res = JSON.parse(message.value)
console.log('收到服务器消息:', res)
const result = await getTask(res)
if (result.type) {
useParams.setRootTask = { id: taskId, type: 'image', status: 'success', name: '模特展示图', time, files: [result.url] }
setTaskRootId({ taskRootId: newTaskRootId, userId: useUser.userInfo.id, imageUrl: newRootTaskImage })
useDisplay.getNewHistory = true
websocketSuccess()
} else {
websocketError(4403, res.message)
}
} else {
websocketError(event.code, event.reason)
useParams.setRootTask = { id: taskId, type: 'image', status: 'error', name: '生成失败', time, files: [] }
}
}
// 等待 WebSocket 连接打开
socket.onopen = async () => {
console.log('WebSocket连接已建立')
// 启动心跳机制
heartbeatInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send('ping')
console.log('发送心跳包')
}
}, heartbeatIntervalTime)
}
} catch (error) {
console.log('Error creating AI3D_file:', error)
ElNotification({
title: '生成通知',
message: h('i', { style: 'color: teal' }, '生成失败,请检查参数后重新提交任务'),
type: 'error'
})
}
}
export async function generateSubImage(taskType, data, title) {
const progress_text = ref('')
const message = ref('')
const useParams = useParamStore()
const useDisplay = useDisplayStore()
const newTaskRootId = useDisplay.taskRootId
console.log('newTaskRootId', newTaskRootId)
useParams.taskRootId = newTaskRootId
const taskId = crypto.randomUUID()
const time = await getFormattedTime()
const token = getToken()
const result = await createTask(taskType, { ...data, time, taskId, taskRootId: newTaskRootId, token }, title)
// const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjY0NDEwODAyMjk1OTgzNzIzMCwicm5TdHIiOiJiWkVwS2JLWFJyZmRIaFFHWXZKTkdzOGdGM0JSRmxQOCJ9.5eQ2GtVdrDntQDe2tnF8vl_DhTfd2uW-KNqzvl1imc0'
const wsURL = `${import.meta.env.VITE_API_WORKFLOW_WS}/?token=${token}`
const socket = new WebSocket(wsURL)
console.log('WebSocket连接已建立')
// 心跳机制相关变量
let heartbeatInterval = null
const heartbeatIntervalTime = 20000 // 30秒发送一次心跳
try {
// 接收服务器消息
socket.onmessage = async (event) => {
// 处理pong响应
if (event.data === 'pong') {
console.log('收到心跳响应')
return
} else if (event.data === 'please give me taskId') {
socket.send(`setTaskId:${taskId}`)
progress_text.value = '信息提交中...'
return
} else if (event.data === 'OK! Please continue. ') {
// 发送消息
socket.send(JSON.stringify({
type: 'generate',
data: result
}))
return
} else if (event.data === '任务提交成功,正在排队中...') {
progress_text.value = '生成中...'
useParams.setSubTask = { id: taskId, type: data.file_type, status: 'generate', name: title, time, parentIndex: data.parentIndex, parentTime: data.parentCreateTime, files: [] }
useDisplay.AIvideo = false
return
}
message.value = event.data
}
// 处理链接错误
socket.onerror = (error) => {
console.error('WebSocket链接出错:', error)
// 清理心跳定时器
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
}
ElNotification({
title: '生成通知',
message: h('i', { style: 'color: teal' }, '生成视频失败'),
type: 'error'
})
}
// 处理链接关闭
socket.onclose = async (event) => {
console.log('WebSocket已关闭:', event)
useDisplay.isSubGerenate = false
// 清理心跳定时器
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
}
if (event.code === 1006) {
console.error('用户身份验证失败')
userError()
} else if (event.code === 1000 && event.reason === 'success') {
const res = JSON.parse(message.value)
console.log('收到服务器消息:', res)
const result = await getTask(res)
if (result.type) {
useParams.setSubTask = { id: taskId, type: data.file_type, status: 'success', name: title, time, parentIndex: data.parentIndex, parentTime: data.parentCreateTime, files: [result.url] }
websocketSuccess()
} else {
websocketError(4403, res.message)
}
} else {
websocketError(event.code, event.reason)
useParams.setSubTask = { id: taskId, type: 'image', status: 'error', name: '生成失败', time, files: [] }
}
// 清理心跳定时器
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
}
}
// 等待 WebSocket 连接打开
socket.onopen = async () => {
console.log('WebSocket连接已建立')
// 启动心跳机制
heartbeatInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
socket.send('ping')
console.log('发送心跳包')
}
}, heartbeatIntervalTime)
}
} catch (error) {
console.log('Error creating AI3D_file:', error)
ElNotification({
title: '生成通知',
message: h('i', { style: 'color: teal' }, '生成失败,请检查参数后重新提交任务'),
type: 'error'
})
}
}
export async function generateAIimage(taskType, data, type) {
export async function generate(taskType, data, type) {
const progress_text = ref('')
const message = ref('')
const useDisplay = useDisplayStore()
@ -314,17 +73,21 @@ export async function generateAIimage(taskType, data, type) {
return
} else if (event.data === 'please give me taskId') {
socket.send(`setTaskId:${taskId}`)
progress_text.value = '信息提交中...'
progerss_text.value = '信息提交中...'
return
} else if (event.data === 'OK! Please continue. ') {
// 发送消息
socket.send(JSON.stringify({
type: 'generate',
data: result
}))
return
} else if (event.data === '任务提交成功,正在排队中...') {
progress_text.value = '视频生成中...'
progerss_text.value = '视频生成中...'
// 启动进度条更新
startTime.value = Date.now()
progressPercent.value = 0
progressInterval.value = window.setInterval(updateProgress, 1000)
progress.value = true
return
}
message.value = event.data
@ -338,30 +101,34 @@ export async function generateAIimage(taskType, data, type) {
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
}
// eslint-disable-next-line no-undef
ElNotification({
title: '生成通知',
// eslint-disable-next-line no-undef
message: h('i', { style: 'color: teal' }, '生成视频失败'),
type: 'error'
})
}
// 处理链接关闭
socket.onclose = async (event) => {
console.log('WebSocket已关闭:', event)
useDisplay.isAIgenerate[type] = false
isGenerating.value = false
// 清理心跳定时器
if (heartbeatInterval) {
console.log('清理心跳定时器')
clearInterval(heartbeatInterval)
}
const res = JSON.parse(message.value)
if (event.code === 1006) {
console.error('用户身份验证失败')
userError()
return
}
if (event.code === 1000 && event.reason === 'success') {
const res = JSON.parse(message.value)
} else if (event.code === 1000 && event.reason === 'success') {
console.log('收到服务器消息:', res)
const result = await getTask(res)
const result = await getVideo(tempPlatform.value, res)
if (result.type) {
useDisplay.AIimage[type] = result.url
console.log('生成成功', useDisplay.AIimage[type])
previewUrl.value = result.url
websocketSuccess()
} else {
websocketError(4403, res.message)
@ -369,11 +136,20 @@ export async function generateAIimage(taskType, data, type) {
} else {
websocketError(event.code, event.reason)
}
progress.value = false
clearInterval(progressInterval.value)
progressInterval.value = null
isGenerating.value = false
// 清理心跳定时器
if (heartbeatInterval) {
clearInterval(heartbeatInterval)
}
}
// 等待 WebSocket 连接打开
socket.onopen = () => {
console.log('WebSocket连接已建立')
// 启动心跳机制
heartbeatInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) {
@ -381,19 +157,13 @@ export async function generateAIimage(taskType, data, type) {
console.log('发送心跳包')
}
}, heartbeatIntervalTime)
// 发送消息
socket.send(JSON.stringify({
type: 'generate',
data: result
}))
}
} catch (error) {
console.log('Error creating AI3D_file:', error)
// eslint-disable-next-line no-undef
ElNotification({
title: '生成通知',
// eslint-disable-next-line no-undef
message: h('i', { style: 'color: teal' }, '生成失败,请检查参数后重新提交任务'),
type: 'error'
})

View File

@ -4,12 +4,13 @@
<!-- 标题 -->
<div class="title">
<div class="style">
<span class="name">{{ props.item.name }}</span>
<span v-show="props.item.time" class="time">({{ props.item.time }})</span>
<span class="name">{{ props.item.text }}</span>
</div>
<div v-if="props.item.parentIndex" class="dividing-line"></div>
<div v-if="props.item.parentIndex" class="style">
<span class="time">源自 {{ props.item.parentTime }}</span>
<div class="dividing-line"></div>
<span class="time">源自 {{ props.item.parentTime }}</span>
<div v-if="props.item.parentIndex" class="dividing-line"></div>
<span class="time"> {{ props.item.parentIndex }} 张图片</span>
</div>
</div>
@ -57,7 +58,7 @@
content="画笔"
placement="top"
>
<div class="brush" @click.stop="AIvideo(file, index)"><img :src="brush" /></div>
<img @click.stop="AIvideo(file, index)" :src="brush" />
</el-tooltip>
</div>
</div>
@ -77,6 +78,9 @@
import brush from '@/assets/display/brush.svg'
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
import { downloadImage } from '@/utils/downloadImage.js'
import reEditIcon from '@/assets/display/reEdit.svg'
import againGenerateIcon from '@/assets/display/againGenerate.svg'
import deleteImageIcon from '@/assets/display/deleteImage.svg'
const props = defineProps({
item: {
@ -114,17 +118,17 @@ const deleteImage = (url, number) => {
const bottomBtnGroup = [
{
name: '重新编辑',
icon: '@/assets/display/dialogModification.svg',
icon: reEditIcon,
click: reEdit
},
{
name: '再次生成',
icon: '@/assets/display/AIvideo.svg',
icon: againGenerateIcon,
click: againGenerate
},
{
name: '删除该批次',
icon: '@/assets/display/delete.svg',
icon: deleteImageIcon,
click: deleteImage
}
]
@ -351,6 +355,7 @@ const addCollection = (url) => {
.bottom-btn{
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
padding: 5px 10px;
border-radius: 5px;

View File

@ -8,30 +8,48 @@
</div>
<div v-if="props.if" class="btn-container">
<div class="btn" @click="activeTab = 'all'">
<div class="btn">
<!-- <span class="btn-text">全部</span> -->
<img src="@/assets/display/arrow.svg" alt="">
<img src="@/assets/display/search.svg" alt="">
</div>
<span class="line"></span>
<div class="btn" @click="activeTab = 'success'">
<span class="btn-text">时间</span>
<img src="@/assets/display/arrow.svg" alt="">
<div class="btn">
<Select v-model="selectedTime" :options="timeOptions" width="auto">
<template #prefix>
<i-ep-Calendar />
</template>
<template #header>
<div class="header">
<el-date-picker
v-model="value1"
type="daterange"
start-placeholder="Start date"
end-placeholder="End date"
:size="size"
/>
</div>
</template>
</Select>
</div>
<span class="line"></span>
<div class="btn" @click="activeTab = 'none'">
<span class="btn-text">收藏</span>
<img src="@/assets/display/arrow.svg" alt="">
<div class="btn">
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto" >
<template #prefix>
<i-ep-Star />
</template>
</Select>
</div>
</div>
<DynamicScroller
ref="scrollerRef"
v-if="props.if"
:items="list"
:min-item-size="54"
class="scroller"
:buffer="250"
@scroll="handleScroll"
@scroll-end="getList"
>
<template #default="{ item, index, active }">
<DynamicScrollerItem
@ -47,9 +65,12 @@
</template>
<script setup>
import { useDisplayStore, useParamStore } from '@/stores'
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
import Set from './components/set.vue'
import RefreshOverlay from './components/RefreshOverlay.vue'
import Select from '@/components/Select/index.vue'
import { getGenerateHistoryList } from '@/apis/display'
import { useRouter } from 'vue-router'
const props = defineProps({
if: {
@ -63,18 +84,37 @@ const props = defineProps({
})
const useDisplay = useDisplayStore()
const useParams = useParamStore() //
const useParams = useParamStore()
const userStore = useUserStore()
const router = useRouter()
const refreshing = ref(false)
const scrollerRef = ref(null)
const isLoadingMore = ref(false)
const activeTab = ref('all')
// const tempList = ref([])
const tempList = ref([
{ id: 0, type: 'image', status: 'none', name: '局部重绘', time: '2025-12-01 18:26', files: [] },
{ id: 1, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
{ id: 2, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
{ id: 3, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
{ id: 4, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] }
])
let total = 0
const timeOptions = [
{ label: '全部', value: 'all' },
{ label: '最近一周', value: 'week' },
{ label: '最近一个月', value: 'month' },
{ label: '最近三个月', value: 'quarter' }
]
const favoriteOptions = [
{ label: '全部', value: 'all' },
{ label: '已收藏', value: 'favorite' }
]
const selectedTime = ref('all')
const selectedFavorite = ref('all')
const tempList = ref([])
// const tempList = ref([
// { id: 0, type: 'image', status: 'none', name: '', time: '2025-12-01 18:26', files: [] },
// { id: 1, type: 'image', status: 'success', name: '', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
// { id: 2, type: 'image', status: 'success', name: '', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
// { id: 3, type: 'image', status: 'success', name: '', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
// { id: 4, type: 'image', status: 'success', name: '', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] }
// ])
const list = ref()
const page = ref(1)
list.value = tempList.value
@ -95,22 +135,19 @@ const conversion = (newlist) => {
const temp = newlist.data.records.map((item) => {
return {
id: item.taskId,
type: item.taskType === 1 || item.taskType === 2 ? 'image' : 'video',
collection: item.collection,
status: 'success',
name: item.title,
prompt: item.prompt,
params: item.params,
time: item.createTime,
parentId: item.parentTaskId,
parentName: item.parentName || '',
parentTime: item.parentCreateTime || '',
parentIndex: item.parentIndex,
files: [item.fileUrl]
}
})
return temp
}
//
const fetchHistoryVideos = async () => {
//
const fetchHistory= async (isScrollTopLoad = false) => {
try {
const result = await getGenerateHistoryList({ userId: userStore.userInfo.id, chargeType: 1 })
total = result.data.length
@ -118,62 +155,95 @@ const fetchHistoryVideos = async () => {
useDisplay.Sender_variant = 'updown'
router.push({ name: 'home' })
}
console.log(result.data)
historys.value = result.data.map((value) => ({
...value,
thumbnail: null
})).sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
console.log(historys.value)
const wrappedData = {
data: {
records: result.data
}
}
const convertedList = conversion(wrappedData)
const adaptedList = convertedList.map((item, index) => {
const originalItem = result.data[index]
return {
...item,
text: originalItem?.title || item.prompt || '生成图片',
name: originalItem?.title || item.prompt || '生成图片',
type: 'image',
title: originalItem?.title || '生成图片'
}
})
if (!isScrollTopLoad && adaptedList.length > 0) {
tempList.value = [adaptedList[adaptedList.length - 1]]
list.value = tempList.value
setTimeout(() => {
const remainingItems = adaptedList.slice(0, -1)
tempList.value = [...remainingItems, ...tempList.value]
list.value = tempList.value
console.log('添加剩余数据后列表长度:', tempList.value.length)
}, 1000)
await nextTick()
} else {
tempList.value = adaptedList
list.value = tempList.value
}
} catch (error) {
console.error('获取历史视频失败:', error)
// eslint-disable-next-line no-undef
console.error('获取历史失败:', error)
ElMessage({
message: '获取历史视频失败,内测状态,请及时下载视频文件,暂不支持云端储存与历史记录',
message: '获取历史失败',
type: 'warning'
})
} finally {
refreshing.value = false
}
}
//
const getList = async () => {
// if (page.value === 0) return
// page.value++
// console.log('getList', page.value)
// const newlist = await getTaskListByRootId({ taskRootId: useParams.taskRootId, page: page.value, size: 15 })
// if (newlist.data.records.length === 0) {
// page.value = 0
// return
// }
// const temp = conversion(newlist)
// list.value.push(...temp)
// tempList.value.push(...temp)
if (isLoadingMore.value) return
isLoadingMore.value = true
await fetchHistory(true)
isLoadingMore.value = false
}
//
const handleScroll = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.target
const distanceToBottom = scrollHeight - scrollTop - clientHeight
// console.log('distanceToBottom', distanceToBottom)
if (scrollTop <= 50 && !isLoadingMore.value) {
getList()
}
if (distanceToBottom <= 50) {
useDisplay.Sender_variant = 'updown'
refreshing.value = false
} else if (distanceToBottom >= 350) {
useDisplay.Sender_variant = 'default'
}
}
// tab
watch(() => activeTab.value, (newValue, oldValue) => {
console.log('activeTab', newValue, oldValue)
toggleDisplay(newValue, oldValue)
})
onMounted(() => {
console.log('display 组件已挂载')
if (!props.loading) return
refreshing.value = true
fetchHistoryVideos()
fetchHistory()
nextTick(() => {
console.log('设置 scrollerRef 到 store')
useDisplay.scrollerRef = scrollerRef.value
useDisplay.scrollToBottom()
})
setTimeout(() => {
console.log('setTimeout 后尝试滚动')
useDisplay.scrollToBottom()
setTimeout(() => {
refreshing.value = false
}, 1000)
}, 1000)
})
</script>
@ -208,7 +278,7 @@ onMounted(() => {
display: flex;
align-items: center;
gap: 6px;
height: 36px;
height: auto;
padding: 4px;
right: 30px;
top: 22px;
@ -244,7 +314,7 @@ onMounted(() => {
.scroller {
height: 100%;
padding: 30px 0px 170px 0px;
padding: 30px 0px 200px 0px;
will-change: scroll-position;
-webkit-overflow-scrolling: touch; /* iOS Safari */
scroll-behavior: smooth; /* 平滑滚动 */
@ -252,5 +322,34 @@ onMounted(() => {
&::-webkit-scrollbar-track {
background: transparent; /* 轨道透明 */
}
:deep(.option-item) {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 20px;
cursor: pointer;
transition: all 0.3s ease;
}
:deep(.option-item:hover) {
background-color: #f5f7fa;
}
:deep(.option-item.selected) {
color: #000F33;
font-weight: 500;
}
:deep(.option-text) {
flex: 1;
text-align: left;
}
:deep(.option-check) {
margin-left: 10px;
font-weight: bold;
}
}
</style>