chore: dev proxy config, sprite animator refactor, cleanups

- add vite dev proxy for `/ingress` to forward requests to backend https://onefeel.brother7.cn
- update .env.development to use relative proxy paths instead of full backend URLs
- move SpriteAnimator component to correct directory, update all imports and global type declarations in components.d.ts
- remove unused CSS styles and fix styling for ChatTopNavBar component, including switching to van-icon and hardcoding temporary site name
- uncomment and enable the ChatMainList component in home page
- clean up unused code, fix formatting, and set default client ID in src/utils/request.ts
- comment out redundant uni API calls in ChatMainList to simplify code
- update WebSocket URL handling to support relative paths in ChatMainList
This commit is contained in:
DEV_DSW
2026-05-27 10:40:21 +08:00
parent 0d46ac0e2c
commit a75bb909f1
11 changed files with 109 additions and 250 deletions

View File

@@ -2,10 +2,10 @@
VITE_APP_ENV = 'development'
# API 基础 URL
VITE_API_BASE_URL = 'https://onefeel.brother7.cn/ingress'
VITE_API_BASE_URL = '/ingress'
# API 请求超时时间(毫秒)
VITE_API_TIMEOUT_MS = 10000
# Socket 基础 URL
VITE_SOCKET_BASE_URL = "wss://onefeel.brother7.cn/ingress/agent/ws/chat"
VITE_SOCKET_BASE_URL = "/ingress/agent/ws/chat"

4
components.d.ts vendored
View File

@@ -39,7 +39,7 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ServiceTipsWord: typeof import('./src/components/ServiceTipsWord/index.vue')['default']
SpriteAnimator: typeof import('./src/components/Sprite/SpriteAnimator.vue')['default']
SpriteAnimator: typeof import('./src/components/SpriteAnimator/index.vue')['default']
Stepper: typeof import('./src/components/Stepper/index.vue')['default']
SumCard: typeof import('./src/components/SumCard/index.vue')['default']
SurveyQuestionnaire: typeof import('./src/components/SurveyQuestionnaire/index.vue')['default']
@@ -83,7 +83,7 @@ declare global {
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
const ServiceTipsWord: typeof import('./src/components/ServiceTipsWord/index.vue')['default']
const SpriteAnimator: typeof import('./src/components/Sprite/SpriteAnimator.vue')['default']
const SpriteAnimator: typeof import('./src/components/SpriteAnimator/index.vue')['default']
const Stepper: typeof import('./src/components/Stepper/index.vue')['default']
const SumCard: typeof import('./src/components/SumCard/index.vue')['default']
const SurveyQuestionnaire: typeof import('./src/components/SurveyQuestionnaire/index.vue')['default']

View File

@@ -1,153 +1,14 @@
<template>
<div class="flex flex-col h-screen relative overflow-hidden">
<!-- 顶部自定义导航栏 -->
<div class="header absolute top-0 left-0 w-full z-10" :style="{ paddingTop: statusBarHeight + 'px' }">
<div class="absolute top-0 left-0 w-full z-10">
<ChatTopNavBar ref="topNavBarRef" :mainPageDataModel="mainPageDataModel" @showDrawer="handleShowDrawer" />
</div>
<div class="relative">
<img class="w-full block" :src="mainPageDataModel?.initPageImages?.backgroundImageUrl" mode="aspectFill"
style="height: 252px;" />
<div class="absolute bottom-0 left-0 right-0 flex-full">
<div class="px-12 pt-12">
<HomeWelcome :mainPageDataModel="mainPageDataModel" />
</div>
<div style="margin-bottom: -1px;">
<AiTabSwitch v-model="tabIndex" :list="tabList" @change="handleChange" />
</div>
</div>
</div>
<div v-show="tabIndex === 0" class="main-scroll-area flex-full overflow-hidden min-height-0"
@touchstart.capture="handleScrollAreaTouchStart" @touchstart="handleScrollAreaTouchStart"
@touchmove="handleScrollAreaTouchMove">
<Discovery @scroll-touch-start="handleScrollAreaTouchStart" @scroll-touch="handleScrollAreaTouchMove" />
</div>
<!-- 消息列表可滚动区域 -->
<div v-show="tabIndex === 1" class="main main-scroll-area flex-full overflow-hidden min-height-0" scroll-y
:scroll-top="scrollTop" :scroll-with-animation="true" @scroll="handleScroll" @scrolltolower="handleScrollToLower"
@touchstart.capture="handleScrollAreaTouchStart" @touchstart="handleScrollAreaTouchStart"
@touchmove="handleScrollAreaTouchMove">
<div 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="flex flex-justify-start" :key="`ai-${item.msgId}-${item.msg ? item.msg.length : 0}`" :text="item.componentName && isLongTextCard(item.componentName)
? ''
: item.msg || ''
" :isLoading="item.isLoading">
<template #content v-if="
item.toolCall ||
(item.componentName && isLongTextCard(item.componentName))
">
<!-- <AnswerComponent
v-if="isLongTextCard(item.componentName)"
:longTextData="item.longTextData"
:finish="item.finish"
/> -->
<LongTextGuideCardPreview v-if="item.componentName && isLongTextCard(item.componentName)"
:componentName="item.componentName" />
<QuickBookingComponent v-if="
item.toolCall &&
item.toolCall.componentName === CompName.quickBookingCard
" />
<DiscoveryCardComponent v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.discoveryCard
" />
<CreateServiceOrder v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.callServiceCard
" :toolCall="item.toolCall" />
<OpenMapComponent v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.mapCard
" />
<GeneratorPhotoComponent v-else-if="
item.toolCall &&
item.toolCall.componentName ===
CompName.aigcPhotoGeneratorCard
" :toolCall="item.toolCall" />
<!-- <ZModuleC01
v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.aigcPhotoGeneratorCard
"
/> -->
<Feedback v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.feedbackCard
" :toolCall="item.toolCall" />
<DetailCardCompontent v-else-if="
item.toolCall &&
item.toolCall.componentName ===
CompName.pictureAndCommodityCard
" :toolCall="item.toolCall" />
<AddCarCrad v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.enterLicensePlateCard
" :toolCall="item.toolCall" />
<SurveyQuestionnaire v-else-if="
item.toolCall &&
item.toolCall.componentName ===
CompName.callSurveyQuestionnaire
" :toolCall="item.toolCall" />
</template>
<template #footer>
<!-- 这个是底部 -->
<AttachListComponent v-if="item.question" :question="item.question" />
</template>
</ChatCardAI>
</template>
<template v-else-if="item.msgType === MessageRole.ME">
<ChatCardMine class="flex flex-justify-end" :text="item.msg" />
</template>
<template v-else>
<ChatCardOther class="flex flex-justify-center" :text="item.msg">
<ChatGuide v-if="chatMsgList.length < 2" />
<ActivityListComponent v-if="
mainPageDataModel.activityList &&
mainPageDataModel.activityList.length > 0
" :activityList="mainPageDataModel.activityList" />
<!-- 先不展示了,等后续有需求再加回来 false -->
<RecommendPostsComponent v-if="
false &&
mainPageDataModel.recommendTheme &&
mainPageDataModel.recommendTheme.length > 0
" :recommendThemeList="mainPageDataModel.recommendTheme" />
</ChatCardOther>
</template>
</div>
</div>
<!-- 输入框区域 -->
<div class="pb-safe-area">
<ChatQuickAccess />
<ChatInputArea ref="inputAreaRef" v-model="inputMessage" :holdKeyboard="holdKeyboard"
:is-session-active="isSessionActive" :stop-request="stopRequest" @send="sendMessageAction"
@noHideKeyboard="handleNoHideKeyboard" @keyboardShow="handleKeyboardShow" @keyboardHide="handleKeyboardHide" />
</div>
</div>
</template>
<script setup>
import { onMounted, nextTick, onUnmounted, ref } from "vue";
import {
SWITCH_TO_COMPANION_TAB,
SWITCH_TO_DISCOVERY_TAB,
SCROLL_TO_BOTTOM,
SEND_MESSAGE_CONTENT_TEXT,
SEND_MESSAGE_COMMAND_TYPE,
NOTICE_EVENT_LOGOUT,
NOTICE_EVENT_LOGIN_SUCCESS,
} from "@/constants/constant";
import { MessageRole, MessageType, CompName, Command } from "@/constants/ChatModel";
import HomeWelcome from "../HomeWelcome/index.vue";
@@ -195,8 +56,7 @@ import { getAccessToken } from "@/constants/token";
const emit = defineEmits(["showDrawer"]);
const appStore = useAppStore();
/// 导航栏相关
const statusBarHeight = ref(20);
/// 输入框组件引用
const inputAreaRef = ref(null);
const topNavBarRef = ref();
@@ -278,7 +138,7 @@ const handleTouchEnd = () => {
holdKeyboardTimer.value = setTimeout(() => {
// 键盘弹出时点击界面则关闭键盘
if (holdKeyboardFlag.value && isKeyboardShow.value) {
uni.hideKeyboard();
// uni.hideKeyboard();
}
holdKeyboardFlag.value = true;
}, 100);
@@ -319,13 +179,7 @@ const hideKeyboardByScroll = () => {
inputAreaRef.value.blurInput();
}
uni.hideKeyboard();
// #ifdef APP-PLUS
if (typeof plus !== "undefined" && plus.key && plus.key.hideSoftKeybord) {
plus.key.hideSoftKeybord();
}
// #endif
};
const hideKeyboardAfterSend = () => {
@@ -338,8 +192,6 @@ const hideKeyboardAfterSend = () => {
typeof inputAreaRef.value.blurInput === "function"
) {
inputAreaRef.value.blurInput();
} else {
uni.hideKeyboard();
}
};
@@ -439,54 +291,48 @@ const sendMessageAction = (inputText) => {
/// 添加通知
const addNoticeListener = () => {
uni.$on(NOTICE_EVENT_LOGIN_SUCCESS, () => {
if (!isWsConnected()) {
initHandler();
}
});
// uni.$on(NOTICE_EVENT_LOGIN_SUCCESS, () => {
// if (!isWsConnected()) {
// initHandler();
// }
// });
uni.$on(NOTICE_EVENT_LOGOUT, () => {
resetConfig();
});
// uni.$on(NOTICE_EVENT_LOGOUT, () => {
// resetConfig();
// });
uni.$on(SCROLL_TO_BOTTOM, () => {
setTimeout(() => {
scrollToBottom(true);
}, 200);
});
// uni.$on(SCROLL_TO_BOTTOM, () => {
// setTimeout(() => {
// scrollToBottom(true);
// }, 200);
// });
uni.$on(SEND_MESSAGE_CONTENT_TEXT, (value) => {
console.log("SEND_MESSAGE_CONTENT_TEXT:", value);
if (value && value.length > 0) {
handleReplyText(value);
}
});
// uni.$on(SEND_MESSAGE_CONTENT_TEXT, (value) => {
// console.log("SEND_MESSAGE_CONTENT_TEXT:", value);
// if (value && value.length > 0) {
// handleReplyText(value);
// }
// });
uni.$on(SEND_MESSAGE_COMMAND_TYPE, (item) => {
console.log("SEND_MESSAGE_COMMAND_TYPE:", item);
if (item && item.type) {
handleReplyInstruct(item);
}
});
// uni.$on(SEND_MESSAGE_COMMAND_TYPE, (item) => {
// console.log("SEND_MESSAGE_COMMAND_TYPE:", item);
// if (item && item.type) {
// handleReplyInstruct(item);
// }
// });
uni.$on(SWITCH_TO_COMPANION_TAB, () => {
tabIndex.value = 1;
});
// uni.$on(SWITCH_TO_COMPANION_TAB, () => {
// tabIndex.value = 1;
// });
uni.$on(SWITCH_TO_DISCOVERY_TAB, () => {
tabIndex.value = 0;
});
// uni.$on(SWITCH_TO_DISCOVERY_TAB, () => {
// tabIndex.value = 0;
// });
};
/// =============生命周期函数↓================
// TODO
// onLoad(() => {
// uni.getSystemInfo({
// success: (res) => {
// statusBarHeight.value = res.statusBarHeight || 20;
// },
// });
// });
// onReady(() => {
// // #ifdef APP-PLUS
@@ -548,9 +394,10 @@ const loadConversationMsgList = async () => {
// 获取首页数据
const getMainPageData = async () => {
/// 从个渠道获取如二维,没有的时候就返回首页的数据
// 从个渠道获取如二维,没有的时候就返回首页的数据
const sceneId = appStore.sceneId || "";
const res = await mainPageData({ sceneId });
console.log("🚀 ~ getMainPageData ~ res:", res)
if (res.code === 0) {
initData();
@@ -582,7 +429,18 @@ const initWebSocket = async () => {
// 使用配置的WebSocket服务器地址
const token = getAccessToken();
const wsUrl = `${appStore.serverConfig.wssUrl}?access_token=${token}`;
const rawWsUrl = appStore.serverConfig.wssUrl;
const resolvedWsUrl =
typeof rawWsUrl === "string" &&
(rawWsUrl.startsWith("ws://") || rawWsUrl.startsWith("wss://"))
? rawWsUrl
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}${typeof rawWsUrl === "string"
? rawWsUrl.startsWith("/")
? rawWsUrl
: `/${rawWsUrl}`
: ""
}`;
const wsUrl = `${resolvedWsUrl}?access_token=${token}`;
// 初始化WebSocket管理器
webSocketManager = new WebSocketManager({
@@ -917,9 +775,9 @@ const sendMessage = async (message, isInstruct = false) => {
if (!isWsConnected()) {
console.log("WebSocket未连接尝试重新连接...");
// 显示加载提示
uni.showLoading({
title: "正在连接服务器...",
});
// uni.showLoading({
// title: "正在连接服务器...",
// });
// 尝试重新初始化WebSocket连接
try {
@@ -929,34 +787,34 @@ const sendMessage = async (message, isInstruct = false) => {
// 检查连接是否成功建立
if (!isWsConnected()) {
uni.hideLoading();
// uni.hideLoading();
setTimeout(() => {
uni.showToast({
title: "连接服务器失败,请稍后重试",
icon: "none",
});
// uni.showToast({
// title: "连接服务器失败,请稍后重试",
// icon: "none",
// });
}, 100);
return;
}
uni.hideLoading();
// uni.hideLoading();
} catch (error) {
console.error("重新连接WebSocket失败:", error);
uni.hideLoading();
// uni.hideLoading();
setTimeout(() => {
uni.showToast({
title: "连接服务器失败,请稍后重试",
icon: "none",
});
// uni.showToast({
// title: "连接服务器失败,请稍后重试",
// icon: "none",
// });
}, 100);
return;
}
}
if (isSessionActive.value) {
uni.showToast({
title: "请等待当前回复完成",
icon: "none",
});
// uni.showToast({
// title: "请等待当前回复完成",
// icon: "none",
// });
return;
}
isSessionActive.value = true;
@@ -1230,11 +1088,11 @@ const stopRequest = async () => {
// 组件销毁时清理资源
onUnmounted(() => {
uni.$off(NOTICE_EVENT_LOGIN_SUCCESS);
uni.$off(SCROLL_TO_BOTTOM);
uni.$off(SEND_MESSAGE_CONTENT_TEXT);
uni.$off(SEND_MESSAGE_COMMAND_TYPE);
uni.$off(NOTICE_EVENT_LOGOUT);
// uni.$off(NOTICE_EVENT_LOGIN_SUCCESS);
// uni.$off(SCROLL_TO_BOTTOM);
// uni.$off(SEND_MESSAGE_CONTENT_TEXT);
// uni.$off(SEND_MESSAGE_COMMAND_TYPE);
// uni.$off(NOTICE_EVENT_LOGOUT);
resetConfig();
});

View File

@@ -1,32 +1,31 @@
<template>
<div class="border-box h-44 flex flex-items-center pl-12 pr-12">
<div class="nav-icon-button" @tap.stop="showDrawer">
<uni-icons type="bars" size="24" color="#ffffff" />
<div class="h-[44px] flex items-center pl-[12px] pr-[12px]">
<div class="w-[44px] h-[44px] flex items-center justify-center" @tap.stop="showDrawer">
<van-icon name="apps-o" size="24" color="#333" />
</div>
<!-- 隐藏 -->
<div v-if="false" class="flex-full h-full flex flex-items-center flex-justify-center">
<div v-if="false" class="flex-1 h-full flex items-center justify-center">
<!-- ChatTopWelcome不在可视区显示并添加动画在可视区隐藏 -->
<SpriteAnimator v-show="show" class="image-animated" :src="spriteStyle.ipSmallImage"
:frameWidth="spriteStyle.frameWidth" :frameHeight="spriteStyle.frameHeight"
:totalFrames="spriteStyle.totalFrames" :columns="spriteStyle.columns" :displayWidth="spriteStyle.displayWidth"
:fps="16" />
<span v-show="show" :class="[
'font-size-14 font-500 color-171717 ml-10',
'text-[14px] text-500 text-[#171717] ml-[10px]',
{ 'text-animated': show },
]">
{{ config.name }}
nianxx
</span>
</div>
<div class="w-24 h-24"></div>
<div class="w-[24px] h-[24px]"></div>
</div>
</template>
<script setup>
import { ref, defineProps, computed, defineExpose } from "vue";
import { getCurrentConfig } from "@/constants/base";
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
import SpriteAnimator from "@/components/SpriteAnimator/index.vue";
const props = defineProps({
mainPageDataModel: {
@@ -42,16 +41,15 @@ const initPageImages = computed(() => {
});
const show = ref(false);
const config = getCurrentConfig();
const spriteStyle = computed(() => {
const images = initPageImages.value;
return {
ipSmallImage: images.ipSmallImage ?? config.ipSmallImage,
frameWidth: images.ipSmallImageWidth ?? config.ipSmallImageWidth,
frameHeight: images.ipSmallImageHeight ?? config.ipSmallImageHeight,
totalFrames: images.ipSmallTotalFrames ?? config.ipSmallTotalFrames,
columns: images.ipSmallColumns ?? config.ipSmallColumns,
ipSmallImage: images.ipSmallImage,
frameWidth: images.ipSmallImageWidth,
frameHeight: images.ipSmallImageHeight,
totalFrames: images.ipSmallTotalFrames,
columns: images.ipSmallColumns,
displayWidth: 32,
};
});

View File

@@ -1,11 +1,3 @@
.nav-icon-button {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
// 图片从0%到100%动画
.image-animated {
animation: logo-scale 0.3s ease-in-out;
@@ -36,4 +28,4 @@
opacity: 1;
transform: translateX(0);
}
}
}

View File

@@ -20,7 +20,7 @@ v
import { defineProps, computed, getCurrentInstance, defineExpose } from "vue";
import { getCurrentConfig } from "@/constants/base";
import ChatMoreTips from "../ChatMoreTips/index.vue";
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
import SpriteAnimator from "@/components/SpriteAnimator/index.vue";
const props = defineProps({
mainPageDataModel: {

View File

@@ -18,7 +18,7 @@
<script setup>
import { onMounted, ref } from "vue";
import { defineProps, computed } from "vue";
import SpriteAnimator from "@/components/Sprite/SpriteAnimator.vue";
import SpriteAnimator from "@/components/SpriteAnimator/index.vue";
import NoticeMessage from "../NoticeMessage/index.vue";
import { getLocalWeather } from "@/api/home";

View File

@@ -1,6 +1,6 @@
<template>
<div class="index-page w-full h-screen overflow-hidden bg-liner">
<!-- <ChatMainList @showDrawer="showDrawer" /> -->
<ChatMainList @showDrawer="showDrawer" />
<!-- 日历组件 -->
<!-- <Calender :visible="calendarVisible" mode="single" :default-value="selectedDate" @close="handleCalendarClose"
@@ -20,7 +20,7 @@ import { emitter } from '@/utils/events'
// import { getUrlParams } from "@/utils/UrlParams";
// import { useAppStore } from "@/store";
// import { checkToken } from "@/hooks/useGoLogin";
// import ChatMainList from "./components/ChatMainList/index.vue";
import ChatMainList from "./components/ChatMainList/index.vue";
import MoreService from "./components/MoreService/index.vue";
import DrawerSection from "./components/DrawerSection/index.vue";
// import Calender from "@/components/Calender/index.vue";

View File

@@ -10,7 +10,8 @@ import type {
} from "@/shared/request-types";
function resolveBaseURL(): string {
const baseURL = (import.meta.env as Record<string, unknown>).VITE_API_BASE_URL;
const baseURL = (import.meta.env as Record<string, unknown>)
.VITE_API_BASE_URL;
if (typeof baseURL !== "string" || baseURL.trim() === "") {
throw new Error("Missing VITE_API_BASE_URL");
}
@@ -33,7 +34,7 @@ const http = axios.create({
let context: RequestContext = {
token: null,
clientId: null,
clientId: "6",
latitude: null,
longitude: null,
language: null,
@@ -47,7 +48,10 @@ export function setClientId(clientId: string | null): void {
context = { ...context, clientId };
}
export function setLocation(latitude: number | null, longitude: number | null): void {
export function setLocation(
latitude: number | null,
longitude: number | null,
): void {
context = { ...context, latitude, longitude };
}
@@ -207,4 +211,3 @@ export async function requestData<T>(
const res = await request<T>(config, options);
return res.data;
}

View File

@@ -38,5 +38,13 @@ export default defineConfig({
host: "0.0.0.0",
port: 5174,
open: false,
proxy: {
"/ingress": {
target: "https://onefeel.brother7.cn",
changeOrigin: true,
ws: true,
secure: false,
},
},
},
});