feat: 第一次上传代码

This commit is contained in:
2025-06-29 23:41:37 +08:00
commit 875c60d3ec
478 changed files with 385642 additions and 0 deletions

45
pages/chat/ChatCardAI.vue Normal file
View File

@@ -0,0 +1,45 @@
<template>
<view class="chat-ai">
<text>{{ text }}</text>
<slot></slot>
</view>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
text: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.chat-ai {
margin: 6px 12px;
padding: 16px;
background: rgba(255,255,255,0.4);
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1);
border-radius: 4px 20px 20px 20px;
border: 1px solid;
border-color: #FFFFFF;
display: flex;
flex-direction: column;
max-width: 100%; // ✅ 限制最大宽度
overflow-x: hidden; // ✅ 防止横向撑开
text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #333333;
line-height: 22px;
text-align: justify;
font-style: normal;
text-transform: none;
}
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<view class="chat-mine">
<text>{{ text }}</text>
<slot></slot>
</view>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
text: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.chat-mine {
margin: 6px 12px;
padding: 8px 16px;
background-color: #00A6FF;
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.1);
border-radius: 20px 4px 20px 20px;
border: 1px solid;
border-color: #FFFFFF;
display: flex;
flex-direction: column;
max-width: 100%; // ✅ 限制最大宽度
overflow-x: hidden; // ✅ 防止横向撑开
text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #FFFFFF;
line-height: 22px;
text-align: justify;
font-style: normal;
text-transform: none;
}
}
</style>

420
pages/chat/ChatMainList.vue Normal file
View File

@@ -0,0 +1,420 @@
<template>
<view class="chat-container" @touchend="handleTouchEnd">
<!-- 顶部的背景 -->
<chat-top-bg-img class="chat-container-bg"></chat-top-bg-img>
<!-- 顶部自定义导航栏 -->
<view class="nav-bar-container" :style="{
paddingTop: statusBarHeight + 'px',
backgroundColor: navBgColor,
}">
<chat-top-nav-bar @openDrawer="openDrawer"></chat-top-nav-bar>
</view>
<view class="chat-container-msg-list">
<!-- logo栏 -->
<chat-top-banner class="chat-container-top-bannar"></chat-top-banner>
<!-- 消息列表可滚动区域 -->
<scroll-view
scroll-y
:scroll-into-view="lastMsgId"
:scroll-with-animation="true"
class="area-msg-list"
>
<view style="padding: 6px 12px;">
<OneFeelMK001></OneFeelMK001>
</view>
<view style="padding: 6px 12px;">
<OneFeelMK001></OneFeelMK001>
</view>
<view style="padding: 6px 12px;">
<OneFeelMK001></OneFeelMK001>
</view>
<view class="area-msg-list-content" v-for="item in chatMsgList" :key="item.msgId" :id="item.msgId">
<template v-if="item.msgType === MessageRole.AI">
<ChatCardAI class="message-item message-item-ai" :text="item.msg">
<image v-if="item.msgContent && item.msgContent.type === MessageType.IMAGE" src="/static/logo.png" style="width: 100px;height: 100px;"></image>
<OneFeelMK001></OneFeelMK001>
</ChatCardAI>
</template>
<template v-else-if="item.msgType === MessageRole.ME">
<ChatCardMine class="message-item message-item-mine" :text="item.msg">
</ChatCardMine>
</template>
<template v-else>
<text class="message-item message-item-other">{{item.msg}}</text>
</template>
</view>
<!-- 底部锚点用于滚动到底部 -->
<view :id="lastMsgId"></view>
</scroll-view>
<!-- 输入框区域 -->
<view class="footer-area">
<ChatMoreTips @replySent="handleReply"></ChatMoreTips>
<ChatQuickAccess @replySent="handleReply"></ChatQuickAccess>
<view class="area-input">
<!-- 发送语音 -->
<view class="input-container-voice">
<image src='/static/input_voice_icon.png'></image>
</view>
<!-- 输入框 -->
<textarea
class="textarea"
type="text"
placeholder="快速订票,呼叫服务"
cursor-spacing="65"
confirm-type='done'
v-model="inputMessage"
@confirm="sendMessage"
@touchend="handleNoHideKeyboard"
:confirm-hold="true"
auto-height
:show-confirm-bar='false'
:hold-keyboard="holdKeyboard"
maxlength="300"
/>
<view class="input-container-send" @click="sendMessage">
<image src='/static/input_send_icon.png'></image>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { onMounted, getCurrentInstance, nextTick } from 'vue'
import { ref, watch } from 'vue'
import { defineEmits } from 'vue'
import { onLoad } from '@dcloudio/uni-app';
import ChatTopBanner from './ChatTopBanner.vue';
import ChatTopBgImg from './ChatTopBgImg.vue';
import ChatTopNavBar from './ChatTopNavBar.vue';
import ChatCardAI from './ChatCardAI.vue';
import ChatCardMine from './ChatCardMine.vue';
import ChatQuickAccess from './ChatQuickAccess.vue';
import ChatMoreTips from './ChatMoreTips.vue';
import { MessageRole, ChatModel, MessageType } from '../../model/ChatModel';
import OneFeelMK001 from '../module/OneFeelMK001.vue';
const instance = getCurrentInstance(); // 获取当前组件实例
// 导航栏相关
const statusBarHeight = ref(20);
const navBgColor = ref('rgba(66, 173, 249, 0)');
const navOpacity = ref(0);
const timer = ref(null)
const holdKeyboard = ref(false) // focus时点击页面的时候不收起键盘
const holdKeyboardFlag = ref(true) // 是否在键盘弹出,点击界面时关闭键盘
const chatMsgList = ref<ChatModel[]>([])
const inputMessage = ref('')
// 锚点ID控制滚动位置
const lastMsgId = ref('anchor-bottom');
// 针对小程序键盘收起问题处理
// #ifdef MP-WEIXIN
holdKeyboard.value = true
// #endif
// 滚动临界值根据实际banner高度调整
const SCROLL_THRESHOLD = 200;
const handleScroll = (e) => {
const scrollTopVal = e.detail.scrollTop;
// 计算透明度 (0-1之间)
let opacity = Math.min(scrollTopVal / SCROLL_THRESHOLD, 1);
// 如果滚动到接近顶部透明度设为0
if (scrollTopVal < 10) {
opacity = 0;
}
navOpacity.value = opacity;
navBgColor.value = `rgba(66, 173, 249, ${opacity})`;
};
// 打开抽屉
const emits = defineEmits(['openDrawer'])
const openDrawer = () => {
emits('openDrawer')
console.log('=============打开抽屉')
}
const handleReply = (text: string) => {
loadMessage(text)
scrollToBottom()
};
onLoad(() => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20;
}
});
});
onMounted(() => {
initData()
})
const initData = () => {
const msg: ChatModel = {
msgId: `msg_${0}`,
msgType: MessageRole.AI,
msg: '查信息、预定下单、探索玩法、呼叫服务、我通通可以满足,快试试问我问题吧!',
}
chatMsgList.value.push(msg)
}
const handleTouchEnd = () => {
// #ifdef MP-WEIXIN
clearTimeout(timer.value)
timer.value = setTimeout(() => {
// 键盘弹出时点击界面则关闭键盘
if (handleNoHideKeyboard) {
uni.hideKeyboard()
}
holdKeyboardFlag.value = true
}, 50)
// #endif
}
// 点击输入框、发送按钮时,不收键盘
const handleNoHideKeyboard = () => {
// #ifdef MP-WEIXIN
holdKeyboardFlag.value = false
// #endif
}
// 发送消息
const sendMessage = () => {
if (!inputMessage.value.trim()) return;
handleNoHideKeyboard()
// 发送消息代码
loadMessage(inputMessage.value)
inputMessage.value = ''
scrollToBottom()
}
const loadMessage = (text: string) => {
const newMsg: ChatModel = {
msgId: `msg_${chatMsgList.value.length}`,
msgType: MessageRole.ME,
msg: text,
msgContent: {
type: MessageType.TEXT,
text: text
}
}
chatMsgList.value.push(newMsg)
let type = chatMsgList.value.length % 3 === 0
const newMsgAI: ChatModel = {
msgId: `msg_${chatMsgList.value.length}`,
msgType: MessageRole.AI,
msg: `我是ai,你输入的内容是:${text}`,
msgContent: {
type: type ? MessageType.IMAGE : MessageType.TEXT,
url: ''
}
}
chatMsgList.value.push(newMsgAI)
console.log("发送的新消息:",JSON.stringify(newMsg))
}
const scrollToBottom = () => {
// 短暂指向最新消息的ID触发滚动
lastMsgId.value = `${chatMsgList.value[chatMsgList.value.length - 1].msgId}`;
// 等待DOM更新后切回底部锚点确保下次能继续滚动到底
nextTick(() => {
lastMsgId.value = 'anchor-bottom';
});
}
</script>
<style lang="scss" scoped>
.chat-container {
width: 100vw;
height: 100vh;
background-color: #E9F3F7;
display: flex;
flex-direction: column;
overflow: hidden !important;
position: relative;
.chat-container-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 0;
height: 270px;
background: linear-gradient( 180deg, #42ADF9 0%, #6CD1FF 51%, #E9F3F7 99%);
}
/* 顶部导航栏样式 */
.nav-bar-container {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
transition: all 0.3s ease;
}
.chat-container-msg-list {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
z-index: 1;
overflow: hidden;
}
.chat-container-top-bannar {
width: 100vw;
flex-shrink: 0;
touch-action: none;
}
.area-msg-list {
width: 100vw;
flex: 1;
overflow-y: auto;
min-height: 0;
padding: 4px 0 0;
overscroll-behavior: contain; /* 阻止滚动穿透 */
-webkit-overflow-scrolling: touch;
display: flex;
flex-direction: column;
.area-msg-list-content {
/* 隐藏滚动条 */
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.message-item {
display: flex;
}
.message-item-ai {
justify-content: flex-start;
}
.message-item-mine {
justify-content: flex-end;
}
.message-item-other {
justify-content: center;
background-color: white;
margin: 6px 12px;
padding: 8px 24px;
border-radius: 4px;
font-size: 14px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #333333;
}
}
}
}
.footer-area {
width: 100vw;
flex-shrink: 0;
padding: 4px 0 24px 0;
background-color: #E9F3F7;
touch-action: pan-x; /* 仅允许横向触摸滚动 */
overflow-x: auto; /* 允许横向滚动 */
overflow-y: hidden; /* 禁止垂直滚动 */
}
.area-input {
display: flex;
align-items: center;
border-radius: 22px;
background-color: #FFFFFF;
box-shadow: 0px 0px 20px 0px rgba(52,25,204,0.05);
margin: 0 12px;
.input-container-voice {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
flex-shrink: 0;
align-self: flex-end;
image {
width: 22px;
height: 22px;
}
}
.input-container-send {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
flex-shrink: 0;
align-self: flex-end;
image {
width: 28px;
height: 28px;
}
}
.textarea {
flex: 1;
max-height: 92px;
min-height: 22px;
font-size: 16px;
line-height: 22px;
margin-bottom: 2px;
align-items: center;
}
}
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
color: transparent;
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<view class="more-tips">
<view class="more-tips-scroll">
<view class="more-tips-item" v-for="(item, index) in itemList" :key="index">
<text class="more-tips-item-title" @click="sendReply(item.title)">{{ item.title }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const itemList = ref([])
const emits = defineEmits(['replySent']);
const sendReply = (text) => {
emits('replySent', text); // 向父组件传递数据
}
onMounted(() => {
initData()
})
const initData = () => {
itemList.value = [
{
title: '定温泉票',
},
{
title: '定酒店',
},
{
title: '优惠套餐',
},
{
title: '亲子玩法',
},
{
title: '了解交通',
},
{
title: '看看酒店',
},
{
title: '看看美食',
}
]
}
</script>
<style lang="scss" scoped>
.more-tips {
width: 100%;
&-scroll {
display: flex;
flex-direction: row;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
/* 隐藏滚动条 */
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.more-tips-item {
border-radius: 8px;
margin: 4px;
box-shadow: 0 2px 5px 0px rgba(0,0,0,0.1);
background-color: #FFFFFF;
padding: 2px 12px;
display: flex;
flex-direction: column;
min-width: 46px;
&:first-child {
margin-left: 12px;
}
&:last-child {
margin-right: 12px;
}
.more-tips-item-title {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
color: #00A6FF;
line-height: 24px;
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<view class="quick-access">
<view class="quick-access-scroll">
<view class="quick-access-item" v-for="(item, index) in itemList" :key="index" @click="sendReply(item.title)">
<image class="quick-access-item-bg" src="/static/quick/quick_icon_bg.png" mode="aspectFill"></image>
<view class="quick-access-item-title">
<image :src="item.icon"></image>
<text>{{ item.title }}</text>
</view>
<text class="quick-access-item-content">{{ item.content }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const itemList = ref([])
const emits = defineEmits(['replySent']);
const sendReply = (text) => {
emits('replySent', text); // 向父组件传递数据
}
onMounted(() => {
initData()
})
const initData = () => {
itemList.value = [
{
icon: '/static/quick/quick_icon_yuding.png',
title: '预定门票',
content: '快速预定天沐温泉门票',
},
{
icon: '/static/quick/quick_icon_find.png',
title: '探索发现',
content: '亲子、团建等更多玩法',
},
{
icon: '/static/quick/quick_icon_call.png',
title: '呼叫服务',
content: '加床、订麻将机...',
},
{
icon: '/static/quick/quick_icon_order.png',
title: '我的订单',
content: '快速查看订单',
}
]
}
</script>
<style lang="scss" scoped>
.quick-access {
width: 100%;
&-scroll {
display: flex;
flex-direction: row;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
/* 隐藏滚动条 */
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.quick-access-item {
flex: 0 0 104px;
border-radius: 8px;
margin: 4px 4px 8px 4px;
box-shadow: 0 2px 5px 0px rgba(0,0,0,0.1);
padding: 12px;
display: inline-flex;
flex-direction: column;
position: relative;
&:first-child {
margin-left: 12px;
}
&:last-child {
margin-right: 12px;
}
.quick-access-item-bg {
position: absolute;
top: 0;
left: 0;
z-index: 0;
border-radius: 8px;
width: 128px;
height: 56px;
}
.quick-access-item-title {
display: flex;
align-items: center;
z-index: 1;
image {
width: 16px;
height: 16px;
margin-right: 4px;
}
text {
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
color: #201F32;
line-height: 16px;
}
}
.quick-access-item-content {
z-index: 1;
margin-top: 4px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 10px;
color: #678CAD;
line-height: 18px;
}
}
}
</style>

View File

@@ -0,0 +1,75 @@
<template>
<view class="top-bg-content" :style="{marginTop: marginContentTop + 'px'}">
<view class="top-item1">
<view class="top-item1-left">
<image src="/static/hello_xiaomu_icon@2x.png"></image>
<text>2025/02/10 多云 -36</text>
</view>
<view class="top-item1-right">
<image src="/static/hello_logo_icon@2x.png" ></image>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const statusBarHeight = ref(20);
const marginContentTop = ref(44)
onLoad(() => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20;
marginContentTop.value += statusBarHeight.value;
}
});
});
</script>
<style lang="scss" scoped>
.top-bg-content {
display: flex;
justify-content: flex-end;
align-items: stretch;
flex-direction: column;
}
.top-item1 {
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 0 46px 0 32px;
.top-item1-left {
display: flex;
flex-direction: column;
justify-content: flex-end;
image {
width: 118px;
height: 52px;
}
text {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #1E4C69;
line-height: 24px;
text-align: justify;
font-style: normal;
text-transform: none;
}
}
.top-item1-right {
image {
width: 96px;
height: 96px;
}
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<view class="top-bg">
<image src="/static/top_bg_icon.png" mode="aspectFit" class="top-bg-img"></image>
</view>
</template>
<script>
</script>
<style lang="scss" scoped>
.top-bg {
width: 100%;
height: 270px;
overflow: hidden;
.top-bg-img {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<view class="nav-bar">
<view class="nav-item" @click="openDrawer">
<image src="/static/drawer_icon.png" mode="aspectFit" class="nav-item-icon"></image>
</view>
</view>
</template>
<script setup>
import { defineEmits } from 'vue'
const emits = defineEmits(['openDrawer'])
const openDrawer = () => {
emits('openDrawer')
console.log('=============打开抽屉')
}
</script>
<style lang="scss" scoped>
.nav-bar {
display: flex;
align-items: center;
height: 44px;
padding: 0 15px;
.nav-item {
width: 24px;
height: 24px;
margin-right: 10px;
}
.nav-item-icon {
width: 100%;
height: 100%;
}
}
</style>