Merge branches 'master' and 'master' of https://gitee.com/lvYeJu/lane-crawford-3

This commit is contained in:
X1627315083
2025-10-20 15:34:14 +08:00
7 changed files with 249 additions and 193 deletions

View File

@@ -0,0 +1 @@
<svg t="1760927343641" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5713" width="200" height="200"><path d="M512 51.2c254.08 0 460.8 206.72 460.8 460.8s-206.72 460.8-460.8 460.8S51.2 766.08 51.2 512 257.92 51.2 512 51.2M512 0C229.248 0 0 229.248 0 512s229.248 512 512 512 512-229.248 512-512S794.752 0 512 0L512 0z" fill="currentColor" p-id="5714"></path><path d="M741.312 700.352c0 16-12.928 28.992-28.928 28.992L311.616 729.344c-16 0-28.928-12.992-28.928-28.992L282.688 299.648c0-16 12.928-28.864 28.928-28.864l400.768 0c16 0 28.928 12.864 28.928 28.864L741.312 700.352z" fill="currentColor" p-id="5715"></path></svg>

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -42,6 +42,7 @@
font-size: 3rem; font-size: 3rem;
color: #000; color: #000;
text-align: center; text-align: center;
margin: var(--my-list-footer-margin, 0);
> .placeholder { > .placeholder {
height: 1px; height: 1px;
} }

View File

@@ -2,24 +2,31 @@
import { ref, reactive, onMounted, inject } from 'vue' import { ref, reactive, onMounted, inject } from 'vue'
import HeaderTitle from '@/components/HeaderTitle.vue' import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue' import FooterNavigation from '@/components/FooterNavigation.vue'
import MyList from '@/components/myList.vue'
const emit = defineEmits(['view-type']) const emit = defineEmits(['view-type'])
onMounted(() => { onMounted(() => {
emit('view-type', 1) emit('view-type', 1)
}) })
const loading = ref(false)
const finish = ref(false)
const list = reactive([])
const list = reactive([ const onLoad = () => {
{ id: 1, userID: '1111111111', datetime: '7/22/2025 18:20', lastopened: '18:20' }, loading.value = true
{ id: 2, userID: '2222222222', datetime: '7/22/2025 18:20', lastopened: '18:20' }, setTimeout(() => {
{ id: 3, userID: '3333333333', datetime: '7/22/2025 18:20', lastopened: '18:20' }, for (var i = 0; i < 10; i++) {
{ id: 4, userID: '4444444444', datetime: '7/22/2025 18:20', lastopened: '18:20' }, list.push({
{ id: 5, userID: '5555555555', datetime: '7/22/2025 18:20', lastopened: '18:20' }, id: list.length + 1,
{ id: 6, userID: '6666666666', datetime: '7/22/2025 18:20', lastopened: '18:20' }, userID: 10000000 + list.length,
{ id: 7, userID: '7777777777', datetime: '7/22/2025 18:20', lastopened: '18:20' }, datetime: '7/22/2025 18:20',
{ id: 8, userID: '8888888888', datetime: '7/22/2025 18:20', lastopened: '18:20' }, lastopened: '18:20'
{ id: 9, userID: '9999999999', datetime: '7/22/2025 18:20', lastopened: '18:20' }, })
{ id: 10, userID: '0000000000', datetime: '7/22/2025 18:20', lastopened: '18:20' } }
]) loading.value = false
if (list.length >= 50) finish.value = true
}, 1500)
}
const onRetrieveItem = (i: number) => { const onRetrieveItem = (i: number) => {
console.log('检索' + i) console.log('检索' + i)
} }
@@ -34,6 +41,7 @@
<div class="library"> <div class="library">
<div class="title">Library</div> <div class="title">Library</div>
<div class="list"> <div class="list">
<my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad">
<div class="item" v-for="(v, i) in list" :key="v.id"> <div class="item" v-for="(v, i) in list" :key="v.id">
<div class="image"> <div class="image">
<img src="@/assets/images/workshop/posture/posture_1.png" /> <img src="@/assets/images/workshop/posture/posture_1.png" />
@@ -46,6 +54,7 @@
</div> </div>
<div class="delete" @click="deleteItem(i)"><SvgIcon name="delete2" size="30" /></div> <div class="delete" @click="deleteItem(i)"><SvgIcon name="delete2" size="30" /></div>
</div> </div>
</my-list>
</div> </div>
</div> </div>
<footer-navigation is-placeholder /> <footer-navigation is-placeholder />
@@ -88,19 +97,24 @@
} }
> .list { > .list {
flex: 1; flex: 1;
overflow-y: auto; overflow: hidden;
padding: 0 3.8rem;
margin: 0 3rem; margin: 0 3rem;
> .my-list {
padding: 0 3.8rem;
--my-list-footer-margin: 2rem 0;
> .item { > .item {
position: relative; position: relative;
padding: 2.8rem; padding: 2.8rem;
width: 100%; width: 100%;
height: 34.4rem; height: 34.4rem;
border-radius: 1.88rem; border-radius: 1.88rem;
margin-bottom: 7.66rem;
background-color: #f3f3f3; background-color: #f3f3f3;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 7.66rem;
&:first-child {
margin-top: 0;
}
> .image { > .image {
width: 21.4rem; width: 21.4rem;
height: 100%; height: 100%;
@@ -178,4 +192,5 @@
} }
} }
} }
}
</style> </style>

View File

@@ -1,13 +1,19 @@
<template> <template>
<div class="audio-visualizer" ref="containerRef"> <div class="audio-visualizer" ref="containerRef">
<div class="visualizer-container" ref="visualizerRef"> <div class="visualizer-container" ref="visualizerRef">
<div v-for="(line, index) in lines" :key="index" :class="['line', `line-${line.type}`]"></div> <template v-if="isInitialized">
<div
v-for="(line, index) in lines"
:key="index"
:class="['line', `line-${line.type}`]"
></div> </template
>>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue' import { ref, onMounted, nextTick } from 'vue'
// 定义线条类型 // 定义线条类型
interface Line { interface Line {
@@ -17,12 +23,16 @@ interface Line {
const containerRef = ref<HTMLElement>() const containerRef = ref<HTMLElement>()
const visualizerRef = ref<HTMLElement>() const visualizerRef = ref<HTMLElement>()
const lines = ref<Line[]>([]) const lines = ref<Line[]>([])
const isInitialized = ref<boolean>(false)
// 计算需要的线条数量 // 计算需要的线条数量
const calculateLines = (): Line[] => { const calculateLines = (): Line[] => {
if (!visualizerRef.value) return [] if (!visualizerRef.value) return []
const containerWidth = visualizerRef.value.offsetWidth const containerWidth = visualizerRef.value.offsetWidth
// 如果容器宽度为0或很小说明还没有正确渲染返回空数组
if (containerWidth < 50) return []
const lineWidth = 0.96 // 每条线的宽度 (rem) const lineWidth = 0.96 // 每条线的宽度 (rem)
const gap = 0.96 // 线条之间的间距 (rem) const gap = 0.96 // 线条之间的间距 (rem)
@@ -91,22 +101,38 @@ const calculateLines = (): Line[] => {
// 更新线条 // 更新线条
const updateLines = () => { const updateLines = () => {
lines.value = calculateLines() if (isInitialized.value) return
const newLines = calculateLines()
if (newLines.length > 0) {
lines.value = newLines
isInitialized.value = true
}
} }
// 监听窗口大小变化 // 暴露方法给父组件
const handleResize = () => { defineExpose({
updateLines() updateLines
} })
onMounted(async () => { onMounted(async () => {
await nextTick() await nextTick()
updateLines()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => { // 立即尝试第一次更新
window.removeEventListener('resize', handleResize) updateLines()
// 如果第一次没有成功,快速重试
setTimeout(() => {
if (!isInitialized.value) {
updateLines()
}
}, 50)
// 最后的备用尝试
setTimeout(() => {
if (!isInitialized.value) {
updateLines()
}
}, 150)
}) })
</script> </script>
@@ -117,7 +143,6 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 20px; padding: 20px;
min-height: 200px;
border-radius: 10px; border-radius: 10px;
} }

View File

@@ -13,7 +13,8 @@
<div class="input-container"> <div class="input-container">
<!-- 加号图标 --> <!-- 加号图标 -->
<div class="icon-wrapper"> <div class="icon-wrapper">
<SvgIcon name="plus" size="40" /> <SvgIcon v-if="!isRecording" name="plus" size="40" />
<SvgIcon v-else name="pause" size="60" @click="stopRecording" />
</div> </div>
<!-- 分隔线 --> <!-- 分隔线 -->
@@ -21,18 +22,20 @@
<!-- 正常状态显示输入框 --> <!-- 正常状态显示输入框 -->
<div class="input-wrapper"> <div class="input-wrapper">
<input <textarea
id="textarea" id="textarea"
v-show="!isRecording" v-show="!isRecording"
v-model="inputValue" v-model="inputValue"
type="text" rows="1"
placeholder="Ask anything about your desired outfit" placeholder="Ask anything about your desired outfit"
class="text-input" class="text-input"
@keyup.enter="handleSend" @keydown="handleKeyDown"
/> @input="handleInput"
ref="textareaRef"
></textarea>
<div v-show="isRecording" class="recording-wrapper"> <div v-show="isRecording" class="recording-wrapper">
<!-- <div class="recording-animation"> --> <!-- <div class="recording-animation"> -->
<AudioVisualizer /> <AudioVisualizer ref="audioVisualizerRef" />
<!-- </div> --> <!-- </div> -->
</div> </div>
</div> </div>
@@ -51,13 +54,16 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onUnmounted } from 'vue' import { ref, onUnmounted, nextTick } from 'vue'
import AudioVisualizer from './AudioVisualizer.vue' import AudioVisualizer from './AudioVisualizer.vue'
import { showToast } from 'vant'
const emit = defineEmits(['send-message']) const emit = defineEmits(['send-message'])
const inputValue = ref<string>('') const inputValue = ref<string>('')
const isRecording = ref<boolean>(false) const isRecording = ref<boolean>(false)
const audioVisualizerRef = ref<InstanceType<typeof AudioVisualizer> | null>(null)
const textareaRef = ref<HTMLTextAreaElement | null>(null)
const shortcutList: string[] = [ const shortcutList: string[] = [
'Suggest shoe styles', 'Suggest shoe styles',
'Recommend evening bags', 'Recommend evening bags',
@@ -72,6 +78,34 @@ const handleSend = (): void => {
console.log('input发送消息:', inputValue.value) console.log('input发送消息:', inputValue.value)
emit('send-message', inputValue.value) emit('send-message', inputValue.value)
inputValue.value = '' inputValue.value = ''
// 重置textarea高度
if (textareaRef.value) {
textareaRef.value.style.height = 'auto'
}
}
}
// 处理键盘事件
const handleKeyDown = (event: KeyboardEvent): void => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault()
handleSend()
}
}
// 处理输入事件,自动调整高度
const handleInput = (): void => {
if (textareaRef.value) {
textareaRef.value.style.height = 'auto'
// const lineHeight = 4.8
// const maxLines = 3
// const maxHeight = lineHeight * maxLines
const scrollHeight = textareaRef.value.scrollHeight
// const newHeight = Math.min(scrollHeight, maxHeight * 10)
textareaRef.value.style.height = `${scrollHeight}px`
} }
} }
@@ -89,10 +123,22 @@ onUnmounted(() => {
} }
}) })
const handleClickAudio = (): void => { const handleClickAudio = async (): Promise<void> => {
isRecording.value = !isRecording.value isRecording.value = !isRecording.value
// 当开始录音时等待DOM更新后触发AudioVisualizer重新计算
if (isRecording.value) { if (isRecording.value) {
await nextTick()
// 立即尝试更新
if (audioVisualizerRef.value) {
audioVisualizerRef.value.updateLines?.()
}
// 快速重试
setTimeout(() => {
if (audioVisualizerRef.value) {
audioVisualizerRef.value.updateLines?.()
}
}, 50)
startRecording() startRecording()
} else { } else {
stopRecording() stopRecording()
@@ -126,7 +172,7 @@ const startRecording = () => {
} }
// 识别结果 // 识别结果
speechRecognition.onresult = (event) => { speechRecognition.onresult = (event: any) => {
let finalTranscript = '' let finalTranscript = ''
let interimTranscript = '' let interimTranscript = ''
@@ -165,10 +211,11 @@ const startRecording = () => {
} }
// 识别错误 // 识别错误
speechRecognition.onerror = (event) => { speechRecognition.onerror = (event: any) => {
console.error('语音识别错误:', event.error) console.error('语音识别错误:', event.error)
isRecording.value = false isRecording.value = false
alert('语音识别失败,请重试') // alert('语音识别失败,请重试')
showToast(event.error)
} }
// 开始识别 // 开始识别
@@ -215,8 +262,8 @@ const stopRecording = () => {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #efefef; background-color: #efefef;
padding: 0 4.85rem 0 5.2rem; padding: 1.5rem 4.85rem 1.5rem 5.2rem;
height: 19.3rem; min-height: 19.3rem;
} }
.icon-wrapper { .icon-wrapper {
@@ -224,6 +271,7 @@ const stopRecording = () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
color: #6d6868;
&.send-icon { &.send-icon {
margin-left: 4.38rem; margin-left: 4.38rem;
@@ -236,6 +284,7 @@ const stopRecording = () => {
margin-left: 5.59rem; margin-left: 5.59rem;
margin-right: 3.5rem; margin-right: 3.5rem;
background-color: #888; background-color: #888;
align-self: center;
} }
.input-wrapper { .input-wrapper {
@@ -243,26 +292,22 @@ const stopRecording = () => {
display: flex; display: flex;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
// height: 100%;
} }
.text-input { .text-input {
width: 100%; width: 100%;
// height: 100%;
height: auto;
// min-height: 4.8rem; // min-height: 4.8rem;
// max-height: 100%; max-height: 14.4rem;
border: none; border: none;
outline: none; outline: none;
background: transparent; background: transparent;
font-size: 4rem; font-size: 4rem;
font-family: 'robotoBold'; font-family: 'robotoBold';
font-weight: 400; font-weight: 400;
line-height: 121%; line-height: 4.8rem; /* 设置行高等于实际渲染高度,实现垂直居中 */
// padding-right: 2rem; padding: 0;
margin-right: 4.21rem;
color: #000; color: #000;
// resize: none; word-break: break-all;
&::placeholder { &::placeholder {
color: #888; color: #888;
@@ -288,6 +333,7 @@ const stopRecording = () => {
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
min-height: 4.8rem;
} }
.recording-animation { .recording-animation {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="asistant-container flex flex-column"> <div class="asistant-container flex flex-column">
<div class="header"> <div class="header">
<HeaderTitle light hasSetting /> <HeaderTitle hasSetting styleType="2" />
</div> </div>
<div class="loading-container" v-if="isLoading"> <div class="loading-container" v-if="isLoading">
<GenerateLoading /> <GenerateLoading />
@@ -80,7 +80,6 @@ onMounted(() => {
// handleSendMessage('123') // handleSendMessage('123')
}) })
let loadingTimer: any = null
const handleSendMessage = (message: string): void => { const handleSendMessage = (message: string): void => {
console.log('收到消息:', message) console.log('收到消息:', message)
messageList.value.push({ messageList.value.push({
@@ -91,27 +90,6 @@ const handleSendMessage = (message: string): void => {
thumb: '' thumb: ''
}) })
// //
// 模拟请求延迟
// setTimeout(() => {
// // 调用NoticeList的方法添加新消息
// if (noticeListRef.value) {
// isLoading.value = true
// loadingTimer = setTimeout(() => {
// const newMessage: ChatMessage = {
// id: Date.now().toString(),
// content:
// "Thanks for your message! I'm processing your request and will provide you with the best fashion advice.",
// userId: '2',
// time: new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
// thumb: 'https://files-dev.deercal.com/uploads/platforms/logo_code/669933e1b873e798.jpg'
// }
// messageList.value.push(newMessage)
// isLoading.value = false
// }, 3000)
// }
// }, 10000) // 3秒后完成
} }
const handleContinue = () => { const handleContinue = () => {

View File

@@ -11,15 +11,7 @@
<div class="carousel-container"> <div class="carousel-container">
<!-- 左箭头 --> <!-- 左箭头 -->
<div class="nav-arrow left" @click="handleChangeSwiper('prev')"> <div class="nav-arrow left" @click="handleChangeSwiper('prev')">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <van-icon name="arrow-left" color="#fff" size="40" />
<path
d="M15 18L9 12L15 6"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div> </div>
<van-swipe touchable ref="swiperRef"> <van-swipe touchable ref="swiperRef">
@@ -37,7 +29,8 @@
</van-swipe> </van-swipe>
<div class="nav-arrow right" @click="handleChangeSwiper('next')"> <div class="nav-arrow right" @click="handleChangeSwiper('next')">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <van-icon name="arrow" color="#fff" size="40" />
<!-- <svg width="15" height="26" viewBox="0 0 24 24" fill="none">
<path <path
d="M9 18L15 12L9 6" d="M9 18L15 12L9 6"
stroke="white" stroke="white"
@@ -45,7 +38,7 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> />
</svg> </svg> -->
</div> </div>
</div> </div>
</div> </div>
@@ -182,8 +175,8 @@ watch(showVideo, (newValue) => {
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
width: 5rem; width: 8.6rem;
height: 5rem; height: 8.4rem;
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
@@ -192,15 +185,9 @@ watch(showVideo, (newValue) => {
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
z-index: 3; z-index: 3;
box-shadow: 0 0.4rem 0.8rem rgba(0, 0, 0, 0.2), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.1), box-shadow: 0 2rem 2.5rem rgba(0, 0, 0, 0.25), 0 0 6rem rgba(0, 0, 0, 0.25);
inset 0 0.1rem 0.2rem rgba(255, 255, 255, 0.3);
border: 0.1rem solid rgba(255, 255, 255, 0.2); border: 0.1rem solid rgba(255, 255, 255, 0.2);
filter: drop-shadow(2px 4px 6.6px rgba(0, 0, 0, 0.25));
&:active {
transform: translateY(-50%) scale(0.95);
box-shadow: 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2), inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.1);
}
&.left { &.left {
left: 1rem; left: 1rem;
} }
@@ -223,6 +210,15 @@ watch(showVideo, (newValue) => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 4.25rem 0 2.65rem; padding: 4.25rem 0 2.65rem;
background: radial-gradient(
100% 100% at 0% 0%,
rgba(115, 115, 115, 0.4) 0%,
rgba(115, 115, 115, 0.2) 50%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(35px);
overflow: hidden;
position: relative;
img { img {
width: 59rem; width: 59rem;
height: 63rem; height: 63rem;
@@ -299,25 +295,19 @@ watch(showVideo, (newValue) => {
cursor: pointer; cursor: pointer;
backdrop-filter: blur(1rem); backdrop-filter: blur(1rem);
border: 1px solid rgba(255, 255, 255, 0.3); border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: box-shadow: 0 0.8rem 1.6rem rgba(0, 0, 0, 0.4), inset 0 0.2rem 0.4rem rgba(255, 255, 255, 0.1),
0 0.8rem 1.6rem rgba(0, 0, 0, 0.4),
inset 0 0.2rem 0.4rem rgba(255, 255, 255, 0.1),
inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.3); inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.3);
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover { &:hover {
transform: translateY(-0.2rem); transform: translateY(-0.2rem);
box-shadow: box-shadow: 0 1.2rem 2.4rem rgba(0, 0, 0, 0.5),
0 1.2rem 2.4rem rgba(0, 0, 0, 0.5), inset 0 0.2rem 0.4rem rgba(255, 255, 255, 0.15), inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.4);
inset 0 0.2rem 0.4rem rgba(255, 255, 255, 0.15),
inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.4);
} }
&:active { &:active {
transform: translateY(0.1rem); transform: translateY(0.1rem);
box-shadow: box-shadow: 0 0.4rem 0.8rem rgba(0, 0, 0, 0.3), inset 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);
0 0.4rem 0.8rem rgba(0, 0, 0, 0.3),
inset 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2);
} }
.close-icon { .close-icon {