chore: merge dev/1.0 into main
包含以下修复: - ai-chat 键盘弹出、输入框闪烁、滚动、WebSocket 等问题 - 小米微信小程序魔方组件布局修复 - 三处 calc() 精度统一避免小米子像素舍入差异 - H5 构建配置优化
This commit is contained in:
@@ -62,5 +62,5 @@ const localDevConfig = ({
|
||||
uniacid: 2075,
|
||||
domain: 'https://xcx.aigc-quickapp.com/',
|
||||
},
|
||||
})['2811']; // 选择要使用的环境配置
|
||||
})['1']; // 选择要使用的环境配置
|
||||
export default localDevConfig;
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -318,7 +318,7 @@
|
||||
{
|
||||
"path": "ai-chat/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI 客服"
|
||||
"navigationBarTitleText": "AI智能客服"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.wrapper {
|
||||
position: fixed;
|
||||
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占位样式 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user