chore: merge dev/1.0 into main

包含以下修复:
- ai-chat 键盘弹出、输入框闪烁、滚动、WebSocket 等问题
- 小米微信小程序魔方组件布局修复
- 三处 calc() 精度统一避免小米子像素舍入差异
- H5 构建配置优化
This commit is contained in:
2026-05-14 08:43:45 +08:00
12 changed files with 752 additions and 457 deletions

View File

@@ -62,5 +62,5 @@ const localDevConfig = ({
uniacid: 2075, uniacid: 2075,
domain: 'https://xcx.aigc-quickapp.com/', domain: 'https://xcx.aigc-quickapp.com/',
}, },
})['2811']; // 选择要使用的环境配置 })['1']; // 选择要使用的环境配置
export default localDevConfig; export default localDevConfig;

View File

@@ -68,7 +68,7 @@ export default {
} }
return result; return result;
} catch (error) { } catch (error) {
console.error('[AI Service] Dify API请求失败,详细错误:', error); console.error('[AI Service] AiChat API请求失败,详细错误:', error);
console.error('[AI Service] 错误堆栈:', error.stack); console.error('[AI Service] 错误堆栈:', error.stack);
// 打印更多信息帮助调试 // 打印更多信息帮助调试
console.error('[AI Service] store 状态:', { console.error('[AI Service] store 状态:', {
@@ -88,6 +88,16 @@ export default {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/); const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/);
const domain = match ? match[1] : ''; const domain = match ? match[1] : '';
// 修复 5:Domain 提取失败时提前拒绝
if (!domain) {
const errorMsg = 'WebSocket 域名配置无效';
console.error(errorMsg);
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
return;
}
const wssUrl = `wss://${domain}/ws/aikefu`; const wssUrl = `wss://${domain}/ws/aikefu`;
const socketTask = wx.connectSocket({ const socketTask = wx.connectSocket({
url: wssUrl, url: wssUrl,
@@ -97,6 +107,29 @@ export default {
let content = ''; let content = '';
let conversationId = ''; let conversationId = '';
let isAuthenticated = false; let isAuthenticated = false;
let closedNormally = false;
let settled = false;
// 安全地拒绝(防止重复调用)
function safeReject(error) {
if (settled) return;
settled = true;
clearTimeout(timeout);
reject(error);
}
// 安全地完成
function safeResolve(result) {
if (settled) return;
settled = true;
clearTimeout(timeout);
resolve(result);
}
// 安全地触发 onComplete
function safeComplete(result) {
if (onComplete) onComplete(result);
}
socketTask.onOpen(() => { socketTask.onOpen(() => {
socketTask.send({ socketTask.send({
@@ -128,8 +161,8 @@ export default {
} else if (data.type === 'auth_failed') { } else if (data.type === 'auth_failed') {
const errorMsg = '认证失败,请重新登录'; const errorMsg = '认证失败,请重新登录';
console.error(errorMsg, data); console.error(errorMsg, data);
reject(new Error(errorMsg)); safeComplete({ error: errorMsg });
if (onComplete) onComplete({ error: errorMsg }); safeReject(new Error(errorMsg));
socketTask.close(); socketTask.close();
} }
@@ -146,10 +179,9 @@ export default {
if (conversationId) { if (conversationId) {
this.setConversationId(conversationId); this.setConversationId(conversationId);
} }
if (onComplete) { closedNormally = true;
onComplete({ content, conversation_id: conversationId }); safeComplete({ content, conversation_id: conversationId });
} safeResolve({ content, conversation_id: conversationId });
resolve({ content, conversation_id: conversationId });
socketTask.close(); socketTask.close();
} }
@@ -165,21 +197,19 @@ export default {
socketTask.onError((err) => { socketTask.onError((err) => {
const errorMsg = 'WebSocket 连接失败'; const errorMsg = 'WebSocket 连接失败';
console.error(errorMsg, err); console.error(errorMsg, err);
reject(new Error(errorMsg)); safeComplete({ error: errorMsg });
if (onComplete) onComplete({ error: errorMsg }); safeReject(new Error(errorMsg));
}); });
socketTask.onClose(() => { socketTask.onClose(() => {
if (!closedNormally && !settled) {
const errorMsg = 'WebSocket 连接意外关闭';
console.error(errorMsg);
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
}
}); });
const timeout = setTimeout(() => {
if (!isAuthenticated || content === '') {
console.warn('WebSocket 超时,强制关闭');
socketTask.close();
reject(new Error('AI服务响应超时'));
if (onComplete) onComplete({ error: 'AI服务响应超时' });
}
}, 10000);
}); });
// #endif // #endif
}, },

View File

@@ -21,9 +21,9 @@ try {
let defaultDomain = ''; let defaultDomain = '';
// #ifdef H5 // #ifdef H5
// 域名由 package.json 中对应脚本的 env.UNI_APP_DOMAIN 传入 // 域名由 vue.config.js 中 DefinePlugin 注入的 process.env.UNI_APP_DOMAIN 传入
try { try {
if (process?.env?.UNI_APP_DOMAIN) { if (process.env.UNI_APP_DOMAIN) {
defaultDomain = process.env.UNI_APP_DOMAIN; defaultDomain = process.env.UNI_APP_DOMAIN;
} }
} catch (e) {} } catch (e) {}

View File

@@ -119,9 +119,9 @@ export class CustomerService {
} }
/** /**
* 跳转到 AI 客服页面(Dify) * 跳转到 AI智能客服页面(AiChat)
*/ */
openDifyService() { openAiChatService() {
try { try {
// 清除未读数(如果存在) // 清除未读数(如果存在)
if (typeof this.vm.setAiUnreadCount === 'function') { if (typeof this.vm.setAiUnreadCount === 'function') {
@@ -135,7 +135,7 @@ export class CustomerService {
uni.navigateTo({ uni.navigateTo({
url: aiChatUrl, url: aiChatUrl,
fail: (err) => { fail: (err) => {
console.error('跳转 AI 客服失败:', err); console.error('跳转 AI智能客服失败:', err);
// H5 兜底 // H5 兜底
// #ifdef H5 // #ifdef H5
window.location.href = aiChatUrl; window.location.href = aiChatUrl;
@@ -144,7 +144,7 @@ export class CustomerService {
} }
}); });
} catch (e) { } catch (e) {
console.error('跳转 AI 客服异常:', e); console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' }); uni.showToast({ title: '打开客服失败', icon: 'none' });
} }
} }
@@ -177,7 +177,7 @@ export class CustomerService {
// 核心路由:根据 type 决定行为 // 核心路由:根据 type 决定行为
switch (config.type) { switch (config.type) {
case 'aikefu': case 'aikefu':
this.openDifyService(); this.openAiChatService();
break; break;
case 'wxwork': case 'wxwork':
this.openWxworkService(false, config, options); this.openWxworkService(false, config, options);
@@ -364,7 +364,7 @@ export class CustomerService {
openAliappService(config) { openAliappService(config) {
if (config.type === 'aikefu') { if (config.type === 'aikefu') {
this.openDifyService(); this.openAiChatService();
} else if (config.type === 'third') { } else if (config.type === 'third') {
this.openThirdService(config); this.openThirdService(config);
} else { } else {
@@ -565,7 +565,7 @@ export class CustomerService {
uni.navigateTo({ uni.navigateTo({
url: aiChatUrl, url: aiChatUrl,
fail: (err) => { fail: (err) => {
console.error('跳转 AI 客服失败:', err); console.error('跳转 AI智能客服失败:', err);
// #ifdef H5 // #ifdef H5
window.location.href = aiChatUrl; window.location.href = aiChatUrl;
// #endif // #endif
@@ -573,7 +573,7 @@ export class CustomerService {
} }
}); });
} catch (e) { } catch (e) {
console.error('跳转 AI 客服异常:', e); console.error('跳转 AI智能客服异常:', e);
uni.showToast({ title: '打开客服失败', icon: 'none' }); uni.showToast({ title: '打开客服失败', icon: 'none' });
} }
} }

View File

@@ -337,7 +337,7 @@ export default {
width: calc(50% - 10rpx); width: calc(50% - 10rpx);
&:nth-child(2n + 2) { &:nth-child(2n + 2) {
width: calc(50% - 11rpx); width: calc(50% - 10rpx);
margin-right: 0; margin-right: 0;
} }

View File

@@ -1018,10 +1018,6 @@ export default {
width: calc(33.3333333% - 14rpx); width: calc(33.3333333% - 14rpx);
box-sizing: border-box; box-sizing: border-box;
&:nth-child(3n + 3) {
width: calc(33.33% - 15rpx);
}
&:nth-of-type(1), &:nth-of-type(1),
&:nth-of-type(2), &:nth-of-type(2),
&:nth-of-type(3) { &:nth-of-type(3) {

View File

@@ -19,7 +19,7 @@
<template v-for="(item, index) in list"> <template v-for="(item, index) in list">
<template v-if="index > 0"> <template v-if="index > 0">
<view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)" <view :key="index" :class="['item', value.mode]" @tap.stop="handlerClick(item.link, item.imageUrl)"
:style="{ marginBottom: value.imageGap * 2 + 'rpx', width: item.imgWidth, height: item.imgHeight + 'px' }"> :style="{ marginBottom: index < list.length - 1 ? value.imageGap * 2 + 'rpx' : 0, width: item.imgWidth, height: item.imgHeight + 'px' }">
<image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'" <image :src="$util.img(item.imageUrl)" :mode="item.imageMode || 'scaleToFill'"
:style="item.pageItemStyle" show-menu-by-longpress /> :style="item.pageItemStyle" show-menu-by-longpress />
</view> </view>
@@ -235,8 +235,6 @@ export default {
*/ */
calcRowOneLeftOfTwoRight() { calcRowOneLeftOfTwoRight() {
let rightHeight = 0; // 右侧两图平分高度 let rightHeight = 0; // 右侧两图平分高度
let divide = 'left'; // 划分规则,left:左,right:右
if (this.list[1].imgWidth === this.list[2].imgWidth) divide = 'right';
uni.getSystemInfo({ uni.getSystemInfo({
success: res => { success: res => {
this.list.forEach((item, index) => { this.list.forEach((item, index) => {
@@ -246,12 +244,14 @@ export default {
item.imgWidth = item.imgWidth / 2; item.imgWidth = item.imgWidth / 2;
item.imgHeight = item.imgWidth * ratio; item.imgHeight = item.imgWidth * ratio;
rightHeight = (item.imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2; rightHeight = (item.imgHeight - uni.upx2px(this.value.imageGap * 2)) / 2;
item.imgWidth += 'px';
} else { } else {
item.imgWidth = this.list[0].imgWidth;
item.imgHeight = rightHeight; item.imgHeight = rightHeight;
} }
}); });
// 统一追加 px 单位
this.list.forEach((item) => {
item.imgWidth += 'px';
});
} }
}); });
}, },
@@ -311,7 +311,6 @@ export default {
this.list.forEach((item, index) => { this.list.forEach((item, index) => {
item.imgWidth += 'px'; item.imgWidth += 'px';
item.imgHeight;
}); });
} }
}); });
@@ -482,7 +481,7 @@ export default {
// 一左两右 // 一左两右
.rubik-cube .template-left, .rubik-cube .template-left,
.rubik-cube .template-right { .rubik-cube .template-right {
// width: 50%; width: 50%;
box-sizing: border-box; box-sizing: border-box;
} }

View File

@@ -230,10 +230,6 @@ export default {
width: calc(33.3333333% - 14rpx); width: calc(33.3333333% - 14rpx);
box-sizing: border-box; box-sizing: border-box;
&:nth-child(3n + 3) {
width: calc(33.33% - 15rpx);
}
&:nth-of-type(1), &:nth-of-type(1),
&:nth-of-type(2), &:nth-of-type(2),
&:nth-of-type(3) { &:nth-of-type(3) {

View File

@@ -318,7 +318,7 @@
{ {
"path": "ai-chat/index", "path": "ai-chat/index",
"style": { "style": {
"navigationBarTitleText": "AI 客服" "navigationBarTitleText": "AI智能客服"
} }
}, },
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,19 @@
<template> <template>
<view class="container" :style="wrapperPageStyle"> <view class="wrapper">
<!--自定义导航头部 - 只在微信小程序显示,H5端隐藏 --> <!--自定义导航头部 - 绝对定位在顶部,不随键盘上移 -->
<!-- #ifdef MP-WEIXIN --> <!-- #ifdef MP-WEIXIN -->
<view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar"> <view class="custom-navbar" ref="pageHeader" v-if="showCustomNavbar">
<view class="header-left"> <view class="header-left">
<button class="back-btn" v-show="showBackButton" @click="goBack"> <button class="back-btn" v-show="showBackButton" @click="goBack">
<text class="iconfont icon-back"></text> <text class="iconfont icon-back"></text>
</button> </button>
<!-- 占位元素,确保布局平衡 -->
<view v-show="!showMenuButton" class="placeholder"></view> <view v-show="!showMenuButton" class="placeholder"></view>
</view> </view>
</view> </view>
<!-- #endif --> <!-- #endif -->
<!-- 聊天内容区域 --> <!-- 聊天内容区域:从导航栏下方到容器底部 -->
<view class="chat-content" <view class="chat-content">
:style="wrapperChatContentStyle"
>
<!-- AI聊天组件 -->
<ai-chat-message <ai-chat-message
ref="chat" ref="chat"
:initial-messages="initialMessages" :initial-messages="initialMessages"
@@ -131,37 +127,6 @@ export default {
return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar) return this.globalAIKefuConfig?.aiAvatar || this.$util.img(IMAGE_CONFIG.aiAvatar)
}, },
/// ---- others ---- /// ---- others ----
containerHeight() {
return `calc(100vh - ${this.navBarHeight + this.statusBarHeight}px)`
},
wrapperPageStyle() {
// #ifdef H5
return `top: $ {this.navBarHeight + 'px'};`
// #endif
// #ifdef MP-WEIXIN
return `top: -1px;` // 微信小程序需要上移1px, 否则与系统导航栏出现1px的空隙
// #endif
return ``
},
wrapperChatContentStyle() {
// #ifdef H5
// H5端没有自定义导航栏,不需要paddingTop
return {
height: this.containerHeight,
paddingTop: '0'
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序端根据导航栏显示情况决定
return {
height: this.containerHeight,
paddingTop: this.showCustomNavbar ? '0' : (this.navBarHeight + 'px')
}
// #endif
}
}, },
async onLoad(options) { async onLoad(options) {
@@ -574,20 +539,22 @@ export default {
@import url('/common/css/iconfont.css'); @import url('/common/css/iconfont.css');
/* 页面样式 */ /* 页面样式 */
.container { .wrapper {
display: flex; position: fixed;
flex-direction: column; top: 0;
background-color: white; /* 白色 */ left: 0;
overflow: hidden; right: 0;
position: fixed; bottom: 0;
top: 48px; overflow: hidden;
left: 0; }
right: 0;
bottom: 0;
}
/* 自定义导航头部 */ /* 自定义导航头部:固定在wrapper顶部,不随键盘上移 */
.custom-navbar { .custom-navbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 88rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -667,12 +634,18 @@ export default {
} }
} }
/* 聊天内容区域 */ /* 聊天内容区域:从导航栏下方到容器底部 */
.chat-content { .chat-content {
flex: 1; position: absolute;
display: flex; /* #ifdef MP-WEIXIN */
flex-direction: column; top: 0;
overflow: hidden; /* #endif */
/* #ifndef MP-WEIXIN */
top: 44px;
/* #endif */
left: 0;
right: 0;
bottom: 0;
} }
/* 底部tabBar占位样式 */ /* 底部tabBar占位样式 */

View File

@@ -5,6 +5,23 @@ const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webp
console.log('[vue.config] 已加载'); console.log('[vue.config] 已加载');
// 确保 UNI_APP_DOMAIN 有值(从 package.json 脚本配置中读取)
if (!process.env.UNI_APP_DOMAIN && process.env.UNI_SCRIPT) {
try {
const pkg = require('./package.json');
const scriptCfg = pkg['uni-app']?.scripts?.[process.env.UNI_SCRIPT];
if (scriptCfg?.env?.UNI_APP_DOMAIN) {
process.env.UNI_APP_DOMAIN = scriptCfg.env.UNI_APP_DOMAIN;
}
} catch (e) {}
}
// 输出 H5 域名信息到控制台
if (process.env.UNI_PLATFORM === 'h5') {
console.log(`\n[H5 发行] 脚本: ${process.env.UNI_SCRIPT || '(默认)'}`);
console.log(`[H5 发行] 域名: ${process.env.UNI_APP_DOMAIN || '(未配置)'}\n`);
}
// 打印出process.env // 打印出process.env
// for (let key in process.env) { // for (let key in process.env) {
// console.log(key, process.env[key]); // console.log(key, process.env[key]);
@@ -217,16 +234,15 @@ class VersionInjectPlugin {
console.log(`[H5Zip] 输出目录名正确: ${outputDirName}`); console.log(`[H5Zip] 输出目录名正确: ${outputDirName}`);
} }
// 上海时区时间戳格式: 20260511_1058 // 标准时区时间戳格式: 20260511_1058
const now = new Date(); const fmt = new Intl.DateTimeFormat('en-CA', {
const shanghaiOffset = 8 * 60; timeZone: 'Asia/Shanghai',
const shanghaiTime = new Date(now.getTime() + shanghaiOffset * 60 * 1000); year: 'numeric', month: '2-digit', day: '2-digit',
const y = shanghaiTime.getFullYear(); hour: '2-digit', minute: '2-digit', hour12: false
const M = String(shanghaiTime.getMonth() + 1).padStart(2, '0'); });
const d = String(shanghaiTime.getDate()).padStart(2, '0'); const parts = fmt.formatToParts(new Date());
const h = String(shanghaiTime.getHours()).padStart(2, '0'); const get = (t) => parts.find(p => p.type === t).value;
const m = String(shanghaiTime.getMinutes()).padStart(2, '0'); const ts = `${get('year')}${get('month')}${get('day')}_${get('hour')}${get('minute')}`;
const ts = `${y}${M}${d}_${h}${m}`;
const zipFileName = `${outputDirName}-${this._cachedCommitId}-${ts}.zip`; const zipFileName = `${outputDirName}-${this._cachedCommitId}-${ts}.zip`;
const zipFilePath = path.join(h5Dir, zipFileName); const zipFilePath = path.join(h5Dir, zipFileName);
@@ -248,8 +264,8 @@ class VersionInjectPlugin {
execSync(command, { stdio: 'inherit' }); execSync(command, { stdio: 'inherit' });
console.log(`✅ H5 ZIP: ${zipFileName}`); console.log(`✅ H5 ZIP: ${zipFileName}`);
// 删除已打包的源目录 // 删除已打包的源目录
fs.rmSync(targetDir, { recursive: true, force: true }); // fs.rmSync(targetDir, { recursive: true, force: true });
console.log(`[H5Zip] 已删除源目录: ${outputDirName}`); // console.log(`[H5Zip] 已删除源目录: ${outputDirName}`);
} catch (e) { } catch (e) {
console.error('H5 ZIP 打包失败:', e.message); console.error('H5 ZIP 打包失败:', e.message);
} }