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,
domain: 'https://xcx.aigc-quickapp.com/',
},
})['2811']; // 选择要使用的环境配置
})['1']; // 选择要使用的环境配置
export default localDevConfig;

View File

@@ -68,7 +68,7 @@ export default {
}
return result;
} 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] store 状态:', {
@@ -88,6 +88,16 @@ export default {
return new Promise((resolve, reject) => {
const match = Config.baseUrl.match(/^(?:https?:\/\/|wss?:\/\/)([^\/\s:]+)/);
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 socketTask = wx.connectSocket({
url: wssUrl,
@@ -97,6 +107,29 @@ export default {
let content = '';
let conversationId = '';
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.send({
@@ -128,8 +161,8 @@ export default {
} else if (data.type === 'auth_failed') {
const errorMsg = '认证失败,请重新登录';
console.error(errorMsg, data);
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
socketTask.close();
}
@@ -146,10 +179,9 @@ export default {
if (conversationId) {
this.setConversationId(conversationId);
}
if (onComplete) {
onComplete({ content, conversation_id: conversationId });
}
resolve({ content, conversation_id: conversationId });
closedNormally = true;
safeComplete({ content, conversation_id: conversationId });
safeResolve({ content, conversation_id: conversationId });
socketTask.close();
}
@@ -165,21 +197,19 @@ export default {
socketTask.onError((err) => {
const errorMsg = 'WebSocket 连接失败';
console.error(errorMsg, err);
reject(new Error(errorMsg));
if (onComplete) onComplete({ error: errorMsg });
safeComplete({ error: errorMsg });
safeReject(new Error(errorMsg));
});
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
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -5,6 +5,23 @@ const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webp
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
// for (let key in process.env) {
// console.log(key, process.env[key]);
@@ -217,16 +234,15 @@ class VersionInjectPlugin {
console.log(`[H5Zip] 输出目录名正确: ${outputDirName}`);
}
// 上海时区时间戳格式: 20260511_1058
const now = new Date();
const shanghaiOffset = 8 * 60;
const shanghaiTime = new Date(now.getTime() + shanghaiOffset * 60 * 1000);
const y = shanghaiTime.getFullYear();
const M = String(shanghaiTime.getMonth() + 1).padStart(2, '0');
const d = String(shanghaiTime.getDate()).padStart(2, '0');
const h = String(shanghaiTime.getHours()).padStart(2, '0');
const m = String(shanghaiTime.getMinutes()).padStart(2, '0');
const ts = `${y}${M}${d}_${h}${m}`;
// 标准时区时间戳格式: 20260511_1058
const fmt = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', hour12: false
});
const parts = fmt.formatToParts(new Date());
const get = (t) => parts.find(p => p.type === t).value;
const ts = `${get('year')}${get('month')}${get('day')}_${get('hour')}${get('minute')}`;
const zipFileName = `${outputDirName}-${this._cachedCommitId}-${ts}.zip`;
const zipFilePath = path.join(h5Dir, zipFileName);
@@ -248,8 +264,8 @@ class VersionInjectPlugin {
execSync(command, { stdio: 'inherit' });
console.log(`✅ H5 ZIP: ${zipFileName}`);
// 删除已打包的源目录
fs.rmSync(targetDir, { recursive: true, force: true });
console.log(`[H5Zip] 已删除源目录: ${outputDirName}`);
// fs.rmSync(targetDir, { recursive: true, force: true });
// console.log(`[H5Zip] 已删除源目录: ${outputDirName}`);
} catch (e) {
console.error('H5 ZIP 打包失败:', e.message);
}